feat: expand live chat and work server tools
This commit is contained in:
@@ -7,6 +7,10 @@ import {
|
||||
upsertAppConfig,
|
||||
upsertChatTypesConfig,
|
||||
} from '../services/app-config-service.js';
|
||||
import {
|
||||
getAutomationContextsConfig,
|
||||
upsertAutomationContextsConfig,
|
||||
} from '../services/automation-context-config-service.js';
|
||||
import { getAutomationTypesConfig, upsertAutomationTypesConfig } from '../services/automation-type-config-service.js';
|
||||
|
||||
export async function registerAppConfigRoutes(app: FastifyInstance) {
|
||||
@@ -37,6 +41,15 @@ export async function registerAppConfigRoutes(app: FastifyInstance) {
|
||||
};
|
||||
});
|
||||
|
||||
app.get('/api/automation-contexts', async () => {
|
||||
const automationContexts = await getAutomationContextsConfig();
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
automationContexts,
|
||||
};
|
||||
});
|
||||
|
||||
app.put('/api/chat-types', async (request, reply) => {
|
||||
try {
|
||||
let payload: unknown = request.body ?? {};
|
||||
@@ -95,6 +108,35 @@ export async function registerAppConfigRoutes(app: FastifyInstance) {
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/automation-contexts', async (request, reply) => {
|
||||
try {
|
||||
let payload: unknown = request.body ?? {};
|
||||
|
||||
if (typeof payload === 'string') {
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
} catch {
|
||||
payload = {};
|
||||
}
|
||||
}
|
||||
|
||||
const parsed = z.object({
|
||||
automationContexts: z.array(z.unknown()),
|
||||
}).parse(payload ?? {});
|
||||
|
||||
const savedAutomationContexts = await upsertAutomationContextsConfig(parsed.automationContexts);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
automationContexts: savedAutomationContexts,
|
||||
};
|
||||
} catch (error) {
|
||||
return reply.code(409).send({
|
||||
message: error instanceof Error ? error.message : '자동화 Context 저장에 실패했습니다.',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/app-config', async (request, reply) => {
|
||||
try {
|
||||
let payload: unknown = request.body ?? {};
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { FastifyInstance } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
listIosNotificationTokens,
|
||||
listWebPushSubscriptions,
|
||||
getAutomationNotificationPreference,
|
||||
getWebPushConfig,
|
||||
registerIosNotificationToken,
|
||||
@@ -51,6 +52,10 @@ export async function registerNotificationRoutes(app: FastifyInstance) {
|
||||
items: await listIosNotificationTokens(),
|
||||
}));
|
||||
|
||||
app.get('/api/notifications/subscriptions/web', async () => ({
|
||||
items: await listWebPushSubscriptions(),
|
||||
}));
|
||||
|
||||
app.get('/api/notifications/webpush/config', async () => getWebPushConfig());
|
||||
|
||||
app.get('/api/notifications/messages', async (request) => {
|
||||
|
||||
@@ -164,11 +164,22 @@ export async function registerPlanRoutes(app: FastifyInstance) {
|
||||
|
||||
const payload = createPlanScheduledTaskSchema.parse(request.body ?? {});
|
||||
const row = await createPlanScheduledTask(payload);
|
||||
const immediateRegistration = payload.enabled && payload.immediateRunEnabled ? await registerPlanScheduledTaskNow(Number(row.id)) : null;
|
||||
const ignoreScheduleDueForImmediateRegistration = !payload.repeatWindowStartTime;
|
||||
const immediateRegistration =
|
||||
payload.executionMode === 'managed-service' && payload.recreateManagedServiceOnNextSave
|
||||
? await registerPlanScheduledTaskNow(Number(row.id), new Date(), {
|
||||
forceManagedServiceGeneration: true,
|
||||
})
|
||||
: payload.enabled && payload.immediateRunEnabled
|
||||
? await registerPlanScheduledTaskNow(Number(row.id), new Date(), {
|
||||
ignoreScheduleDue: ignoreScheduleDueForImmediateRegistration,
|
||||
})
|
||||
: null;
|
||||
const latestRow = await getPlanScheduledTaskById(Number(row.id));
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
item: mapPlanScheduledTaskRow(row),
|
||||
item: mapPlanScheduledTaskRow(latestRow ?? row),
|
||||
registeredPlan: immediateRegistration?.createdPlan ? await getPlanItemById(Number(immediateRegistration.createdPlan.id)) : null,
|
||||
registeredBoardPosts: immediateRegistration?.createdBoardPosts ?? [],
|
||||
};
|
||||
@@ -210,16 +221,34 @@ export async function registerPlanRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
const shouldTriggerImmediateRegistration =
|
||||
row &&
|
||||
Boolean(row.enabled ?? true) &&
|
||||
Boolean(row.immediate_run_enabled ?? true) &&
|
||||
payload.enabled !== false;
|
||||
const immediateRegistration = shouldTriggerImmediateRegistration ? await registerPlanScheduledTaskNow(id) : null;
|
||||
const shouldTriggerImmediateRegistration = (
|
||||
row
|
||||
&& String(row.execution_mode ?? '') === 'managed-service'
|
||||
&& payload.recreateManagedServiceOnNextSave === true
|
||||
) || (
|
||||
row
|
||||
&& Boolean(row.enabled ?? true)
|
||||
&& Boolean(row.immediate_run_enabled ?? true)
|
||||
&& payload.enabled !== false
|
||||
);
|
||||
const effectiveRepeatWindowStartTime =
|
||||
payload.repeatWindowStartTime !== undefined
|
||||
? payload.repeatWindowStartTime
|
||||
: (typeof row?.repeat_window_start_time === 'string' ? row.repeat_window_start_time : null);
|
||||
const immediateRegistration = shouldTriggerImmediateRegistration
|
||||
? await registerPlanScheduledTaskNow(id, new Date(), {
|
||||
ignoreScheduleDue:
|
||||
payload.recreateManagedServiceOnNextSave === true
|
||||
? false
|
||||
: !effectiveRepeatWindowStartTime,
|
||||
forceManagedServiceGeneration: payload.recreateManagedServiceOnNextSave === true,
|
||||
})
|
||||
: null;
|
||||
const latestRow = await getPlanScheduledTaskById(Number(id));
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
item: mapPlanScheduledTaskRow(row),
|
||||
item: mapPlanScheduledTaskRow(latestRow ?? row),
|
||||
registeredPlan: immediateRegistration?.createdPlan ? await getPlanItemById(Number(immediateRegistration.createdPlan.id)) : null,
|
||||
registeredBoardPosts: immediateRegistration?.createdBoardPosts ?? [],
|
||||
};
|
||||
|
||||
146
etc/servers/work-server/src/routes/stock-alert.ts
Normal file
146
etc/servers/work-server/src/routes/stock-alert.ts
Normal 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,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
textMemoNoteCreateSchema,
|
||||
textMemoNoteImportSchema,
|
||||
textMemoNoteUpdateSchema,
|
||||
updateTextMemoLayoutFeatureDescription,
|
||||
updateTextMemoNote,
|
||||
} from '../services/text-memo-service.js';
|
||||
|
||||
@@ -17,6 +18,7 @@ function resolveClientId(headers: Record<string, unknown>) {
|
||||
|
||||
export async function registerTextMemoRoutes(app: FastifyInstance) {
|
||||
app.get('/api/text-memo/notes', async (request) => {
|
||||
await updateTextMemoLayoutFeatureDescription().catch(() => false);
|
||||
const items = await listTextMemoNotes(resolveClientId(request.headers));
|
||||
return {
|
||||
ok: true,
|
||||
@@ -27,6 +29,7 @@ export async function registerTextMemoRoutes(app: FastifyInstance) {
|
||||
app.post('/api/text-memo/notes', async (request) => {
|
||||
const payload = textMemoNoteCreateSchema.parse(request.body ?? {});
|
||||
const item = await createTextMemoNote(resolveClientId(request.headers), payload);
|
||||
await updateTextMemoLayoutFeatureDescription().catch(() => false);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
@@ -37,6 +40,7 @@ export async function registerTextMemoRoutes(app: FastifyInstance) {
|
||||
app.post('/api/text-memo/notes/import', async (request) => {
|
||||
const payload = textMemoNoteImportSchema.parse(request.body ?? {});
|
||||
const items = await importTextMemoNotes(resolveClientId(request.headers), payload);
|
||||
await updateTextMemoLayoutFeatureDescription().catch(() => false);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
@@ -55,6 +59,8 @@ export async function registerTextMemoRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
await updateTextMemoLayoutFeatureDescription().catch(() => false);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
item,
|
||||
@@ -71,6 +77,8 @@ export async function registerTextMemoRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
await updateTextMemoLayoutFeatureDescription().catch(() => false);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user