chore: test deploy snapshot
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import { hasErrorLogViewAccessToken } from '../services/error-log-service.js';
|
||||
import { getSharedResourceTokenDetailByShareToken } from '../services/shared-resource-token-service.js';
|
||||
import {
|
||||
createBaseballTicketBayAlert,
|
||||
createBaseballTicketBayLog,
|
||||
@@ -39,46 +41,115 @@ function readHeader(request: { headers: Record<string, string | string[] | undef
|
||||
return Array.isArray(raw) ? String(raw[0] ?? '').trim() : String(raw ?? '').trim();
|
||||
}
|
||||
|
||||
function hasBaseballTicketBayGlobalAccess(request: { headers: Record<string, string | string[] | undefined> }) {
|
||||
return hasErrorLogViewAccessToken(request.headers['x-access-token']);
|
||||
}
|
||||
|
||||
type BaseballTicketBayRouteAccessContext =
|
||||
| { scope: 'all' }
|
||||
| { scope: 'client'; clientId: string }
|
||||
| { scope: 'shared-token'; clientId: string; tokenId: string };
|
||||
|
||||
function toOwnerScope(accessContext: Exclude<BaseballTicketBayRouteAccessContext, { scope: 'all' }> | { scope: 'all' }) {
|
||||
if (accessContext.scope === 'all') {
|
||||
return { kind: 'all' } as const;
|
||||
}
|
||||
|
||||
if (accessContext.scope === 'shared-token') {
|
||||
return { kind: 'owner', ownerType: 'shared-token', ownerId: accessContext.tokenId } as const;
|
||||
}
|
||||
|
||||
return { kind: 'owner', ownerType: 'client', ownerId: accessContext.clientId } as const;
|
||||
}
|
||||
|
||||
async function resolveBaseballTicketBayAccessContext(
|
||||
request: { headers: Record<string, string | string[] | undefined> },
|
||||
) : Promise<BaseballTicketBayRouteAccessContext | null> {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
|
||||
if (hasBaseballTicketBayGlobalAccess(request)) {
|
||||
return { scope: 'all' };
|
||||
}
|
||||
|
||||
const accessToken = readHeader(request, 'x-access-token');
|
||||
|
||||
if (accessToken) {
|
||||
const sharedTokenDetail = await getSharedResourceTokenDetailByShareToken(accessToken);
|
||||
|
||||
if (
|
||||
sharedTokenDetail
|
||||
&& sharedTokenDetail.token.enabled !== false
|
||||
&& !sharedTokenDetail.token.revokedAt
|
||||
&& sharedTokenDetail.token.allowedAppIds.some((item) => item.trim().toLowerCase() === 'baseball-ticket-bay')
|
||||
) {
|
||||
if (!clientId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
scope: 'shared-token',
|
||||
clientId,
|
||||
tokenId: sharedTokenDetail.token.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!clientId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
scope: 'client',
|
||||
clientId,
|
||||
};
|
||||
}
|
||||
|
||||
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');
|
||||
const accessContext = await resolveBaseballTicketBayAccessContext(request);
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 알림 목록을 불러올 수 없습니다.' });
|
||||
if (!accessContext) {
|
||||
return reply.code(400).send({ message: '접근 식별값이 없어 알림 목록을 불러올 수 없습니다.' });
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
items: await listBaseballTicketBayAlerts(clientId),
|
||||
includeAllClients: accessContext.scope === 'all',
|
||||
accessScope: accessContext.scope,
|
||||
scopeOwnerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.scope === 'client' ? accessContext.clientId : null,
|
||||
items: await listBaseballTicketBayAlerts(toOwnerScope(accessContext)),
|
||||
};
|
||||
});
|
||||
|
||||
app.get('/api/baseball-ticket-bay/logs', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
const accessContext = await resolveBaseballTicketBayAccessContext(request);
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 로그를 불러올 수 없습니다.' });
|
||||
if (!accessContext) {
|
||||
return reply.code(400).send({ message: '접근 식별값이 없어 로그를 불러올 수 없습니다.' });
|
||||
}
|
||||
|
||||
const query = z.object({ alertId: z.string().trim().min(1).optional() }).parse(request.query ?? {});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
items: await listBaseballTicketBayLogs(clientId, query.alertId),
|
||||
includeAllClients: accessContext.scope === 'all',
|
||||
accessScope: accessContext.scope,
|
||||
scopeOwnerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.scope === 'client' ? accessContext.clientId : null,
|
||||
items: await listBaseballTicketBayLogs(toOwnerScope(accessContext), query.alertId),
|
||||
};
|
||||
});
|
||||
|
||||
app.delete('/api/baseball-ticket-bay/logs/:id', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
const accessContext = await resolveBaseballTicketBayAccessContext(request);
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 로그를 삭제할 수 없습니다.' });
|
||||
if (!accessContext || accessContext.scope === 'all') {
|
||||
return reply.code(400).send({ message: '현재 접근 범위에서는 로그를 삭제할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
|
||||
const item = await deleteBaseballTicketBayLog(params.id, clientId);
|
||||
const item = await deleteBaseballTicketBayLog(params.id, toOwnerScope(accessContext));
|
||||
|
||||
if (!item) {
|
||||
return reply.code(404).send({ message: '삭제할 로그를 찾지 못했습니다.' });
|
||||
@@ -91,20 +162,24 @@ export async function registerBaseballTicketBayRoutes(app: FastifyInstance) {
|
||||
});
|
||||
|
||||
app.post('/api/baseball-ticket-bay/alerts', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
const accessContext = await resolveBaseballTicketBayAccessContext(request);
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 알림을 저장할 수 없습니다.' });
|
||||
if (!accessContext || accessContext.scope === 'all') {
|
||||
return reply.code(400).send({ message: '현재 접근 범위에서는 알림을 저장할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const payload = alertPayloadSchema.parse(request.body ?? {});
|
||||
const item = await createBaseballTicketBayAlert(payload, {
|
||||
clientId,
|
||||
clientId: accessContext.clientId,
|
||||
ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
|
||||
ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
|
||||
appOrigin: readHeader(request, 'x-app-origin'),
|
||||
appDomain: readHeader(request, 'x-app-domain'),
|
||||
});
|
||||
await createBaseballTicketBayLog({
|
||||
clientId,
|
||||
clientId: accessContext.clientId,
|
||||
ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
|
||||
ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
|
||||
alertId: item.id,
|
||||
alertTitle: item.title,
|
||||
action: 'create',
|
||||
@@ -120,21 +195,25 @@ export async function registerBaseballTicketBayRoutes(app: FastifyInstance) {
|
||||
});
|
||||
|
||||
app.patch('/api/baseball-ticket-bay/alerts/:id', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
const accessContext = await resolveBaseballTicketBayAccessContext(request);
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 알림을 수정할 수 없습니다.' });
|
||||
if (!accessContext || accessContext.scope === 'all') {
|
||||
return reply.code(400).send({ message: '현재 접근 범위에서는 알림을 수정할 수 없습니다.' });
|
||||
}
|
||||
|
||||
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,
|
||||
clientId: accessContext.clientId,
|
||||
ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
|
||||
ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
|
||||
appOrigin: readHeader(request, 'x-app-origin'),
|
||||
appDomain: readHeader(request, 'x-app-domain'),
|
||||
});
|
||||
await createBaseballTicketBayLog({
|
||||
clientId,
|
||||
clientId: accessContext.clientId,
|
||||
ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
|
||||
ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
|
||||
alertId: item.id,
|
||||
alertTitle: item.title,
|
||||
action: payload.active === false ? 'pause' : payload.active === true ? 'resume' : 'run',
|
||||
@@ -155,21 +234,23 @@ export async function registerBaseballTicketBayRoutes(app: FastifyInstance) {
|
||||
});
|
||||
|
||||
app.delete('/api/baseball-ticket-bay/alerts/:id', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
const accessContext = await resolveBaseballTicketBayAccessContext(request);
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 알림을 삭제할 수 없습니다.' });
|
||||
if (!accessContext || accessContext.scope === 'all') {
|
||||
return reply.code(400).send({ message: '현재 접근 범위에서는 알림을 삭제할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
|
||||
const item = await deleteBaseballTicketBayAlert(params.id, clientId);
|
||||
const item = await deleteBaseballTicketBayAlert(params.id, toOwnerScope(accessContext));
|
||||
|
||||
if (!item) {
|
||||
return reply.code(404).send({ message: '삭제할 알림을 찾지 못했습니다.' });
|
||||
}
|
||||
|
||||
await createBaseballTicketBayLog({
|
||||
clientId,
|
||||
clientId: accessContext.clientId,
|
||||
ownerType: accessContext.scope === 'shared-token' ? 'shared-token' : 'client',
|
||||
ownerId: accessContext.scope === 'shared-token' ? accessContext.tokenId : accessContext.clientId,
|
||||
alertId: item.id,
|
||||
alertTitle: item.title,
|
||||
action: 'delete',
|
||||
@@ -185,14 +266,22 @@ export async function registerBaseballTicketBayRoutes(app: FastifyInstance) {
|
||||
});
|
||||
|
||||
app.post('/api/baseball-ticket-bay/alerts/:id/run', async (request, reply) => {
|
||||
const clientId = readHeader(request, 'x-client-id');
|
||||
const accessContext = await resolveBaseballTicketBayAccessContext(request);
|
||||
|
||||
if (!clientId) {
|
||||
return reply.code(400).send({ message: '클라이언트 ID가 없어 즉시 실행할 수 없습니다.' });
|
||||
if (!accessContext) {
|
||||
return reply.code(400).send({ message: '접근 식별값이 없어 즉시 실행할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
|
||||
const result = await runBaseballTicketBayAlert(params.id, { ignoreTimeWindow: true });
|
||||
|
||||
if (accessContext.scope === 'all') {
|
||||
return reply.code(403).send({ message: '전체 보기 범위에서는 즉시 실행할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const result = await runBaseballTicketBayAlert(params.id, {
|
||||
ignoreTimeWindow: true,
|
||||
scope: toOwnerScope(accessContext),
|
||||
});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
|
||||
Reference in New Issue
Block a user