feat: refresh shared chat and server workflows
This commit is contained in:
205
etc/servers/work-server/src/routes/baseball-ticket-bay.ts
Normal file
205
etc/servers/work-server/src/routes/baseball-ticket-bay.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createBaseballTicketBayAlert,
|
||||
createBaseballTicketBayLog,
|
||||
deleteBaseballTicketBayLog,
|
||||
deleteBaseballTicketBayAlert,
|
||||
listBaseballTicketBayAlerts,
|
||||
listBaseballTicketBayLogs,
|
||||
runBaseballTicketBayAlert,
|
||||
searchBaseballTicketBayListings,
|
||||
updateBaseballTicketBayAlert,
|
||||
} from '../services/baseball-ticket-bay-service.js';
|
||||
|
||||
const timeWindowSchema = z.object({
|
||||
id: z.string().trim().min(1),
|
||||
start: z.string().trim().regex(/^\d{2}:\d{2}$/),
|
||||
end: z.string().trim().regex(/^\d{2}:\d{2}$/),
|
||||
});
|
||||
|
||||
const alertPayloadSchema = z.object({
|
||||
title: z.string().trim().min(1).max(255),
|
||||
eventDate: z.string().trim().regex(/^\d{4}-\d{2}-\d{2}$/),
|
||||
team: z.string().trim().min(1).max(50),
|
||||
zone: z.string().trim().min(1).max(100),
|
||||
aisleSide: z.string().trim().min(1).max(100),
|
||||
seatDirections: z.array(z.string().trim().min(1).max(50)).max(10),
|
||||
maxPrice: z.number().finite().positive().nullable(),
|
||||
seatCount: z.number().int().positive().max(10),
|
||||
batchIntervalMinutes: z.number().int().min(1).max(120),
|
||||
sameProductAlertEnabled: z.boolean(),
|
||||
sameProductNotifyOnce: z.boolean(),
|
||||
active: z.boolean().default(true),
|
||||
timeWindows: z.array(timeWindowSchema).min(1).max(24),
|
||||
});
|
||||
|
||||
function readHeader(request: { headers: Record<string, string | string[] | undefined> }, key: string) {
|
||||
const raw = request.headers[key];
|
||||
return Array.isArray(raw) ? String(raw[0] ?? '').trim() : String(raw ?? '').trim();
|
||||
}
|
||||
|
||||
export async function registerBaseballTicketBayRoutes(app: FastifyInstance) {
|
||||
app.post('/api/baseball-ticket-bay/search', async (request) => searchBaseballTicketBayListings(request.body ?? {}));
|
||||
|
||||
app.get('/api/baseball-ticket-bay/alerts', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 알림 목록을 불러올 수 없습니다.' });
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
items: await listBaseballTicketBayAlerts(clientId),
|
||||
};
|
||||
});
|
||||
|
||||
app.get('/api/baseball-ticket-bay/logs', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 로그를 불러올 수 없습니다.' });
|
||||
}
|
||||
|
||||
const query = z.object({ alertId: z.string().trim().min(1).optional() }).parse(request.query ?? {});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
items: await listBaseballTicketBayLogs(clientId, query.alertId),
|
||||
};
|
||||
});
|
||||
|
||||
app.delete('/api/baseball-ticket-bay/logs/:id', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 로그를 삭제할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
|
||||
const item = await deleteBaseballTicketBayLog(params.id, clientId);
|
||||
|
||||
if (!item) {
|
||||
return reply.code(404).send({ message: '삭제할 로그를 찾지 못했습니다.' });
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
item,
|
||||
};
|
||||
});
|
||||
|
||||
app.post('/api/baseball-ticket-bay/alerts', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 알림을 저장할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const payload = alertPayloadSchema.parse(request.body ?? {});
|
||||
const item = await createBaseballTicketBayAlert(payload, {
|
||||
clientId,
|
||||
appOrigin: readHeader(request, 'x-app-origin'),
|
||||
appDomain: readHeader(request, 'x-app-domain'),
|
||||
});
|
||||
await createBaseballTicketBayLog({
|
||||
clientId,
|
||||
alertId: item.id,
|
||||
alertTitle: item.title,
|
||||
action: 'create',
|
||||
status: 'info',
|
||||
message: '알림 조건을 저장했습니다.',
|
||||
detail: `${item.team} · ${item.eventDate}`,
|
||||
});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
item,
|
||||
};
|
||||
});
|
||||
|
||||
app.patch('/api/baseball-ticket-bay/alerts/:id', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 알림을 수정할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
|
||||
const payload = alertPayloadSchema.partial().parse(request.body ?? {});
|
||||
const item = await updateBaseballTicketBayAlert(params.id, payload, {
|
||||
clientId,
|
||||
appOrigin: readHeader(request, 'x-app-origin'),
|
||||
appDomain: readHeader(request, 'x-app-domain'),
|
||||
});
|
||||
await createBaseballTicketBayLog({
|
||||
clientId,
|
||||
alertId: item.id,
|
||||
alertTitle: item.title,
|
||||
action: payload.active === false ? 'pause' : payload.active === true ? 'resume' : 'run',
|
||||
status: 'info',
|
||||
message:
|
||||
payload.active === false
|
||||
? '알림을 중지했습니다.'
|
||||
: payload.active === true
|
||||
? '알림을 다시 실행 상태로 전환했습니다.'
|
||||
: '알림 조건을 수정 저장했습니다.',
|
||||
detail: `${item.team} · ${item.eventDate}`,
|
||||
});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
item,
|
||||
};
|
||||
});
|
||||
|
||||
app.delete('/api/baseball-ticket-bay/alerts/:id', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 알림을 삭제할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
|
||||
const item = await deleteBaseballTicketBayAlert(params.id, clientId);
|
||||
|
||||
if (!item) {
|
||||
return reply.code(404).send({ message: '삭제할 알림을 찾지 못했습니다.' });
|
||||
}
|
||||
|
||||
await createBaseballTicketBayLog({
|
||||
clientId,
|
||||
alertId: item.id,
|
||||
alertTitle: item.title,
|
||||
action: 'delete',
|
||||
status: 'info',
|
||||
message: '알림 항목을 삭제했습니다.',
|
||||
detail: `${item.team} · ${item.eventDate}`,
|
||||
});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
item,
|
||||
};
|
||||
});
|
||||
|
||||
app.post('/api/baseball-ticket-bay/alerts/:id/run', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 즉시 실행할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
|
||||
const result = await runBaseballTicketBayAlert(params.id, { ignoreTimeWindow: true });
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
alert: result.alert,
|
||||
matches: result.matches,
|
||||
notifiedMatches: result.notifiedMatches,
|
||||
log: result.log,
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user