diff --git a/src/dtos/in/defaultModel.dto.ts b/src/dtos/in/defaultModel.dto.ts index 8004ada..50e5237 100644 --- a/src/dtos/in/defaultModel.dto.ts +++ b/src/dtos/in/defaultModel.dto.ts @@ -19,7 +19,7 @@ export const DefaultModelQueryStringDto = Type.Object({ categoryId: Type.Optional(Type.String({ description: 'The category id' })), likes_ge: Type.Optional(Type.Number({ minimum: 0, description: 'The minimum threshold for number of likes' })), start: Type.Optional( - Type.Number({ minimum: 1, multipleOf: 1, description: 'For pagination purpose - the index of the start item, starting at 1' }) + Type.Number({ minimum: 0, multipleOf: 1, description: 'For pagination purpose - the index of the start item, starting at 0' }) ), noItems: Type.Optional( Type.Number({ minimum: 1, multipleOf: 1, description: 'For pagination purpose - the number of items to return' }) diff --git a/src/dtos/in/index.ts b/src/dtos/in/index.ts index d5142f3..b41fd30 100644 --- a/src/dtos/in/index.ts +++ b/src/dtos/in/index.ts @@ -12,4 +12,5 @@ export * from './updateDefaultModel.dto'; export * from './updateUserModel.dto'; export * from './uploadDefaultModel.dto'; export * from './uploadUserModel.dto'; +export * from './userModel.dto'; export * from './verifyOTP.dto'; diff --git a/src/dtos/in/order.dto.ts b/src/dtos/in/order.dto.ts index 20cc814..dd25633 100644 --- a/src/dtos/in/order.dto.ts +++ b/src/dtos/in/order.dto.ts @@ -1,3 +1,4 @@ +import { Status } from '@prisma/client'; import { Static, Type } from '@sinclair/typebox'; export const CreateOrderInputDto = Type.Object( @@ -28,4 +29,46 @@ export const CreateOrderInputDto = Type.Object( } ); +export const OrderQueryStringDto = Type.Object({ + created_after: Type.Optional( + Type.String({ + format: 'date-time', + description: 'The time after which the order was created, specified in ISO format', + examples: ['2023-12-18'] + }) + ), + created_before: Type.Optional( + Type.String({ + format: 'date-time', + description: 'The time before which the order was created, specified in ISO format', + examples: ['2023-12-25'] + }) + ), + isPaid: Type.Optional(Type.Boolean({ description: 'Filter on paid statud' })), + status: Type.Optional( + Type.Union( + Object.values(Status).map((e) => Type.Literal(e)), + { description: 'Filter on order status' } + ) + ), + userId: Type.Optional(Type.String({ description: 'Filter on user id, only meaningful for managers' })), + start: Type.Optional( + Type.Number({ minimum: 0, multipleOf: 1, description: 'For pagination purpose - the index of the start item, starting at 0' }) + ), + noItems: Type.Optional( + Type.Number({ minimum: 1, multipleOf: 1, description: 'For pagination purpose - the number of items to return' }) + ), + orderBy: Type.Optional( + Type.Union([Type.Literal('totalPrice'), Type.Literal('shippingFee'), Type.Literal('creationTime')], { + description: 'The name of the field to order on', + examples: ['totalPrice', 'shippingFee', 'creationTime'] + }) + ), + order: Type.Optional( + Type.Union([Type.Literal('asc'), Type.Literal('desc')], { description: 'Sort ascending or descending', examples: ['asc', 'desc'] }) + ) +}); + +export type OrderQueryStringDto = Static; + export type CreateOrderInputDto = Static; diff --git a/src/dtos/in/userModel.dto.ts b/src/dtos/in/userModel.dto.ts new file mode 100644 index 0000000..9c13e19 --- /dev/null +++ b/src/dtos/in/userModel.dto.ts @@ -0,0 +1,36 @@ +import { Static, Type } from '@sinclair/typebox'; + +export const UserModelQueryStringDto = Type.Object({ + keyword: Type.Optional(Type.String({ description: 'The substring that the model name should contain' })), + uploaded_after: Type.Optional( + Type.String({ + format: 'date-time', + description: 'The time after which the model was uploaded, specified in ISO format', + examples: ['2023-12-18'] + }) + ), + uploaded_before: Type.Optional( + Type.String({ + format: 'date-time', + description: 'The time before which the model was uploaded, specified in ISO format', + examples: ['2023-12-25'] + }) + ), + start: Type.Optional( + Type.Number({ minimum: 0, multipleOf: 1, description: 'For pagination purpose - the index of the start item, starting at 0' }) + ), + noItems: Type.Optional( + Type.Number({ minimum: 1, multipleOf: 1, description: 'For pagination purpose - the number of items to return' }) + ), + orderBy: Type.Optional( + Type.Union([Type.Literal('uploadedTime'), Type.Literal('price'), Type.Literal('name')], { + description: 'The name of the field to order on', + examples: ['uploadedTime', 'price', 'name'] + }) + ), + order: Type.Optional( + Type.Union([Type.Literal('asc'), Type.Literal('desc')], { description: 'Sort ascending or descending', examples: ['asc', 'desc'] }) + ) +}); + +export type UserModelQueryStringDto = Static; diff --git a/src/handlers/order.handler.ts b/src/handlers/order.handler.ts index d0b8808..21bfe8a 100644 --- a/src/handlers/order.handler.ts +++ b/src/handlers/order.handler.ts @@ -3,6 +3,7 @@ import { OrderListResultDto, OrderResultDto } from '@dtos/out'; import { prisma } from '@repositories'; import { USER_NOT_FOUND, ORDER_NOT_FOUND } from '@constants'; import { UserRole } from '@prisma/client'; +import { OrderQueryStringDto } from '@dtos/in'; const getOrderById: Handler = async (req, res) => { const userId = req.userId; @@ -36,8 +37,7 @@ const getOrderById: Handler = a ward: true }, where: { - id: req.params.orderId, - user_id: user.role === UserRole.MANAGER ? undefined : userId + id: req.params.orderId } }); @@ -63,7 +63,7 @@ const getOrderById: Handler = a }; }; -const getOrders: Handler = async (req, res) => { +const getOrders: Handler = async (req, res) => { const userId = req.userId; const user = await prisma.user.findFirst({ @@ -79,44 +79,60 @@ const getOrders: Handler = async (req, res) => { return res.badRequest(USER_NOT_FOUND); } - const orders = await prisma.order.findMany({ - select: { - id: true, - user_id: true, - creation_time: true, - digital_order_id: true, - district: true, - est_deli_time: true, - extra_note: true, - isPaid: true, - shipping_fee: true, - status: true, - street: true, - streetNo: true, - total_price: true, - ward: true - }, - where: { - user_id: user.role === UserRole.MANAGER ? undefined : userId - } - }); + try { + const orders = await prisma.order.findMany({ + select: { + id: true, + user_id: true, + creation_time: true, + digital_order_id: true, + district: true, + est_deli_time: true, + extra_note: true, + isPaid: true, + shipping_fee: true, + status: true, + street: true, + streetNo: true, + total_price: true, + ward: true + }, + where: { + user_id: user.role === UserRole.MANAGER ? req.query.userId : userId, + creation_time: { + gt: req.query.created_after && new Date(req.query.created_after), + lt: req.query.created_before && new Date(req.query.created_before) + }, + isPaid: req.query.isPaid + }, + orderBy: { + creation_time: req.query.orderBy === 'creationTime' ? req.query.order || 'desc' : undefined, + shipping_fee: req.query.orderBy === 'shippingFee' ? req.query.order || 'desc' : undefined, + total_price: req.query.orderBy === 'totalPrice' ? req.query.order || 'desc' : undefined + }, + skip: req.query.start, + take: req.query.noItems + }); - return orders.map((order) => ({ - creationTime: order.creation_time.toISOString(), - digitalOrderId: order.digital_order_id || undefined, - district: order.district, - estimatedDeliveryTime: order.est_deli_time.toISOString(), - id: order.id, - isPaid: order.isPaid, - shippingFee: order.shipping_fee, - status: order.status, - street: order.street, - streetNo: order.streetNo, - totalPrice: order.total_price, - ward: order.ward, - note: order.extra_note || undefined, - userId: order.user_id - })); + return orders.map((order) => ({ + creationTime: order.creation_time.toISOString(), + digitalOrderId: order.digital_order_id || undefined, + district: order.district, + estimatedDeliveryTime: order.est_deli_time.toISOString(), + id: order.id, + isPaid: order.isPaid, + shippingFee: order.shipping_fee, + status: order.status, + street: order.street, + streetNo: order.streetNo, + totalPrice: order.total_price, + ward: order.ward, + note: order.extra_note || undefined, + userId: order.user_id + })); + } catch (e) { + return []; + } }; export const ordersHandler = { diff --git a/src/handlers/userModel.handler.ts b/src/handlers/userModel.handler.ts index 073ba48..10efe87 100644 --- a/src/handlers/userModel.handler.ts +++ b/src/handlers/userModel.handler.ts @@ -5,8 +5,9 @@ import { Handler } from '@interfaces'; import { UserRole } from '@prisma/client'; import { prisma } from '@repositories'; import { estimatePrice } from '@utils'; +import { UserModelQueryStringDto } from '@dtos/in'; -const getAll: Handler = async (req) => { +const getAll: Handler = async (req) => { const user_id = req.userId; const user = await prisma.user.findFirst({ @@ -18,28 +19,51 @@ const getAll: Handler = async (req) => { } }); - const userModels = await prisma.uploadedModel.findMany({ - select: { - model_id: true, - model: { - select: { - name: true, - price: true, - uploadTime: true + try { + const userModels = await prisma.uploadedModel.findMany({ + select: { + model_id: true, + model: { + select: { + name: true, + price: true, + uploadTime: true + } } - } - }, - where: { - user_id: user?.role === UserRole.CUSTOMER ? user_id : undefined - } - }); + }, + where: { + user_id: user?.role === UserRole.CUSTOMER ? user_id : undefined, + model: { + name: { + contains: req.query.keyword, + mode: 'insensitive' + }, + uploadTime: { + gt: req.query.uploaded_after && new Date(req.query.uploaded_after), + lt: req.query.uploaded_before && new Date(req.query.uploaded_before) + } + } + }, + orderBy: { + model: { + uploadTime: req.query.orderBy === 'uploadedTime' ? req.query.order || 'desc' : undefined, + price: req.query.orderBy === 'price' ? req.query.order || 'asc' : undefined, + name: req.query.orderBy === 'name' ? req.query.order || 'asc' : undefined + } + }, + skip: req.query.start, + take: req.query.noItems + }); - return userModels.map((model) => ({ - id: model.model_id, - name: model.model.name, - price: model.model.price, - uploadTime: model.model.uploadTime.toISOString() - })); + return userModels.map((model) => ({ + id: model.model_id, + name: model.model.name, + price: model.model.price, + uploadTime: model.model.uploadTime.toISOString() + })); + } catch (e) { + return []; + } }; const get: Handler = async (req, res) => { diff --git a/src/routes/apis/order.plugin.ts b/src/routes/apis/order.plugin.ts index 6711d87..cddc71a 100644 --- a/src/routes/apis/order.plugin.ts +++ b/src/routes/apis/order.plugin.ts @@ -1,3 +1,4 @@ +import { OrderQueryStringDto } from '@dtos/in'; import { OrderListResultDto, OrderResultDto } from '@dtos/out'; import { ordersHandler } from '@handlers'; import { verifyToken } from '@hooks'; @@ -11,6 +12,7 @@ export const orderPlugin = createRoutes('Order', [ onRequest: [verifyToken], schema: { summary: 'Get all orders of the current customer. For managers, return all orders', + querystring: OrderQueryStringDto, response: { 200: OrderListResultDto, 400: Type.String() diff --git a/src/routes/apis/userModel.plugin.ts b/src/routes/apis/userModel.plugin.ts index 82fc218..eca5008 100644 --- a/src/routes/apis/userModel.plugin.ts +++ b/src/routes/apis/userModel.plugin.ts @@ -1,4 +1,4 @@ -import { UpdateUserModelInputDto, UploadUserModelInputDto } from '@dtos/in'; +import { UpdateUserModelInputDto, UploadUserModelInputDto, UserModelQueryStringDto } from '@dtos/in'; import { UserModelListResultDto, UserModelResultDto } from '@dtos/out'; import { userModelHandler } from '@handlers'; import { verifyToken, verifyUserRole } from '@hooks'; @@ -14,6 +14,7 @@ export const userModelPlugin = createRoutes('User Model', [ schema: { summary: 'Get all user models', description: 'If the user is a manager, it can view all models. If the user is a student, it can view its models', + querystring: UserModelQueryStringDto, response: { 200: UserModelListResultDto }