Phase 3 scaffold: chat engine (WebSocket, FCM, pricing, timer, extension, history)

- Backend: WebSocket plugin, chat/pricing/timer/extension/closure/notification services
- Client app: ChatBloc, pricing dialog, chat screen with message status, extension/goodbye flow, history
- Mitra app: MitraChatBloc, ExtensionBloc, chat screen, extension accept/reject, history
- Control center: free trial, extension timeout, early end config toggles
- DB migration: chat_messages, session_closures, session_extensions, customer_transactions tables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 23:58:11 +08:00
parent 844d7234e6
commit b4efcf14c2
47 changed files with 4361 additions and 44 deletions

View File

@@ -29,3 +29,73 @@ export const setMaxCustomersPerMitra = async (value) => {
`
return { max_customers_per_mitra: value }
}
// --- Phase 3 config ---
export const getFreeTrialConfig = async () => {
const [enabledRow] = await sql`SELECT value FROM app_config WHERE key = 'free_trial_enabled'`
const [durationRow] = await sql`SELECT value FROM app_config WHERE key = 'free_trial_duration_minutes'`
return {
enabled: enabledRow?.value?.value ?? false,
duration_minutes: durationRow?.value?.value ?? 5,
}
}
export const setFreeTrialConfig = async ({ enabled, duration_minutes }) => {
if (enabled !== undefined) {
await sql`
INSERT INTO app_config (key, value, updated_at)
VALUES ('free_trial_enabled', ${sql.json({ value: enabled })}, NOW())
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()
`
}
if (duration_minutes !== undefined) {
await sql`
INSERT INTO app_config (key, value, updated_at)
VALUES ('free_trial_duration_minutes', ${sql.json({ value: duration_minutes })}, NOW())
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()
`
}
return getFreeTrialConfig()
}
export const getExtensionTimeoutConfig = async () => {
const [row] = await sql`SELECT value FROM app_config WHERE key = 'extension_timeout_seconds'`
return { extension_timeout_seconds: row?.value?.value ?? 60 }
}
export const setExtensionTimeoutConfig = async (seconds) => {
await sql`
INSERT INTO app_config (key, value, updated_at)
VALUES ('extension_timeout_seconds', ${sql.json({ value: seconds })}, NOW())
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()
`
return { extension_timeout_seconds: seconds }
}
export const getEarlyEndConfig = async () => {
const [mitraRow] = await sql`SELECT value FROM app_config WHERE key = 'early_end_mitra_enabled'`
const [customerRow] = await sql`SELECT value FROM app_config WHERE key = 'early_end_customer_enabled'`
return {
mitra_enabled: mitraRow?.value?.value ?? false,
customer_enabled: customerRow?.value?.value ?? false,
}
}
export const setEarlyEndConfig = async ({ mitra_enabled, customer_enabled }) => {
if (mitra_enabled !== undefined) {
await sql`
INSERT INTO app_config (key, value, updated_at)
VALUES ('early_end_mitra_enabled', ${sql.json({ value: mitra_enabled })}, NOW())
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()
`
}
if (customer_enabled !== undefined) {
await sql`
INSERT INTO app_config (key, value, updated_at)
VALUES ('early_end_customer_enabled', ${sql.json({ value: customer_enabled })}, NOW())
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()
`
}
return getEarlyEndConfig()
}