From 7d29d4cf83bdd2d3d93e59e8d073c9b0ae0bddab Mon Sep 17 00:00:00 2001 From: Oyinlola Olasunkanmi Raymond <60177090+olasunkanmi-SE@users.noreply.github.com> Date: Sun, 21 Jan 2024 16:51:04 +0800 Subject: [PATCH] Development (#484) * include restaurant information as part of the menu response (#471) * include restaurant information as part of the menu response * fix build errors * fix build errors --------- Co-authored-by: Olasunkanmi Oyinlola * 349 place order (#473) * include restaurant information as part of the menu response * fix build errors * fix build errors * fix the create order flow, from frontend to backend --------- Co-authored-by: Olasunkanmi Oyinlola * 349 place order (#476) * 349 place order (#478) * include restaurant information as part of the menu response * fix build errors * fix build errors * fix the create order flow, from frontend to backend * place order, add success animation and clear local storage if the cart has been filled for an hr and more * fix build errors * fix build errors * move the implementation of adding expiry to cart to addItemToCart handler * fix issues with creating notes when none was passwd * npm audit to upgrade packages with issues --------- Co-authored-by: Olasunkanmi Oyinlola * display api error message * display api error message * save the ordersummary of each order, to recreate the order in order history * include mogoDB transaction during order creation * fix build error --------- Co-authored-by: Olasunkanmi Oyinlola --- .../repositories/cart-item.repository.ts | 24 ++++-- .../cart-item-repository.interface.ts | 4 +- .../interfaces/order-repository.interface.ts | 3 +- .../mongoDB/generic-document.interface.ts | 12 ++- .../mongoDB/generic-document.repository.ts | 76 ++++++++++++++++--- backend/src/order/order.service.ts | 14 +++- 6 files changed, 108 insertions(+), 25 deletions(-) diff --git a/backend/src/infrastructure/data_access/repositories/cart-item.repository.ts b/backend/src/infrastructure/data_access/repositories/cart-item.repository.ts index adaf83ee..d44fcc13 100644 --- a/backend/src/infrastructure/data_access/repositories/cart-item.repository.ts +++ b/backend/src/infrastructure/data_access/repositories/cart-item.repository.ts @@ -1,8 +1,10 @@ -import { Injectable } from '@nestjs/common'; +import { HttpStatus, Injectable } from '@nestjs/common'; import { InjectConnection, InjectModel } from '@nestjs/mongoose'; import { Connection, Model } from 'mongoose'; import { CartItem } from 'src/cart/cart-item'; +import { Result } from 'src/domain'; import { GenericDocumentRepository } from 'src/infrastructure/database'; +import { throwApplicationError } from 'src/infrastructure/utilities/exception-instance'; import { CartItemMapper } from './../../../cart/cart-item.mapper'; import { ICartItemRepository } from './interfaces/cart-item-repository.interface'; import { CartItemDataModel, CartItemDocument } from './schemas/cartItem.schema'; @@ -22,10 +24,20 @@ export class CartItemRepository this.cartItemMapper = cartItemMapper; } - async updateCartItemSelectedItems(cartItems: CartItem[]): Promise { - const document = cartItems.map((doc) => this.cartItemMapper.toPersistence(doc)); - document.forEach((item) => { - this.updateOne({ _id: item._id }, { selectedItems: item.selectedItems }); - }); + async updateCartItemSelectedItems(cartItems: CartItem[]): Promise> { + try { + const document = cartItems.map((doc) => this.cartItemMapper.toPersistence(doc)); + const selectedItemsToUpdate = document.map((doc) => ({ _id: doc._id, selectedItems: doc.selectedItems })); + const result = await this.updateMany( + { _id: { $in: document.map((doc) => doc._id) } }, + { $set: { selectedItems: selectedItemsToUpdate } }, + ); + if (!result.isSuccess) { + throwApplicationError(HttpStatus.BAD_REQUEST, ''); + } + return result; + } catch (error) { + console.error('an error occured', error); + } } } diff --git a/backend/src/infrastructure/data_access/repositories/interfaces/cart-item-repository.interface.ts b/backend/src/infrastructure/data_access/repositories/interfaces/cart-item-repository.interface.ts index 6c6177ec..e495690f 100644 --- a/backend/src/infrastructure/data_access/repositories/interfaces/cart-item-repository.interface.ts +++ b/backend/src/infrastructure/data_access/repositories/interfaces/cart-item-repository.interface.ts @@ -1,7 +1,9 @@ import { CartItem } from 'src/cart/cart-item'; import { IGenericDocument } from 'src/infrastructure/database'; import { CartItemDocument } from '../schemas/cartItem.schema'; +import { Result } from 'src/domain'; +import { ClientSession } from 'mongoose'; export interface ICartItemRepository extends IGenericDocument { - updateCartItemSelectedItems(cartItems: CartItem[]): Promise; + updateCartItemSelectedItems(cartItems: CartItem[], options?: { session: ClientSession }): Promise>; } diff --git a/backend/src/infrastructure/data_access/repositories/interfaces/order-repository.interface.ts b/backend/src/infrastructure/data_access/repositories/interfaces/order-repository.interface.ts index 1c4f5af0..f6b5aa71 100644 --- a/backend/src/infrastructure/data_access/repositories/interfaces/order-repository.interface.ts +++ b/backend/src/infrastructure/data_access/repositories/interfaces/order-repository.interface.ts @@ -3,9 +3,10 @@ import { OrderDataModel, OrderDocument } from '../schemas/order.schema'; import { IGenericDocument } from 'src/infrastructure/database'; import { Result } from 'src/domain'; import { CreateCartItemsDTO } from 'src/order/dto/create-order.dto'; +import { ClientSession } from 'mongoose'; export interface IOrderRepository extends IGenericDocument { - createOrder(order: OrderDataModel): Promise>; + createOrder(order: OrderDataModel, options?: { session: ClientSession }): Promise>; getDuplicateOrder(type: string, singleclientId: string, cartItems: CreateCartItemsDTO[]): Promise; getOrders(): Promise>; } diff --git a/backend/src/infrastructure/database/mongoDB/generic-document.interface.ts b/backend/src/infrastructure/database/mongoDB/generic-document.interface.ts index 303fe566..bbf4d965 100644 --- a/backend/src/infrastructure/database/mongoDB/generic-document.interface.ts +++ b/backend/src/infrastructure/database/mongoDB/generic-document.interface.ts @@ -14,9 +14,13 @@ export interface IGenericDocument { create(document: any, options?: SaveOptions): Promise>; - findOneAndUpdate(filterQuery: FilterQuery, update: UpdateQuery): Promise>; + findOneAndUpdate( + filterQuery: FilterQuery, + update: UpdateQuery, + options?: { session: ClientSession }, + ): Promise>; - upsert(filterQuery: FilterQuery, document: Partial): Promise>; + upsert(filterQuery: FilterQuery, document: Partial, options?: QueryOptions): Promise>; deleteMany(filterQuery: FilterQuery): Promise; @@ -31,4 +35,8 @@ export interface IGenericDocument { objectIdToString(objectId: Types.ObjectId): string; stringToObjectId(prop: string): Types.ObjectId; + + updateMany(query: FilterQuery, updateBody: UpdateQuery, options?: QueryOptions): Promise>; + + insertManyWithSession(docs: any, options?: QueryOptions): Promise>; } diff --git a/backend/src/infrastructure/database/mongoDB/generic-document.repository.ts b/backend/src/infrastructure/database/mongoDB/generic-document.repository.ts index b1ab3349..06e4d7f5 100644 --- a/backend/src/infrastructure/database/mongoDB/generic-document.repository.ts +++ b/backend/src/infrastructure/database/mongoDB/generic-document.repository.ts @@ -96,7 +96,7 @@ export abstract class GenericDocumentRepository imp } async create(document: any, options?: SaveOptions): Promise> { - const doc = this.createDocument(document); + const doc = this.createDocument(document, options); const result = (await (await doc.save(options)).toJSON()) as T; if (!result) { return Result.fail('An Error occured, unable to save document in the db', HttpStatus.INTERNAL_SERVER_ERROR); @@ -105,10 +105,18 @@ export abstract class GenericDocumentRepository imp return Result.ok(entity); } - async findOneAndUpdate(filterQuery: FilterQuery, update: UpdateQuery): Promise> { - const result = await this.DocumentModel.findByIdAndUpdate(filterQuery, update, { + async findOneAndUpdate( + filterQuery: FilterQuery, + update: UpdateQuery, + options?: { session: ClientSession }, + ): Promise> { + const queryOptions: any = { new: true, - }); + }; + if (options?.session) { + queryOptions.session = options.session; + } + const result = await this.DocumentModel.findByIdAndUpdate(filterQuery, update, queryOptions); if (!result) { return Result.fail('An Error occured, unable to update the database', HttpStatus.INTERNAL_SERVER_ERROR); } @@ -116,12 +124,20 @@ export abstract class GenericDocumentRepository imp return Result.ok(entity); } - async upsert(filterQuery: FilterQuery, document: Partial): Promise> { - const result = await this.DocumentModel.findOneAndUpdate(filterQuery, document, { + async upsert( + filterQuery: FilterQuery, + document: Partial, + options?: { session?: ClientSession }, + ): Promise> { + const queryOption: any = { lean: true, upsert: true, new: true, - }); + }; + if (options?.session) { + queryOption.session = options.session; + } + const result = await this.DocumentModel.findOneAndUpdate(filterQuery, document, { session: options.session }); if (!result) { return Result.fail('Unable to update the database', HttpStatus.INTERNAL_SERVER_ERROR); @@ -158,6 +174,26 @@ export abstract class GenericDocumentRepository imp } } + async insertManyWithSession(docs: any, options?: QueryOptions): Promise> { + try { + let documentIds: Types.ObjectId[]; + const documentsToSave = docs.map((doc) => this.createDocument(doc)); + const documents = await this.DocumentModel.insertMany(documentsToSave, { + rawResult: true, + }); + if (documents.insertedCount < docs.length) { + throwApplicationError(HttpStatus.INTERNAL_SERVER_ERROR, 'Unable to insert documents into the database'); + } + const insertedDocIds: any = Object.values(documents.insertedIds); + if (insertedDocIds?.length) { + documentIds = insertedDocIds.map((id) => this.stringToObjectId(id)); + } + return Result.ok(documentIds); + } catch (error) { + console.error(error); + } + } + async updateOne(filter: any, query: any): Promise> { try { const document = await this.DocumentModel.updateOne(filter, { $set: query }); @@ -171,14 +207,25 @@ export abstract class GenericDocumentRepository imp } } - async updateMany(query: FilterQuery, updateBody: UpdateQuery): Promise> { + async updateMany( + query: FilterQuery, + updateBody: UpdateQuery, + options?: { session?: ClientSession }, + ): Promise> { try { - const saved = await this.DocumentModel.updateMany(query, updateBody, { + const updateOptions: QueryOptions = { multi: true, - }); + }; + + if (options?.session) { + updateOptions.session = options.session; + } + + const saved = await this.DocumentModel.updateMany(query, updateBody, updateOptions); if (saved.matchedCount < 1) { throwApplicationError(HttpStatus.INTERNAL_SERVER_ERROR, 'Unable to update documents into the database'); } + const updatedDocuments = await this.DocumentModel.find(query); const entities: TEntity[] = updatedDocuments.map((doc) => this.mapper.toDomain(doc)); return Result.ok(entities); @@ -187,10 +234,15 @@ export abstract class GenericDocumentRepository imp } } - createDocument(document: any) { - return new this.DocumentModel({ + private createDocument(document: any, options?: { session?: ClientSession }) { + const doc = new this.DocumentModel({ ...document, _id: new Types.ObjectId(), }); + + if (options?.session) { + doc.$session(options.session); + } + return doc; } } diff --git a/backend/src/order/order.service.ts b/backend/src/order/order.service.ts index baeac02b..323de902 100644 --- a/backend/src/order/order.service.ts +++ b/backend/src/order/order.service.ts @@ -95,8 +95,9 @@ export class OrderService implements IOrderService { const orderWithCartItems = await this.orderRepository.upsert( { _id: orderId }, this.orderMapper.toPersistence(savedOrder), + { session }, ); - if (orderWithCartItems.isSuccess === false) { + if (!orderWithCartItems.isSuccess) { throwApplicationError(HttpStatus.INTERNAL_SERVER_ERROR, `Error while creating order`); } @@ -122,7 +123,7 @@ export class OrderService implements IOrderService { }), ); const selectedCartItemsDataModel = selectedItems.map((item) => this.selectedItemMapper.toPersistence(item)); - const insertedItems: Result = await this.selectedCartItemRepository.insertMany( + const insertedItems: Result = await this.selectedCartItemRepository.insertManyWithSession( selectedCartItemsDataModel, ); if (!insertedItems.isSuccess) { @@ -131,7 +132,14 @@ export class OrderService implements IOrderService { const notes = await this.createOrderNotes(cartItems, orderId); const notesToSave: OrderNote[] = notes || []; const response: IOrderResponseDTO | undefined = OrderParser.createOrderResponse(savedOrder, notesToSave); - const savedSelectedItems = insertedItems.getValue(); + const savedSelectedItemIds = insertedItems.getValue(); + let savedSelectedItems: SelectedCartItem[]; + if (savedSelectedItemIds.length) { + const result = await this.selectedCartItemRepository.find({ _id: { $in: savedSelectedItemIds } }); + if (result.isSuccess) { + savedSelectedItems = result.getValue(); + } + } const savedItemsMap = savedSelectedItems.reduce((map, item) => { const cartItemIdToString = this.cartItemRepository.objectIdToString(item.cartItemId); !map.has(cartItemIdToString) ? map.set(cartItemIdToString, [item]) : map.get(cartItemIdToString).push(item);