feat: expand live chat and work server tools

This commit is contained in:
2026-04-30 11:40:02 +09:00
parent 42ae640470
commit 2df0ba30cb
112 changed files with 15241 additions and 996 deletions

View File

@@ -0,0 +1,146 @@
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,
};
});
}