Skip to content

Commit

Permalink
Merge pull request #61 from yeha98555/feature/shared-ticket-code-api
Browse files Browse the repository at this point in the history
feat: Add shared code api
  • Loading branch information
yurychang committed Jun 15, 2023
2 parents 9e298b7 + f1ed8b4 commit 7824f57
Show file tree
Hide file tree
Showing 18 changed files with 386 additions and 168 deletions.
18 changes: 0 additions & 18 deletions mock/tickets.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@
"order_id": {
"$oid": "6466812d6a4040db3709fc0c"
},
"original_order_id": {
"$oid": "6466812d6a4040db3709fc0c"
},
"price": 1000,
"row": 1,
"seat": 2,
Expand All @@ -44,9 +41,6 @@
"order_id": {
"$oid": "6466812d6a4040db3709fc0c"
},
"original_order_id": {
"$oid": "6466812d6a4040db3709fc0c"
},
"price": 1000,
"row": 1,
"seat": 3,
Expand All @@ -72,9 +66,6 @@
"order_id": {
"$oid": "6466812d6a4040db3709fc0c"
},
"original_order_id": {
"$oid": "6466812d6a4040db3709fc0c"
},
"price": 1000,
"row": 1,
"seat": 4,
Expand All @@ -100,9 +91,6 @@
"order_id": {
"$oid": "6466812d6a4040db3709fc10"
},
"original_order_id": {
"$oid": "6466812d6a4040db3709fc10"
},
"price": 1000,
"row": 1,
"seat": 1,
Expand All @@ -128,9 +116,6 @@
"order_id": {
"$oid": "6466812d6a4040db3709fc10"
},
"original_order_id": {
"$oid": "6466812d6a4040db3709fc10"
},
"price": 1000,
"row": 1,
"seat": 5,
Expand All @@ -156,9 +141,6 @@
"order_id": {
"$oid": "6466812d6a4040db3709fc10"
},
"original_order_id": {
"$oid": "6466812d6a4040db3709fc10"
},
"price": 1000,
"row": 1,
"seat": 6,
Expand Down
21 changes: 21 additions & 0 deletions src/controllers/exchangeTicket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import catchAsyncError from '@/utils/catchAsyncError';
import { Body } from '@/utils/response';
import { Request, Response } from 'express';
import shareTicketService from '@/services/shareTicket';

const exchangeTicketController = {
exchangeTicket: catchAsyncError(async (req: Request, res: Response) => {
const { ticketNo, shareCode } = req.body as {
ticketNo: string;
shareCode: string;
};
const result = await shareTicketService.exchangeTicket(
req.userId!,
ticketNo,
shareCode,
);
res.json(Body.success(result));
}),
};

export default exchangeTicketController;
25 changes: 18 additions & 7 deletions src/controllers/ticket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ import catchAsyncError from '@/utils/catchAsyncError';
import ticketService from '@/services/ticket';
import { Body } from '@/utils/response';
import { Request, Response } from 'express';
import shareTicketService from '@/services/shareTicket';
import checkInService from '@/services/checkIn';

const ticketController = {
getTickets: catchAsyncError(async (req: Request, res: Response) => {
const { page = 1, pageSize = 10, isValid = 1 } = req.query;
const { tickets, ...pagination } = await ticketService.getAllTickets({
userId: req.userId!,
isValid: Boolean(+isValid),
page: Number(page),
pageSize: Number(pageSize),
});
res.json(Body.success(tickets).pagination(pagination));
const { ticketGroups, ...pagination } = await ticketService.getTicketGroups(
{
userId: req.userId!,
isValid: Boolean(+isValid),
page: Number(page),
pageSize: Number(pageSize),
},
);
res.json(Body.success(ticketGroups).pagination(pagination));
}),
checkInToken: catchAsyncError(async (req: Request, res: Response) => {
const { ticketNo } = req.params;
Expand All @@ -23,6 +26,14 @@ const ticketController = {
);
res.json(Body.success(token));
}),
generateShareCode: catchAsyncError(async (req: Request, res: Response) => {
const { ticketNo } = req.params;
const result = await shareTicketService.generateShareCode(
req.userId!,
ticketNo,
);
res.json(Body.success(result));
}),
};

