From 28184a322bb4cfbcaa9082ac06e44a1e2fc511a4 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 13 Aug 2024 13:20:53 -0400 Subject: [PATCH 01/25] make discord-bot and netlify-functions-ecommerce work with tables --- discord-bot/.env.test | 5 +- discord-bot/README.md | 13 +++- discord-bot/commands/createdocument.js | 2 +- discord-bot/models/bot.js | 7 ++- discord-bot/test/countdocuments.test.js | 4 +- discord-bot/test/createdocument.test.js | 2 +- discord-bot/test/setup.js | 7 +-- netlify-functions-ecommerce/.env.test | 4 +- netlify-functions-ecommerce/README.md | 29 +++++++++ .../frontend/src/cart/cart.js | 2 +- .../frontend/src/home/home.js | 10 +-- .../frontend/src/product/product.js | 10 +-- .../frontend/src/products/products.js | 10 +-- netlify-functions-ecommerce/models.js | 42 +++++++++++-- .../netlify/functions/addToCart.js | 22 ++++--- .../netlify/functions/checkout.js | 8 +-- .../netlify/functions/getCart.js | 3 +- .../netlify/functions/removeFromCart.js | 15 +++-- netlify-functions-ecommerce/public/app.js | 37 +++++------ netlify-functions-ecommerce/seed.js | 61 +++++++++---------- .../test/addToCart.test.js | 12 ++-- .../test/checkout.test.js | 6 +- .../test/fixtures/createCart.js | 2 +- .../test/fixtures/createOrder.js | 2 +- .../test/fixtures/createProducts.js | 2 +- .../test/getCart.test.js | 4 +- .../test/removeFromCart.test.js | 16 ++--- .../test/setup.test.js | 4 +- 28 files changed, 209 insertions(+), 132 deletions(-) diff --git a/discord-bot/.env.test b/discord-bot/.env.test index 7d9ebfe..cd42340 100644 --- a/discord-bot/.env.test +++ b/discord-bot/.env.test @@ -1,4 +1,3 @@ -DATA_API_URI=http://127.0.0.1:8181/v1/discordbot_test +DATA_API_URI=http://127.0.0.1:8181/v1/demo DATA_API_AUTH_USERNAME=cassandra -DATA_API_AUTH_PASSWORD=cassandra -DATA_API_AUTH_URL=http://localhost:8081/v1/auth \ No newline at end of file +DATA_API_AUTH_PASSWORD=cassandra \ No newline at end of file diff --git a/discord-bot/README.md b/discord-bot/README.md index e9dd87d..f5452de 100644 --- a/discord-bot/README.md +++ b/discord-bot/README.md @@ -46,4 +46,15 @@ Below is a screenshot demonstrating executing each of the commands. ![image](https://user-images.githubusercontent.com/1620265/213293087-53505a73-3038-4db8-b21b-d9149a5396ed.png) -Anytime you add or update commands in the command folder, run step 3 again. \ No newline at end of file +Anytime you add or update commands in the command folder, run step 3 again. + +## With tables + +Create table: + +``` +CREATE TABLE bots ( + id text, + name text, + PRIMARY KEY (id)); +``` \ No newline at end of file diff --git a/discord-bot/commands/createdocument.js b/discord-bot/commands/createdocument.js index 254f41e..3c096b6 100644 --- a/discord-bot/commands/createdocument.js +++ b/discord-bot/commands/createdocument.js @@ -7,7 +7,7 @@ module.exports = { data: new SlashCommandBuilder().setName('createdocument').setDescription('creates a document'), async execute(interaction) { console.log(new Date(), 'createdocument'); - await Bot.create({ name: 'I am a document' }); + await Bot.insertMany([{ name: 'I am a document' }]); await interaction.reply('done!'); } }; \ No newline at end of file diff --git a/discord-bot/models/bot.js b/discord-bot/models/bot.js index e49bb77..b3ea7b9 100644 --- a/discord-bot/models/bot.js +++ b/discord-bot/models/bot.js @@ -3,8 +3,13 @@ const mongoose = require('../mongoose'); const botSchema = new mongoose.Schema({ + id: { + type: mongoose.Types.ObjectId, + required: true, + default: () => new mongoose.Types.ObjectId() + }, name: String -}); +}, { _id: false, versionKey: false }); const Bot = mongoose.model('Bot', botSchema); diff --git a/discord-bot/test/countdocuments.test.js b/discord-bot/test/countdocuments.test.js index 50d563a..52b9f95 100644 --- a/discord-bot/test/countdocuments.test.js +++ b/discord-bot/test/countdocuments.test.js @@ -8,8 +8,8 @@ const sinon = require('sinon'); describe('countdocuments', function() { it('returns the number of bot documents', async function() { - await Bot.deleteMany({}); - await Bot.create({ name: 'test' }); + //await Bot.deleteMany({}); + await Bot.insertMany({ name: 'test' }); const interaction = { reply: sinon.stub() diff --git a/discord-bot/test/createdocument.test.js b/discord-bot/test/createdocument.test.js index 0f6d0d4..5f5dc65 100644 --- a/discord-bot/test/createdocument.test.js +++ b/discord-bot/test/createdocument.test.js @@ -8,7 +8,7 @@ const sinon = require('sinon'); describe('createdocument', function() { it('inserts a new document', async function() { - await Bot.deleteMany({}); + //await Bot.deleteMany({}); const interaction = { reply: sinon.stub() diff --git a/discord-bot/test/setup.js b/discord-bot/test/setup.js index 7c8513e..7682073 100644 --- a/discord-bot/test/setup.js +++ b/discord-bot/test/setup.js @@ -9,16 +9,15 @@ const mongoose = require('../mongoose'); const uri = process.env.DATA_API_URI; const jsonApiConnectOptions = { username: process.env.DATA_API_AUTH_USERNAME, - password: process.env.DATA_API_AUTH_PASSWORD, - authUrl: process.env.DATA_API_AUTH_URL + password: process.env.DATA_API_AUTH_PASSWORD }; before(async function() { this.timeout(30000); await mongoose.connect(uri, jsonApiConnectOptions); // dropCollection() can be slower - await Bot.db.dropCollection('bots').catch(() => {}); - await Bot.createCollection(); + // await Bot.db.dropCollection('bots').catch(() => {}); + // await Bot.createCollection(); }); after(async function() { diff --git a/netlify-functions-ecommerce/.env.test b/netlify-functions-ecommerce/.env.test index ace48da..5cdffff 100644 --- a/netlify-functions-ecommerce/.env.test +++ b/netlify-functions-ecommerce/.env.test @@ -1,8 +1,6 @@ #Fill the Local JSON API related details only when NODE_ENV is set to 'jsonapi' #Local JSON API URL for example: http://127.0.0.1:8181/v1/ecommerce_test where 'ecommerce_test' is the keyspace name -DATA_API_URI=http://127.0.0.1:8181/v1/ecommerce_test -#Auth URL for example: http://127.0.0.1:8081/v1/auth -DATA_API_AUTH_URL=http://127.0.0.1:8081/v1/auth +DATA_API_URI=http://127.0.0.1:8181/v1/demo #Auth username and password DATA_API_AUTH_USERNAME=cassandra DATA_API_AUTH_PASSWORD=cassandra diff --git a/netlify-functions-ecommerce/README.md b/netlify-functions-ecommerce/README.md index ffc52d4..889c1f3 100644 --- a/netlify-functions-ecommerce/README.md +++ b/netlify-functions-ecommerce/README.md @@ -71,3 +71,32 @@ Using test 8 passing (112ms) ``` + + +## With tables + +``` +CREATE TABLE products ( + id text, + name text, + price decimal, + image text, + description text, + PRIMARY KEY (id)); + +CREATE TABLE orders ( + id text, + total decimal, + name text, + paymentMethod text, + items text, + PRIMARY KEY (id)); + +CREATE TABLE carts ( + id text, + items text, + orderId text, + total decimal, + stripeSessionId text, + PRIMARY KEY (id)); +``` \ No newline at end of file diff --git a/netlify-functions-ecommerce/frontend/src/cart/cart.js b/netlify-functions-ecommerce/frontend/src/cart/cart.js index 2366fdd..ffa5054 100644 --- a/netlify-functions-ecommerce/frontend/src/cart/cart.js +++ b/netlify-functions-ecommerce/frontend/src/cart/cart.js @@ -16,7 +16,7 @@ module.exports = app => app.component('cart', { }, methods: { product(item) { - const product = this.state.products.find(product => product._id === item.productId); + const product = this.state.products.find(product => product.id === item.productId); return product; }, formatTotal(item, product) { diff --git a/netlify-functions-ecommerce/frontend/src/home/home.js b/netlify-functions-ecommerce/frontend/src/home/home.js index 0635c36..cb45d72 100644 --- a/netlify-functions-ecommerce/frontend/src/home/home.js +++ b/netlify-functions-ecommerce/frontend/src/home/home.js @@ -14,10 +14,10 @@ module.exports = app => app.component('home', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product._id, quantity: 1 }] + items: [{ productId: product.id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart._id; + body.cartId = this.state.cart.id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -27,9 +27,9 @@ module.exports = app => app.component('home', { body: JSON.stringify(body) }).then(res => res.json()); this.state.cart = res; - if (!this.state.cartId || this.state.cartId !== res._id) { - this.state.cartId = res._id; - window.localStorage.setItem('__cartKey', res._id); + if (!this.state.cartId || this.state.cartId !== res.id) { + this.state.cartId = res.id; + window.localStorage.setItem('__cartKey', res.id); } this.submitting = null; } diff --git a/netlify-functions-ecommerce/frontend/src/product/product.js b/netlify-functions-ecommerce/frontend/src/product/product.js index 1658dba..98f3694 100644 --- a/netlify-functions-ecommerce/frontend/src/product/product.js +++ b/netlify-functions-ecommerce/frontend/src/product/product.js @@ -10,7 +10,7 @@ module.exports = app => app.component('product', { }), computed: { product() { - return this.state.products.find(p => p._id === this.productId); + return this.state.products.find(p => p.id === this.productId); } }, methods: { @@ -20,10 +20,10 @@ module.exports = app => app.component('product', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product._id, quantity: 1 }] + items: [{ productId: product.id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart._id; + body.cartId = this.state.cart.id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -34,8 +34,8 @@ module.exports = app => app.component('product', { }).then(res => res.json()); this.state.cart = res; if (!this.state.cartId) { - this.state.cartId = res._id; - window.localStorage.setItem('__cartKey', res._id); + this.state.cartId = res.id; + window.localStorage.setItem('__cartKey', res.id); } this.submitting = null; } diff --git a/netlify-functions-ecommerce/frontend/src/products/products.js b/netlify-functions-ecommerce/frontend/src/products/products.js index 6ee750d..a444c3b 100644 --- a/netlify-functions-ecommerce/frontend/src/products/products.js +++ b/netlify-functions-ecommerce/frontend/src/products/products.js @@ -14,10 +14,10 @@ module.exports = app => app.component('products', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product._id, quantity: 1 }] + items: [{ productId: product.id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart._id; + body.cartId = this.state.cart.id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -27,9 +27,9 @@ module.exports = app => app.component('products', { body: JSON.stringify(body) }).then(res => res.json()); this.state.cart = res; - if (!this.state.cartId || this.state.cartId !== res._id) { - this.state.cartId = res._id; - window.localStorage.setItem('__cartKey', res._id); + if (!this.state.cartId || this.state.cartId !== res.id) { + this.state.cartId = res.id; + window.localStorage.setItem('__cartKey', res.id); } this.submitting = null; } diff --git a/netlify-functions-ecommerce/models.js b/netlify-functions-ecommerce/models.js index 17f590a..9fb16ee 100644 --- a/netlify-functions-ecommerce/models.js +++ b/netlify-functions-ecommerce/models.js @@ -3,17 +3,27 @@ const mongoose = require('./mongoose'); const productSchema = new mongoose.Schema({ + id: { + type: mongoose.Types.ObjectId, + required: true, + default: () => new mongoose.Types.ObjectId() + }, name: String, price: Number, image: String, description: String -}); +}, { _id: false, versionKey: false }); const Product = mongoose.model('Product', productSchema); module.exports.Product = Product; const orderSchema = new mongoose.Schema({ + id: { + type: mongoose.Types.ObjectId, + required: true, + default: () => new mongoose.Types.ObjectId() + }, items: [{ _id: false, productId: { type: mongoose.ObjectId, required: true, ref: 'Product' }, @@ -31,25 +41,45 @@ const orderSchema = new mongoose.Schema({ brand: String, last4: String } -}, { optimisticConcurrency: true }); +}, { _id: false, versionKey: false }); const Order = mongoose.model('Order', orderSchema); module.exports.Order = Order; const cartSchema = new mongoose.Schema({ - items: [{ + id: { + type: mongoose.Types.ObjectId, + required: true, + default: () => new mongoose.Types.ObjectId() + }, + items: { + type: String, + get(v) { + return v == null ? v : JSON.parse(v); + }, + set(v) { + if (v == null) { + return v; + } + return typeof v === 'string' ? v : JSON.stringify(v); + }, + }, /*[{ _id: false, productId: { type: mongoose.ObjectId, required: true, ref: 'Product' }, quantity: { type: Number, required: true } - }], + }],*/ orderId: { type: mongoose.ObjectId, ref: 'Order' }, total: Number, stripeSessionId: { type: String } -}, { timestamps: true }); +}, { _id: false, versionKey: false, timestamps: false, toObject: { getters: true }, toJSON: { getters: true } }); cartSchema.virtual('numItems').get(function numItems() { - return this.items.reduce((sum, item) => sum + item.quantity, 0); + if (this.items == null) { + return 0; + } + const items = typeof this.items === 'string' ? JSON.parse(this.items) : this.items; + return items.reduce((sum, item) => sum + item.quantity, 0); }); const Cart = mongoose.model('Cart', cartSchema); diff --git a/netlify-functions-ecommerce/netlify/functions/addToCart.js b/netlify-functions-ecommerce/netlify/functions/addToCart.js index 60ecc06..b990600 100644 --- a/netlify-functions-ecommerce/netlify/functions/addToCart.js +++ b/netlify-functions-ecommerce/netlify/functions/addToCart.js @@ -13,7 +13,7 @@ const handler = async(event) => { if (event.body.cartId) { // get the document containing the specified cartId const cart = await Cart. - findOne({ _id: event.body.cartId }). + findOne({ id: event.body.cartId }). setOptions({ sanitizeFilter: true }); if (cart == null) { @@ -29,13 +29,16 @@ const handler = async(event) => { }; } for (const product of event.body.items) { - const exists = cart.items.find(item => item?.productId?.toString() === product?.productId?.toString()); - if (!exists && products.find(p => product?.productId?.toString() === p?._id?.toString())) { - cart.items.push(product); - await cart.save(); + const exists = cart.items?.find(item => item?.productId?.toString() === product?.productId?.toString()); + if (!exists) { + if (products.find(p => product?.productId?.toString() === p?.id?.toString())) { + cart.items = [...(cart.items || []), product]; + } } else { - exists.quantity += product.quantity; - await cart.save(); + cart.items = [ + ...cart.items.filter(item => item?.productId?.toString() !== product?.productId?.toString()), + { productId: product.productId, quantity: exists.quantity + product.quantity } + ]; } } @@ -43,14 +46,15 @@ const handler = async(event) => { return { statusCode: 200, body: JSON.stringify({ cart: null }) }; } - await cart.save(); + await Cart.updateOne({ id: cart.id }, cart.getChanges()); return { statusCode: 200, body: JSON.stringify(cart) }; } else { // If no cartId, create a new cart - const cart = await Cart.create({ items: event.body.items }); + const [cart] = await Cart.insertMany([{ items: event.body.items }]); return { statusCode: 200, body: JSON.stringify(cart) }; } } catch (error) { + console.error(error); return { statusCode: 500, body: error.toString() }; } }; diff --git a/netlify-functions-ecommerce/netlify/functions/checkout.js b/netlify-functions-ecommerce/netlify/functions/checkout.js index f396720..16eeb39 100644 --- a/netlify-functions-ecommerce/netlify/functions/checkout.js +++ b/netlify-functions-ecommerce/netlify/functions/checkout.js @@ -11,14 +11,14 @@ const handler = async(event) => { event.body = JSON.parse(event.body || {}); await connect(); const cart = await Cart. - findOne({ _id: event.body.cartId }). + findOne({ id: event.body.cartId }). setOptions({ sanitizeFilter: true }). orFail(); const stripeProducts = { line_items: [] }; let total = 0; for (let i = 0; i < cart.items.length; i++) { - const product = await Product.findOne({ _id: cart.items[i].productId }); + const product = await Product.findOne({ id: cart.items[i].productId }); stripeProducts.line_items.push({ price_data: { currency: 'usd', @@ -35,7 +35,7 @@ const handler = async(event) => { cart.total = total; if (process.env.STRIPE_SECRET_KEY === 'test') { - await cart.save(); + await Cart.updateOne({ id: cart.id }, cart.getChanges()); return { statusCode: 200, body: JSON.stringify({ cart: cart, url: '/order-confirmation' }) @@ -50,7 +50,7 @@ const handler = async(event) => { }); cart.stripeSessionId = session.id; - await cart.save(); + await Cart.updateOne({ id: cart.id }, cart.getChanges()); return { statusCode: 200, diff --git a/netlify-functions-ecommerce/netlify/functions/getCart.js b/netlify-functions-ecommerce/netlify/functions/getCart.js index 6555623..7f24008 100644 --- a/netlify-functions-ecommerce/netlify/functions/getCart.js +++ b/netlify-functions-ecommerce/netlify/functions/getCart.js @@ -10,10 +10,11 @@ const handler = async(event) => { await connect(); // get the document containing the specified cartId const cart = await Cart. - findOne({ _id: event.queryStringParameters.cartId }). + findOne({ id: event.queryStringParameters.cartId }). setOptions({ sanitizeFilter: true }); return { statusCode: 200, body: JSON.stringify({ cart }) }; } catch (error) { + console.error(error); return { statusCode: 500, body: error.toString() }; } }; diff --git a/netlify-functions-ecommerce/netlify/functions/removeFromCart.js b/netlify-functions-ecommerce/netlify/functions/removeFromCart.js index e0f5e87..70daf70 100644 --- a/netlify-functions-ecommerce/netlify/functions/removeFromCart.js +++ b/netlify-functions-ecommerce/netlify/functions/removeFromCart.js @@ -9,7 +9,7 @@ const handler = async(event) => { try { event.body = JSON.parse(event.body || {}); await connect(); - const cart = await Cart.findOne({ _id: event.body.cartId }); + const cart = await Cart.findOne({ id: event.body.cartId }); const index = cart.items.findIndex((item) => item.productId.toString() == event.body.item.productId.toString() ); @@ -17,15 +17,22 @@ const handler = async(event) => { return { statusCode: 200, body: cart }; } if (event.body?.item?.quantity) { + cart.items = [ + ...cart.items.slice(0, index), + { ...cart.items[index], quantity: cart.items[index].quantity - event.body.item.quantity }, + ...cart.items.slice(index + 1) + ]; cart.items[index].quantity -= event.body.item.quantity; if (cart.items[index].quantity <= 0) { cart.items.splice(index, 1); } - await cart.save(); } else { - cart.items.splice(index, 1); - await cart.save(); + cart.items = [ + ...cart.items.slice(0, index), + ...cart.items.slice(index + 1) + ]; } + await Cart.updateOne({ id: cart.id }, cart.getChanges()); return { statusCode: 200, body: JSON.stringify(cart) }; } catch (error) { console.log(error); diff --git a/netlify-functions-ecommerce/public/app.js b/netlify-functions-ecommerce/public/app.js index 921946f..2c6356d 100644 --- a/netlify-functions-ecommerce/public/app.js +++ b/netlify-functions-ecommerce/public/app.js @@ -63,7 +63,7 @@ module.exports = app => app.component('cart', { }, methods: { product(item) { - const product = this.state.products.find(product => product._id === item.productId); + const product = this.state.products.find(product => product.id === item.productId); return product; }, formatTotal(item, product) { @@ -118,10 +118,10 @@ module.exports = app => app.component('home', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product._id, quantity: 1 }] + items: [{ productId: product.id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart._id; + body.cartId = this.state.cart.id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -131,9 +131,9 @@ module.exports = app => app.component('home', { body: JSON.stringify(body) }).then(res => res.json()); this.state.cart = res; - if (!this.state.cartId || this.state.cartId !== res._id) { - this.state.cartId = res._id; - window.localStorage.setItem('__cartKey', res._id); + if (!this.state.cartId || this.state.cartId !== res.id) { + this.state.cartId = res.id; + window.localStorage.setItem('__cartKey', res.id); } this.submitting = null; } @@ -218,7 +218,7 @@ module.exports = app => app.component('product', { }), computed: { product() { - return this.state.products.find(p => p._id === this.productId); + return this.state.products.find(p => p.id === this.productId); } }, methods: { @@ -228,10 +228,10 @@ module.exports = app => app.component('product', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product._id, quantity: 1 }] + items: [{ productId: product.id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart._id; + body.cartId = this.state.cart.id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -242,8 +242,8 @@ module.exports = app => app.component('product', { }).then(res => res.json()); this.state.cart = res; if (!this.state.cartId) { - this.state.cartId = res._id; - window.localStorage.setItem('__cartKey', res._id); + this.state.cartId = res.id; + window.localStorage.setItem('__cartKey', res.id); } this.submitting = null; } @@ -275,10 +275,10 @@ module.exports = app => app.component('products', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product._id, quantity: 1 }] + items: [{ productId: product.id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart._id; + body.cartId = this.state.cart.id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -288,9 +288,9 @@ module.exports = app => app.component('products', { body: JSON.stringify(body) }).then(res => res.json()); this.state.cart = res; - if (!this.state.cartId || this.state.cartId !== res._id) { - this.state.cartId = res._id; - window.localStorage.setItem('__cartKey', res._id); + if (!this.state.cartId || this.state.cartId !== res.id) { + this.state.cartId = res.id; + window.localStorage.setItem('__cartKey', res.id); } this.submitting = null; } @@ -480,9 +480,6 @@ module.exports = "
\n

All Products

