Skip to content

Commit

Permalink
Merge pull request #58 from ngyngcphu/feat/add-search-filter-pagination
Browse files Browse the repository at this point in the history
Feat/add search filter pagination
  • Loading branch information
HuyDNA authored Dec 18, 2023
2 parents c64ef19 + 2cf497d commit fb3654c
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 63 deletions.
2 changes: 1 addition & 1 deletion src/dtos/in/defaultModel.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
Expand Down
1 change: 1 addition & 0 deletions src/dtos/in/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
43 changes: 43 additions & 0 deletions src/dtos/in/order.dto.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Status } from '@prisma/client';
import { Static, Type } from '@sinclair/typebox';

export const CreateOrderInputDto = Type.Object(
Expand Down Expand Up @@ -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<typeof OrderQueryStringDto>;

export type CreateOrderInputDto = Static<typeof CreateOrderInputDto>;
36 changes: 36 additions & 0 deletions src/dtos/in/userModel.dto.ts
Original file line number Diff line number Diff line change
@@ -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<typeof UserModelQueryStringDto>;
96 changes: 56 additions & 40 deletions src/handlers/order.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OrderResultDto, { Params: { orderId: string } }> = async (req, res) => {
const userId = req.userId;
Expand Down Expand Up @@ -36,8 +37,7 @@ const getOrderById: Handler<OrderResultDto, { Params: { orderId: string } }> = a
ward: true
},
where: {
id: req.params.orderId,
user_id: user.role === UserRole.MANAGER ? undefined : userId
id: req.params.orderId
}
});

Expand All @@ -63,7 +63,7 @@ const getOrderById: Handler<OrderResultDto, { Params: { orderId: string } }> = a
};
};

const getOrders: Handler<OrderListResultDto> = async (req, res) => {
const getOrders: Handler<OrderListResultDto, { Querystring: OrderQueryStringDto }> = async (req, res) => {
const userId = req.userId;

const user = await prisma.user.findFirst({
Expand All @@ -79,44 +79,60 @@ const getOrders: Handler<OrderListResultDto> = 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 = {
Expand Down
66 changes: 45 additions & 21 deletions src/handlers/userModel.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<UserModelListResultDto> = async (req) => {
const getAll: Handler<UserModelListResultDto, { Querystring: UserModelQueryStringDto }> = async (req) => {
const user_id = req.userId;

const user = await prisma.user.findFirst({
Expand All @@ -18,28 +19,51 @@ const getAll: Handler<UserModelListResultDto> = 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<UserModelResultDto, { Params: { id: string } }> = async (req, res) => {
Expand Down
2 changes: 2 additions & 0 deletions src/routes/apis/order.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { OrderQueryStringDto } from '@dtos/in';
import { OrderListResultDto, OrderResultDto } from '@dtos/out';
import { ordersHandler } from '@handlers';
import { verifyToken } from '@hooks';
Expand All @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion src/routes/apis/userModel.plugin.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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
}
Expand Down

0 comments on commit fb3654c

Please sign in to comment.