export default ticketController;
1 change: 0 additions & 1 deletion src/models/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const orderSchema = new Schema(
},
event_id: { type: Schema.Types.ObjectId, required: true },
order_no: { type: String, required: true, unique: true },
transfer_from_order: Schema.Types.ObjectId,
seat_reservation_id: {
type: Schema.Types.ObjectId,
ref: 'seat_reservation',
Expand Down
14 changes: 9 additions & 5 deletions src/models/ticket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@ import { InferSchemaType, Schema, model } from 'mongoose';
const ticketSchema = new Schema(
{
ticket_no: { type: String, required: true, unique: true },
user_id: { type: Schema.Types.ObjectId, required: true, ref: 'user' },
order_id: { type: Schema.Types.ObjectId, required: true, ref: 'order' },
original_order_id: {
type: Schema.Types.ObjectId,
required: true,
ref: 'order',
},
activity_id: {
type: Schema.Types.ObjectId,
required: true,
Expand All @@ -22,12 +18,20 @@ const ticketSchema = new Schema(
price: { type: Number, required: true },
is_used: { type: Boolean, default: false },
token: { type: String },
shared_by: Schema.Types.ObjectId,
share_code: String,
share_code_create_at: Date,
},
{
timestamps: {
createdAt: 'create_at',
updatedAt: 'update_at',
},
query: {
byNo(ticketNo: string) {
return this.where({ ticket_no: ticketNo });
},
},
},
);

Expand Down
21 changes: 21 additions & 0 deletions src/routes/exchangeTicket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Router } from 'express';
import { isAuth } from '@/middleware/auth';
import exchangeTicketController from '@/controllers/exchangeTicket';
import { validateRequestBody } from '@/middleware/paramsValidator';
import { z } from 'zod';

const exchangeTicketRouter = Router();

exchangeTicketRouter.post(
'/',
isAuth,
validateRequestBody(
z.object({
ticketNo: z.string(),
shareCode: z.string(),
}),
),
exchangeTicketController.exchangeTicket,
);

export default exchangeTicketRouter;
2 changes: 2 additions & 0 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import activityRouter from './activity';
import eventRouter from './event';
import orderRouter from './order';
import ticketRouter from './ticket';
import exchangeTicketRouter from './exchangeTicket';
import checkInRouter from './checkIn';

const router = Router();
Expand Down Expand Up @@ -65,6 +66,7 @@ router.use('/activities', activityRouter);
router.use('/events', eventRouter);
router.use('/orders', orderRouter);
router.use('/tickets', ticketRouter);
router.use('/exchange-ticket', exchangeTicketRouter);
router.use('/check-in', checkInRouter);

export default router;
5 changes: 5 additions & 0 deletions src/routes/ticket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,10 @@ ticketRouter.post(
isAuth,
ticketController.checkInToken,
);
ticketRouter.post(
'/:ticketNo/share-code',
isAuth,
ticketController.generateShareCode,
);

export default ticketRouter;
2 changes: 1 addition & 1 deletion src/services/activity/searchActivities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ const searchActivities = async ({
);

const totalCount = await ActivityModel.countDocuments(filter);
const totalPages = Math.floor(totalCount / pageSize) || 1;
const totalPages = Math.ceil(totalCount / pageSize) || 1;

return {
page,
Expand Down
2 changes: 1 addition & 1 deletion src/services/order/getOrders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const getOrders = async ({

const totalCount = await OrderModel.countDocuments(filter);

const totalPages = Math.floor(totalCount / pageSize) || 1;
const totalPages = Math.ceil(totalCount / pageSize) || 1;

const data = orders.map((o) => {
const { _id, order_no, price, status, activity, seats, create_at } = o;
Expand Down
10 changes: 5 additions & 5 deletions src/services/order/paymentNotify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ const paymentNotify = async (tradeInfo: string) => {
return {
_id: ticketId,
ticket_no: createTicketNo(ticketId, date, s.row, s.seat),
order_id: order?._id,
original_order_id: order?._id,
activity_id: order?.activity_id,
event_id: order?.event_id,
user_id: order!.user_id,
order_id: order!._id,
activity_id: order!.activity_id,
event_id: order!.event_id,
area_id: s.area_id,
subarea_id: s.subarea_id,
row: s.row,
Expand All @@ -77,7 +77,7 @@ const paymentNotify = async (tradeInfo: string) => {
order!.status = status;
order!.seat_reservation_id = undefined;

await order?.save();
await order!.save();

// TODO: Save the payment info to neweb_paymethods
// info.Result.PayTime
Expand Down
39 changes: 39 additions & 0 deletions src/services/shareTicket/generateShareCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { add, isAfter } from 'date-fns';
import { ConflictException } from '@/exceptions/Conflict';
import { NotFoundException } from '@/exceptions/NotFoundException';
import TicketModel from '@/models/ticket';
import { randomString } from '@/utils/makeId';

const shareCodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

export const generateShareCode = async (userId: string, ticketNo: string) => {
const ticket = await TicketModel.findOne({ user_id: userId }).byNo(ticketNo);

if (!ticket) throw new NotFoundException();
if (ticket.shared_by || ticket.is_used) throw new ConflictException();

if (ticket.share_code_create_at) {
const isExpire = isAfter(
new Date(),
add(ticket.share_code_create_at, { minutes: 15 }),
);

if (!isExpire)
return {
shareCode: ticket.share_code,
createAt: ticket.share_code_create_at,
};
}

const shareCode = randomString(8, shareCodeChars);

ticket.share_code = shareCode;
ticket.share_code_create_at = new Date();

await ticket.save();

return {
shareCode,
createAt: ticket.share_code_create_at,
};
};
9 changes: 9 additions & 0 deletions src/services/shareTicket/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { generateShareCode } from './generateShareCode';
import { exchangeTicket } from './shareTicket';

const shareTicketService = {
generateShareCode,
exchangeTicket,
};

export default shareTicketService;
28 changes: 28 additions & 0 deletions src/services/shareTicket/shareTicket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { add } from 'date-fns';
import TicketModel from '@/models/ticket';
import { NotFoundException } from '@/exceptions/NotFoundException';
import { Types } from 'mongoose';

export const exchangeTicket = async (
userId: string,
ticketNo: string,
shareCode: string,
) => {
const ticket = await TicketModel.findOne({
ticket_no: ticketNo,
share_code: shareCode,
share_code_create_at: { $gte: add(new Date(), { minutes: -15 }) },
user_id: { $ne: new Types.ObjectId(userId) },
});

if (!ticket) throw new NotFoundException();

ticket.shared_by = ticket.user_id;
ticket.user_id = new Types.ObjectId(userId);
ticket.share_code = '';
ticket.share_code_create_at = undefined;

await ticket.save();

return true;
};

0 comments on commit 7824f57

Please sign in to comment.