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

@@ -142,6 +142,125 @@ const migrate = async () => {
ON CONFLICT (key) DO NOTHING
`
// --- Phase 3: Chat Engine ---
// Add session duration/pricing columns to chat_sessions
await sql`
ALTER TABLE chat_sessions
ADD COLUMN IF NOT EXISTS duration_minutes INT,
ADD COLUMN IF NOT EXISTS price INT DEFAULT 0,
ADD COLUMN IF NOT EXISTS is_free_trial BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS extended_minutes INT NOT NULL DEFAULT 0
`
// Add FCM token columns
await sql`
ALTER TABLE customers
ADD COLUMN IF NOT EXISTS fcm_token VARCHAR(255)
`
await sql`
ALTER TABLE mitras
ADD COLUMN IF NOT EXISTS fcm_token VARCHAR(255)
`
await sql`
CREATE TABLE IF NOT EXISTS chat_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES chat_sessions(id),
sender_type VARCHAR(10) NOT NULL,
sender_id UUID NOT NULL,
type VARCHAR(20) NOT NULL DEFAULT 'text',
content TEXT NOT NULL,
metadata JSONB,
status VARCHAR(20) NOT NULL DEFAULT 'sent',
delivered_at TIMESTAMPTZ,
read_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`
await sql`
CREATE INDEX IF NOT EXISTS idx_chat_messages_session_created
ON chat_messages (session_id, created_at)
`
await sql`
CREATE INDEX IF NOT EXISTS idx_chat_messages_session_status
ON chat_messages (session_id, status)
`
await sql`
CREATE TABLE IF NOT EXISTS session_closures (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES chat_sessions(id),
user_type VARCHAR(10) NOT NULL,
user_id UUID NOT NULL,
message TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`
await sql`
CREATE TABLE IF NOT EXISTS session_extensions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES chat_sessions(id),
requested_duration_minutes INT NOT NULL,
requested_price INT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
responded_at TIMESTAMPTZ
)
`
await sql`
CREATE TABLE IF NOT EXISTS customer_transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID NOT NULL REFERENCES customers(id),
session_id UUID NOT NULL REFERENCES chat_sessions(id),
type VARCHAR(20) NOT NULL,
amount INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`
await sql`
CREATE INDEX IF NOT EXISTS idx_customer_transactions_customer_id
ON customer_transactions (customer_id)
`
// Phase 3 config keys
await sql`
INSERT INTO app_config (key, value)
VALUES ('free_trial_enabled', '{"value": true}')
ON CONFLICT (key) DO NOTHING
`
await sql`
INSERT INTO app_config (key, value)
VALUES ('free_trial_duration_minutes', '{"value": 5}')
ON CONFLICT (key) DO NOTHING
`
await sql`
INSERT INTO app_config (key, value)
VALUES ('extension_timeout_seconds', '{"value": 60}')
ON CONFLICT (key) DO NOTHING
`
await sql`
INSERT INTO app_config (key, value)
VALUES ('early_end_mitra_enabled', '{"value": false}')
ON CONFLICT (key) DO NOTHING
`
await sql`
INSERT INTO app_config (key, value)
VALUES ('early_end_customer_enabled', '{"value": false}')
ON CONFLICT (key) DO NOTHING
`
console.log('Migration complete.')
await sql.end()
}