147 lines
4.1 KiB
TypeScript
147 lines
4.1 KiB
TypeScript
import type { FastifyInstance } from 'fastify';
|
|
import { z } from 'zod';
|
|
import {
|
|
STOCK_ALERT_TYPE_OPTIONS,
|
|
createStockAlert,
|
|
deleteStockAlert,
|
|
listStockAlerts,
|
|
sendCurrentPriceStockAlertWebPush,
|
|
searchStockAlertCandidates,
|
|
saveStockAlerts,
|
|
updateStockAlert,
|
|
updateStockAlertLayoutFeatureDescription,
|
|
} from '../services/stock-alert-service.js';
|
|
|
|
const filterTypeSchema = z.enum(STOCK_ALERT_TYPE_OPTIONS.map((option) => option.value) as [string, ...string[]]).default('all');
|
|
|
|
const stockAlertMutationSchema = z
|
|
.object({
|
|
id: z.string().trim().optional(),
|
|
stockCode: z.string().trim().optional(),
|
|
stockName: z.string().trim().optional(),
|
|
alertType: z.string().trim().optional(),
|
|
alertTypes: z.array(z.string().trim()).optional(),
|
|
})
|
|
.transform((value) => ({
|
|
id: value.id,
|
|
stockCode: value.stockCode,
|
|
stockName: value.stockName,
|
|
alertTypes: value.alertTypes?.length
|
|
? value.alertTypes
|
|
: value.alertType?.trim()
|
|
? [value.alertType.trim()]
|
|
: [],
|
|
}));
|
|
|
|
const stockAlertMutationBodySchema = z
|
|
.object({
|
|
stockCode: z.string().trim().optional(),
|
|
stockName: z.string().trim().optional(),
|
|
alertType: z.string().trim().optional(),
|
|
alertTypes: z.array(z.string().trim()).optional(),
|
|
})
|
|
.transform((value) => ({
|
|
stockCode: value.stockCode,
|
|
stockName: value.stockName,
|
|
alertTypes: value.alertTypes?.length
|
|
? value.alertTypes
|
|
: value.alertType?.trim()
|
|
? [value.alertType.trim()]
|
|
: [],
|
|
}));
|
|
|
|
export async function registerStockAlertRoutes(app: FastifyInstance) {
|
|
app.get('/api/stock-alerts/search', async (request) => {
|
|
const query = z
|
|
.object({
|
|
query: z.string().trim().min(1),
|
|
limit: z.coerce.number().int().min(1).max(50).optional(),
|
|
})
|
|
.parse(request.query ?? {});
|
|
|
|
const items = await searchStockAlertCandidates(query.query, query.limit ?? 20);
|
|
|
|
return {
|
|
ok: true,
|
|
items,
|
|
};
|
|
});
|
|
|
|
app.get('/api/stock-alerts', async (request) => {
|
|
const query = z
|
|
.object({
|
|
alertType: filterTypeSchema.optional(),
|
|
})
|
|
.parse(request.query ?? {});
|
|
const alertType = (query.alertType ?? 'all') as 'all' | 'price' | 'top3';
|
|
|
|
await updateStockAlertLayoutFeatureDescription().catch(() => false);
|
|
const items = await listStockAlerts(alertType);
|
|
|
|
return {
|
|
ok: true,
|
|
items,
|
|
};
|
|
});
|
|
|
|
app.post('/api/stock-alerts/notify-current-price', async () => {
|
|
const result = await sendCurrentPriceStockAlertWebPush();
|
|
|
|
return {
|
|
ok: result.ok,
|
|
skipped: Boolean(result.web?.skipped),
|
|
title: result.title,
|
|
body: result.body,
|
|
itemCount: result.itemCount,
|
|
lines: result.lines,
|
|
ios: result.ios,
|
|
web: result.web,
|
|
};
|
|
});
|
|
|
|
app.post('/api/stock-alerts', async (request) => {
|
|
const payload = stockAlertMutationSchema.parse(request.body ?? {});
|
|
const item = await createStockAlert(payload);
|
|
await updateStockAlertLayoutFeatureDescription().catch(() => false);
|
|
|
|
return {
|
|
ok: true,
|
|
item,
|
|
};
|
|
});
|
|
|
|
app.patch('/api/stock-alerts/:id', async (request) => {
|
|
const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
|
|
const payload = stockAlertMutationBodySchema.parse(request.body ?? {});
|
|
const item = await updateStockAlert(params.id, payload);
|
|
await updateStockAlertLayoutFeatureDescription().catch(() => false);
|
|
|
|
return {
|
|
ok: true,
|
|
item,
|
|
};
|
|
});
|
|
|
|
app.put('/api/stock-alerts/batch', async (request) => {
|
|
const payload = z.object({ items: z.array(stockAlertMutationSchema).default([]) }).parse(request.body ?? {});
|
|
const items = await saveStockAlerts(payload.items);
|
|
await updateStockAlertLayoutFeatureDescription().catch(() => false);
|
|
|
|
return {
|
|
ok: true,
|
|
items,
|
|
};
|
|
});
|
|
|
|
app.delete('/api/stock-alerts/:id', async (request) => {
|
|
const params = z.object({ id: z.string().trim().min(1) }).parse(request.params ?? {});
|
|
await deleteStockAlert(params.id);
|
|
await updateStockAlertLayoutFeatureDescription().catch(() => false);
|
|
|
|
return {
|
|
ok: true,
|
|
id: params.id,
|
|
};
|
|
});
|
|
}
|