\n
{ /*!*******************************!*\ !*** ./frontend/src/index.js ***! \*******************************/ @@ -556,7 +553,5 @@ router.replace(window.location.pathname); app.use(router); app.mount('#content'); -})(); - /******/ })() ; \ No newline at end of file diff --git a/netlify-functions-ecommerce/seed.js b/netlify-functions-ecommerce/seed.js index 8b6b48f..34ff43a 100644 --- a/netlify-functions-ecommerce/seed.js +++ b/netlify-functions-ecommerce/seed.js @@ -9,7 +9,7 @@ const mongoose = require('./mongoose'); async function createProducts() { await connect(); - const existingCollections = await mongoose.connection.listCollections() + /*const existingCollections = await mongoose.connection.listCollections() .then(collections => collections.map(c => c.name)); for (const Model of Object.values(models)) { if (existingCollections.includes(Model.collection.collectionName)) { @@ -17,39 +17,38 @@ async function createProducts() { } console.log('Creating', Model.collection.collectionName); await Model.createCollection(); - } - await Promise.all( + }*/ + /*await Promise.all( Object.values(models).map(Model => Model.deleteMany({})) - ); + );*/ const { Product } = models; - await Product.create({ - name: 'iPhone 12', - price: 499, - image: '/images/iphone-12.png', - description: '5G speed. A14 Bionic, the fastest chip in a smartphone. The iPhone 12 features Super Retina XDR display with a 6.1‐inch edge-to-edge OLED display, MagSafe wireless charging, Ceramic Shield with four times better drop performance and Night mode on every camera as well as Ultra Wide and Wide cameras.' - }); - - await Product.create({ - name: 'iPhone SE', - price: 429, - image: '/images/iphone-se.png', - description: 'The new iPhone SE delivers best-in-class performance and great photos for an affordable price, if you can live with a small screen' - }); - - await Product.create({ - name: 'iPhone 12 Pro', - price: 799, - image: '/images/iphone-12-pro.png', - description: 'Shoot amazing videos and photos with the Ultra Wide, Wide, and Telephoto cameras. Capture your best low-light photos with Night mode. Watch HDR movies and shows on the Super Retina XDR display—the brightest iPhone display yet. Experience unprecedented performance with A13 Bionic for gaming, augmented reality (AR), and photography. And get all-day battery life and a new level of water resistance. All in the first iPhone powerful enough to be called Pro.' - }); - - await Product.create({ - name: 'iPhone 11', - price: 300, - image: '/images/iphone-11.png', - description: 'The iPhone 11 offers superb cameras, fast performance and excellent battery life for an affordable price' - }); + await Product.insertMany([ + { + name: 'iPhone 12', + price: 499, + image: '/images/iphone-12.png', + description: '5G speed. A14 Bionic, the fastest chip in a smartphone. The iPhone 12 features Super Retina XDR display with a 6.1‐inch edge-to-edge OLED display, MagSafe wireless charging, Ceramic Shield with four times better drop performance and Night mode on every camera as well as Ultra Wide and Wide cameras.' + }, + { + name: 'iPhone SE', + price: 429, + image: '/images/iphone-se.png', + description: 'The new iPhone SE delivers best-in-class performance and great photos for an affordable price, if you can live with a small screen' + }, + { + name: 'iPhone 12 Pro', + price: 799, + image: '/images/iphone-12-pro.png', + description: 'Shoot amazing videos and photos with the Ultra Wide, Wide, and Telephoto cameras. Capture your best low-light photos with Night mode. Watch HDR movies and shows on the Super Retina XDR display—the brightest iPhone display yet. Experience unprecedented performance with A13 Bionic for gaming, augmented reality (AR), and photography. And get all-day battery life and a new level of water resistance. All in the first iPhone powerful enough to be called Pro.' + }, + { + name: 'iPhone 11', + price: 300, + image: '/images/iphone-11.png', + description: 'The iPhone 11 offers superb cameras, fast performance and excellent battery life for an affordable price' + } + ]); const products = await Product.find(); console.log('done', products.length, products); diff --git a/netlify-functions-ecommerce/test/addToCart.test.js b/netlify-functions-ecommerce/test/addToCart.test.js index 0584afa..accc44b 100644 --- a/netlify-functions-ecommerce/test/addToCart.test.js +++ b/netlify-functions-ecommerce/test/addToCart.test.js @@ -38,10 +38,10 @@ describe('Add to Cart', function() { const { cart } = await fixtures.createCart({ products: [] }); const params = { body: { - cartId: cart._id, + cartId: cart.id, items: [ - { productId: products[0]._id, quantity: 2 }, - { productId: products[1]._id, quantity: 1 } + { productId: products[0].id, quantity: 2 }, + { productId: products[1].id, quantity: 1 } ] } }; @@ -58,10 +58,10 @@ describe('Add to Cart', function() { const { cart } = await fixtures.createCart({ products: [] }); const params = { body: { - cartId: cart._id, + cartId: cart.id, items: [ - { productId: products[0]._id, quantity: 2 }, - { productId: products[1]._id, quantity: 1 } + { productId: products[0].id, quantity: 2 }, + { productId: products[1].id, quantity: 1 } ] } }; diff --git a/netlify-functions-ecommerce/test/checkout.test.js b/netlify-functions-ecommerce/test/checkout.test.js index 7b207ad..8cbea52 100644 --- a/netlify-functions-ecommerce/test/checkout.test.js +++ b/netlify-functions-ecommerce/test/checkout.test.js @@ -22,8 +22,8 @@ describe('Checkout', function() { body: { cartId: null, items: [ - { productId: products[0]._id, quantity: 2 }, - { productId: products[1]._id, quantity: 1 } + { productId: products[0].id, quantity: 2 }, + { productId: products[1].id, quantity: 1 } ] } }; @@ -33,7 +33,7 @@ describe('Checkout', function() { assert(result.body); assert(result.body.items.length); - params.body.cartId = result.body._id; + params.body.cartId = result.body.id; sinon.stub(stripe.paymentIntents, 'retrieve').returns({ status: 'succeeded', id: '123', brand: 'visa', last4: '1234' }); sinon.stub(stripe.paymentMethods, 'retrieve').returns({ status: 'succeeded', id: '123', brand: 'visa', last4: '1234' }); sinon.stub(stripe.checkout.sessions, 'create').returns({ diff --git a/netlify-functions-ecommerce/test/fixtures/createCart.js b/netlify-functions-ecommerce/test/fixtures/createCart.js index 71948dc..2bc1ae4 100644 --- a/netlify-functions-ecommerce/test/fixtures/createCart.js +++ b/netlify-functions-ecommerce/test/fixtures/createCart.js @@ -3,6 +3,6 @@ const { Cart } = require('../../models'); module.exports = async function createCart(params) { - const cart = await Cart.create({ items: params.items }); + const [cart] = await Cart.insertMany({ items: params.items }); return { cart }; }; diff --git a/netlify-functions-ecommerce/test/fixtures/createOrder.js b/netlify-functions-ecommerce/test/fixtures/createOrder.js index 1b17c67..9f51367 100644 --- a/netlify-functions-ecommerce/test/fixtures/createOrder.js +++ b/netlify-functions-ecommerce/test/fixtures/createOrder.js @@ -4,6 +4,6 @@ const { Order } = require('../../models'); module.exports = async function createOrder(params) { - const order = await Order.create(params.order); + const [order] = await Order.insertMany(params.order); return { order }; }; diff --git a/netlify-functions-ecommerce/test/fixtures/createProducts.js b/netlify-functions-ecommerce/test/fixtures/createProducts.js index 421c2aa..a5d6308 100644 --- a/netlify-functions-ecommerce/test/fixtures/createProducts.js +++ b/netlify-functions-ecommerce/test/fixtures/createProducts.js @@ -3,7 +3,7 @@ const { Product } = require('../../models'); module.exports = async function createProducts(params) { - const products = await Product.create(params.product); + const products = await Product.insertMany(params.product); return { products }; }; diff --git a/netlify-functions-ecommerce/test/getCart.test.js b/netlify-functions-ecommerce/test/getCart.test.js index 70476b8..aaa17dd 100644 --- a/netlify-functions-ecommerce/test/getCart.test.js +++ b/netlify-functions-ecommerce/test/getCart.test.js @@ -11,12 +11,12 @@ describe('Get the cart given an id', function() { const params = { queryStringParameters: { - cartId: cart._id + cartId: cart.id } }; const findCart = await getCart(params); assert.equal(findCart.statusCode, 200); findCart.body = JSON.parse(findCart.body); - assert.equal(findCart.body.cart._id, cart._id.toString()); + assert.equal(findCart.body.cart.id, cart.id.toString()); }); }); diff --git a/netlify-functions-ecommerce/test/removeFromCart.test.js b/netlify-functions-ecommerce/test/removeFromCart.test.js index 2b23276..7b63787 100644 --- a/netlify-functions-ecommerce/test/removeFromCart.test.js +++ b/netlify-functions-ecommerce/test/removeFromCart.test.js @@ -20,8 +20,8 @@ describe('Remove From Cart', function() { body: { cartId: null, items: [ - { productId: products[0]._id, quantity: 2 }, - { productId: products[1]._id, quantity: 1 } + { productId: products[0].id, quantity: 2 }, + { productId: products[1].id, quantity: 1 } ] } }; @@ -32,9 +32,9 @@ describe('Remove From Cart', function() { assert.equal(result.body.items.length, 2); const newParams = { body: { - cartId: result.body._id, + cartId: result.body.id, item: { - productId: products[0]._id + productId: products[0].id } } }; @@ -51,8 +51,8 @@ describe('Remove From Cart', function() { body: { cartId: null, items: [ - { productId: products[0]._id, quantity: 2 }, - { productId: products[1]._id, quantity: 1 } + { productId: products[0].id, quantity: 2 }, + { productId: products[1].id, quantity: 1 } ] } }; @@ -63,9 +63,9 @@ describe('Remove From Cart', function() { assert(result.body.items.length); const newParams = { body: { - cartId: result.body._id, + cartId: result.body.id, item: { - productId: products[0]._id, + productId: products[0].id, quantity: 1 } } diff --git a/netlify-functions-ecommerce/test/setup.test.js b/netlify-functions-ecommerce/test/setup.test.js index 37fa0aa..b046584 100644 --- a/netlify-functions-ecommerce/test/setup.test.js +++ b/netlify-functions-ecommerce/test/setup.test.js @@ -10,8 +10,8 @@ before(async function() { this.timeout(30000); await connect(); - await Promise.all(Object.values(mongoose.connection.models).map(Model => Model.createCollection())); - await Promise.all(Object.values(mongoose.connection.models).map(Model => Model.deleteMany({}))); + // await Promise.all(Object.values(mongoose.connection.models).map(Model => Model.createCollection())); + // await Promise.all(Object.values(mongoose.connection.models).map(Model => Model.deleteMany({}))); }); after(async function() { From 9a0610eb40bc07714deb22bc1abc0f4b20cc0f4d Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 13 Aug 2024 16:23:17 -0400 Subject: [PATCH 02/25] soft deletes on discord-bot using create index --- discord-bot/README.md | 3 +++ discord-bot/commands/countdocuments.js | 2 +- discord-bot/models/bot.js | 6 +++++- discord-bot/test/countdocuments.test.js | 4 +++- discord-bot/test/createdocument.test.js | 6 ++++-- discord-bot/test/setup.js | 8 +++++--- .../netlify/functions/confirmOrder.js | 16 ++++++++-------- 7 files changed, 29 insertions(+), 16 deletions(-) diff --git a/discord-bot/README.md b/discord-bot/README.md index f5452de..da8cbf2 100644 --- a/discord-bot/README.md +++ b/discord-bot/README.md @@ -56,5 +56,8 @@ Create table: CREATE TABLE bots ( id text, name text, + deleted int, PRIMARY KEY (id)); + +CREATE INDEX isDeleted ON bots (deleted); ``` \ No newline at end of file diff --git a/discord-bot/commands/countdocuments.js b/discord-bot/commands/countdocuments.js index c18a6e0..15acc8f 100644 --- a/discord-bot/commands/countdocuments.js +++ b/discord-bot/commands/countdocuments.js @@ -8,7 +8,7 @@ module.exports = { async execute(interaction) { // `countDocuments()` currently not implemented, see: // https://github.com/stargate/stargate-mongoose/pull/48 - const num = await Bot.find().then(res => res.length); + const num = await Bot.find({ deleted: 0 }).then(res => res.length); console.log(new Date(), 'count', num); await interaction.reply(num.toString()); } diff --git a/discord-bot/models/bot.js b/discord-bot/models/bot.js index b3ea7b9..8bed592 100644 --- a/discord-bot/models/bot.js +++ b/discord-bot/models/bot.js @@ -8,7 +8,11 @@ const botSchema = new mongoose.Schema({ required: true, default: () => new mongoose.Types.ObjectId() }, - name: String + name: String, + deleted: { + type: Number, + default: 0 + } }, { _id: false, versionKey: false }); const Bot = mongoose.model('Bot', botSchema); diff --git a/discord-bot/test/countdocuments.test.js b/discord-bot/test/countdocuments.test.js index 52b9f95..f0c0a7f 100644 --- a/discord-bot/test/countdocuments.test.js +++ b/discord-bot/test/countdocuments.test.js @@ -8,7 +8,9 @@ const sinon = require('sinon'); describe('countdocuments', function() { it('returns the number of bot documents', async function() { - //await Bot.deleteMany({}); + for (const doc of await Bot.find({ deleted: 0 })) { + await Bot.updateOne({ id: doc.id }, { deleted: 1 }); + } await Bot.insertMany({ name: 'test' }); const interaction = { diff --git a/discord-bot/test/createdocument.test.js b/discord-bot/test/createdocument.test.js index 5f5dc65..2f4ef00 100644 --- a/discord-bot/test/createdocument.test.js +++ b/discord-bot/test/createdocument.test.js @@ -8,7 +8,9 @@ const sinon = require('sinon'); describe('createdocument', function() { it('inserts a new document', async function() { - //await Bot.deleteMany({}); + for (const doc of await Bot.find({ deleted: 0 })) { + await Bot.updateOne({ id: doc.id }, { deleted: 1 }); + } const interaction = { reply: sinon.stub() @@ -17,7 +19,7 @@ describe('createdocument', function() { assert.ok(interaction.reply.calledOnce); assert.deepEqual(interaction.reply.getCalls()[0].args, ['done!']); - const docs = await Bot.find(); + const docs = await Bot.find({ deleted: 0 }); assert.equal(docs.length, 1); assert.equal(docs[0].name, 'I am a document'); }); diff --git a/discord-bot/test/setup.js b/discord-bot/test/setup.js index 7682073..73d5743 100644 --- a/discord-bot/test/setup.js +++ b/discord-bot/test/setup.js @@ -15,9 +15,11 @@ const jsonApiConnectOptions = { before(async function() { this.timeout(30000); await mongoose.connect(uri, jsonApiConnectOptions); - // dropCollection() can be slower - // await Bot.db.dropCollection('bots').catch(() => {}); - // await Bot.createCollection(); + + const docs = await Bot.find({ deleted: 0 }); + for (const doc of docs) { + await Bot.updateOne({ id: doc.id }, { deleted: 1 }); + } }); after(async function() { diff --git a/netlify-functions-ecommerce/netlify/functions/confirmOrder.js b/netlify-functions-ecommerce/netlify/functions/confirmOrder.js index 4057bc7..7d06809 100644 --- a/netlify-functions-ecommerce/netlify/functions/confirmOrder.js +++ b/netlify-functions-ecommerce/netlify/functions/confirmOrder.js @@ -11,12 +11,12 @@ const handler = async(event) => { event.body = JSON.parse(event.body || {}); await connect(); const cart = await Cart. - findOne({ _id: event.body.cartId }). + findOne({ id: event.body.cartId }). setOptions({ sanitizeFilter: true }). orFail(); if (cart.orderId) { - const order = await Order.findOne({ _id: cart.orderId }).orFail(); + const order = await Order.findOne({ id: cart.orderId }).orFail(); return { statusCode: 200, @@ -24,7 +24,7 @@ const handler = async(event) => { }; } if (process.env.STRIPE_SECRET_KEY === 'test') { - const order = await Order.create({ + const [order] = await Order.insertMany({ items: cart.items, name: 'Stripe Test', total: cart.total, @@ -35,8 +35,8 @@ const handler = async(event) => { } }); - cart.orderId = order._id; - await cart.save(); + cart.orderId = order.id; + await Cart.updateOne({ id: cart.id }, cart.getChanges()); return { statusCode: 200, @@ -55,7 +55,7 @@ const handler = async(event) => { const intent = await stripe.paymentIntents.retrieve(session.payment_intent); const paymentMethod = await stripe.paymentMethods.retrieve(intent['payment_method']); - const order = await Order.create({ + const [order] = await Order.insertMany({ items: cart.items, name: session['customer_details'].name, total: +(session['amount_total'] / 100).toFixed(2), @@ -64,8 +64,8 @@ const handler = async(event) => { null }); - cart.orderId = order._id; - await cart.save(); + cart.orderId = order.id; + await Cart.updateOne({ id: cart.id }, cart.getChanges()); return { statusCode: 200, From 92ad56b0baec82e0bdbd0dce83161b92ffbd1289 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 14 Aug 2024 14:46:57 -0400 Subject: [PATCH 03/25] make typescript-express-reviews work with tables minus skip+limit --- netlify-functions-ecommerce/models.js | 6 +-- typescript-express-reviews/.env.test | 5 +-- typescript-express-reviews/README.md | 42 +++++++++++++++++++ .../src/api/Review/create.ts | 4 +- .../src/api/User/login.ts | 7 ++-- .../src/api/User/register.ts | 10 ++--- .../src/api/Vehicle/findById.ts | 2 +- .../src/models/authentication.ts | 7 +++- .../src/models/review.ts | 17 +++++++- typescript-express-reviews/src/models/user.ts | 7 +++- .../src/models/vehicle.ts | 18 +++++++- typescript-express-reviews/src/seed/seed.ts | 31 +++++++------- .../tests/Review.test.ts | 32 +++++++------- typescript-express-reviews/tests/User.test.ts | 10 ++--- .../tests/Vehicle.test.ts | 20 ++++----- .../tests/index.test.ts | 7 +++- 16 files changed, 152 insertions(+), 73 deletions(-) diff --git a/netlify-functions-ecommerce/models.js b/netlify-functions-ecommerce/models.js index 9fb16ee..0c4005c 100644 --- a/netlify-functions-ecommerce/models.js +++ b/netlify-functions-ecommerce/models.js @@ -64,11 +64,7 @@ const cartSchema = new mongoose.Schema({ } return typeof v === 'string' ? v : JSON.stringify(v); }, - }, /*[{ - _id: false, - productId: { type: mongoose.ObjectId, required: true, ref: 'Product' }, - quantity: { type: Number, required: true } - }],*/ + }, orderId: { type: mongoose.ObjectId, ref: 'Order' }, total: Number, stripeSessionId: { type: String } diff --git a/typescript-express-reviews/.env.test b/typescript-express-reviews/.env.test index 120d479..cd42340 100644 --- a/typescript-express-reviews/.env.test +++ b/typescript-express-reviews/.env.test @@ -1,4 +1,3 @@ -DATA_API_URI=http://127.0.0.1:8181/v1/reviews_test +DATA_API_URI=http://127.0.0.1:8181/v1/demo DATA_API_AUTH_USERNAME=cassandra -DATA_API_AUTH_PASSWORD=cassandra -DATA_API_AUTH_URL=http://localhost:8081/v1/auth \ No newline at end of file +DATA_API_AUTH_PASSWORD=cassandra \ No newline at end of file diff --git a/typescript-express-reviews/README.md b/typescript-express-reviews/README.md index f19b44f..cc2dd62 100644 --- a/typescript-express-reviews/README.md +++ b/typescript-express-reviews/README.md @@ -31,3 +31,45 @@ Make sure you have Node.js 14 or higher and a local Stargate instance running as ] } ``` + +## With tables + +``` +CREATE TABLE authentications ( + id text, + type text, + userId text, + secret text, + PRIMARY KEY (id)); + +CREATE TABLE reviews ( + id text, + rating int, + text text, + userId text, + vehicleId text, + createdAt decimal, + updatedAt decimal, + PRIMARY KEY (id)); + +CREATE TABLE users ( + id text, + email text, + firstName text, + lastName text, + PRIMARY KEY (id)); + +CREATE TABLE vehicles ( + id text, + make text, + model text, + year int, + images text, + numReviews int, + averageReview decimal, + PRIMARY KEY (id)); + +CREATE INDEX ON reviews (vehicleId); +CREATE INDEX ON users (email); +CREATE INDEX ON authentications (userId); +``` \ No newline at end of file diff --git a/typescript-express-reviews/src/api/Review/create.ts b/typescript-express-reviews/src/api/Review/create.ts index b528eba..03bca2f 100644 --- a/typescript-express-reviews/src/api/Review/create.ts +++ b/typescript-express-reviews/src/api/Review/create.ts @@ -3,14 +3,14 @@ import Review from '../../models/review'; import Vehicle from '../../models/vehicle'; async function create(request: Request, response: Response): Promise { - const review = await Review.create({ + const [review] = await Review.insertMany({ text: request.body.text, rating: request.body.rating, userId: request.body.userId, vehicleId: request.body.vehicleId }); - const vehicle = await Vehicle.findById({ _id: request.body.vehicleId }).orFail(); + const vehicle = await Vehicle.findOne({ id: request.body.vehicleId }).orFail(); response.status(200).json({ vehicle: vehicle, review: review }); } diff --git a/typescript-express-reviews/src/api/User/login.ts b/typescript-express-reviews/src/api/User/login.ts index 45a6d01..abf218e 100644 --- a/typescript-express-reviews/src/api/User/login.ts +++ b/typescript-express-reviews/src/api/User/login.ts @@ -16,10 +16,9 @@ async function login(request: Request, response: Response): Promise { return; } - const authentication = await Authentication.findOne({ - type: 'password', - userId: user._id - }); + const authentication = await Authentication.find({ + userId: user.id + }).then(authentications => authentications.find(auth => auth.type === 'password')); if (authentication == null) { response.status(404).json({ error: 'This account does not have a password set' diff --git a/typescript-express-reviews/src/api/User/register.ts b/typescript-express-reviews/src/api/User/register.ts index 800912f..2378608 100644 --- a/typescript-express-reviews/src/api/User/register.ts +++ b/typescript-express-reviews/src/api/User/register.ts @@ -13,19 +13,19 @@ async function register(request: Request, response: Response): Promise { return; } - const user = await User.create({ + const [user] = await User.insertMany([{ firstName: request.body.firstName, lastName: request.body.lastName, email: request.body.email - }); + }]); const salt = bcrypt.genSaltSync(10); const hash = bcrypt.hashSync(request.body.password, salt); - await Authentication.create({ + await Authentication.insertMany([{ type: 'password', - userId: user._id, + userId: user.id, secret: hash - }); + }]); response.status(200).json({ user: user }); } diff --git a/typescript-express-reviews/src/api/Vehicle/findById.ts b/typescript-express-reviews/src/api/Vehicle/findById.ts index 0625ee4..5379079 100644 --- a/typescript-express-reviews/src/api/Vehicle/findById.ts +++ b/typescript-express-reviews/src/api/Vehicle/findById.ts @@ -14,7 +14,7 @@ async function last5(request: Request, response: Response): Promise { } const vehicle = await Vehicle. - findById({ _id: request.query?._id }). + findOne({ id: request.query?._id }). setOptions({ sanitizeFilter: true }); const reviews = await Review. find({ vehicleId }). diff --git a/typescript-express-reviews/src/models/authentication.ts b/typescript-express-reviews/src/models/authentication.ts index ee46210..8e8224c 100644 --- a/typescript-express-reviews/src/models/authentication.ts +++ b/typescript-express-reviews/src/models/authentication.ts @@ -1,6 +1,11 @@ import mongoose from './mongoose'; const schema = new mongoose.Schema({ + id: { + type: mongoose.Types.ObjectId, + required: true, + default: () => new mongoose.Types.ObjectId() + }, type: { type: String, required: true, @@ -9,7 +14,7 @@ const schema = new mongoose.Schema({ }, userId: { type: mongoose.Types.ObjectId, required: true }, secret: { type: String, required: true } -}); +}, { _id: false, versionKey: false }); const Authentication = mongoose.model('Authentication', schema); diff --git a/typescript-express-reviews/src/models/review.ts b/typescript-express-reviews/src/models/review.ts index 93f82ad..1b9feca 100644 --- a/typescript-express-reviews/src/models/review.ts +++ b/typescript-express-reviews/src/models/review.ts @@ -1,6 +1,11 @@ import mongoose from './mongoose'; const schema = new mongoose.Schema({ + id: { + type: mongoose.Types.ObjectId, + required: true, + default: () => new mongoose.Types.ObjectId() + }, rating: { type: Number, required: true, @@ -18,8 +23,16 @@ const schema = new mongoose.Schema({ vehicleId: { type: 'ObjectId', required: true + }, + createdAt: { + type: Number, + default: () => Date.now() + }, + updatedAt: { + type: Number, + default: () => Date.now() } -}, { timestamps: true }); +}, { _id: false, versionKey: false, timestamps: true }); schema.virtual('user', { ref: 'User', @@ -39,7 +52,7 @@ schema.pre('save', async function updateVehicleRating() { if (!this.isNew) { return; } - const vehicle = await mongoose.model('Vehicle').findById(this.vehicleId).orFail(); + const vehicle = await mongoose.model('Vehicle').findOne({ id: this.vehicleId }).orFail(); vehicle.numReviews += 1; const vehicleReviews = await mongoose.model('Review').find({ vehicleId: this.vehicleId }); const reviewRatings = vehicleReviews.map((entry) => entry.rating); diff --git a/typescript-express-reviews/src/models/user.ts b/typescript-express-reviews/src/models/user.ts index d3a9b7f..47be6d0 100644 --- a/typescript-express-reviews/src/models/user.ts +++ b/typescript-express-reviews/src/models/user.ts @@ -1,6 +1,11 @@ import mongoose from './mongoose'; const schema = new mongoose.Schema({ + id: { + type: mongoose.Types.ObjectId, + required: true, + default: () => new mongoose.Types.ObjectId() + }, email: { type: String, required: true, @@ -14,7 +19,7 @@ const schema = new mongoose.Schema({ type: String, required: true } -}); +}, { _id: false, versionKey: false }); schema.virtual('displayName').get(function() { return this.firstName + ' ' + this.lastName.slice(0, 1) + '.'; diff --git a/typescript-express-reviews/src/models/vehicle.ts b/typescript-express-reviews/src/models/vehicle.ts index a724e88..ba40bbb 100644 --- a/typescript-express-reviews/src/models/vehicle.ts +++ b/typescript-express-reviews/src/models/vehicle.ts @@ -1,6 +1,11 @@ import mongoose from './mongoose'; const schema = new mongoose.Schema({ + id: { + type: mongoose.Types.ObjectId, + required: true, + default: () => new mongoose.Types.ObjectId() + }, make: { type: String, required: true @@ -15,7 +20,16 @@ const schema = new mongoose.Schema({ validate: (v: number) => Number.isInteger(v) && v >= 1950 }, images: { - type: [String] + type: String, + get(v?: string | null) { + return v == null ? v : JSON.parse(v); + }, + set(v: unknown) { + if (v == null) { + return v; + } + return typeof v === 'string' ? v : JSON.stringify(v); + }, }, numReviews: { type: Number, @@ -27,7 +41,7 @@ const schema = new mongoose.Schema({ required: true, default: 0 } -}); +}, { _id: false, versionKey: false }); const Vehicle = mongoose.model('Vehicle', schema); diff --git a/typescript-express-reviews/src/seed/seed.ts b/typescript-express-reviews/src/seed/seed.ts index 323ae41..1532411 100644 --- a/typescript-express-reviews/src/seed/seed.ts +++ b/typescript-express-reviews/src/seed/seed.ts @@ -10,6 +10,9 @@ import User from '../models/user'; import Vehicle from '../models/vehicle'; import bcrypt from 'bcryptjs'; +import util from 'util'; +util.inspect.defaultOptions.depth = 6; + run().catch(err => { console.error(err); process.exit(-1); @@ -18,7 +21,7 @@ run().catch(err => { async function run() { await connect(); - const existingCollections = await mongoose.connection.listCollections() + /*const existingCollections = await mongoose.connection.listCollections() .then(collections => collections.map(c => c.name)); for (const Model of Object.values(mongoose.connection.models)) { @@ -32,9 +35,9 @@ async function run() { } // Then make sure the collection is empty await Model.deleteMany({}); - } + }*/ - const users = await User.create([ + const users = await User.insertMany([ { firstName: 'Dominic', lastName: 'Toretto', @@ -47,15 +50,15 @@ async function run() { } ]); for (let i = 0; i < users.length; i++) { - await Authentication.create({ + await Authentication.insertMany([{ type: 'password', - userId: users[i]._id, + userId: users[i].id, secret: await bcrypt.hash(users[i].firstName.toLowerCase(), 10) - }); + }]); } - const vehicles = await Vehicle.create([ + const vehicles = await Vehicle.insertMany([ { - _id: '0'.repeat(24), + id: '0'.repeat(24), make: 'Tesla', model: 'Model S', year: 2022, @@ -67,7 +70,7 @@ async function run() { averageReviews: 0 }, { - _id: '1'.repeat(24), + id: '1'.repeat(24), make: 'Porsche', model: 'Taycan', year: 2022, @@ -80,16 +83,16 @@ async function run() { } ]); - await Review.create([ + await Review.insertMany([ { - vehicleId: vehicles[1]._id, - userId: users[0]._id, + vehicleId: vehicles[1].id, + userId: users[0].id, text: 'When you live your life a quarter of a mile at a time, it ain\'t just about being fast. I needed a 10 second car, and this car delivers.', rating: 4 }, { - vehicleId: vehicles[0]._id, - userId: users[1]._id, + vehicleId: vehicles[0].id, + userId: users[1].id, text: 'I need NOS. My car topped out at 140 miles per hour this morning.', rating: 3 } diff --git a/typescript-express-reviews/tests/Review.test.ts b/typescript-express-reviews/tests/Review.test.ts index 5f8c0c2..202d559 100644 --- a/typescript-express-reviews/tests/Review.test.ts +++ b/typescript-express-reviews/tests/Review.test.ts @@ -25,12 +25,12 @@ describe('Review', function() { }; return res; }; - const user = await User.create({ + const [user] = await User.insertMany([{ email: 'test@localhost.com', firstName: 'Test', lastName: 'Testerson' - }); - const vehicle = await Vehicle.create( + }]); + const [vehicle] = await Vehicle.insertMany([ { make: 'Tesla', model: 'Model S', @@ -42,10 +42,10 @@ describe('Review', function() { numReviews: 0, averageReview: 0 } - ); + ]); const req = mockRequest({ - vehicleId: vehicle._id.toString(), - userId: user._id, + vehicleId: vehicle.id.toString(), + userId: user.id, rating: 4, text: 'The length of this text must be greater than 30 to pass validation.' }); @@ -65,12 +65,12 @@ describe('Review', function() { }; return res; }; - const user = await User.create({ + const [user] = await User.insertMany([{ email: 'test@localhost.com', firstName: 'Test', lastName: 'Testerson' - }); - const vehicle = await Vehicle.create( + }]); + const [vehicle] = await Vehicle.insertMany([ { make: 'Tesla', model: 'Model S', @@ -82,19 +82,19 @@ describe('Review', function() { numReviews: 0, averageReview: 0 }, - ); + ]); for (let i = 0; i < 6; i++) { - await Review.create({ + await Review.insertMany([{ rating: i > 5 ? 5 : i, text: 'This is a review that must have length greater than 30. ' + i, - vehicleId: vehicle._id, - userId: user._id - }); + vehicleId: vehicle.id, + userId: user.id + }]); } vehicle.numReviews = 6; vehicle.averageReview = 3; - await vehicle.save(); - const req = mockRequest({ vehicleId: vehicle._id.toString(), limit: 3, skip: 1 }); + await Vehicle.updateOne({ id: vehicle.id }, vehicle.getChanges()); + const req = mockRequest({ vehicleId: vehicle.id.toString(), limit: 3, skip: 1 }); const res = mockResponse(); await findByVehicle(req, res); diff --git a/typescript-express-reviews/tests/User.test.ts b/typescript-express-reviews/tests/User.test.ts index a437399..cee0bca 100644 --- a/typescript-express-reviews/tests/User.test.ts +++ b/typescript-express-reviews/tests/User.test.ts @@ -34,19 +34,19 @@ describe('User', function() { }); it('should login a user', async function() { - const user = await User.create({ + const [user] = await User.insertMany([{ email: 'test1@localhost.com', firstName: 'Test', lastName: 'Testerson' - }); + }]); const salt = bcrypt.genSaltSync(10); const hash = bcrypt.hashSync('password', salt); - await Authentication.create({ + await Authentication.insertMany([{ type: 'password', - userId: user._id, + userId: user.id, secret: hash - }); + }]); const mockRequest = (body) => ({ body diff --git a/typescript-express-reviews/tests/Vehicle.test.ts b/typescript-express-reviews/tests/Vehicle.test.ts index 03c74a1..64973fa 100644 --- a/typescript-express-reviews/tests/Vehicle.test.ts +++ b/typescript-express-reviews/tests/Vehicle.test.ts @@ -26,12 +26,12 @@ describe('Vehicle', function() { }; return res; }; - const user = await User.create({ + const [user] = await User.insertMany([{ email: 'test@localhost.com', firstName: 'Test', lastName: 'Testerson' - }); - const vehicle = await Vehicle.create( + }]); + const [vehicle] = await Vehicle.insertMany([ { make: 'Tesla', model: 'Model S', @@ -43,19 +43,19 @@ describe('Vehicle', function() { numReviews: 0, averageReview: 0 } - ); + ]); for (let i = 1; i < 7; i++) { - await Review.create({ + await Review.insertMany([{ rating: i > 5 ? 5 : i, text: 'This is a review that must have length greater than 30. ' + i, - vehicleId: vehicle._id, - userId: user._id - }); + vehicleId: vehicle.id, + userId: user.id + }]); } vehicle.numReviews = 5; vehicle.averageReview = 3; - await vehicle.save(); - const req = mockRequest({ _id: vehicle._id.toString(), limit: 5 }); + await Vehicle.updateOne({ id: vehicle.id }, vehicle.getChanges()); + const req = mockRequest({ _id: vehicle.id.toString(), limit: 5 }); const res = mockResponse(); await findById(req, res); assert(res.json.getCall(0).args[0].vehicle); diff --git a/typescript-express-reviews/tests/index.test.ts b/typescript-express-reviews/tests/index.test.ts index 56c51a7..5f24a2c 100644 --- a/typescript-express-reviews/tests/index.test.ts +++ b/typescript-express-reviews/tests/index.test.ts @@ -7,6 +7,9 @@ import { after, before } from 'mocha'; import connect from '../src/models/connect'; import mongoose from 'mongoose'; +import util from 'util'; +util.inspect.defaultOptions.depth = 6; + before(async function() { this.timeout(30000); @@ -15,9 +18,9 @@ before(async function() { // Make sure all collections are created in Stargate, _after_ calling // `connect()`. stargate-mongoose doesn't currently support buffering on // connection helpers. - await Promise.all(Object.values(mongoose.models).map(Model => { + /*await Promise.all(Object.values(mongoose.models).map(Model => { return Model.createCollection(); - })); + }));*/ }); // `deleteMany()` currently does nothing From ea2c7753363675a10a5cae5ecb7848d979590c66 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 16 Aug 2024 14:45:59 -0400 Subject: [PATCH 04/25] change discord bot to not use soft deletes, instead deleteOne() on all --- discord-bot/README.md | 3 --- discord-bot/commands/countdocuments.js | 2 +- discord-bot/models/bot.js | 6 +----- discord-bot/test/countdocuments.test.js | 5 +++-- discord-bot/test/createdocument.test.js | 7 ++++--- discord-bot/test/setup.js | 4 ++-- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/discord-bot/README.md b/discord-bot/README.md index da8cbf2..f5452de 100644 --- a/discord-bot/README.md +++ b/discord-bot/README.md @@ -56,8 +56,5 @@ Create table: CREATE TABLE bots ( id text, name text, - deleted int, PRIMARY KEY (id)); - -CREATE INDEX isDeleted ON bots (deleted); ``` \ No newline at end of file diff --git a/discord-bot/commands/countdocuments.js b/discord-bot/commands/countdocuments.js index 15acc8f..ad77c1d 100644 --- a/discord-bot/commands/countdocuments.js +++ b/discord-bot/commands/countdocuments.js @@ -8,7 +8,7 @@ module.exports = { async execute(interaction) { // `countDocuments()` currently not implemented, see: // https://github.com/stargate/stargate-mongoose/pull/48 - const num = await Bot.find({ deleted: 0 }).then(res => res.length); + const num = await Bot.find({}).then(res => res.length); console.log(new Date(), 'count', num); await interaction.reply(num.toString()); } diff --git a/discord-bot/models/bot.js b/discord-bot/models/bot.js index 8bed592..b3ea7b9 100644 --- a/discord-bot/models/bot.js +++ b/discord-bot/models/bot.js @@ -8,11 +8,7 @@ const botSchema = new mongoose.Schema({ required: true, default: () => new mongoose.Types.ObjectId() }, - name: String, - deleted: { - type: Number, - default: 0 - } + name: String }, { _id: false, versionKey: false }); const Bot = mongoose.model('Bot', botSchema); diff --git a/discord-bot/test/countdocuments.test.js b/discord-bot/test/countdocuments.test.js index f0c0a7f..a34cab6 100644 --- a/discord-bot/test/countdocuments.test.js +++ b/discord-bot/test/countdocuments.test.js @@ -8,8 +8,9 @@ const sinon = require('sinon'); describe('countdocuments', function() { it('returns the number of bot documents', async function() { - for (const doc of await Bot.find({ deleted: 0 })) { - await Bot.updateOne({ id: doc.id }, { deleted: 1 }); + const docs = await Bot.find({}); + for (const doc of docs) { + await Bot.deleteOne({ id: doc.id }); } await Bot.insertMany({ name: 'test' }); diff --git a/discord-bot/test/createdocument.test.js b/discord-bot/test/createdocument.test.js index 2f4ef00..061aba3 100644 --- a/discord-bot/test/createdocument.test.js +++ b/discord-bot/test/createdocument.test.js @@ -8,8 +8,9 @@ const sinon = require('sinon'); describe('createdocument', function() { it('inserts a new document', async function() { - for (const doc of await Bot.find({ deleted: 0 })) { - await Bot.updateOne({ id: doc.id }, { deleted: 1 }); + let docs = await Bot.find({}); + for (const doc of docs) { + await Bot.deleteOne({ id: doc.id }); } const interaction = { @@ -19,7 +20,7 @@ describe('createdocument', function() { assert.ok(interaction.reply.calledOnce); assert.deepEqual(interaction.reply.getCalls()[0].args, ['done!']); - const docs = await Bot.find({ deleted: 0 }); + docs = await Bot.find({}); assert.equal(docs.length, 1); assert.equal(docs[0].name, 'I am a document'); }); diff --git a/discord-bot/test/setup.js b/discord-bot/test/setup.js index 73d5743..5bd33e3 100644 --- a/discord-bot/test/setup.js +++ b/discord-bot/test/setup.js @@ -16,9 +16,9 @@ before(async function() { this.timeout(30000); await mongoose.connect(uri, jsonApiConnectOptions); - const docs = await Bot.find({ deleted: 0 }); + const docs = await Bot.find({}); for (const doc of docs) { - await Bot.updateOne({ id: doc.id }, { deleted: 1 }); + await Bot.deleteOne({ id: doc.id }); } }); From 89550929247cdeb408fae2fd2e0c4d414a3b41b7 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 16 Aug 2024 15:58:59 -0400 Subject: [PATCH 05/25] Revert "change discord bot to not use soft deletes, instead deleteOne() on all" This reverts commit ea2c7753363675a10a5cae5ecb7848d979590c66. --- discord-bot/README.md | 3 +++ discord-bot/commands/countdocuments.js | 2 +- discord-bot/models/bot.js | 6 +++++- discord-bot/test/countdocuments.test.js | 5 ++--- discord-bot/test/createdocument.test.js | 7 +++---- discord-bot/test/setup.js | 4 ++-- 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/discord-bot/README.md b/discord-bot/README.md index f5452de..da8cbf2 100644 --- a/discord-bot/README.md +++ b/discord-bot/README.md @@ -56,5 +56,8 @@ Create table: CREATE TABLE bots ( id text, name text, + deleted int, PRIMARY KEY (id)); + +CREATE INDEX isDeleted ON bots (deleted); ``` \ No newline at end of file diff --git a/discord-bot/commands/countdocuments.js b/discord-bot/commands/countdocuments.js index ad77c1d..15acc8f 100644 --- a/discord-bot/commands/countdocuments.js +++ b/discord-bot/commands/countdocuments.js @@ -8,7 +8,7 @@ module.exports = { async execute(interaction) { // `countDocuments()` currently not implemented, see: // https://github.com/stargate/stargate-mongoose/pull/48 - const num = await Bot.find({}).then(res => res.length); + const num = await Bot.find({ deleted: 0 }).then(res => res.length); console.log(new Date(), 'count', num); await interaction.reply(num.toString()); } diff --git a/discord-bot/models/bot.js b/discord-bot/models/bot.js index b3ea7b9..8bed592 100644 --- a/discord-bot/models/bot.js +++ b/discord-bot/models/bot.js @@ -8,7 +8,11 @@ const botSchema = new mongoose.Schema({ required: true, default: () => new mongoose.Types.ObjectId() }, - name: String + name: String, + deleted: { + type: Number, + default: 0 + } }, { _id: false, versionKey: false }); const Bot = mongoose.model('Bot', botSchema); diff --git a/discord-bot/test/countdocuments.test.js b/discord-bot/test/countdocuments.test.js index a34cab6..f0c0a7f 100644 --- a/discord-bot/test/countdocuments.test.js +++ b/discord-bot/test/countdocuments.test.js @@ -8,9 +8,8 @@ const sinon = require('sinon'); describe('countdocuments', function() { it('returns the number of bot documents', async function() { - const docs = await Bot.find({}); - for (const doc of docs) { - await Bot.deleteOne({ id: doc.id }); + for (const doc of await Bot.find({ deleted: 0 })) { + await Bot.updateOne({ id: doc.id }, { deleted: 1 }); } await Bot.insertMany({ name: 'test' }); diff --git a/discord-bot/test/createdocument.test.js b/discord-bot/test/createdocument.test.js index 061aba3..2f4ef00 100644 --- a/discord-bot/test/createdocument.test.js +++ b/discord-bot/test/createdocument.test.js @@ -8,9 +8,8 @@ const sinon = require('sinon'); describe('createdocument', function() { it('inserts a new document', async function() { - let docs = await Bot.find({}); - for (const doc of docs) { - await Bot.deleteOne({ id: doc.id }); + for (const doc of await Bot.find({ deleted: 0 })) { + await Bot.updateOne({ id: doc.id }, { deleted: 1 }); } const interaction = { @@ -20,7 +19,7 @@ describe('createdocument', function() { assert.ok(interaction.reply.calledOnce); assert.deepEqual(interaction.reply.getCalls()[0].args, ['done!']); - docs = await Bot.find({}); + const docs = await Bot.find({ deleted: 0 }); assert.equal(docs.length, 1); assert.equal(docs[0].name, 'I am a document'); }); diff --git a/discord-bot/test/setup.js b/discord-bot/test/setup.js index 5bd33e3..73d5743 100644 --- a/discord-bot/test/setup.js +++ b/discord-bot/test/setup.js @@ -16,9 +16,9 @@ before(async function() { this.timeout(30000); await mongoose.connect(uri, jsonApiConnectOptions); - const docs = await Bot.find({}); + const docs = await Bot.find({ deleted: 0 }); for (const doc of docs) { - await Bot.deleteOne({ id: doc.id }); + await Bot.updateOne({ id: doc.id }, { deleted: 1 }); } }); From 6961b6c69c39b06c9c59c83014ec45a5de6a658c Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 19 Aug 2024 17:04:09 -0400 Subject: [PATCH 06/25] Revert "Revert "change discord bot to not use soft deletes, instead deleteOne() on all"" This reverts commit 89550929247cdeb408fae2fd2e0c4d414a3b41b7. --- discord-bot/README.md | 3 --- discord-bot/commands/countdocuments.js | 2 +- discord-bot/models/bot.js | 6 +----- discord-bot/test/countdocuments.test.js | 5 +++-- discord-bot/test/createdocument.test.js | 7 ++++--- discord-bot/test/setup.js | 4 ++-- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/discord-bot/README.md b/discord-bot/README.md index da8cbf2..f5452de 100644 --- a/discord-bot/README.md +++ b/discord-bot/README.md @@ -56,8 +56,5 @@ Create table: CREATE TABLE bots ( id text, name text, - deleted int, PRIMARY KEY (id)); - -CREATE INDEX isDeleted ON bots (deleted); ``` \ No newline at end of file diff --git a/discord-bot/commands/countdocuments.js b/discord-bot/commands/countdocuments.js index 85f712f..6668e81 100644 --- a/discord-bot/commands/countdocuments.js +++ b/discord-bot/commands/countdocuments.js @@ -6,7 +6,7 @@ const Bot = require('../models/bot'); module.exports = { data: new SlashCommandBuilder().setName('count').setDescription('counts documents in the database'), async execute(interaction) { - const num = await Bot.find({ deleted: 0 }).then(res => res.length); + const num = await Bot.find({}).then(res => res.length); console.log(new Date(), 'count', num); await interaction.reply(num.toString()); } diff --git a/discord-bot/models/bot.js b/discord-bot/models/bot.js index 8bed592..b3ea7b9 100644 --- a/discord-bot/models/bot.js +++ b/discord-bot/models/bot.js @@ -8,11 +8,7 @@ const botSchema = new mongoose.Schema({ required: true, default: () => new mongoose.Types.ObjectId() }, - name: String, - deleted: { - type: Number, - default: 0 - } + name: String }, { _id: false, versionKey: false }); const Bot = mongoose.model('Bot', botSchema); diff --git a/discord-bot/test/countdocuments.test.js b/discord-bot/test/countdocuments.test.js index f0c0a7f..a34cab6 100644 --- a/discord-bot/test/countdocuments.test.js +++ b/discord-bot/test/countdocuments.test.js @@ -8,8 +8,9 @@ const sinon = require('sinon'); describe('countdocuments', function() { it('returns the number of bot documents', async function() { - for (const doc of await Bot.find({ deleted: 0 })) { - await Bot.updateOne({ id: doc.id }, { deleted: 1 }); + const docs = await Bot.find({}); + for (const doc of docs) { + await Bot.deleteOne({ id: doc.id }); } await Bot.insertMany({ name: 'test' }); diff --git a/discord-bot/test/createdocument.test.js b/discord-bot/test/createdocument.test.js index 2f4ef00..061aba3 100644 --- a/discord-bot/test/createdocument.test.js +++ b/discord-bot/test/createdocument.test.js @@ -8,8 +8,9 @@ const sinon = require('sinon'); describe('createdocument', function() { it('inserts a new document', async function() { - for (const doc of await Bot.find({ deleted: 0 })) { - await Bot.updateOne({ id: doc.id }, { deleted: 1 }); + let docs = await Bot.find({}); + for (const doc of docs) { + await Bot.deleteOne({ id: doc.id }); } const interaction = { @@ -19,7 +20,7 @@ describe('createdocument', function() { assert.ok(interaction.reply.calledOnce); assert.deepEqual(interaction.reply.getCalls()[0].args, ['done!']); - const docs = await Bot.find({ deleted: 0 }); + docs = await Bot.find({}); assert.equal(docs.length, 1); assert.equal(docs[0].name, 'I am a document'); }); diff --git a/discord-bot/test/setup.js b/discord-bot/test/setup.js index 73d5743..5bd33e3 100644 --- a/discord-bot/test/setup.js +++ b/discord-bot/test/setup.js @@ -16,9 +16,9 @@ before(async function() { this.timeout(30000); await mongoose.connect(uri, jsonApiConnectOptions); - const docs = await Bot.find({ deleted: 0 }); + const docs = await Bot.find({}); for (const doc of docs) { - await Bot.updateOne({ id: doc.id }, { deleted: 1 }); + await Bot.deleteOne({ id: doc.id }); } }); From 6ff96d12f35d26835c8daaf66527d12e723fecd9 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 21 Aug 2024 17:03:43 -0400 Subject: [PATCH 07/25] snake_case all property names re: stargate/data-api#1349 --- typescript-express-reviews/README.md | 22 +++++++-------- .../src/api/Review/create.ts | 4 +-- .../src/api/Review/findByVehicle.ts | 8 +++--- .../src/api/Vehicle/findById.ts | 8 +++--- .../src/models/authentication.ts | 2 +- .../src/models/review.ts | 22 +++++++-------- typescript-express-reviews/src/models/user.ts | 6 ++-- .../src/models/vehicle.ts | 12 ++++---- typescript-express-reviews/src/seed/seed.ts | 28 +++++++++---------- 9 files changed, 57 insertions(+), 55 deletions(-) diff --git a/typescript-express-reviews/README.md b/typescript-express-reviews/README.md index cc2dd62..f40d1fb 100644 --- a/typescript-express-reviews/README.md +++ b/typescript-express-reviews/README.md @@ -38,7 +38,7 @@ Make sure you have Node.js 14 or higher and a local Stargate instance running as CREATE TABLE authentications ( id text, type text, - userId text, + user_id text, secret text, PRIMARY KEY (id)); @@ -46,17 +46,17 @@ CREATE TABLE reviews ( id text, rating int, text text, - userId text, - vehicleId text, - createdAt decimal, - updatedAt decimal, + user_id text, + vehicle_id text, + created_at decimal, + updated_at decimal, PRIMARY KEY (id)); CREATE TABLE users ( id text, email text, - firstName text, - lastName text, + first_name text, + last_name text, PRIMARY KEY (id)); CREATE TABLE vehicles ( @@ -65,11 +65,11 @@ CREATE TABLE vehicles ( model text, year int, images text, - numReviews int, - averageReview decimal, + num_reviews int, + average_review decimal, PRIMARY KEY (id)); -CREATE INDEX ON reviews (vehicleId); +CREATE INDEX ON reviews (vehicle_id); CREATE INDEX ON users (email); -CREATE INDEX ON authentications (userId); +CREATE INDEX ON authentications (user_id); ``` \ No newline at end of file diff --git a/typescript-express-reviews/src/api/Review/create.ts b/typescript-express-reviews/src/api/Review/create.ts index 03bca2f..e7d9189 100644 --- a/typescript-express-reviews/src/api/Review/create.ts +++ b/typescript-express-reviews/src/api/Review/create.ts @@ -6,8 +6,8 @@ async function create(request: Request, response: Response): Promise { const [review] = await Review.insertMany({ text: request.body.text, rating: request.body.rating, - userId: request.body.userId, - vehicleId: request.body.vehicleId + user_id: request.body.userId, + vehicle_id: request.body.vehicleId }); const vehicle = await Vehicle.findOne({ id: request.body.vehicleId }).orFail(); diff --git a/typescript-express-reviews/src/api/Review/findByVehicle.ts b/typescript-express-reviews/src/api/Review/findByVehicle.ts index aabb461..01849e8 100644 --- a/typescript-express-reviews/src/api/Review/findByVehicle.ts +++ b/typescript-express-reviews/src/api/Review/findByVehicle.ts @@ -17,12 +17,12 @@ async function findByVehicle (request: Request, response: Response): Promise { findOne({ id: request.query?._id }). setOptions({ sanitizeFilter: true }); const reviews = await Review. - find({ vehicleId }). - sort({ createdAt: -1 }). + find({ vehicle_id: vehicleId }). + sort({ created_at: -1 }). limit(limit). - populate('user'). - populate('vehicle'). + //populate('user'). + //populate('vehicle'). setOptions({ sanitizeFilter: true }); response.status(200).json({ diff --git a/typescript-express-reviews/src/models/authentication.ts b/typescript-express-reviews/src/models/authentication.ts index 8e8224c..5716436 100644 --- a/typescript-express-reviews/src/models/authentication.ts +++ b/typescript-express-reviews/src/models/authentication.ts @@ -12,7 +12,7 @@ const schema = new mongoose.Schema({ enum: ['password', 'one time'], default: 'password' }, - userId: { type: mongoose.Types.ObjectId, required: true }, + user_id: { type: mongoose.Types.ObjectId, required: true }, secret: { type: String, required: true } }, { _id: false, versionKey: false }); diff --git a/typescript-express-reviews/src/models/review.ts b/typescript-express-reviews/src/models/review.ts index 1b9feca..f72f017 100644 --- a/typescript-express-reviews/src/models/review.ts +++ b/typescript-express-reviews/src/models/review.ts @@ -16,35 +16,35 @@ const schema = new mongoose.Schema({ required: true, validate: (v: string) => v.length >= 30 }, - userId: { + user_id: { type: 'ObjectId', required: true }, - vehicleId: { + vehicle_id: { type: 'ObjectId', required: true }, - createdAt: { + created_at: { type: Number, default: () => Date.now() }, - updatedAt: { + updated_at: { type: Number, default: () => Date.now() } -}, { _id: false, versionKey: false, timestamps: true }); +}, { _id: false, versionKey: false, timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }); schema.virtual('user', { ref: 'User', - localField: 'userId', - foreignField: '_id', + localField: 'user_id', + foreignField: 'id', justOne: true }); schema.virtual('vehicle', { ref: 'Vehicle', - localField: 'vehicleId', - foreignField: '_id', + localField: 'vehicle_id', + foreignField: 'id', justOne: true }); @@ -52,9 +52,9 @@ schema.pre('save', async function updateVehicleRating() { if (!this.isNew) { return; } - const vehicle = await mongoose.model('Vehicle').findOne({ id: this.vehicleId }).orFail(); + const vehicle = await mongoose.model('Vehicle').findOne({ id: this.vehicle_id }).orFail(); vehicle.numReviews += 1; - const vehicleReviews = await mongoose.model('Review').find({ vehicleId: this.vehicleId }); + const vehicleReviews = await mongoose.model('Review').find({ vehicle_id: this.vehicle_id }); const reviewRatings = vehicleReviews.map((entry) => entry.rating); reviewRatings.push(this.rating); const average = calculateAverage(reviewRatings); diff --git a/typescript-express-reviews/src/models/user.ts b/typescript-express-reviews/src/models/user.ts index 47be6d0..8f19475 100644 --- a/typescript-express-reviews/src/models/user.ts +++ b/typescript-express-reviews/src/models/user.ts @@ -11,18 +11,18 @@ const schema = new mongoose.Schema({ required: true, unique: true }, - firstName: { + first_name: { type: String, required: true }, - lastName: { + last_name: { type: String, required: true } }, { _id: false, versionKey: false }); schema.virtual('displayName').get(function() { - return this.firstName + ' ' + this.lastName.slice(0, 1) + '.'; + return this.first_name + ' ' + this.last_name.slice(0, 1) + '.'; }); const User = mongoose.model('User', schema); diff --git a/typescript-express-reviews/src/models/vehicle.ts b/typescript-express-reviews/src/models/vehicle.ts index ba40bbb..c5c056f 100644 --- a/typescript-express-reviews/src/models/vehicle.ts +++ b/typescript-express-reviews/src/models/vehicle.ts @@ -1,5 +1,7 @@ import mongoose from './mongoose'; +const imagesSchemaType = new mongoose.Schema.Types.Array('images', { type: [String] }); + const schema = new mongoose.Schema({ id: { type: mongoose.Types.ObjectId, @@ -24,19 +26,19 @@ const schema = new mongoose.Schema({ get(v?: string | null) { return v == null ? v : JSON.parse(v); }, - set(v: unknown) { + set(this: mongoose.Document, v: unknown) { if (v == null) { return v; } - return typeof v === 'string' ? v : JSON.stringify(v); - }, + return typeof v === 'string' ? v : JSON.stringify(imagesSchemaType.cast(v, this)); + } }, - numReviews: { + num_reviews: { type: Number, required: true, default: 0 }, - averageReview: { + average_review: { type: Number, required: true, default: 0 diff --git a/typescript-express-reviews/src/seed/seed.ts b/typescript-express-reviews/src/seed/seed.ts index 1532411..0649636 100644 --- a/typescript-express-reviews/src/seed/seed.ts +++ b/typescript-express-reviews/src/seed/seed.ts @@ -39,21 +39,21 @@ async function run() { const users = await User.insertMany([ { - firstName: 'Dominic', - lastName: 'Toretto', + first_name: 'Dominic', + last_name: 'Toretto', email: 'dom@fastandfurious.com' }, { - firstName: 'Brian', - lastName: 'O\'Connor', + first_name: 'Brian', + last_name: 'O\'Connor', email: 'brian@fastandfurious.com' } ]); for (let i = 0; i < users.length; i++) { await Authentication.insertMany([{ type: 'password', - userId: users[i].id, - secret: await bcrypt.hash(users[i].firstName.toLowerCase(), 10) + user_id: users[i].id, + secret: await bcrypt.hash(users[i].first_name.toLowerCase(), 10) }]); } const vehicles = await Vehicle.insertMany([ @@ -66,8 +66,8 @@ async function run() { 'https://tesla-cdn.thron.com/delivery/public/image/tesla/6139697c-9d6a-4579-837e-a9fc5df4a773/bvlatuR/std/1200x628/Model-3-Homepage-Social-LHD', 'https://www.tesla.com/sites/default/files/images/blogs/models_blog_post.jpg' ], - numReviews: 0, - averageReviews: 0 + num_reviews: 0, + average_reviews: 0 }, { id: '1'.repeat(24), @@ -78,21 +78,21 @@ async function run() { 'https://www.motortrend.com/uploads/sites/5/2020/02/2020-Porsche-Taycan-Turbo-S-Track-Ride-5.gif?fit=around%7C875:492', 'https://newsroom.porsche.com/.imaging/mte/porsche-templating-theme/image_1290x726/dam/pnr/2021/Products/Free-Software-Update-Taycan/Free-Software-Update-for-early-Porsche-Taycan_2.jpeg/jcr:content/Free%20Software-Update%20for%20early%20Porsche%20Taycan_2.jpeg' ], - numReviews: 0, - averageReviews: 0 + num_reviews: 0, + average_reviews: 0 } ]); await Review.insertMany([ { - vehicleId: vehicles[1].id, - userId: users[0].id, + vehicle_id: vehicles[1].id, + user_id: users[0].id, text: 'When you live your life a quarter of a mile at a time, it ain\'t just about being fast. I needed a 10 second car, and this car delivers.', rating: 4 }, { - vehicleId: vehicles[0].id, - userId: users[1].id, + vehicle_id: vehicles[0].id, + user_id: users[1].id, text: 'I need NOS. My car topped out at 140 miles per hour this morning.', rating: 3 } From 79c3e1af6dde57e15b0e194476d8db82e0e26d6e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 25 Aug 2024 17:27:14 -0400 Subject: [PATCH 08/25] camelcase names re: stargate/data-api#1355 --- typescript-express-reviews/README.md | 22 +++++++-------- .../src/api/Review/create.ts | 4 +-- .../src/api/Review/findByVehicle.ts | 2 +- .../src/api/Vehicle/findById.ts | 4 +-- .../src/models/authentication.ts | 2 +- .../src/models/review.ts | 16 +++++------ typescript-express-reviews/src/models/user.ts | 6 ++-- .../src/models/vehicle.ts | 4 +-- typescript-express-reviews/src/seed/seed.ts | 28 +++++++++---------- 9 files changed, 44 insertions(+), 44 deletions(-) diff --git a/typescript-express-reviews/README.md b/typescript-express-reviews/README.md index f40d1fb..f8fa226 100644 --- a/typescript-express-reviews/README.md +++ b/typescript-express-reviews/README.md @@ -38,7 +38,7 @@ Make sure you have Node.js 14 or higher and a local Stargate instance running as CREATE TABLE authentications ( id text, type text, - user_id text, + "userId" text, secret text, PRIMARY KEY (id)); @@ -46,17 +46,17 @@ CREATE TABLE reviews ( id text, rating int, text text, - user_id text, - vehicle_id text, - created_at decimal, - updated_at decimal, + "userId" text, + "vehicleId" text, + "createdAt" decimal, + "updatedAt" decimal, PRIMARY KEY (id)); CREATE TABLE users ( id text, email text, - first_name text, - last_name text, + "firstName" text, + "lastName" text, PRIMARY KEY (id)); CREATE TABLE vehicles ( @@ -65,11 +65,11 @@ CREATE TABLE vehicles ( model text, year int, images text, - num_reviews int, - average_review decimal, + "numReviews" int, + "averageReview" decimal, PRIMARY KEY (id)); -CREATE INDEX ON reviews (vehicle_id); +CREATE INDEX ON reviews ("vehicleId"); CREATE INDEX ON users (email); -CREATE INDEX ON authentications (user_id); +CREATE INDEX ON authentications ("userId"); ``` \ No newline at end of file diff --git a/typescript-express-reviews/src/api/Review/create.ts b/typescript-express-reviews/src/api/Review/create.ts index e7d9189..03bca2f 100644 --- a/typescript-express-reviews/src/api/Review/create.ts +++ b/typescript-express-reviews/src/api/Review/create.ts @@ -6,8 +6,8 @@ async function create(request: Request, response: Response): Promise { const [review] = await Review.insertMany({ text: request.body.text, rating: request.body.rating, - user_id: request.body.userId, - vehicle_id: request.body.vehicleId + userId: request.body.userId, + vehicleId: request.body.vehicleId }); const vehicle = await Vehicle.findOne({ id: request.body.vehicleId }).orFail(); diff --git a/typescript-express-reviews/src/api/Review/findByVehicle.ts b/typescript-express-reviews/src/api/Review/findByVehicle.ts index 01849e8..3ed9251 100644 --- a/typescript-express-reviews/src/api/Review/findByVehicle.ts +++ b/typescript-express-reviews/src/api/Review/findByVehicle.ts @@ -17,7 +17,7 @@ async function findByVehicle (request: Request, response: Response): Promise { findOne({ id: request.query?._id }). setOptions({ sanitizeFilter: true }); const reviews = await Review. - find({ vehicle_id: vehicleId }). - sort({ created_at: -1 }). + find({ vehicleId: vehicleId }). + sort({ createdAt: -1 }). limit(limit). //populate('user'). //populate('vehicle'). diff --git a/typescript-express-reviews/src/models/authentication.ts b/typescript-express-reviews/src/models/authentication.ts index 5716436..8e8224c 100644 --- a/typescript-express-reviews/src/models/authentication.ts +++ b/typescript-express-reviews/src/models/authentication.ts @@ -12,7 +12,7 @@ const schema = new mongoose.Schema({ enum: ['password', 'one time'], default: 'password' }, - user_id: { type: mongoose.Types.ObjectId, required: true }, + userId: { type: mongoose.Types.ObjectId, required: true }, secret: { type: String, required: true } }, { _id: false, versionKey: false }); diff --git a/typescript-express-reviews/src/models/review.ts b/typescript-express-reviews/src/models/review.ts index f72f017..bc47b88 100644 --- a/typescript-express-reviews/src/models/review.ts +++ b/typescript-express-reviews/src/models/review.ts @@ -16,23 +16,23 @@ const schema = new mongoose.Schema({ required: true, validate: (v: string) => v.length >= 30 }, - user_id: { + userId: { type: 'ObjectId', required: true }, - vehicle_id: { + vehicleId: { type: 'ObjectId', required: true }, - created_at: { + createdAt: { type: Number, default: () => Date.now() }, - updated_at: { + updatedAt: { type: Number, default: () => Date.now() } -}, { _id: false, versionKey: false, timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }); +}, { _id: false, versionKey: false, timestamps: true }); schema.virtual('user', { ref: 'User', @@ -43,7 +43,7 @@ schema.virtual('user', { schema.virtual('vehicle', { ref: 'Vehicle', - localField: 'vehicle_id', + localField: 'vehicleId', foreignField: 'id', justOne: true }); @@ -52,9 +52,9 @@ schema.pre('save', async function updateVehicleRating() { if (!this.isNew) { return; } - const vehicle = await mongoose.model('Vehicle').findOne({ id: this.vehicle_id }).orFail(); + const vehicle = await mongoose.model('Vehicle').findOne({ id: this.vehicleId }).orFail(); vehicle.numReviews += 1; - const vehicleReviews = await mongoose.model('Review').find({ vehicle_id: this.vehicle_id }); + const vehicleReviews = await mongoose.model('Review').find({ vehicle_id: this.vehicleId }); const reviewRatings = vehicleReviews.map((entry) => entry.rating); reviewRatings.push(this.rating); const average = calculateAverage(reviewRatings); diff --git a/typescript-express-reviews/src/models/user.ts b/typescript-express-reviews/src/models/user.ts index 8f19475..47be6d0 100644 --- a/typescript-express-reviews/src/models/user.ts +++ b/typescript-express-reviews/src/models/user.ts @@ -11,18 +11,18 @@ const schema = new mongoose.Schema({ required: true, unique: true }, - first_name: { + firstName: { type: String, required: true }, - last_name: { + lastName: { type: String, required: true } }, { _id: false, versionKey: false }); schema.virtual('displayName').get(function() { - return this.first_name + ' ' + this.last_name.slice(0, 1) + '.'; + return this.firstName + ' ' + this.lastName.slice(0, 1) + '.'; }); const User = mongoose.model('User', schema); diff --git a/typescript-express-reviews/src/models/vehicle.ts b/typescript-express-reviews/src/models/vehicle.ts index c5c056f..50dec51 100644 --- a/typescript-express-reviews/src/models/vehicle.ts +++ b/typescript-express-reviews/src/models/vehicle.ts @@ -33,12 +33,12 @@ const schema = new mongoose.Schema({ return typeof v === 'string' ? v : JSON.stringify(imagesSchemaType.cast(v, this)); } }, - num_reviews: { + numReviews: { type: Number, required: true, default: 0 }, - average_review: { + averageReview: { type: Number, required: true, default: 0 diff --git a/typescript-express-reviews/src/seed/seed.ts b/typescript-express-reviews/src/seed/seed.ts index 0649636..1532411 100644 --- a/typescript-express-reviews/src/seed/seed.ts +++ b/typescript-express-reviews/src/seed/seed.ts @@ -39,21 +39,21 @@ async function run() { const users = await User.insertMany([ { - first_name: 'Dominic', - last_name: 'Toretto', + firstName: 'Dominic', + lastName: 'Toretto', email: 'dom@fastandfurious.com' }, { - first_name: 'Brian', - last_name: 'O\'Connor', + firstName: 'Brian', + lastName: 'O\'Connor', email: 'brian@fastandfurious.com' } ]); for (let i = 0; i < users.length; i++) { await Authentication.insertMany([{ type: 'password', - user_id: users[i].id, - secret: await bcrypt.hash(users[i].first_name.toLowerCase(), 10) + userId: users[i].id, + secret: await bcrypt.hash(users[i].firstName.toLowerCase(), 10) }]); } const vehicles = await Vehicle.insertMany([ @@ -66,8 +66,8 @@ async function run() { 'https://tesla-cdn.thron.com/delivery/public/image/tesla/6139697c-9d6a-4579-837e-a9fc5df4a773/bvlatuR/std/1200x628/Model-3-Homepage-Social-LHD', 'https://www.tesla.com/sites/default/files/images/blogs/models_blog_post.jpg' ], - num_reviews: 0, - average_reviews: 0 + numReviews: 0, + averageReviews: 0 }, { id: '1'.repeat(24), @@ -78,21 +78,21 @@ async function run() { 'https://www.motortrend.com/uploads/sites/5/2020/02/2020-Porsche-Taycan-Turbo-S-Track-Ride-5.gif?fit=around%7C875:492', 'https://newsroom.porsche.com/.imaging/mte/porsche-templating-theme/image_1290x726/dam/pnr/2021/Products/Free-Software-Update-Taycan/Free-Software-Update-for-early-Porsche-Taycan_2.jpeg/jcr:content/Free%20Software-Update%20for%20early%20Porsche%20Taycan_2.jpeg' ], - num_reviews: 0, - average_reviews: 0 + numReviews: 0, + averageReviews: 0 } ]); await Review.insertMany([ { - vehicle_id: vehicles[1].id, - user_id: users[0].id, + vehicleId: vehicles[1].id, + userId: users[0].id, text: 'When you live your life a quarter of a mile at a time, it ain\'t just about being fast. I needed a 10 second car, and this car delivers.', rating: 4 }, { - vehicle_id: vehicles[0].id, - user_id: users[1].id, + vehicleId: vehicles[0].id, + userId: users[1].id, text: 'I need NOS. My car topped out at 140 miles per hour this morning.', rating: 3 } From 189cde8411685d76c1f6938f1d6dbf35bb36ec88 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 27 Aug 2024 15:41:31 -0400 Subject: [PATCH 09/25] working _id queries and save() re: stargate/data-api#1359 --- discord-bot/README.md | 4 +- discord-bot/commands/createdocument.js | 2 +- discord-bot/models/bot.js | 7 +-- discord-bot/test/countdocuments.test.js | 2 +- discord-bot/test/createdocument.test.js | 2 +- discord-bot/test/setup.js | 2 +- netlify-functions-ecommerce/README.md | 28 +++++------ netlify-functions-ecommerce/models.js | 49 +++++++++---------- .../netlify/functions/addToCart.js | 6 +-- .../netlify/functions/checkout.js | 8 +-- .../netlify/functions/confirmOrder.js | 14 +++--- .../netlify/functions/getCart.js | 2 +- .../netlify/functions/removeFromCart.js | 4 +- .../test/addToCart.test.js | 10 ++-- .../test/checkout.test.js | 6 +-- .../test/fixtures/createCart.js | 2 +- .../test/fixtures/createOrder.js | 3 +- .../test/fixtures/createProducts.js | 2 +- .../test/getCart.test.js | 4 +- .../test/removeFromCart.test.js | 16 +++--- typescript-express-reviews/README.md | 16 +++--- .../src/models/authentication.ts | 7 +-- .../src/models/review.ts | 15 ++---- typescript-express-reviews/src/models/user.ts | 7 +-- .../src/models/vehicle.ts | 7 +-- typescript-express-reviews/src/seed/seed.ts | 4 +- 26 files changed, 101 insertions(+), 128 deletions(-) diff --git a/discord-bot/README.md b/discord-bot/README.md index f5452de..3d072c6 100644 --- a/discord-bot/README.md +++ b/discord-bot/README.md @@ -54,7 +54,7 @@ Create table: ``` CREATE TABLE bots ( - id text, + "_id" text, name text, - PRIMARY KEY (id)); + PRIMARY KEY ("_id")); ``` \ No newline at end of file diff --git a/discord-bot/commands/createdocument.js b/discord-bot/commands/createdocument.js index 3c096b6..254f41e 100644 --- a/discord-bot/commands/createdocument.js +++ b/discord-bot/commands/createdocument.js @@ -7,7 +7,7 @@ module.exports = { data: new SlashCommandBuilder().setName('createdocument').setDescription('creates a document'), async execute(interaction) { console.log(new Date(), 'createdocument'); - await Bot.insertMany([{ name: 'I am a document' }]); + await Bot.create({ name: 'I am a document' }); await interaction.reply('done!'); } }; \ No newline at end of file diff --git a/discord-bot/models/bot.js b/discord-bot/models/bot.js index b3ea7b9..078d324 100644 --- a/discord-bot/models/bot.js +++ b/discord-bot/models/bot.js @@ -3,13 +3,8 @@ const mongoose = require('../mongoose'); const botSchema = new mongoose.Schema({ - id: { - type: mongoose.Types.ObjectId, - required: true, - default: () => new mongoose.Types.ObjectId() - }, name: String -}, { _id: false, versionKey: false }); +}, { versionKey: false }); const Bot = mongoose.model('Bot', botSchema); diff --git a/discord-bot/test/countdocuments.test.js b/discord-bot/test/countdocuments.test.js index a34cab6..c35ba94 100644 --- a/discord-bot/test/countdocuments.test.js +++ b/discord-bot/test/countdocuments.test.js @@ -10,7 +10,7 @@ describe('countdocuments', function() { it('returns the number of bot documents', async function() { const docs = await Bot.find({}); for (const doc of docs) { - await Bot.deleteOne({ id: doc.id }); + await Bot.deleteOne({ _id: doc._id }); } await Bot.insertMany({ name: 'test' }); diff --git a/discord-bot/test/createdocument.test.js b/discord-bot/test/createdocument.test.js index 061aba3..ffe231e 100644 --- a/discord-bot/test/createdocument.test.js +++ b/discord-bot/test/createdocument.test.js @@ -10,7 +10,7 @@ describe('createdocument', function() { it('inserts a new document', async function() { let docs = await Bot.find({}); for (const doc of docs) { - await Bot.deleteOne({ id: doc.id }); + await Bot.deleteOne({ _id: doc._id }); } const interaction = { diff --git a/discord-bot/test/setup.js b/discord-bot/test/setup.js index 5bd33e3..569e396 100644 --- a/discord-bot/test/setup.js +++ b/discord-bot/test/setup.js @@ -18,7 +18,7 @@ before(async function() { const docs = await Bot.find({}); for (const doc of docs) { - await Bot.deleteOne({ id: doc.id }); + await Bot.deleteOne({ _id: doc._id }); } }); diff --git a/netlify-functions-ecommerce/README.md b/netlify-functions-ecommerce/README.md index 889c1f3..eda3641 100644 --- a/netlify-functions-ecommerce/README.md +++ b/netlify-functions-ecommerce/README.md @@ -77,26 +77,26 @@ Using test ``` CREATE TABLE products ( - id text, + "_id" text, name text, price decimal, image text, description text, - PRIMARY KEY (id)); + PRIMARY KEY ("_id")); CREATE TABLE orders ( - id text, - total decimal, - name text, - paymentMethod text, - items text, - PRIMARY KEY (id)); + "_id" text, + total decimal, + name text, + "paymentMethod" text, + items text, + PRIMARY KEY ("_id")); CREATE TABLE carts ( - id text, - items text, - orderId text, - total decimal, - stripeSessionId text, - PRIMARY KEY (id)); + "_id" text, + items text, + "orderId" text, + total decimal, + "stripeSessionId" text, + PRIMARY KEY ("_id")); ``` \ No newline at end of file diff --git a/netlify-functions-ecommerce/models.js b/netlify-functions-ecommerce/models.js index 0c4005c..eeda37e 100644 --- a/netlify-functions-ecommerce/models.js +++ b/netlify-functions-ecommerce/models.js @@ -3,32 +3,29 @@ const mongoose = require('./mongoose'); const productSchema = new mongoose.Schema({ - id: { - type: mongoose.Types.ObjectId, - required: true, - default: () => new mongoose.Types.ObjectId() - }, name: String, price: Number, image: String, description: String -}, { _id: false, versionKey: false }); +}, { versionKey: false }); const Product = mongoose.model('Product', productSchema); module.exports.Product = Product; const orderSchema = new mongoose.Schema({ - id: { - type: mongoose.Types.ObjectId, - required: true, - default: () => new mongoose.Types.ObjectId() + items: { + type: String, + get(v) { + return v == null ? v : JSON.parse(v); + }, + set(v) { + if (v == null) { + return v; + } + return typeof v === 'string' ? v : JSON.stringify(v); + }, }, - items: [{ - _id: false, - productId: { type: mongoose.ObjectId, required: true, ref: 'Product' }, - quantity: { type: Number, required: true, validate: v => v > 0 } - }], total: { type: Number, default: 0 @@ -37,22 +34,24 @@ const orderSchema = new mongoose.Schema({ type: String }, paymentMethod: { - id: String, - brand: String, - last4: String + type: String, + get(v) { + return v == null ? v : JSON.parse(v); + }, + set(v) { + if (v == null) { + return v; + } + return typeof v === 'string' ? v : JSON.stringify(v); + }, } -}, { _id: false, versionKey: false }); +}, { versionKey: false }); const Order = mongoose.model('Order', orderSchema); module.exports.Order = Order; const cartSchema = new mongoose.Schema({ - id: { - type: mongoose.Types.ObjectId, - required: true, - default: () => new mongoose.Types.ObjectId() - }, items: { type: String, get(v) { @@ -68,7 +67,7 @@ const cartSchema = new mongoose.Schema({ orderId: { type: mongoose.ObjectId, ref: 'Order' }, total: Number, stripeSessionId: { type: String } -}, { _id: false, versionKey: false, timestamps: false, toObject: { getters: true }, toJSON: { getters: true } }); +}, { versionKey: false, timestamps: false, toObject: { getters: true }, toJSON: { getters: true } }); cartSchema.virtual('numItems').get(function numItems() { if (this.items == null) { diff --git a/netlify-functions-ecommerce/netlify/functions/addToCart.js b/netlify-functions-ecommerce/netlify/functions/addToCart.js index b990600..22195ae 100644 --- a/netlify-functions-ecommerce/netlify/functions/addToCart.js +++ b/netlify-functions-ecommerce/netlify/functions/addToCart.js @@ -13,7 +13,7 @@ const handler = async(event) => { if (event.body.cartId) { // get the document containing the specified cartId const cart = await Cart. - findOne({ id: event.body.cartId }). + findOne({ _id: event.body.cartId }). setOptions({ sanitizeFilter: true }); if (cart == null) { @@ -31,7 +31,7 @@ const handler = async(event) => { for (const product of event.body.items) { const exists = cart.items?.find(item => item?.productId?.toString() === product?.productId?.toString()); if (!exists) { - if (products.find(p => product?.productId?.toString() === p?.id?.toString())) { + if (products.find(p => product?.productId?.toString() === p?._id?.toString())) { cart.items = [...(cart.items || []), product]; } } else { @@ -46,7 +46,7 @@ const handler = async(event) => { return { statusCode: 200, body: JSON.stringify({ cart: null }) }; } - await Cart.updateOne({ id: cart.id }, cart.getChanges()); + await cart.save(); return { statusCode: 200, body: JSON.stringify(cart) }; } else { // If no cartId, create a new cart diff --git a/netlify-functions-ecommerce/netlify/functions/checkout.js b/netlify-functions-ecommerce/netlify/functions/checkout.js index 16eeb39..f396720 100644 --- a/netlify-functions-ecommerce/netlify/functions/checkout.js +++ b/netlify-functions-ecommerce/netlify/functions/checkout.js @@ -11,14 +11,14 @@ const handler = async(event) => { event.body = JSON.parse(event.body || {}); await connect(); const cart = await Cart. - findOne({ id: event.body.cartId }). + findOne({ _id: event.body.cartId }). setOptions({ sanitizeFilter: true }). orFail(); const stripeProducts = { line_items: [] }; let total = 0; for (let i = 0; i < cart.items.length; i++) { - const product = await Product.findOne({ id: cart.items[i].productId }); + const product = await Product.findOne({ _id: cart.items[i].productId }); stripeProducts.line_items.push({ price_data: { currency: 'usd', @@ -35,7 +35,7 @@ const handler = async(event) => { cart.total = total; if (process.env.STRIPE_SECRET_KEY === 'test') { - await Cart.updateOne({ id: cart.id }, cart.getChanges()); + await cart.save(); return { statusCode: 200, body: JSON.stringify({ cart: cart, url: '/order-confirmation' }) @@ -50,7 +50,7 @@ const handler = async(event) => { }); cart.stripeSessionId = session.id; - await Cart.updateOne({ id: cart.id }, cart.getChanges()); + await cart.save(); return { statusCode: 200, diff --git a/netlify-functions-ecommerce/netlify/functions/confirmOrder.js b/netlify-functions-ecommerce/netlify/functions/confirmOrder.js index 7d06809..2969674 100644 --- a/netlify-functions-ecommerce/netlify/functions/confirmOrder.js +++ b/netlify-functions-ecommerce/netlify/functions/confirmOrder.js @@ -11,12 +11,12 @@ const handler = async(event) => { event.body = JSON.parse(event.body || {}); await connect(); const cart = await Cart. - findOne({ id: event.body.cartId }). + findOne({ _id: event.body.cartId }). setOptions({ sanitizeFilter: true }). orFail(); if (cart.orderId) { - const order = await Order.findOne({ id: cart.orderId }).orFail(); + const order = await Order.findOne({ _id: cart.orderId }).orFail(); return { statusCode: 200, @@ -35,8 +35,8 @@ const handler = async(event) => { } }); - cart.orderId = order.id; - await Cart.updateOne({ id: cart.id }, cart.getChanges()); + cart.orderId = order._id; + await cart.save(); return { statusCode: 200, @@ -55,7 +55,7 @@ const handler = async(event) => { const intent = await stripe.paymentIntents.retrieve(session.payment_intent); const paymentMethod = await stripe.paymentMethods.retrieve(intent['payment_method']); - const [order] = await Order.insertMany({ + const order = await Order.create({ items: cart.items, name: session['customer_details'].name, total: +(session['amount_total'] / 100).toFixed(2), @@ -64,8 +64,8 @@ const handler = async(event) => { null }); - cart.orderId = order.id; - await Cart.updateOne({ id: cart.id }, cart.getChanges()); + cart.orderId = order._id; + await cart.save(); return { statusCode: 200, diff --git a/netlify-functions-ecommerce/netlify/functions/getCart.js b/netlify-functions-ecommerce/netlify/functions/getCart.js index 7f24008..a88f316 100644 --- a/netlify-functions-ecommerce/netlify/functions/getCart.js +++ b/netlify-functions-ecommerce/netlify/functions/getCart.js @@ -10,7 +10,7 @@ const handler = async(event) => { await connect(); // get the document containing the specified cartId const cart = await Cart. - findOne({ id: event.queryStringParameters.cartId }). + findOne({ _id: event.queryStringParameters.cartId }). setOptions({ sanitizeFilter: true }); return { statusCode: 200, body: JSON.stringify({ cart }) }; } catch (error) { diff --git a/netlify-functions-ecommerce/netlify/functions/removeFromCart.js b/netlify-functions-ecommerce/netlify/functions/removeFromCart.js index 70daf70..2ccb60e 100644 --- a/netlify-functions-ecommerce/netlify/functions/removeFromCart.js +++ b/netlify-functions-ecommerce/netlify/functions/removeFromCart.js @@ -9,7 +9,7 @@ const handler = async(event) => { try { event.body = JSON.parse(event.body || {}); await connect(); - const cart = await Cart.findOne({ id: event.body.cartId }); + const cart = await Cart.findOne({ _id: event.body.cartId }); const index = cart.items.findIndex((item) => item.productId.toString() == event.body.item.productId.toString() ); @@ -32,7 +32,7 @@ const handler = async(event) => { ...cart.items.slice(index + 1) ]; } - await Cart.updateOne({ id: cart.id }, cart.getChanges()); + await cart.save(); return { statusCode: 200, body: JSON.stringify(cart) }; } catch (error) { console.log(error); diff --git a/netlify-functions-ecommerce/test/addToCart.test.js b/netlify-functions-ecommerce/test/addToCart.test.js index accc44b..fc23af8 100644 --- a/netlify-functions-ecommerce/test/addToCart.test.js +++ b/netlify-functions-ecommerce/test/addToCart.test.js @@ -40,8 +40,8 @@ describe('Add to Cart', function() { body: { cartId: cart.id, items: [ - { productId: products[0].id, quantity: 2 }, - { productId: products[1].id, quantity: 1 } + { productId: products[0]._id, quantity: 2 }, + { productId: products[1]._id, quantity: 1 } ] } }; @@ -58,10 +58,10 @@ describe('Add to Cart', function() { const { cart } = await fixtures.createCart({ products: [] }); const params = { body: { - cartId: cart.id, + cartId: cart._id, items: [ - { productId: products[0].id, quantity: 2 }, - { productId: products[1].id, quantity: 1 } + { productId: products[0]._id, quantity: 2 }, + { productId: products[1]._id, quantity: 1 } ] } }; diff --git a/netlify-functions-ecommerce/test/checkout.test.js b/netlify-functions-ecommerce/test/checkout.test.js index 8cbea52..7b207ad 100644 --- a/netlify-functions-ecommerce/test/checkout.test.js +++ b/netlify-functions-ecommerce/test/checkout.test.js @@ -22,8 +22,8 @@ describe('Checkout', function() { body: { cartId: null, items: [ - { productId: products[0].id, quantity: 2 }, - { productId: products[1].id, quantity: 1 } + { productId: products[0]._id, quantity: 2 }, + { productId: products[1]._id, quantity: 1 } ] } }; @@ -33,7 +33,7 @@ describe('Checkout', function() { assert(result.body); assert(result.body.items.length); - params.body.cartId = result.body.id; + params.body.cartId = result.body._id; sinon.stub(stripe.paymentIntents, 'retrieve').returns({ status: 'succeeded', id: '123', brand: 'visa', last4: '1234' }); sinon.stub(stripe.paymentMethods, 'retrieve').returns({ status: 'succeeded', id: '123', brand: 'visa', last4: '1234' }); sinon.stub(stripe.checkout.sessions, 'create').returns({ diff --git a/netlify-functions-ecommerce/test/fixtures/createCart.js b/netlify-functions-ecommerce/test/fixtures/createCart.js index 2bc1ae4..71948dc 100644 --- a/netlify-functions-ecommerce/test/fixtures/createCart.js +++ b/netlify-functions-ecommerce/test/fixtures/createCart.js @@ -3,6 +3,6 @@ const { Cart } = require('../../models'); module.exports = async function createCart(params) { - const [cart] = await Cart.insertMany({ items: params.items }); + const cart = await Cart.create({ items: params.items }); return { cart }; }; diff --git a/netlify-functions-ecommerce/test/fixtures/createOrder.js b/netlify-functions-ecommerce/test/fixtures/createOrder.js index 9f51367..3a28148 100644 --- a/netlify-functions-ecommerce/test/fixtures/createOrder.js +++ b/netlify-functions-ecommerce/test/fixtures/createOrder.js @@ -3,7 +3,6 @@ const { Order } = require('../../models'); module.exports = async function createOrder(params) { - - const [order] = await Order.insertMany(params.order); + const [order] = await Order.create(params.order); return { order }; }; diff --git a/netlify-functions-ecommerce/test/fixtures/createProducts.js b/netlify-functions-ecommerce/test/fixtures/createProducts.js index a5d6308..421c2aa 100644 --- a/netlify-functions-ecommerce/test/fixtures/createProducts.js +++ b/netlify-functions-ecommerce/test/fixtures/createProducts.js @@ -3,7 +3,7 @@ const { Product } = require('../../models'); module.exports = async function createProducts(params) { - const products = await Product.insertMany(params.product); + const products = await Product.create(params.product); return { products }; }; diff --git a/netlify-functions-ecommerce/test/getCart.test.js b/netlify-functions-ecommerce/test/getCart.test.js index aaa17dd..70476b8 100644 --- a/netlify-functions-ecommerce/test/getCart.test.js +++ b/netlify-functions-ecommerce/test/getCart.test.js @@ -11,12 +11,12 @@ describe('Get the cart given an id', function() { const params = { queryStringParameters: { - cartId: cart.id + cartId: cart._id } }; const findCart = await getCart(params); assert.equal(findCart.statusCode, 200); findCart.body = JSON.parse(findCart.body); - assert.equal(findCart.body.cart.id, cart.id.toString()); + assert.equal(findCart.body.cart._id, cart._id.toString()); }); }); diff --git a/netlify-functions-ecommerce/test/removeFromCart.test.js b/netlify-functions-ecommerce/test/removeFromCart.test.js index 7b63787..2b23276 100644 --- a/netlify-functions-ecommerce/test/removeFromCart.test.js +++ b/netlify-functions-ecommerce/test/removeFromCart.test.js @@ -20,8 +20,8 @@ describe('Remove From Cart', function() { body: { cartId: null, items: [ - { productId: products[0].id, quantity: 2 }, - { productId: products[1].id, quantity: 1 } + { productId: products[0]._id, quantity: 2 }, + { productId: products[1]._id, quantity: 1 } ] } }; @@ -32,9 +32,9 @@ describe('Remove From Cart', function() { assert.equal(result.body.items.length, 2); const newParams = { body: { - cartId: result.body.id, + cartId: result.body._id, item: { - productId: products[0].id + productId: products[0]._id } } }; @@ -51,8 +51,8 @@ describe('Remove From Cart', function() { body: { cartId: null, items: [ - { productId: products[0].id, quantity: 2 }, - { productId: products[1].id, quantity: 1 } + { productId: products[0]._id, quantity: 2 }, + { productId: products[1]._id, quantity: 1 } ] } }; @@ -63,9 +63,9 @@ describe('Remove From Cart', function() { assert(result.body.items.length); const newParams = { body: { - cartId: result.body.id, + cartId: result.body._id, item: { - productId: products[0].id, + productId: products[0]._id, quantity: 1 } } diff --git a/typescript-express-reviews/README.md b/typescript-express-reviews/README.md index f8fa226..0178440 100644 --- a/typescript-express-reviews/README.md +++ b/typescript-express-reviews/README.md @@ -36,38 +36,38 @@ Make sure you have Node.js 14 or higher and a local Stargate instance running as ``` CREATE TABLE authentications ( - id text, + "_id" text, type text, "userId" text, secret text, - PRIMARY KEY (id)); + PRIMARY KEY ("_id")); CREATE TABLE reviews ( - id text, + "_id" text, rating int, text text, "userId" text, "vehicleId" text, "createdAt" decimal, "updatedAt" decimal, - PRIMARY KEY (id)); + PRIMARY KEY ("_id")); CREATE TABLE users ( - id text, + "_id" text, email text, "firstName" text, "lastName" text, - PRIMARY KEY (id)); + PRIMARY KEY ("_id")); CREATE TABLE vehicles ( - id text, + "_id" text, make text, model text, year int, images text, "numReviews" int, "averageReview" decimal, - PRIMARY KEY (id)); + PRIMARY KEY ("_id")); CREATE INDEX ON reviews ("vehicleId"); CREATE INDEX ON users (email); diff --git a/typescript-express-reviews/src/models/authentication.ts b/typescript-express-reviews/src/models/authentication.ts index 8e8224c..5dac867 100644 --- a/typescript-express-reviews/src/models/authentication.ts +++ b/typescript-express-reviews/src/models/authentication.ts @@ -1,11 +1,6 @@ import mongoose from './mongoose'; const schema = new mongoose.Schema({ - id: { - type: mongoose.Types.ObjectId, - required: true, - default: () => new mongoose.Types.ObjectId() - }, type: { type: String, required: true, @@ -14,7 +9,7 @@ const schema = new mongoose.Schema({ }, userId: { type: mongoose.Types.ObjectId, required: true }, secret: { type: String, required: true } -}, { _id: false, versionKey: false }); +}, { versionKey: false }); const Authentication = mongoose.model('Authentication', schema); diff --git a/typescript-express-reviews/src/models/review.ts b/typescript-express-reviews/src/models/review.ts index bc47b88..3ccea0d 100644 --- a/typescript-express-reviews/src/models/review.ts +++ b/typescript-express-reviews/src/models/review.ts @@ -1,11 +1,6 @@ import mongoose from './mongoose'; const schema = new mongoose.Schema({ - id: { - type: mongoose.Types.ObjectId, - required: true, - default: () => new mongoose.Types.ObjectId() - }, rating: { type: Number, required: true, @@ -32,19 +27,19 @@ const schema = new mongoose.Schema({ type: Number, default: () => Date.now() } -}, { _id: false, versionKey: false, timestamps: true }); +}, { versionKey: false, timestamps: true }); schema.virtual('user', { ref: 'User', - localField: 'user_id', - foreignField: 'id', + localField: 'userId', + foreignField: '_id', justOne: true }); schema.virtual('vehicle', { ref: 'Vehicle', localField: 'vehicleId', - foreignField: 'id', + foreignField: '_id', justOne: true }); @@ -54,7 +49,7 @@ schema.pre('save', async function updateVehicleRating() { } const vehicle = await mongoose.model('Vehicle').findOne({ id: this.vehicleId }).orFail(); vehicle.numReviews += 1; - const vehicleReviews = await mongoose.model('Review').find({ vehicle_id: this.vehicleId }); + const vehicleReviews = await mongoose.model('Review').find({ vehicleId: this.vehicleId }); const reviewRatings = vehicleReviews.map((entry) => entry.rating); reviewRatings.push(this.rating); const average = calculateAverage(reviewRatings); diff --git a/typescript-express-reviews/src/models/user.ts b/typescript-express-reviews/src/models/user.ts index 47be6d0..f863a89 100644 --- a/typescript-express-reviews/src/models/user.ts +++ b/typescript-express-reviews/src/models/user.ts @@ -1,11 +1,6 @@ import mongoose from './mongoose'; const schema = new mongoose.Schema({ - id: { - type: mongoose.Types.ObjectId, - required: true, - default: () => new mongoose.Types.ObjectId() - }, email: { type: String, required: true, @@ -19,7 +14,7 @@ const schema = new mongoose.Schema({ type: String, required: true } -}, { _id: false, versionKey: false }); +}, { versionKey: false }); schema.virtual('displayName').get(function() { return this.firstName + ' ' + this.lastName.slice(0, 1) + '.'; diff --git a/typescript-express-reviews/src/models/vehicle.ts b/typescript-express-reviews/src/models/vehicle.ts index 50dec51..d614bdd 100644 --- a/typescript-express-reviews/src/models/vehicle.ts +++ b/typescript-express-reviews/src/models/vehicle.ts @@ -3,11 +3,6 @@ import mongoose from './mongoose'; const imagesSchemaType = new mongoose.Schema.Types.Array('images', { type: [String] }); const schema = new mongoose.Schema({ - id: { - type: mongoose.Types.ObjectId, - required: true, - default: () => new mongoose.Types.ObjectId() - }, make: { type: String, required: true @@ -43,7 +38,7 @@ const schema = new mongoose.Schema({ required: true, default: 0 } -}, { _id: false, versionKey: false }); +}, { versionKey: false }); const Vehicle = mongoose.model('Vehicle', schema); diff --git a/typescript-express-reviews/src/seed/seed.ts b/typescript-express-reviews/src/seed/seed.ts index 1532411..02b7f33 100644 --- a/typescript-express-reviews/src/seed/seed.ts +++ b/typescript-express-reviews/src/seed/seed.ts @@ -58,7 +58,7 @@ async function run() { } const vehicles = await Vehicle.insertMany([ { - id: '0'.repeat(24), + _id: '0'.repeat(24), make: 'Tesla', model: 'Model S', year: 2022, @@ -70,7 +70,7 @@ async function run() { averageReviews: 0 }, { - id: '1'.repeat(24), + _id: '1'.repeat(24), make: 'Porsche', model: 'Taycan', year: 2022, From b64e661290ecd36d5b4e3e970be18ebff01b9b8e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 28 Aug 2024 14:54:37 -0400 Subject: [PATCH 10/25] clean up a bunch of unnecessary tables changes --- .../frontend/src/cart/cart.js | 2 +- .../frontend/src/home/home.js | 10 +++--- .../frontend/src/product/product.js | 10 +++--- .../frontend/src/products/products.js | 10 +++--- netlify-functions-ecommerce/models.js | 6 ++-- .../netlify/functions/confirmOrder.js | 2 +- netlify-functions-ecommerce/public/app.js | 32 +++++++++---------- .../test/addToCart.test.js | 2 +- .../test/fixtures/createOrder.js | 2 +- .../src/api/Review/create.ts | 4 +-- .../src/api/Review/findByVehicle.ts | 2 +- .../src/api/User/register.ts | 8 ++--- .../src/api/Vehicle/findById.ts | 4 +-- .../src/models/review.ts | 2 +- .../tests/index.test.ts | 3 -- 15 files changed, 48 insertions(+), 51 deletions(-) diff --git a/netlify-functions-ecommerce/frontend/src/cart/cart.js b/netlify-functions-ecommerce/frontend/src/cart/cart.js index ffa5054..2366fdd 100644 --- a/netlify-functions-ecommerce/frontend/src/cart/cart.js +++ b/netlify-functions-ecommerce/frontend/src/cart/cart.js @@ -16,7 +16,7 @@ module.exports = app => app.component('cart', { }, methods: { product(item) { - const product = this.state.products.find(product => product.id === item.productId); + const product = this.state.products.find(product => product._id === item.productId); return product; }, formatTotal(item, product) { diff --git a/netlify-functions-ecommerce/frontend/src/home/home.js b/netlify-functions-ecommerce/frontend/src/home/home.js index cb45d72..0635c36 100644 --- a/netlify-functions-ecommerce/frontend/src/home/home.js +++ b/netlify-functions-ecommerce/frontend/src/home/home.js @@ -14,10 +14,10 @@ module.exports = app => app.component('home', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product.id, quantity: 1 }] + items: [{ productId: product._id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart.id; + body.cartId = this.state.cart._id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -27,9 +27,9 @@ module.exports = app => app.component('home', { body: JSON.stringify(body) }).then(res => res.json()); this.state.cart = res; - if (!this.state.cartId || this.state.cartId !== res.id) { - this.state.cartId = res.id; - window.localStorage.setItem('__cartKey', res.id); + if (!this.state.cartId || this.state.cartId !== res._id) { + this.state.cartId = res._id; + window.localStorage.setItem('__cartKey', res._id); } this.submitting = null; } diff --git a/netlify-functions-ecommerce/frontend/src/product/product.js b/netlify-functions-ecommerce/frontend/src/product/product.js index 98f3694..1658dba 100644 --- a/netlify-functions-ecommerce/frontend/src/product/product.js +++ b/netlify-functions-ecommerce/frontend/src/product/product.js @@ -10,7 +10,7 @@ module.exports = app => app.component('product', { }), computed: { product() { - return this.state.products.find(p => p.id === this.productId); + return this.state.products.find(p => p._id === this.productId); } }, methods: { @@ -20,10 +20,10 @@ module.exports = app => app.component('product', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product.id, quantity: 1 }] + items: [{ productId: product._id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart.id; + body.cartId = this.state.cart._id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -34,8 +34,8 @@ module.exports = app => app.component('product', { }).then(res => res.json()); this.state.cart = res; if (!this.state.cartId) { - this.state.cartId = res.id; - window.localStorage.setItem('__cartKey', res.id); + this.state.cartId = res._id; + window.localStorage.setItem('__cartKey', res._id); } this.submitting = null; } diff --git a/netlify-functions-ecommerce/frontend/src/products/products.js b/netlify-functions-ecommerce/frontend/src/products/products.js index a444c3b..6ee750d 100644 --- a/netlify-functions-ecommerce/frontend/src/products/products.js +++ b/netlify-functions-ecommerce/frontend/src/products/products.js @@ -14,10 +14,10 @@ module.exports = app => app.component('products', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product.id, quantity: 1 }] + items: [{ productId: product._id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart.id; + body.cartId = this.state.cart._id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -27,9 +27,9 @@ module.exports = app => app.component('products', { body: JSON.stringify(body) }).then(res => res.json()); this.state.cart = res; - if (!this.state.cartId || this.state.cartId !== res.id) { - this.state.cartId = res.id; - window.localStorage.setItem('__cartKey', res.id); + if (!this.state.cartId || this.state.cartId !== res._id) { + this.state.cartId = res._id; + window.localStorage.setItem('__cartKey', res._id); } this.submitting = null; } diff --git a/netlify-functions-ecommerce/models.js b/netlify-functions-ecommerce/models.js index eeda37e..d27ab93 100644 --- a/netlify-functions-ecommerce/models.js +++ b/netlify-functions-ecommerce/models.js @@ -24,7 +24,7 @@ const orderSchema = new mongoose.Schema({ return v; } return typeof v === 'string' ? v : JSON.stringify(v); - }, + } }, total: { type: Number, @@ -43,7 +43,7 @@ const orderSchema = new mongoose.Schema({ return v; } return typeof v === 'string' ? v : JSON.stringify(v); - }, + } } }, { versionKey: false }); @@ -62,7 +62,7 @@ const cartSchema = new mongoose.Schema({ return v; } return typeof v === 'string' ? v : JSON.stringify(v); - }, + } }, orderId: { type: mongoose.ObjectId, ref: 'Order' }, total: Number, diff --git a/netlify-functions-ecommerce/netlify/functions/confirmOrder.js b/netlify-functions-ecommerce/netlify/functions/confirmOrder.js index 2969674..4057bc7 100644 --- a/netlify-functions-ecommerce/netlify/functions/confirmOrder.js +++ b/netlify-functions-ecommerce/netlify/functions/confirmOrder.js @@ -24,7 +24,7 @@ const handler = async(event) => { }; } if (process.env.STRIPE_SECRET_KEY === 'test') { - const [order] = await Order.insertMany({ + const order = await Order.create({ items: cart.items, name: 'Stripe Test', total: cart.total, diff --git a/netlify-functions-ecommerce/public/app.js b/netlify-functions-ecommerce/public/app.js index 2c6356d..6572c9b 100644 --- a/netlify-functions-ecommerce/public/app.js +++ b/netlify-functions-ecommerce/public/app.js @@ -63,7 +63,7 @@ module.exports = app => app.component('cart', { }, methods: { product(item) { - const product = this.state.products.find(product => product.id === item.productId); + const product = this.state.products.find(product => product._id === item.productId); return product; }, formatTotal(item, product) { @@ -118,10 +118,10 @@ module.exports = app => app.component('home', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product.id, quantity: 1 }] + items: [{ productId: product._id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart.id; + body.cartId = this.state.cart._id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -131,9 +131,9 @@ module.exports = app => app.component('home', { body: JSON.stringify(body) }).then(res => res.json()); this.state.cart = res; - if (!this.state.cartId || this.state.cartId !== res.id) { - this.state.cartId = res.id; - window.localStorage.setItem('__cartKey', res.id); + if (!this.state.cartId || this.state.cartId !== res._id) { + this.state.cartId = res._id; + window.localStorage.setItem('__cartKey', res._id); } this.submitting = null; } @@ -218,7 +218,7 @@ module.exports = app => app.component('product', { }), computed: { product() { - return this.state.products.find(p => p.id === this.productId); + return this.state.products.find(p => p._id === this.productId); } }, methods: { @@ -228,10 +228,10 @@ module.exports = app => app.component('product', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product.id, quantity: 1 }] + items: [{ productId: product._id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart.id; + body.cartId = this.state.cart._id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -242,8 +242,8 @@ module.exports = app => app.component('product', { }).then(res => res.json()); this.state.cart = res; if (!this.state.cartId) { - this.state.cartId = res.id; - window.localStorage.setItem('__cartKey', res.id); + this.state.cartId = res._id; + window.localStorage.setItem('__cartKey', res._id); } this.submitting = null; } @@ -275,10 +275,10 @@ module.exports = app => app.component('products', { async addToCart(product) { this.submitting = product; const body = { - items: [{ productId: product.id, quantity: 1 }] + items: [{ productId: product._id, quantity: 1 }] }; if (this.state.cart) { - body.cartId = this.state.cart.id; + body.cartId = this.state.cart._id; } const res = await fetch('/.netlify/functions/addToCart', { method: 'PUT', @@ -288,9 +288,9 @@ module.exports = app => app.component('products', { body: JSON.stringify(body) }).then(res => res.json()); this.state.cart = res; - if (!this.state.cartId || this.state.cartId !== res.id) { - this.state.cartId = res.id; - window.localStorage.setItem('__cartKey', res.id); + if (!this.state.cartId || this.state.cartId !== res._id) { + this.state.cartId = res._id; + window.localStorage.setItem('__cartKey', res._id); } this.submitting = null; } diff --git a/netlify-functions-ecommerce/test/addToCart.test.js b/netlify-functions-ecommerce/test/addToCart.test.js index fc23af8..0584afa 100644 --- a/netlify-functions-ecommerce/test/addToCart.test.js +++ b/netlify-functions-ecommerce/test/addToCart.test.js @@ -38,7 +38,7 @@ describe('Add to Cart', function() { const { cart } = await fixtures.createCart({ products: [] }); const params = { body: { - cartId: cart.id, + cartId: cart._id, items: [ { productId: products[0]._id, quantity: 2 }, { productId: products[1]._id, quantity: 1 } diff --git a/netlify-functions-ecommerce/test/fixtures/createOrder.js b/netlify-functions-ecommerce/test/fixtures/createOrder.js index 3a28148..9f564c7 100644 --- a/netlify-functions-ecommerce/test/fixtures/createOrder.js +++ b/netlify-functions-ecommerce/test/fixtures/createOrder.js @@ -3,6 +3,6 @@ const { Order } = require('../../models'); module.exports = async function createOrder(params) { - const [order] = await Order.create(params.order); + const order = await Order.create(params.order); return { order }; }; diff --git a/typescript-express-reviews/src/api/Review/create.ts b/typescript-express-reviews/src/api/Review/create.ts index 03bca2f..b528eba 100644 --- a/typescript-express-reviews/src/api/Review/create.ts +++ b/typescript-express-reviews/src/api/Review/create.ts @@ -3,14 +3,14 @@ import Review from '../../models/review'; import Vehicle from '../../models/vehicle'; async function create(request: Request, response: Response): Promise { - const [review] = await Review.insertMany({ + const review = await Review.create({ text: request.body.text, rating: request.body.rating, userId: request.body.userId, vehicleId: request.body.vehicleId }); - const vehicle = await Vehicle.findOne({ id: request.body.vehicleId }).orFail(); + const vehicle = await Vehicle.findById({ _id: request.body.vehicleId }).orFail(); response.status(200).json({ vehicle: vehicle, review: review }); } diff --git a/typescript-express-reviews/src/api/Review/findByVehicle.ts b/typescript-express-reviews/src/api/Review/findByVehicle.ts index 3ed9251..3084f59 100644 --- a/typescript-express-reviews/src/api/Review/findByVehicle.ts +++ b/typescript-express-reviews/src/api/Review/findByVehicle.ts @@ -18,7 +18,7 @@ async function findByVehicle (request: Request, response: Response): Promise { return; } - const [user] = await User.insertMany([{ + const user = await User.create({ firstName: request.body.firstName, lastName: request.body.lastName, email: request.body.email - }]); + }); const salt = bcrypt.genSaltSync(10); const hash = bcrypt.hashSync(request.body.password, salt); - await Authentication.insertMany([{ + await Authentication.create({ type: 'password', userId: user.id, secret: hash - }]); + }); response.status(200).json({ user: user }); } diff --git a/typescript-express-reviews/src/api/Vehicle/findById.ts b/typescript-express-reviews/src/api/Vehicle/findById.ts index d4d2a0e..2a64cf9 100644 --- a/typescript-express-reviews/src/api/Vehicle/findById.ts +++ b/typescript-express-reviews/src/api/Vehicle/findById.ts @@ -14,10 +14,10 @@ async function last5(request: Request, response: Response): Promise { } const vehicle = await Vehicle. - findOne({ id: request.query?._id }). + findById({ _id: request.query?._id }). setOptions({ sanitizeFilter: true }); const reviews = await Review. - find({ vehicleId: vehicleId }). + find({ vehicleId }). sort({ createdAt: -1 }). limit(limit). //populate('user'). diff --git a/typescript-express-reviews/src/models/review.ts b/typescript-express-reviews/src/models/review.ts index 3ccea0d..9692937 100644 --- a/typescript-express-reviews/src/models/review.ts +++ b/typescript-express-reviews/src/models/review.ts @@ -47,7 +47,7 @@ schema.pre('save', async function updateVehicleRating() { if (!this.isNew) { return; } - const vehicle = await mongoose.model('Vehicle').findOne({ id: this.vehicleId }).orFail(); + const vehicle = await mongoose.model('Vehicle').findById(this.vehicleId).orFail(); vehicle.numReviews += 1; const vehicleReviews = await mongoose.model('Review').find({ vehicleId: this.vehicleId }); const reviewRatings = vehicleReviews.map((entry) => entry.rating); diff --git a/typescript-express-reviews/tests/index.test.ts b/typescript-express-reviews/tests/index.test.ts index 5f24a2c..1f98c73 100644 --- a/typescript-express-reviews/tests/index.test.ts +++ b/typescript-express-reviews/tests/index.test.ts @@ -7,9 +7,6 @@ import { after, before } from 'mocha'; import connect from '../src/models/connect'; import mongoose from 'mongoose'; -import util from 'util'; -util.inspect.defaultOptions.depth = 6; - before(async function() { this.timeout(30000); From ee5dcfc2c71b14abd4637aab2302cbd61ee42b11 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 10 Sep 2024 15:07:58 -0400 Subject: [PATCH 11/25] almost working local tests for sample apps, excluding stargate/data-api#1404 --- discord-bot/.env.test | 3 +- discord-bot/seed.js | 29 +++- discord-bot/test/setup.js | 19 +++ netlify-functions-ecommerce/.env.test | 1 + netlify-functions-ecommerce/seed.js | 71 ++++++++-- .../test/setup.test.js | 55 +++++++- typescript-express-reviews/.env.test | 3 +- typescript-express-reviews/src/seed/seed.ts | 117 ++++++++++++++-- .../tests/Vehicle.test.ts | 6 +- .../tests/index.test.ts | 129 ++++++++++++++++-- 10 files changed, 386 insertions(+), 47 deletions(-) diff --git a/discord-bot/.env.test b/discord-bot/.env.test index cd42340..f958d19 100644 --- a/discord-bot/.env.test +++ b/discord-bot/.env.test @@ -1,3 +1,4 @@ DATA_API_URI=http://127.0.0.1:8181/v1/demo DATA_API_AUTH_USERNAME=cassandra -DATA_API_AUTH_PASSWORD=cassandra \ No newline at end of file +DATA_API_AUTH_PASSWORD=cassandra +DATA_API_TABLES=true \ No newline at end of file diff --git a/discord-bot/seed.js b/discord-bot/seed.js index b0c0463..5595ded 100644 --- a/discord-bot/seed.js +++ b/discord-bot/seed.js @@ -13,11 +13,30 @@ seed().catch(err => { async function seed() { await connect(); - - const existingCollections = await mongoose.connection.listCollections() - .then(collections => collections.map(c => c.name)); - if (!existingCollections.includes(Bot.collection.collectionName)) { - await Bot.createCollection(); + + if (process.env.DATA_API_TABLES) { + await mongoose.connection.runCommand({ + createTable: { + name: 'bots', + definition: { + primaryKey: '_id', + columns: { + _id: { + type: 'text' + }, + name: { + type: 'text' + } + } + } + } + }); + } else { + const existingCollections = await mongoose.connection.listCollections() + .then(collections => collections.map(c => c.name)); + if (!existingCollections.includes(Bot.collection.collectionName)) { + await Bot.createCollection(); + } } console.log('Done'); diff --git a/discord-bot/test/setup.js b/discord-bot/test/setup.js index 569e396..1a2971b 100644 --- a/discord-bot/test/setup.js +++ b/discord-bot/test/setup.js @@ -16,6 +16,25 @@ before(async function() { this.timeout(30000); await mongoose.connect(uri, jsonApiConnectOptions); + if (process.env.DATA_API_TABLES) { + await mongoose.connection.runCommand({ + createTable: { + name: 'bots', + definition: { + primaryKey: '_id', + columns: { + _id: { + type: 'text' + }, + name: { + type: 'text' + } + } + } + } + }); + } + const docs = await Bot.find({}); for (const doc of docs) { await Bot.deleteOne({ _id: doc._id }); diff --git a/netlify-functions-ecommerce/.env.test b/netlify-functions-ecommerce/.env.test index 5cdffff..a1e6bc4 100644 --- a/netlify-functions-ecommerce/.env.test +++ b/netlify-functions-ecommerce/.env.test @@ -4,6 +4,7 @@ DATA_API_URI=http://127.0.0.1:8181/v1/demo #Auth username and password DATA_API_AUTH_USERNAME=cassandra DATA_API_AUTH_PASSWORD=cassandra +DATA_API_TABLES=true #Fill in Stripe related details if you want to see Stripe integration. #Otherwise the sample app will bypass Stripe. diff --git a/netlify-functions-ecommerce/seed.js b/netlify-functions-ecommerce/seed.js index 34ff43a..e03f105 100644 --- a/netlify-functions-ecommerce/seed.js +++ b/netlify-functions-ecommerce/seed.js @@ -9,18 +9,67 @@ const mongoose = require('./mongoose'); async function createProducts() { await connect(); - /*const existingCollections = await mongoose.connection.listCollections() - .then(collections => collections.map(c => c.name)); - for (const Model of Object.values(models)) { - if (existingCollections.includes(Model.collection.collectionName)) { - continue; + if (process.env.DATA_API_TABLES) { + await mongoose.connection.runCommand({ + createTable: { + name: 'products', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + name: { type: 'text' }, + price: { type: 'decimal' }, + image: { type: 'text' }, + description: { type: 'text' } + } + } + } + }); + await mongoose.connection.runCommand({ + createTable: { + name: 'orders', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + total: { type: 'decimal' }, + name: { type: 'text' }, + paymentMethod: { type: 'text' }, + items: { type: 'text' } + } + } + } + }); + await mongoose.connection.runCommand({ + createTable: { + name: 'carts', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + items: { type: 'text' }, + orderId: { type: 'text' }, + total: { type: 'decimal' }, + stripeSessionId: { type: 'text' } + } + } + } + }); + } else { + const existingCollections = await mongoose.connection.listCollections() + .then(collections => collections.map(c => c.name)); + for (const Model of Object.values(models)) { + if (existingCollections.includes(Model.collection.collectionName)) { + continue; + } + console.log('Creating', Model.collection.collectionName); + await Model.createCollection(); } - console.log('Creating', Model.collection.collectionName); - await Model.createCollection(); - }*/ - /*await Promise.all( - Object.values(models).map(Model => Model.deleteMany({})) - );*/ + await Promise.all( + Object.values(models).map(Model => Model.deleteMany({})) + ); + } + const { Product } = models; await Product.insertMany([ diff --git a/netlify-functions-ecommerce/test/setup.test.js b/netlify-functions-ecommerce/test/setup.test.js index b046584..af8d701 100644 --- a/netlify-functions-ecommerce/test/setup.test.js +++ b/netlify-functions-ecommerce/test/setup.test.js @@ -10,8 +10,59 @@ before(async function() { this.timeout(30000); await connect(); - // await Promise.all(Object.values(mongoose.connection.models).map(Model => Model.createCollection())); - // await Promise.all(Object.values(mongoose.connection.models).map(Model => Model.deleteMany({}))); + if (process.env.DATA_API_TABLES) { + await mongoose.connection.runCommand({ + createTable: { + name: 'products', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + name: { type: 'text' }, + price: { type: 'decimal' }, + image: { type: 'text' }, + description: { type: 'text' } + } + } + } + }); + await mongoose.connection.runCommand({ + createTable: { + name: 'orders', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + total: { type: 'decimal' }, + name: { type: 'text' }, + paymentMethod: { type: 'text' }, + items: { type: 'text' } + } + } + } + }); + await mongoose.connection.runCommand({ + createTable: { + name: 'carts', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + items: { type: 'text' }, + orderId: { type: 'text' }, + total: { type: 'decimal' }, + stripeSessionId: { type: 'text' } + } + } + } + }); + await Promise.all(Object.values(mongoose.connection.models).map(Model => { + return Model.find().then(docs => Promise.all(docs.map(doc => Model.deleteOne({ _id: doc._id })))); + })); + } else { + await Promise.all(Object.values(mongoose.connection.models).map(Model => Model.createCollection())); + await Promise.all(Object.values(mongoose.connection.models).map(Model => Model.deleteMany({}))); + } }); after(async function() { diff --git a/typescript-express-reviews/.env.test b/typescript-express-reviews/.env.test index cd42340..f958d19 100644 --- a/typescript-express-reviews/.env.test +++ b/typescript-express-reviews/.env.test @@ -1,3 +1,4 @@ DATA_API_URI=http://127.0.0.1:8181/v1/demo DATA_API_AUTH_USERNAME=cassandra -DATA_API_AUTH_PASSWORD=cassandra \ No newline at end of file +DATA_API_AUTH_PASSWORD=cassandra +DATA_API_TABLES=true \ No newline at end of file diff --git a/typescript-express-reviews/src/seed/seed.ts b/typescript-express-reviews/src/seed/seed.ts index 02b7f33..b83a14a 100644 --- a/typescript-express-reviews/src/seed/seed.ts +++ b/typescript-express-reviews/src/seed/seed.ts @@ -21,21 +21,112 @@ run().catch(err => { async function run() { await connect(); - /*const existingCollections = await mongoose.connection.listCollections() - .then(collections => collections.map(c => c.name)); + if (process.env.DATA_API_TABLES) { + // @ts-ignore + await mongoose.connection.runCommand({ + createTable: { + name: 'authentications', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + type: { type: 'text' }, + userId: { type: 'text' }, + secret: { type: 'text' } + } + } + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + createTable: { + name: 'reviews', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + rating: { type: 'int' }, + text: { type: 'text' }, + userId: { type: 'text' }, + vehicleId: { type: 'text' }, + createdAt: { type: 'decimal' }, + updatedAt: { type: 'decimal' } + } + } + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + "createTable": { + "name": "users", + "definition": { + "primaryKey": "_id", + "columns": { + "_id": { "type": "text" }, + "email": { "type": "text" }, + "firstName": { "type": "text" }, + "lastName": { "type": "text" } + } + } + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + createTable: { + name: 'vehicles', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + make: { type: 'text' }, + model: { type: 'text' }, + year: { type: 'int' }, + images: { type: 'text' }, + numReviews: { type: 'int' }, + averageReview: { type: 'decimal' } + } + } + } + }); - for (const Model of Object.values(mongoose.connection.models)) { - - // First ensure the collection exists - if (!existingCollections.includes(Model.collection.collectionName)) { - console.log('Creating collection', Model.collection.collectionName); - await mongoose.connection.createCollection(Model.collection.collectionName); - } else { - console.log('Resetting collection', Model.collection.collectionName); + // @ts-ignore + await Review.collection.runCommand({ + addIndex: { + column: 'vehicleId', + indexName: 'vehicleId' + } + }); + // @ts-ignore + await User.collection.runCommand({ + addIndex: { + column: 'email', + indexName: 'email' + } + }); + // @ts-ignore + await Authentication.collection.runCommand({ + addIndex: { + column: 'userId', + indexName: 'userId' + } + }); + } else { + const existingCollections = await mongoose.connection.listCollections() + .then(collections => collections.map(c => c.name)); + + for (const Model of Object.values(mongoose.connection.models)) { + + // First ensure the collection exists + if (!existingCollections.includes(Model.collection.collectionName)) { + console.log('Creating collection', Model.collection.collectionName); + await mongoose.connection.createCollection(Model.collection.collectionName); + } else { + console.log('Resetting collection', Model.collection.collectionName); + } + // Then make sure the collection is empty + await Model.deleteMany({}); } - // Then make sure the collection is empty - await Model.deleteMany({}); - }*/ + } const users = await User.insertMany([ { diff --git a/typescript-express-reviews/tests/Vehicle.test.ts b/typescript-express-reviews/tests/Vehicle.test.ts index 64973fa..69d3ec6 100644 --- a/typescript-express-reviews/tests/Vehicle.test.ts +++ b/typescript-express-reviews/tests/Vehicle.test.ts @@ -48,13 +48,13 @@ describe('Vehicle', function() { await Review.insertMany([{ rating: i > 5 ? 5 : i, text: 'This is a review that must have length greater than 30. ' + i, - vehicleId: vehicle.id, - userId: user.id + vehicleId: vehicle._id, + userId: user._id }]); } vehicle.numReviews = 5; vehicle.averageReview = 3; - await Vehicle.updateOne({ id: vehicle.id }, vehicle.getChanges()); + await Vehicle.updateOne({ _id: vehicle.id }, vehicle.getChanges()); const req = mockRequest({ _id: vehicle.id.toString(), limit: 5 }); const res = mockResponse(); await findById(req, res); diff --git a/typescript-express-reviews/tests/index.test.ts b/typescript-express-reviews/tests/index.test.ts index 1f98c73..d7a8df3 100644 --- a/typescript-express-reviews/tests/index.test.ts +++ b/typescript-express-reviews/tests/index.test.ts @@ -7,27 +7,134 @@ import { after, before } from 'mocha'; import connect from '../src/models/connect'; import mongoose from 'mongoose'; +import Authentication from '../src/models/authentication'; +import Review from '../src/models/review'; +import User from '../src/models/user'; +import Vehicle from '../src/models/vehicle'; + before(async function() { this.timeout(30000); await connect(); - // Make sure all collections are created in Stargate, _after_ calling - // `connect()`. stargate-mongoose doesn't currently support buffering on - // connection helpers. - /*await Promise.all(Object.values(mongoose.models).map(Model => { - return Model.createCollection(); - }));*/ + if (process.env.DATA_API_TABLES) { + // @ts-ignore + await mongoose.connection.runCommand({ + createTable: { + name: 'authentications', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + type: { type: 'text' }, + userId: { type: 'text' }, + secret: { type: 'text' } + } + } + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + createTable: { + name: 'reviews', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + rating: { type: 'int' }, + text: { type: 'text' }, + userId: { type: 'text' }, + vehicleId: { type: 'text' }, + createdAt: { type: 'decimal' }, + updatedAt: { type: 'decimal' } + } + } + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + createTable: { + name: 'users', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + email: { type: 'text' }, + firstName: { type: 'text' }, + lastName: { type: 'text' } + } + } + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + createTable: { + name: 'vehicles', + definition: { + primaryKey: '_id', + columns: { + _id: { type: 'text' }, + make: { type: 'text' }, + model: { type: 'text' }, + year: { type: 'int' }, + images: { type: 'text' }, + numReviews: { type: 'int' }, + averageReview: { type: 'decimal' } + } + } + } + }); + console.log(JSON.stringify({ + addIndex: { + column: 'vehicleId', + indexName: 'vehicleId' + } + }, null, ' ')) + // @ts-ignore + await Review.collection.runCommand({ + addIndex: { + column: 'vehicleId', + indexName: 'vehicleId' + } + }); + // @ts-ignore + await User.collection.runCommand({ + addIndex: { + column: 'email', + indexName: 'email' + } + }); + // @ts-ignore + await Authentication.collection.runCommand({ + addIndex: { + column: 'userId', + indexName: 'userId' + } + }); + } else { + // Make sure all collections are created in Stargate, _after_ calling + // `connect()`. stargate-mongoose doesn't currently support buffering on + // connection helpers. + await Promise.all(Object.values(mongoose.models).map(Model => { + return Model.createCollection(); + })); + } }); // `deleteMany()` currently does nothing -/*beforeEach(async function clearDb() { +beforeEach(async function clearDb() { this.timeout(30000); - await Promise.all(Object.values(mongoose.models).map(Model => { - return Model.deleteMany({}); - })); -});*/ + if (process.env.DATA_API_TABLES) { + await Promise.all(Object.values(mongoose.connection.models).map(Model => { + return Model.find().then(docs => Promise.all(docs.map(doc => Model.deleteOne({ _id: doc._id })))); + })); + } else { + await Promise.all(Object.values(mongoose.models).map(Model => { + return Model.deleteMany({}); + })); + } +}); after(async function() { await mongoose.disconnect(); From 55e12db043f01318c563ad0d9498a79f795c27a3 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 18 Sep 2024 15:04:42 -0400 Subject: [PATCH 12/25] set feature flags on connect re: stargate-mongoose#249 --- discord-bot/connect.js | 4 +++- discord-bot/test/setup.js | 4 +++- netlify-functions-ecommerce/connect.js | 4 +++- typescript-express-reviews/src/models/connect.ts | 3 ++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/discord-bot/connect.js b/discord-bot/connect.js index d158284..36984ea 100644 --- a/discord-bot/connect.js +++ b/discord-bot/connect.js @@ -17,10 +17,12 @@ module.exports = async function connect() { }; } else { uri = process.env.DATA_API_URI; + const featureFlags = process.env.DATA_API_TABLES ? ['Feature-Flag-tables'] : []; jsonApiConnectOptions = { username: process.env.DATA_API_AUTH_USERNAME, password: process.env.DATA_API_AUTH_PASSWORD, - authUrl: process.env.DATA_API_AUTH_URL + authUrl: process.env.DATA_API_AUTH_URL, + featureFlags }; } console.log('Connecting to', uri); diff --git a/discord-bot/test/setup.js b/discord-bot/test/setup.js index 1a2971b..5bdc2d0 100644 --- a/discord-bot/test/setup.js +++ b/discord-bot/test/setup.js @@ -7,9 +7,11 @@ const { after, before } = require('mocha'); const mongoose = require('../mongoose'); const uri = process.env.DATA_API_URI; +const featureFlags = process.env.DATA_API_TABLES ? ['Feature-Flag-tables'] : []; const jsonApiConnectOptions = { username: process.env.DATA_API_AUTH_USERNAME, - password: process.env.DATA_API_AUTH_PASSWORD + password: process.env.DATA_API_AUTH_PASSWORD, + featureFlags }; before(async function() { diff --git a/netlify-functions-ecommerce/connect.js b/netlify-functions-ecommerce/connect.js index 0c82b1b..a2d2774 100644 --- a/netlify-functions-ecommerce/connect.js +++ b/netlify-functions-ecommerce/connect.js @@ -27,10 +27,12 @@ module.exports = async function connect() { }; } else { uri = process.env.DATA_API_URI; + const featureFlags = process.env.DATA_API_TABLES ? ['Feature-Flag-tables'] : []; jsonApiConnectOptions = { username: process.env.DATA_API_AUTH_USERNAME, password: process.env.DATA_API_AUTH_PASSWORD, - authUrl: process.env.DATA_API_AUTH_URL + authUrl: process.env.DATA_API_AUTH_URL, + featureFlags }; } await mongoose.connect(uri, jsonApiConnectOptions); diff --git a/typescript-express-reviews/src/models/connect.ts b/typescript-express-reviews/src/models/connect.ts index 2243574..6e8fdf1 100644 --- a/typescript-express-reviews/src/models/connect.ts +++ b/typescript-express-reviews/src/models/connect.ts @@ -26,9 +26,10 @@ export default async function connect() { ); } else { console.log('Connecting to', dataAPIURI); + const featureFlags = process.env.DATA_API_TABLES ? ['Feature-Flag-tables'] : []; await mongoose.connect( dataAPIURI, - { username, password, authUrl } as mongoose.ConnectOptions + { username, password, authUrl, featureFlags } as mongoose.ConnectOptions ); } } From d08781fb9022118b3c761748ca4d5d30ceb77dfc Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 19 Sep 2024 11:19:20 -0400 Subject: [PATCH 13/25] use stargate-mongoose@0.6.5 to test tables --- discord-bot/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord-bot/package.json b/discord-bot/package.json index 1e5ac9d..7cbf485 100644 --- a/discord-bot/package.json +++ b/discord-bot/package.json @@ -22,7 +22,7 @@ "dotenv": "16.4.5", "eslint": "8.57.0", "mongoose": "^8.1", - "stargate-mongoose": "0.6.4" + "stargate-mongoose": "0.6.5" }, "devDependencies": { "mocha": "10.7.3", From 5c31a9fc5d2cff4355e0425a2d6893ee0258b724 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 19 Sep 2024 11:22:21 -0400 Subject: [PATCH 14/25] add log output and bump netlify ecommerce to stargate-mongoose@0.6.5 --- discord-bot/test/setup.js | 3 +++ netlify-functions-ecommerce/package.json | 2 +- netlify-functions-ecommerce/test/setup.test.js | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/discord-bot/test/setup.js b/discord-bot/test/setup.js index 5bdc2d0..b78a543 100644 --- a/discord-bot/test/setup.js +++ b/discord-bot/test/setup.js @@ -13,6 +13,9 @@ const jsonApiConnectOptions = { password: process.env.DATA_API_AUTH_PASSWORD, featureFlags }; +if (process.env.DATA_API_TABLES) { + console.log('Testing Data API tables'); +} before(async function() { this.timeout(30000); diff --git a/netlify-functions-ecommerce/package.json b/netlify-functions-ecommerce/package.json index 51d1d1a..6413081 100644 --- a/netlify-functions-ecommerce/package.json +++ b/netlify-functions-ecommerce/package.json @@ -6,7 +6,7 @@ "dotenv": "16.4.5", "mongoose": "^8.1", "sinon": "19.0.2", - "stargate-mongoose": "0.6.4", + "stargate-mongoose": "0.6.5", "stripe": "16.11.0", "vanillatoasts": "1.6.0", "webpack": "5.x" diff --git a/netlify-functions-ecommerce/test/setup.test.js b/netlify-functions-ecommerce/test/setup.test.js index af8d701..0c7259b 100644 --- a/netlify-functions-ecommerce/test/setup.test.js +++ b/netlify-functions-ecommerce/test/setup.test.js @@ -6,6 +6,10 @@ const { after, before } = require('mocha'); const connect = require('../connect'); const mongoose = require('../mongoose'); +if (process.env.DATA_API_TABLES) { + console.log('Testing Data API tables'); +} + before(async function() { this.timeout(30000); await connect(); From 92d0283c37b952693f73f0f4bc154fbde4698f70 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 19 Sep 2024 11:24:26 -0400 Subject: [PATCH 15/25] use stargate-mongoose@0.6.5 in reviews --- typescript-express-reviews/package.json | 2 +- typescript-express-reviews/tests/index.test.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/typescript-express-reviews/package.json b/typescript-express-reviews/package.json index c0030ba..896426d 100644 --- a/typescript-express-reviews/package.json +++ b/typescript-express-reviews/package.json @@ -20,7 +20,7 @@ "dotenv": "16.4.5", "express": "4.x", "mongoose": "^8.1", - "stargate-mongoose": "0.6.4" + "stargate-mongoose": "0.6.5" }, "devDependencies": { "@types/bcryptjs": "^2.4.2", diff --git a/typescript-express-reviews/tests/index.test.ts b/typescript-express-reviews/tests/index.test.ts index d7a8df3..5c5b3de 100644 --- a/typescript-express-reviews/tests/index.test.ts +++ b/typescript-express-reviews/tests/index.test.ts @@ -12,6 +12,10 @@ import Review from '../src/models/review'; import User from '../src/models/user'; import Vehicle from '../src/models/vehicle'; +if (process.env.DATA_API_TABLES) { + console.log('Testing Data API tables'); +} + before(async function() { this.timeout(30000); From 4f477fddc64e5d491da337a036c23119e04cf112 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 25 Sep 2024 16:16:27 -0400 Subject: [PATCH 16/25] make typescript express reviews tests pass against tables --- .../src/api/Review/create.ts | 2 +- .../src/api/Review/findByVehicle.ts | 15 ++++++- .../src/api/User/login.ts | 2 +- .../src/api/User/register.ts | 2 +- .../src/api/Vehicle/findById.ts | 15 ++++++- .../src/models/authentication.ts | 2 +- .../src/models/review.ts | 8 ++-- .../src/seed/dropCollections.ts | 35 +++++++++++++-- typescript-express-reviews/src/seed/seed.ts | 16 +++---- .../tests/Review.test.ts | 14 +++--- typescript-express-reviews/tests/User.test.ts | 2 +- .../tests/Vehicle.test.ts | 11 ++--- .../tests/index.test.ts | 43 +++++++++++++------ 13 files changed, 120 insertions(+), 47 deletions(-) diff --git a/typescript-express-reviews/src/api/Review/create.ts b/typescript-express-reviews/src/api/Review/create.ts index b528eba..31b74e6 100644 --- a/typescript-express-reviews/src/api/Review/create.ts +++ b/typescript-express-reviews/src/api/Review/create.ts @@ -7,7 +7,7 @@ async function create(request: Request, response: Response): Promise { text: request.body.text, rating: request.body.rating, userId: request.body.userId, - vehicleId: request.body.vehicleId + vehicle_id: request.body.vehicleId }); const vehicle = await Vehicle.findById({ _id: request.body.vehicleId }).orFail(); diff --git a/typescript-express-reviews/src/api/Review/findByVehicle.ts b/typescript-express-reviews/src/api/Review/findByVehicle.ts index 3084f59..927ad57 100644 --- a/typescript-express-reviews/src/api/Review/findByVehicle.ts +++ b/typescript-express-reviews/src/api/Review/findByVehicle.ts @@ -1,5 +1,11 @@ import express, { Request, Response } from 'express'; import Review from '../../models/review'; +import User from '../../models/user'; +import Vehicle from '../../models/vehicle'; + +type ReviewDocument = ReturnType<(typeof Review)['hydrate']>; +type UserDocument = ReturnType<(typeof User)['hydrate']>; +type VehicleDocument = ReturnType<(typeof Vehicle)['hydrate']>; async function findByVehicle (request: Request, response: Response): Promise { let limit = 5; @@ -17,13 +23,20 @@ async function findByVehicle (request: Request, response: Response): Promise({ vehicle_id: vehicleId }). sort({ createdAt: -1 }). skip(skip). limit(limit). //populate('user'). //populate('vehicle'). setOptions({ sanitizeFilter: true }); + + // TODO: populate doesn't work against tables because lack of $in (see stargate/data-api#1446) + for (const review of reviews) { + review.user = await User.findOne({ _id: review.userId }).orFail(); + review.vehicle = await Vehicle.findOne({ _id: review.vehicle_id }).orFail(); + } + response.status(200).json({ reviews: reviews }); return; }; diff --git a/typescript-express-reviews/src/api/User/login.ts b/typescript-express-reviews/src/api/User/login.ts index abf218e..6c85bc8 100644 --- a/typescript-express-reviews/src/api/User/login.ts +++ b/typescript-express-reviews/src/api/User/login.ts @@ -17,7 +17,7 @@ async function login(request: Request, response: Response): Promise { } const authentication = await Authentication.find({ - userId: user.id + user_id: user.id }).then(authentications => authentications.find(auth => auth.type === 'password')); if (authentication == null) { response.status(404).json({ diff --git a/typescript-express-reviews/src/api/User/register.ts b/typescript-express-reviews/src/api/User/register.ts index 50200a4..9f7b5d7 100644 --- a/typescript-express-reviews/src/api/User/register.ts +++ b/typescript-express-reviews/src/api/User/register.ts @@ -23,7 +23,7 @@ async function register(request: Request, response: Response): Promise { const hash = bcrypt.hashSync(request.body.password, salt); await Authentication.create({ type: 'password', - userId: user.id, + user_id: user.id, secret: hash }); response.status(200).json({ user: user }); diff --git a/typescript-express-reviews/src/api/Vehicle/findById.ts b/typescript-express-reviews/src/api/Vehicle/findById.ts index 2a64cf9..14525c3 100644 --- a/typescript-express-reviews/src/api/Vehicle/findById.ts +++ b/typescript-express-reviews/src/api/Vehicle/findById.ts @@ -1,6 +1,11 @@ import express, { Request, Response } from 'express'; -import Vehicle from '../../models/vehicle'; import Review from '../../models/review'; +import User from '../../models/user'; +import Vehicle from '../../models/vehicle'; + +type ReviewDocument = ReturnType<(typeof Review)['hydrate']>; +type UserDocument = ReturnType<(typeof User)['hydrate']>; +type VehicleDocument = ReturnType<(typeof Vehicle)['hydrate']>; async function last5(request: Request, response: Response): Promise { let limit = 5; @@ -17,13 +22,19 @@ async function last5(request: Request, response: Response): Promise { findById({ _id: request.query?._id }). setOptions({ sanitizeFilter: true }); const reviews = await Review. - find({ vehicleId }). + find({ vehicle_id: vehicleId }). sort({ createdAt: -1 }). limit(limit). //populate('user'). //populate('vehicle'). setOptions({ sanitizeFilter: true }); + // TODO: populate doesn't work against tables because lack of $in (see stargate/data-api#1446) + for (const review of reviews) { + review.user = await User.findOne({ _id: review.userId }).orFail(); + review.vehicle = await Vehicle.findOne({ _id: review.vehicle_id }).orFail(); + } + response.status(200).json({ vehicle: vehicle, reviews: reviews diff --git a/typescript-express-reviews/src/models/authentication.ts b/typescript-express-reviews/src/models/authentication.ts index 5dac867..415e597 100644 --- a/typescript-express-reviews/src/models/authentication.ts +++ b/typescript-express-reviews/src/models/authentication.ts @@ -7,7 +7,7 @@ const schema = new mongoose.Schema({ enum: ['password', 'one time'], default: 'password' }, - userId: { type: mongoose.Types.ObjectId, required: true }, + user_id: { type: mongoose.Types.ObjectId, required: true }, secret: { type: String, required: true } }, { versionKey: false }); diff --git a/typescript-express-reviews/src/models/review.ts b/typescript-express-reviews/src/models/review.ts index 9692937..6207cee 100644 --- a/typescript-express-reviews/src/models/review.ts +++ b/typescript-express-reviews/src/models/review.ts @@ -15,7 +15,7 @@ const schema = new mongoose.Schema({ type: 'ObjectId', required: true }, - vehicleId: { + vehicle_id: { type: 'ObjectId', required: true }, @@ -38,7 +38,7 @@ schema.virtual('user', { schema.virtual('vehicle', { ref: 'Vehicle', - localField: 'vehicleId', + localField: 'vehicle_id', foreignField: '_id', justOne: true }); @@ -47,9 +47,9 @@ schema.pre('save', async function updateVehicleRating() { if (!this.isNew) { return; } - const vehicle = await mongoose.model('Vehicle').findById(this.vehicleId).orFail(); + const vehicle = await mongoose.model('Vehicle').findById(this.vehicle_id).orFail(); vehicle.numReviews += 1; - const vehicleReviews = await mongoose.model('Review').find({ vehicleId: this.vehicleId }); + const vehicleReviews = await mongoose.model('Review').find({ vehicle_id: this.vehicle_id }); const reviewRatings = vehicleReviews.map((entry) => entry.rating); reviewRatings.push(this.rating); const average = calculateAverage(reviewRatings); diff --git a/typescript-express-reviews/src/seed/dropCollections.ts b/typescript-express-reviews/src/seed/dropCollections.ts index 13e5f70..8b376b9 100644 --- a/typescript-express-reviews/src/seed/dropCollections.ts +++ b/typescript-express-reviews/src/seed/dropCollections.ts @@ -12,10 +12,37 @@ dropCollections().catch(err => { async function dropCollections() { await connect(); - const collections = await mongoose.connection.listCollections(); - for (const collection of collections) { - console.log('Dropping', collection.name); - await mongoose.connection.dropCollection(collection.name); + if (process.env.DATA_API_TABLES) { + // @ts-ignore + await mongoose.connection.runCommand({ + dropTable: { + name: 'authentications' + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + dropTable: { + name: 'reviews' + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + dropTable: { + name: 'users' + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + dropTable: { + name: 'vehicles' + } + }); + } else { + const collections = await mongoose.connection.listCollections(); + for (const collection of collections) { + console.log('Dropping', collection.name); + await mongoose.connection.dropCollection(collection.name); + } } console.log('Done'); diff --git a/typescript-express-reviews/src/seed/seed.ts b/typescript-express-reviews/src/seed/seed.ts index b83a14a..d87122e 100644 --- a/typescript-express-reviews/src/seed/seed.ts +++ b/typescript-express-reviews/src/seed/seed.ts @@ -31,7 +31,7 @@ async function run() { columns: { _id: { type: 'text' }, type: { type: 'text' }, - userId: { type: 'text' }, + user_id: { type: 'text' }, secret: { type: 'text' } } } @@ -48,7 +48,7 @@ async function run() { rating: { type: 'int' }, text: { type: 'text' }, userId: { type: 'text' }, - vehicleId: { type: 'text' }, + vehicle_id: { type: 'text' }, createdAt: { type: 'decimal' }, updatedAt: { type: 'decimal' } } @@ -92,8 +92,8 @@ async function run() { // @ts-ignore await Review.collection.runCommand({ addIndex: { - column: 'vehicleId', - indexName: 'vehicleId' + column: 'vehicle_id', + indexName: 'vehicle_id' } }); // @ts-ignore @@ -106,8 +106,8 @@ async function run() { // @ts-ignore await Authentication.collection.runCommand({ addIndex: { - column: 'userId', - indexName: 'userId' + column: 'user_id', + indexName: 'user_id' } }); } else { @@ -176,13 +176,13 @@ async function run() { await Review.insertMany([ { - vehicleId: vehicles[1].id, + vehicle_id: vehicles[1].id, userId: users[0].id, text: 'When you live your life a quarter of a mile at a time, it ain\'t just about being fast. I needed a 10 second car, and this car delivers.', rating: 4 }, { - vehicleId: vehicles[0].id, + vehicle_id: vehicles[0].id, userId: users[1].id, text: 'I need NOS. My car topped out at 140 miles per hour this morning.', rating: 3 diff --git a/typescript-express-reviews/tests/Review.test.ts b/typescript-express-reviews/tests/Review.test.ts index 202d559..b6ab43e 100644 --- a/typescript-express-reviews/tests/Review.test.ts +++ b/typescript-express-reviews/tests/Review.test.ts @@ -87,23 +87,25 @@ describe('Review', function() { await Review.insertMany([{ rating: i > 5 ? 5 : i, text: 'This is a review that must have length greater than 30. ' + i, - vehicleId: vehicle.id, + vehicle_id: vehicle.id, userId: user.id }]); } vehicle.numReviews = 6; vehicle.averageReview = 3; - await Vehicle.updateOne({ id: vehicle.id }, vehicle.getChanges()); - const req = mockRequest({ vehicleId: vehicle.id.toString(), limit: 3, skip: 1 }); + await vehicle.save(); + // TODO: skip doesn't work against tables yet + const req = mockRequest({ vehicleId: vehicle.id.toString(), limit: 3, skip: 0 }); const res = mockResponse(); await findByVehicle(req, res); const reviews = res.json.getCall(0).args[0].reviews; assert.equal(reviews.length, 3); - assert.deepEqual( + // TODO: sort doesn't work against tables yet + /*assert.deepEqual( reviews.map((r: typeof Review) => r.rating), - [4, 3, 2] - ); + [3, 2, 1] + );*/ // Test that populate worked assert.equal(reviews[0].vehicle.make, 'Tesla'); diff --git a/typescript-express-reviews/tests/User.test.ts b/typescript-express-reviews/tests/User.test.ts index cee0bca..37bd768 100644 --- a/typescript-express-reviews/tests/User.test.ts +++ b/typescript-express-reviews/tests/User.test.ts @@ -44,7 +44,7 @@ describe('User', function() { const hash = bcrypt.hashSync('password', salt); await Authentication.insertMany([{ type: 'password', - userId: user.id, + user_id: user.id, secret: hash }]); diff --git a/typescript-express-reviews/tests/Vehicle.test.ts b/typescript-express-reviews/tests/Vehicle.test.ts index 69d3ec6..b48e5b1 100644 --- a/typescript-express-reviews/tests/Vehicle.test.ts +++ b/typescript-express-reviews/tests/Vehicle.test.ts @@ -48,13 +48,13 @@ describe('Vehicle', function() { await Review.insertMany([{ rating: i > 5 ? 5 : i, text: 'This is a review that must have length greater than 30. ' + i, - vehicleId: vehicle._id, + vehicle_id: vehicle._id, userId: user._id }]); } vehicle.numReviews = 5; vehicle.averageReview = 3; - await Vehicle.updateOne({ _id: vehicle.id }, vehicle.getChanges()); + await vehicle.save(); const req = mockRequest({ _id: vehicle.id.toString(), limit: 5 }); const res = mockResponse(); await findById(req, res); @@ -62,10 +62,11 @@ describe('Vehicle', function() { const reviews = res.json.getCall(0).args[0].reviews; assert.equal(reviews.length, 5); - assert.deepEqual( - reviews.map((r: typeof Review) => r.rating), + // TODO: sort doesn't work against tables yet + /*assert.deepEqual( + reviews.map((r: typeof Review) => r.rating).sort(), [5, 5, 4, 3, 2] - ); + );*/ // Test that populate worked assert.equal(reviews[0].vehicle.make, 'Tesla'); diff --git a/typescript-express-reviews/tests/index.test.ts b/typescript-express-reviews/tests/index.test.ts index 5c5b3de..0a7c107 100644 --- a/typescript-express-reviews/tests/index.test.ts +++ b/typescript-express-reviews/tests/index.test.ts @@ -22,6 +22,31 @@ before(async function() { await connect(); if (process.env.DATA_API_TABLES) { + // @ts-ignore + await mongoose.connection.runCommand({ + dropTable: { + name: 'authentications' + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + dropTable: { + name: 'reviews' + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + dropTable: { + name: 'users' + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + dropTable: { + name: 'vehicles' + } + }); + // @ts-ignore await mongoose.connection.runCommand({ createTable: { @@ -31,7 +56,7 @@ before(async function() { columns: { _id: { type: 'text' }, type: { type: 'text' }, - userId: { type: 'text' }, + user_id: { type: 'text' }, secret: { type: 'text' } } } @@ -48,7 +73,7 @@ before(async function() { rating: { type: 'int' }, text: { type: 'text' }, userId: { type: 'text' }, - vehicleId: { type: 'text' }, + vehicle_id: { type: 'text' }, createdAt: { type: 'decimal' }, updatedAt: { type: 'decimal' } } @@ -88,17 +113,11 @@ before(async function() { } } }); - console.log(JSON.stringify({ - addIndex: { - column: 'vehicleId', - indexName: 'vehicleId' - } - }, null, ' ')) // @ts-ignore await Review.collection.runCommand({ addIndex: { - column: 'vehicleId', - indexName: 'vehicleId' + column: 'vehicle_id', + indexName: 'vehicle_id' } }); // @ts-ignore @@ -111,8 +130,8 @@ before(async function() { // @ts-ignore await Authentication.collection.runCommand({ addIndex: { - column: 'userId', - indexName: 'userId' + column: 'user_id', + indexName: 'user_id' } }); } else { From ae819afd5b7f6d6e6938b1ccc511dd7ff9f950c2 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 26 Sep 2024 11:51:46 -0400 Subject: [PATCH 17/25] add Mongoose studio to typescript-express reviews, Mongoose studio mostly works on tables --- typescript-express-reviews/package.json | 1 + typescript-express-reviews/src/api/index.ts | 5 +++++ typescript-express-reviews/src/seed/seed.ts | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/typescript-express-reviews/package.json b/typescript-express-reviews/package.json index 896426d..1364c90 100644 --- a/typescript-express-reviews/package.json +++ b/typescript-express-reviews/package.json @@ -23,6 +23,7 @@ "stargate-mongoose": "0.6.5" }, "devDependencies": { + "@mongoosejs/studio": "0.0.40", "@types/bcryptjs": "^2.4.2", "@types/body-parser": "^1.19.2", "@types/express": "4.17.21", diff --git a/typescript-express-reviews/src/api/index.ts b/typescript-express-reviews/src/api/index.ts index 395d097..c7e2fe0 100644 --- a/typescript-express-reviews/src/api/index.ts +++ b/typescript-express-reviews/src/api/index.ts @@ -11,6 +11,8 @@ import findByVehicle from './Review/findByVehicle'; import bodyParser from 'body-parser'; import connect from '../models/connect'; +import studio from '@mongoosejs/studio'; + const port = process.env.PORT || 3000; void async function main() { @@ -32,6 +34,9 @@ void async function main() { app.getAsync('/vehicle/find-by-id', findById); app.getAsync('/review/find-by-vehicle', findByVehicle); + app.use('/studio', studio.express('/studio/api')); + console.log('Mongoose Studio mounted on /studio'); + await app.listen(port); console.log('Listening on port ' + port); }(); diff --git a/typescript-express-reviews/src/seed/seed.ts b/typescript-express-reviews/src/seed/seed.ts index d87122e..a71d331 100644 --- a/typescript-express-reviews/src/seed/seed.ts +++ b/typescript-express-reviews/src/seed/seed.ts @@ -143,7 +143,7 @@ async function run() { for (let i = 0; i < users.length; i++) { await Authentication.insertMany([{ type: 'password', - userId: users[i].id, + user_id: users[i].id, secret: await bcrypt.hash(users[i].firstName.toLowerCase(), 10) }]); } From 10869dcc7439aa0c6eacafd74a0d48490edd3348 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 27 Sep 2024 11:55:33 -0400 Subject: [PATCH 18/25] make sure to drop tables before creating --- netlify-functions-ecommerce/seed.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/netlify-functions-ecommerce/seed.js b/netlify-functions-ecommerce/seed.js index e03f105..d443b35 100644 --- a/netlify-functions-ecommerce/seed.js +++ b/netlify-functions-ecommerce/seed.js @@ -10,6 +10,25 @@ async function createProducts() { await connect(); if (process.env.DATA_API_TABLES) { + // @ts-ignore + await mongoose.connection.runCommand({ + dropTable: { + name: 'products' + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + dropTable: { + name: 'orders' + } + }); + // @ts-ignore + await mongoose.connection.runCommand({ + dropTable: { + name: 'carts' + } + }); + await mongoose.connection.runCommand({ createTable: { name: 'products', From fe09f86987aefe340a30aa68ea6aca58de892d8b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 27 Sep 2024 16:13:20 -0400 Subject: [PATCH 19/25] fix build --- typescript-express-reviews/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript-express-reviews/package.json b/typescript-express-reviews/package.json index 1364c90..455706c 100644 --- a/typescript-express-reviews/package.json +++ b/typescript-express-reviews/package.json @@ -23,7 +23,7 @@ "stargate-mongoose": "0.6.5" }, "devDependencies": { - "@mongoosejs/studio": "0.0.40", + "@mongoosejs/studio": "0.0.41", "@types/bcryptjs": "^2.4.2", "@types/body-parser": "^1.19.2", "@types/express": "4.17.21", From 15f793d9d6d0f83a8044dec3778277553ec4ced1 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 30 Sep 2024 17:48:39 -0400 Subject: [PATCH 20/25] camelcase names re: stargate/data-api#1452 --- typescript-express-reviews/src/api/Review/create.ts | 2 +- .../src/api/Review/findByVehicle.ts | 4 ++-- typescript-express-reviews/src/api/Vehicle/findById.ts | 4 ++-- typescript-express-reviews/src/models/review.ts | 8 ++++---- typescript-express-reviews/src/seed/seed.ts | 10 +++++----- typescript-express-reviews/tests/Review.test.ts | 2 +- typescript-express-reviews/tests/Vehicle.test.ts | 2 +- typescript-express-reviews/tests/index.test.ts | 6 +++--- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/typescript-express-reviews/src/api/Review/create.ts b/typescript-express-reviews/src/api/Review/create.ts index 31b74e6..b528eba 100644 --- a/typescript-express-reviews/src/api/Review/create.ts +++ b/typescript-express-reviews/src/api/Review/create.ts @@ -7,7 +7,7 @@ async function create(request: Request, response: Response): Promise { text: request.body.text, rating: request.body.rating, userId: request.body.userId, - vehicle_id: request.body.vehicleId + vehicleId: request.body.vehicleId }); const vehicle = await Vehicle.findById({ _id: request.body.vehicleId }).orFail(); diff --git a/typescript-express-reviews/src/api/Review/findByVehicle.ts b/typescript-express-reviews/src/api/Review/findByVehicle.ts index 927ad57..f4fbd41 100644 --- a/typescript-express-reviews/src/api/Review/findByVehicle.ts +++ b/typescript-express-reviews/src/api/Review/findByVehicle.ts @@ -23,7 +23,7 @@ async function findByVehicle (request: Request, response: Response): Promise({ vehicle_id: vehicleId }). + find({ vehicleId: vehicleId }). sort({ createdAt: -1 }). skip(skip). limit(limit). @@ -34,7 +34,7 @@ async function findByVehicle (request: Request, response: Response): Promise { findById({ _id: request.query?._id }). setOptions({ sanitizeFilter: true }); const reviews = await Review. - find({ vehicle_id: vehicleId }). + find({ vehicleId: vehicleId }). sort({ createdAt: -1 }). limit(limit). //populate('user'). @@ -32,7 +32,7 @@ async function last5(request: Request, response: Response): Promise { // TODO: populate doesn't work against tables because lack of $in (see stargate/data-api#1446) for (const review of reviews) { review.user = await User.findOne({ _id: review.userId }).orFail(); - review.vehicle = await Vehicle.findOne({ _id: review.vehicle_id }).orFail(); + review.vehicle = await Vehicle.findOne({ _id: review.vehicleId }).orFail(); } response.status(200).json({ diff --git a/typescript-express-reviews/src/models/review.ts b/typescript-express-reviews/src/models/review.ts index 6207cee..9692937 100644 --- a/typescript-express-reviews/src/models/review.ts +++ b/typescript-express-reviews/src/models/review.ts @@ -15,7 +15,7 @@ const schema = new mongoose.Schema({ type: 'ObjectId', required: true }, - vehicle_id: { + vehicleId: { type: 'ObjectId', required: true }, @@ -38,7 +38,7 @@ schema.virtual('user', { schema.virtual('vehicle', { ref: 'Vehicle', - localField: 'vehicle_id', + localField: 'vehicleId', foreignField: '_id', justOne: true }); @@ -47,9 +47,9 @@ schema.pre('save', async function updateVehicleRating() { if (!this.isNew) { return; } - const vehicle = await mongoose.model('Vehicle').findById(this.vehicle_id).orFail(); + const vehicle = await mongoose.model('Vehicle').findById(this.vehicleId).orFail(); vehicle.numReviews += 1; - const vehicleReviews = await mongoose.model('Review').find({ vehicle_id: this.vehicle_id }); + const vehicleReviews = await mongoose.model('Review').find({ vehicleId: this.vehicleId }); const reviewRatings = vehicleReviews.map((entry) => entry.rating); reviewRatings.push(this.rating); const average = calculateAverage(reviewRatings); diff --git a/typescript-express-reviews/src/seed/seed.ts b/typescript-express-reviews/src/seed/seed.ts index a71d331..15d706b 100644 --- a/typescript-express-reviews/src/seed/seed.ts +++ b/typescript-express-reviews/src/seed/seed.ts @@ -48,7 +48,7 @@ async function run() { rating: { type: 'int' }, text: { type: 'text' }, userId: { type: 'text' }, - vehicle_id: { type: 'text' }, + vehicleId: { type: 'text' }, createdAt: { type: 'decimal' }, updatedAt: { type: 'decimal' } } @@ -92,8 +92,8 @@ async function run() { // @ts-ignore await Review.collection.runCommand({ addIndex: { - column: 'vehicle_id', - indexName: 'vehicle_id' + column: 'vehicleId', + indexName: 'vehicleId' } }); // @ts-ignore @@ -176,13 +176,13 @@ async function run() { await Review.insertMany([ { - vehicle_id: vehicles[1].id, + vehicleId: vehicles[1].id, userId: users[0].id, text: 'When you live your life a quarter of a mile at a time, it ain\'t just about being fast. I needed a 10 second car, and this car delivers.', rating: 4 }, { - vehicle_id: vehicles[0].id, + vehicleId: vehicles[0].id, userId: users[1].id, text: 'I need NOS. My car topped out at 140 miles per hour this morning.', rating: 3 diff --git a/typescript-express-reviews/tests/Review.test.ts b/typescript-express-reviews/tests/Review.test.ts index b6ab43e..3c0bfe2 100644 --- a/typescript-express-reviews/tests/Review.test.ts +++ b/typescript-express-reviews/tests/Review.test.ts @@ -87,7 +87,7 @@ describe('Review', function() { await Review.insertMany([{ rating: i > 5 ? 5 : i, text: 'This is a review that must have length greater than 30. ' + i, - vehicle_id: vehicle.id, + vehicleId: vehicle.id, userId: user.id }]); } diff --git a/typescript-express-reviews/tests/Vehicle.test.ts b/typescript-express-reviews/tests/Vehicle.test.ts index b48e5b1..03bbe85 100644 --- a/typescript-express-reviews/tests/Vehicle.test.ts +++ b/typescript-express-reviews/tests/Vehicle.test.ts @@ -48,7 +48,7 @@ describe('Vehicle', function() { await Review.insertMany([{ rating: i > 5 ? 5 : i, text: 'This is a review that must have length greater than 30. ' + i, - vehicle_id: vehicle._id, + vehicleId: vehicle._id, userId: user._id }]); } diff --git a/typescript-express-reviews/tests/index.test.ts b/typescript-express-reviews/tests/index.test.ts index 0a7c107..a2d9943 100644 --- a/typescript-express-reviews/tests/index.test.ts +++ b/typescript-express-reviews/tests/index.test.ts @@ -73,7 +73,7 @@ before(async function() { rating: { type: 'int' }, text: { type: 'text' }, userId: { type: 'text' }, - vehicle_id: { type: 'text' }, + vehicleId: { type: 'text' }, createdAt: { type: 'decimal' }, updatedAt: { type: 'decimal' } } @@ -116,8 +116,8 @@ before(async function() { // @ts-ignore await Review.collection.runCommand({ addIndex: { - column: 'vehicle_id', - indexName: 'vehicle_id' + column: 'vehicleId', + indexName: 'vehicleId' } }); // @ts-ignore From 243c15ad7bec796933ead6f52236bd37dd5eb004 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 17 Oct 2024 14:31:23 -0400 Subject: [PATCH 21/25] rely on ALLOW FILTERING and remove remaining snake_case names --- .../src/api/User/login.ts | 2 +- .../src/api/User/register.ts | 2 +- .../src/models/authentication.ts | 2 +- typescript-express-reviews/src/seed/seed.ts | 26 ++----------------- typescript-express-reviews/tests/User.test.ts | 2 +- .../tests/index.test.ts | 23 +--------------- 6 files changed, 7 insertions(+), 50 deletions(-) diff --git a/typescript-express-reviews/src/api/User/login.ts b/typescript-express-reviews/src/api/User/login.ts index 6c85bc8..abf218e 100644 --- a/typescript-express-reviews/src/api/User/login.ts +++ b/typescript-express-reviews/src/api/User/login.ts @@ -17,7 +17,7 @@ async function login(request: Request, response: Response): Promise { } const authentication = await Authentication.find({ - user_id: user.id + userId: user.id }).then(authentications => authentications.find(auth => auth.type === 'password')); if (authentication == null) { response.status(404).json({ diff --git a/typescript-express-reviews/src/api/User/register.ts b/typescript-express-reviews/src/api/User/register.ts index 9f7b5d7..50200a4 100644 --- a/typescript-express-reviews/src/api/User/register.ts +++ b/typescript-express-reviews/src/api/User/register.ts @@ -23,7 +23,7 @@ async function register(request: Request, response: Response): Promise { const hash = bcrypt.hashSync(request.body.password, salt); await Authentication.create({ type: 'password', - user_id: user.id, + userId: user.id, secret: hash }); response.status(200).json({ user: user }); diff --git a/typescript-express-reviews/src/models/authentication.ts b/typescript-express-reviews/src/models/authentication.ts index 415e597..5dac867 100644 --- a/typescript-express-reviews/src/models/authentication.ts +++ b/typescript-express-reviews/src/models/authentication.ts @@ -7,7 +7,7 @@ const schema = new mongoose.Schema({ enum: ['password', 'one time'], default: 'password' }, - user_id: { type: mongoose.Types.ObjectId, required: true }, + userId: { type: mongoose.Types.ObjectId, required: true }, secret: { type: String, required: true } }, { versionKey: false }); diff --git a/typescript-express-reviews/src/seed/seed.ts b/typescript-express-reviews/src/seed/seed.ts index 15d706b..6b4713b 100644 --- a/typescript-express-reviews/src/seed/seed.ts +++ b/typescript-express-reviews/src/seed/seed.ts @@ -31,7 +31,7 @@ async function run() { columns: { _id: { type: 'text' }, type: { type: 'text' }, - user_id: { type: 'text' }, + userId: { type: 'text' }, secret: { type: 'text' } } } @@ -88,28 +88,6 @@ async function run() { } } }); - - // @ts-ignore - await Review.collection.runCommand({ - addIndex: { - column: 'vehicleId', - indexName: 'vehicleId' - } - }); - // @ts-ignore - await User.collection.runCommand({ - addIndex: { - column: 'email', - indexName: 'email' - } - }); - // @ts-ignore - await Authentication.collection.runCommand({ - addIndex: { - column: 'user_id', - indexName: 'user_id' - } - }); } else { const existingCollections = await mongoose.connection.listCollections() .then(collections => collections.map(c => c.name)); @@ -143,7 +121,7 @@ async function run() { for (let i = 0; i < users.length; i++) { await Authentication.insertMany([{ type: 'password', - user_id: users[i].id, + userId: users[i].id, secret: await bcrypt.hash(users[i].firstName.toLowerCase(), 10) }]); } diff --git a/typescript-express-reviews/tests/User.test.ts b/typescript-express-reviews/tests/User.test.ts index 37bd768..cee0bca 100644 --- a/typescript-express-reviews/tests/User.test.ts +++ b/typescript-express-reviews/tests/User.test.ts @@ -44,7 +44,7 @@ describe('User', function() { const hash = bcrypt.hashSync('password', salt); await Authentication.insertMany([{ type: 'password', - user_id: user.id, + userId: user.id, secret: hash }]); diff --git a/typescript-express-reviews/tests/index.test.ts b/typescript-express-reviews/tests/index.test.ts index a2d9943..4df2e2c 100644 --- a/typescript-express-reviews/tests/index.test.ts +++ b/typescript-express-reviews/tests/index.test.ts @@ -56,7 +56,7 @@ before(async function() { columns: { _id: { type: 'text' }, type: { type: 'text' }, - user_id: { type: 'text' }, + userId: { type: 'text' }, secret: { type: 'text' } } } @@ -113,27 +113,6 @@ before(async function() { } } }); - // @ts-ignore - await Review.collection.runCommand({ - addIndex: { - column: 'vehicleId', - indexName: 'vehicleId' - } - }); - // @ts-ignore - await User.collection.runCommand({ - addIndex: { - column: 'email', - indexName: 'email' - } - }); - // @ts-ignore - await Authentication.collection.runCommand({ - addIndex: { - column: 'user_id', - indexName: 'user_id' - } - }); } else { // Make sure all collections are created in Stargate, _after_ calling // `connect()`. stargate-mongoose doesn't currently support buffering on From c4f5f4e88c18e9b2b2067ac68cf8918767cc3592 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 14 Nov 2024 12:44:37 -0500 Subject: [PATCH 22/25] readme cleanup and add DATA_API_TABLES to .env.example --- discord-bot/.env.example | 2 ++ discord-bot/README.md | 13 +-------- netlify-functions-ecommerce/.env.example | 6 +++-- netlify-functions-ecommerce/README.md | 34 +++--------------------- typescript-express-reviews/.env.example | 2 ++ 5 files changed, 12 insertions(+), 45 deletions(-) diff --git a/discord-bot/.env.example b/discord-bot/.env.example index 0e069d9..50f57d4 100644 --- a/discord-bot/.env.example +++ b/discord-bot/.env.example @@ -5,6 +5,8 @@ DATA_API_URI=http://127.0.0.1:8181/v1/discordbot DATA_API_AUTH_USERNAME=cassandra DATA_API_AUTH_PASSWORD=cassandra DATA_API_AUTH_URL=http://localhost:8081/v1/auth +#Uncomment the following to enable API tables +#DATA_API_TABLES=true #Fill the ASTRA DB related details only when IS_ASTRA is set to 'true' #IS_ASTRA=true diff --git a/discord-bot/README.md b/discord-bot/README.md index 3d072c6..e9dd87d 100644 --- a/discord-bot/README.md +++ b/discord-bot/README.md @@ -46,15 +46,4 @@ Below is a screenshot demonstrating executing each of the commands. ![image](https://user-images.githubusercontent.com/1620265/213293087-53505a73-3038-4db8-b21b-d9149a5396ed.png) -Anytime you add or update commands in the command folder, run step 3 again. - -## With tables - -Create table: - -``` -CREATE TABLE bots ( - "_id" text, - name text, - PRIMARY KEY ("_id")); -``` \ No newline at end of file +Anytime you add or update commands in the command folder, run step 3 again. \ No newline at end of file diff --git a/netlify-functions-ecommerce/.env.example b/netlify-functions-ecommerce/.env.example index c896178..e39c989 100644 --- a/netlify-functions-ecommerce/.env.example +++ b/netlify-functions-ecommerce/.env.example @@ -3,14 +3,16 @@ # false - if not connecting to an AstraDB and connecting to a local/remote jsonapi instead IS_ASTRA= -#Fill the JSON API related details only when IS_ASTRA is set to 'false' -#Local JSON API URL for example: http://127.0.0.1:8181/v1/ecommerce_test where 'ecommerce_test' is the keyspace name +#Fill the Data API related details only when IS_ASTRA is set to 'false' +#Local Data API URL for example: http://127.0.0.1:8181/v1/ecommerce_test where 'ecommerce_test' is the keyspace name DATA_API_URI=http://127.0.0.1:8181/v1/ecommerce_test #Auth URL for example: http://127.0.0.1:8081/v1/auth DATA_API_AUTH_URL=http://127.0.0.1:8081/v1/auth #Auth username and password DATA_API_AUTH_USERNAME=cassandra DATA_API_AUTH_PASSWORD=cassandra +#Uncomment the following to enable API tables +#DATA_API_TABLES=true #Fill the ASTRA DB related details only when IS_ASTRA is set to 'true' #Astra DB API URL diff --git a/netlify-functions-ecommerce/README.md b/netlify-functions-ecommerce/README.md index eda3641..d9d2b8a 100644 --- a/netlify-functions-ecommerce/README.md +++ b/netlify-functions-ecommerce/README.md @@ -12,7 +12,7 @@ Other tools include: Make sure you have a local stargate instance running as described on the [main page](../README.md) of this repo. ## Running This Example -### Setting up .env file to run against JSON API +### Setting up .env file to run against Data API 1. Copy the `.env.example` file to `.env` and fill in the values for the environment variables. 2. Set `IS_ASTRA` to `false` 3. Set `DATA_API_URI` to `http://127.0.0.1:8181/v1/ecommerce_test` @@ -20,6 +20,7 @@ Make sure you have a local stargate instance running as described on the [main p 5. Set `DATA_API_AUTH_USERNAME` to `cassandra` 6. Set `DATA_API_AUTH_PASSWORD` to `cassandra` 7. Remove `ASTRA_DB_ID`, `ASTRA_DB_REGION`, `ASTRA_DB_KEYSPACE`, `ASTRA_DB_APPLICATION_TOKEN` +8. (Optional) Set `DATA_API_TABLES=true` to make stargate-mongoose set the `Feature-Flag-tables` headers to enable API tables ### Setting up .env file to run against AstraDB 1. Copy the `.env.example` file to `.env` and fill in the values for the environment variables. @@ -34,7 +35,7 @@ Make sure you have a local stargate instance running as described on the [main p 1. Run `npm install` 2. Run `npm run seed` to create all collections and insert sample data 3. Run `npm run build` to compile the frontend -4. (Optional) set `STRIPE_SECRET_KEY` to a test Stripe API key in your `.env` file. This will allow you to enable Stripe checkout. +4. (Optional) set `STRIPE_SECRET_KEY` to a test Stripe API key in your `.env` file. This will allow you to enable Stripe checkout. Or set `STRIPE_SECRET_KEY` to `test` to skip Stripe scheckout 5. Run `npm start` Run `npm run test:smoke` to run a smoke test against `http://127.0.0.1:8888` that creates a cart using [Axios](https://masteringjs.io/axios). 6. Visit `http://127.0.0.1:8888/` to see the UI @@ -70,33 +71,4 @@ Using test 8 passing (112ms) -``` - - -## With tables - -``` -CREATE TABLE products ( - "_id" text, - name text, - price decimal, - image text, - description text, - PRIMARY KEY ("_id")); - -CREATE TABLE orders ( - "_id" text, - total decimal, - name text, - "paymentMethod" text, - items text, - PRIMARY KEY ("_id")); - -CREATE TABLE carts ( - "_id" text, - items text, - "orderId" text, - total decimal, - "stripeSessionId" text, - PRIMARY KEY ("_id")); ``` \ No newline at end of file diff --git a/typescript-express-reviews/.env.example b/typescript-express-reviews/.env.example index 0902ac1..a12b32d 100644 --- a/typescript-express-reviews/.env.example +++ b/typescript-express-reviews/.env.example @@ -11,6 +11,8 @@ DATA_API_AUTH_URL=http://127.0.0.1:8081/v1/auth #Auth username and password DATA_API_AUTH_USERNAME=cassandra DATA_API_AUTH_PASSWORD=cassandra +#Uncomment the following to enable API tables +#DATA_API_TABLES=true #Fill the ASTRA DB related details only when IS_ASTRA is set to 'true' #Astra DB API URL From 31a09072efc6a085ca156c6bc78f01d52ea6b539 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 14 Nov 2024 15:08:51 -0500 Subject: [PATCH 23/25] use lists and maps for ecommerce instead of JSON string --- netlify-functions-ecommerce/models.js | 64 ++++++++++--------- .../netlify/functions/addToCart.js | 19 ++++-- netlify-functions-ecommerce/seed.js | 24 +++++-- .../test/setup.test.js | 36 ++++++++++- 4 files changed, 100 insertions(+), 43 deletions(-) diff --git a/netlify-functions-ecommerce/models.js b/netlify-functions-ecommerce/models.js index d27ab93..0f4adc5 100644 --- a/netlify-functions-ecommerce/models.js +++ b/netlify-functions-ecommerce/models.js @@ -15,16 +15,20 @@ module.exports.Product = Product; const orderSchema = new mongoose.Schema({ items: { - type: String, - get(v) { - return v == null ? v : JSON.parse(v); - }, - set(v) { - if (v == null) { - return v; + // `items` is stored as an array of JSON strings for compatibility with Data API tables, which do not + // support lists of objects currently. + type: [{ + type: String, + get(v) { + return v == null ? v : JSON.parse(v); + }, + set(v) { + if (v == null) { + return v; + } + return typeof v === 'string' ? v : JSON.stringify(v); } - return typeof v === 'string' ? v : JSON.stringify(v); - } + }] }, total: { type: Number, @@ -34,16 +38,9 @@ const orderSchema = new mongoose.Schema({ type: String }, paymentMethod: { - type: String, - get(v) { - return v == null ? v : JSON.parse(v); - }, - set(v) { - if (v == null) { - return v; - } - return typeof v === 'string' ? v : JSON.stringify(v); - } + id: String, + brand: String, + last4: String } }, { versionKey: false }); @@ -53,16 +50,20 @@ module.exports.Order = Order; const cartSchema = new mongoose.Schema({ items: { - type: String, - get(v) { - return v == null ? v : JSON.parse(v); - }, - set(v) { - if (v == null) { - return v; + // `items` is stored as an array of JSON strings for compatibility with Data API tables, which do not + // support lists of objects currently. + type: [{ + type: String, + get(v) { + return v == null ? v : JSON.parse(v); + }, + set(v) { + if (v == null) { + return v; + } + return typeof v === 'string' ? v : JSON.stringify(v); } - return typeof v === 'string' ? v : JSON.stringify(v); - } + }] }, orderId: { type: mongoose.ObjectId, ref: 'Order' }, total: Number, @@ -73,7 +74,12 @@ cartSchema.virtual('numItems').get(function numItems() { if (this.items == null) { return 0; } - const items = typeof this.items === 'string' ? JSON.parse(this.items) : this.items; + const items = this.items.map(item => { + if (typeof item === 'string') { + return JSON.parse(item); + } + return item; + }); return items.reduce((sum, item) => sum + item.quantity, 0); }); diff --git a/netlify-functions-ecommerce/netlify/functions/addToCart.js b/netlify-functions-ecommerce/netlify/functions/addToCart.js index 22195ae..183493c 100644 --- a/netlify-functions-ecommerce/netlify/functions/addToCart.js +++ b/netlify-functions-ecommerce/netlify/functions/addToCart.js @@ -30,13 +30,22 @@ const handler = async(event) => { } for (const product of event.body.items) { const exists = cart.items?.find(item => item?.productId?.toString() === product?.productId?.toString()); - if (!exists) { - if (products.find(p => product?.productId?.toString() === p?._id?.toString())) { - cart.items = [...(cart.items || []), product]; - } + if (!exists && products.find(p => product?.productId?.toString() === p?._id?.toString())) { + cart.items = [ + ...cart.items, + product + ]; } else { + const items = []; + for (let i = 0; i < cart.items.length; ++i) { + const item = cart.items[i]; + if (item?.productId?.toString() === product?.productId?.toString()) { + continue; + } + items.push(item); + } cart.items = [ - ...cart.items.filter(item => item?.productId?.toString() !== product?.productId?.toString()), + ...items, { productId: product.productId, quantity: exists.quantity + product.quantity } ]; } diff --git a/netlify-functions-ecommerce/seed.js b/netlify-functions-ecommerce/seed.js index d443b35..e1c9816 100644 --- a/netlify-functions-ecommerce/seed.js +++ b/netlify-functions-ecommerce/seed.js @@ -10,23 +10,35 @@ async function createProducts() { await connect(); if (process.env.DATA_API_TABLES) { - // @ts-ignore await mongoose.connection.runCommand({ dropTable: { name: 'products' } + }).catch(err => { + if (err.errors && err.errors.length === 1 && err.errors[0].errorCode === 'CANNOT_DROP_UNKNOWN_TABLE') { + return; + } + throw err; }); - // @ts-ignore await mongoose.connection.runCommand({ dropTable: { name: 'orders' } + }).catch(err => { + if (err.errors && err.errors.length === 1 && err.errors[0].errorCode === 'CANNOT_DROP_UNKNOWN_TABLE') { + return; + } + throw err; }); - // @ts-ignore await mongoose.connection.runCommand({ dropTable: { name: 'carts' } + }).catch(err => { + if (err.errors && err.errors.length === 1 && err.errors[0].errorCode === 'CANNOT_DROP_UNKNOWN_TABLE') { + return; + } + throw err; }); await mongoose.connection.runCommand({ @@ -53,8 +65,8 @@ async function createProducts() { _id: { type: 'text' }, total: { type: 'decimal' }, name: { type: 'text' }, - paymentMethod: { type: 'text' }, - items: { type: 'text' } + paymentMethod: { type: 'map', keyType: 'text', valueType: 'text' }, + items: { type: 'list', valueType: 'text' } } } } @@ -66,7 +78,7 @@ async function createProducts() { primaryKey: '_id', columns: { _id: { type: 'text' }, - items: { type: 'text' }, + items: { type: 'list', valueType: 'text' }, orderId: { type: 'text' }, total: { type: 'decimal' }, stripeSessionId: { type: 'text' } diff --git a/netlify-functions-ecommerce/test/setup.test.js b/netlify-functions-ecommerce/test/setup.test.js index 0c7259b..3749913 100644 --- a/netlify-functions-ecommerce/test/setup.test.js +++ b/netlify-functions-ecommerce/test/setup.test.js @@ -15,6 +15,36 @@ before(async function() { await connect(); if (process.env.DATA_API_TABLES) { + await mongoose.connection.runCommand({ + dropTable: { + name: 'products' + } + }).catch(err => { + if (err.errors && err.errors.length === 1 && err.errors[0].errorCode === 'CANNOT_DROP_UNKNOWN_TABLE') { + return; + } + throw err; + }); + await mongoose.connection.runCommand({ + dropTable: { + name: 'orders' + } + }).catch(err => { + if (err.errors && err.errors.length === 1 && err.errors[0].errorCode === 'CANNOT_DROP_UNKNOWN_TABLE') { + return; + } + throw err; + }); + await mongoose.connection.runCommand({ + dropTable: { + name: 'carts' + } + }).catch(err => { + if (err.errors && err.errors.length === 1 && err.errors[0].errorCode === 'CANNOT_DROP_UNKNOWN_TABLE') { + return; + } + throw err; + }); await mongoose.connection.runCommand({ createTable: { name: 'products', @@ -39,8 +69,8 @@ before(async function() { _id: { type: 'text' }, total: { type: 'decimal' }, name: { type: 'text' }, - paymentMethod: { type: 'text' }, - items: { type: 'text' } + paymentMethod: { type: 'map', keyType: 'text', valueType: 'text' }, + items: { type: 'list', valueType: 'text' } } } } @@ -52,7 +82,7 @@ before(async function() { primaryKey: '_id', columns: { _id: { type: 'text' }, - items: { type: 'text' }, + items: { type: 'list', valueType: 'text' }, orderId: { type: 'text' }, total: { type: 'decimal' }, stripeSessionId: { type: 'text' } From e44385bd69a3b4595e56285e2c32f46e7c92396b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 14 Nov 2024 16:05:43 -0500 Subject: [PATCH 24/25] clean up unnecessary error logging and make typescript-express-reviews use lists instead of JSON string --- netlify-functions-ecommerce/connect.js | 6 +++-- .../src/models/connect.ts | 2 +- .../src/models/vehicle.ts | 13 +--------- typescript-express-reviews/src/seed/seed.ts | 2 +- .../tests/index.test.ts | 24 +++++++++++++++++-- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/netlify-functions-ecommerce/connect.js b/netlify-functions-ecommerce/connect.js index a2d2774..b7dae7c 100644 --- a/netlify-functions-ecommerce/connect.js +++ b/netlify-functions-ecommerce/connect.js @@ -23,7 +23,8 @@ module.exports = async function connect() { process.env.ASTRA_NAMESPACE ); jsonApiConnectOptions = { - isAstra: true + isAstra: true, + level: 'fatal' }; } else { uri = process.env.DATA_API_URI; @@ -32,7 +33,8 @@ module.exports = async function connect() { username: process.env.DATA_API_AUTH_USERNAME, password: process.env.DATA_API_AUTH_PASSWORD, authUrl: process.env.DATA_API_AUTH_URL, - featureFlags + featureFlags, + level: 'fatal' }; } await mongoose.connect(uri, jsonApiConnectOptions); diff --git a/typescript-express-reviews/src/models/connect.ts b/typescript-express-reviews/src/models/connect.ts index 6e8fdf1..bc65a6e 100644 --- a/typescript-express-reviews/src/models/connect.ts +++ b/typescript-express-reviews/src/models/connect.ts @@ -22,7 +22,7 @@ export default async function connect() { console.log('Connecting to', uri); await mongoose.connect( uri, - { isAstra: true } as mongoose.ConnectOptions + { isAstra: true, level: 'fatal' } as mongoose.ConnectOptions ); } else { console.log('Connecting to', dataAPIURI); diff --git a/typescript-express-reviews/src/models/vehicle.ts b/typescript-express-reviews/src/models/vehicle.ts index d614bdd..a384cf7 100644 --- a/typescript-express-reviews/src/models/vehicle.ts +++ b/typescript-express-reviews/src/models/vehicle.ts @@ -16,18 +16,7 @@ const schema = new mongoose.Schema({ required: true, validate: (v: number) => Number.isInteger(v) && v >= 1950 }, - images: { - type: String, - get(v?: string | null) { - return v == null ? v : JSON.parse(v); - }, - set(this: mongoose.Document, v: unknown) { - if (v == null) { - return v; - } - return typeof v === 'string' ? v : JSON.stringify(imagesSchemaType.cast(v, this)); - } - }, + images: [String], numReviews: { type: Number, required: true, diff --git a/typescript-express-reviews/src/seed/seed.ts b/typescript-express-reviews/src/seed/seed.ts index 6b4713b..3d919a7 100644 --- a/typescript-express-reviews/src/seed/seed.ts +++ b/typescript-express-reviews/src/seed/seed.ts @@ -81,7 +81,7 @@ async function run() { make: { type: 'text' }, model: { type: 'text' }, year: { type: 'int' }, - images: { type: 'text' }, + images: { type: 'list', valueType: 'text' }, numReviews: { type: 'int' }, averageReview: { type: 'decimal' } } diff --git a/typescript-express-reviews/tests/index.test.ts b/typescript-express-reviews/tests/index.test.ts index 4df2e2c..bea0409 100644 --- a/typescript-express-reviews/tests/index.test.ts +++ b/typescript-express-reviews/tests/index.test.ts @@ -27,24 +27,44 @@ before(async function() { dropTable: { name: 'authentications' } + }).catch(err => { + if (err.errors && err.errors.length === 1 && err.errors[0].errorCode === 'CANNOT_DROP_UNKNOWN_TABLE') { + return; + } + throw err; }); // @ts-ignore await mongoose.connection.runCommand({ dropTable: { name: 'reviews' } - }); + }).catch(err => { + if (err.errors && err.errors.length === 1 && err.errors[0].errorCode === 'CANNOT_DROP_UNKNOWN_TABLE') { + return; + } + throw err; + });; // @ts-ignore await mongoose.connection.runCommand({ dropTable: { name: 'users' } + }).catch(err => { + if (err.errors && err.errors.length === 1 && err.errors[0].errorCode === 'CANNOT_DROP_UNKNOWN_TABLE') { + return; + } + throw err; }); // @ts-ignore await mongoose.connection.runCommand({ dropTable: { name: 'vehicles' } + }).catch(err => { + if (err.errors && err.errors.length === 1 && err.errors[0].errorCode === 'CANNOT_DROP_UNKNOWN_TABLE') { + return; + } + throw err; }); // @ts-ignore @@ -106,7 +126,7 @@ before(async function() { make: { type: 'text' }, model: { type: 'text' }, year: { type: 'int' }, - images: { type: 'text' }, + images: { type: 'list', valueType: 'text' }, numReviews: { type: 'int' }, averageReview: { type: 'decimal' } } From 5eabd6388e32a36d31908eafffbbbd22cfa4c264 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 17 Nov 2024 14:48:40 -0500 Subject: [PATCH 25/25] test fixes for sorting --- typescript-express-reviews/tests/Review.test.ts | 11 +++++------ typescript-express-reviews/tests/Vehicle.test.ts | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/typescript-express-reviews/tests/Review.test.ts b/typescript-express-reviews/tests/Review.test.ts index 3c0bfe2..0e3c33e 100644 --- a/typescript-express-reviews/tests/Review.test.ts +++ b/typescript-express-reviews/tests/Review.test.ts @@ -84,12 +84,12 @@ describe('Review', function() { }, ]); for (let i = 0; i < 6; i++) { - await Review.insertMany([{ + await Review.create({ rating: i > 5 ? 5 : i, text: 'This is a review that must have length greater than 30. ' + i, vehicleId: vehicle.id, userId: user.id - }]); + }); } vehicle.numReviews = 6; vehicle.averageReview = 3; @@ -101,11 +101,10 @@ describe('Review', function() { const reviews = res.json.getCall(0).args[0].reviews; assert.equal(reviews.length, 3); - // TODO: sort doesn't work against tables yet - /*assert.deepEqual( + assert.deepEqual( reviews.map((r: typeof Review) => r.rating), - [3, 2, 1] - );*/ + [5, 4, 3] + ); // Test that populate worked assert.equal(reviews[0].vehicle.make, 'Tesla'); diff --git a/typescript-express-reviews/tests/Vehicle.test.ts b/typescript-express-reviews/tests/Vehicle.test.ts index 03bbe85..6b6d6dc 100644 --- a/typescript-express-reviews/tests/Vehicle.test.ts +++ b/typescript-express-reviews/tests/Vehicle.test.ts @@ -45,12 +45,12 @@ describe('Vehicle', function() { } ]); for (let i = 1; i < 7; i++) { - await Review.insertMany([{ + await Review.create({ rating: i > 5 ? 5 : i, text: 'This is a review that must have length greater than 30. ' + i, vehicleId: vehicle._id, userId: user._id - }]); + }); } vehicle.numReviews = 5; vehicle.averageReview = 3; @@ -62,11 +62,10 @@ describe('Vehicle', function() { const reviews = res.json.getCall(0).args[0].reviews; assert.equal(reviews.length, 5); - // TODO: sort doesn't work against tables yet - /*assert.deepEqual( - reviews.map((r: typeof Review) => r.rating).sort(), + assert.deepEqual( + reviews.map((r: typeof Review) => r.rating), [5, 5, 4, 3, 2] - );*/ + ); // Test that populate worked assert.equal(reviews[0].vehicle.make, 'Tesla');