Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add search filter pagination #58

Merged
5 commits merged into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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