Phase 3.1 WS2: Backend FCM fallback, ping config, unread API
- Add require_mitra_ping + mitra_ping_interval_seconds config keys (migration) - Add getMitraPingConfig/setMitraPingConfig to config service - Add GET/PATCH /internal/config/mitra-ping routes for control center - Update mitra status service: honor ping config in auto-offline sweep, include ping config in GET /api/mitra/status response - Enhance pairing FCM payload with action: 'open_accept' for deep-link - Add FCM fallback to closure.service (initiateEarlyEnd, completeSession) - Add FCM fallback to session-timer.service (onSessionExpired) - Add unread count queries (getActiveSessionByCustomerWithUnread, getActiveSessionsByMitraWithUnread) - Add GET /api/client/chat/session/active-with-unread route - Add GET /api/mitra/chat-requests/sessions/active-with-unread route Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
getFreeTrialConfig, setFreeTrialConfig,
|
||||
getExtensionTimeoutConfig, setExtensionTimeoutConfig,
|
||||
getEarlyEndConfig, setEarlyEndConfig,
|
||||
getMitraPingConfig, setMitraPingConfig,
|
||||
} from '../../services/config.service.js'
|
||||
|
||||
const attachCcUser = async (request, reply) => {
|
||||
@@ -102,6 +103,28 @@ export const internalConfigRoutes = async (app) => {
|
||||
return reply.send({ success: true, data: config })
|
||||
})
|
||||
|
||||
// --- Phase 3.1: Mitra Ping Config ---
|
||||
app.get('/mitra-ping', {
|
||||
preHandler: [authenticate, attachCcUser, requirePermission('config', 'read')],
|
||||
}, async (request, reply) => {
|
||||
const config = await getMitraPingConfig()
|
||||
return reply.send({ success: true, data: config })
|
||||
})
|
||||
|
||||
app.patch('/mitra-ping', {
|
||||
preHandler: [authenticate, attachCcUser, requirePermission('config', 'update')],
|
||||
}, async (request, reply) => {
|
||||
const { require_ping, ping_interval_seconds } = request.body ?? {}
|
||||
if (require_ping !== undefined && typeof require_ping !== 'boolean') {
|
||||
return reply.code(422).send({ success: false, error: { code: 'VALIDATION_ERROR', message: 'require_ping must be a boolean' } })
|
||||
}
|
||||
if (ping_interval_seconds !== undefined && (typeof ping_interval_seconds !== 'number' || ping_interval_seconds < 5)) {
|
||||
return reply.code(422).send({ success: false, error: { code: 'VALIDATION_ERROR', message: 'ping_interval_seconds must be a number >= 5' } })
|
||||
}
|
||||
const config = await setMitraPingConfig({ require_ping, ping_interval_seconds })
|
||||
return reply.send({ success: true, data: config })
|
||||
})
|
||||
|
||||
// --- Price Tiers ---
|
||||
app.get('/price-tiers', {
|
||||
preHandler: [authenticate, attachCcUser, requirePermission('config', 'read')],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { authenticate } from '../../plugins/auth.js'
|
||||
import { getCustomerByFirebaseUid } from '../../services/customer.service.js'
|
||||
import { createPairingRequest, cancelPairingRequest } from '../../services/pairing.service.js'
|
||||
import { getActiveSessionByCustomer, endSession, getCustomerHistory } from '../../services/session.service.js'
|
||||
import { getActiveSessionByCustomer, getActiveSessionByCustomerWithUnread, endSession, getCustomerHistory } from '../../services/session.service.js'
|
||||
import { getPricingForCustomer, isValidTier, isCustomerEligibleForFreeTrial, getFreeTrial } from '../../services/pricing.service.js'
|
||||
import { requestExtension } from '../../services/extension.service.js'
|
||||
import { EndedBy } from '../../constants.js'
|
||||
@@ -73,6 +73,11 @@ export const clientChatRoutes = async (app) => {
|
||||
return reply.send({ success: true, data: session ?? null })
|
||||
})
|
||||
|
||||
app.get('/session/active-with-unread', { preHandler: [authenticate, resolveCustomer] }, async (request, reply) => {
|
||||
const session = await getActiveSessionByCustomerWithUnread(request.customer.id)
|
||||
return reply.send({ success: true, data: session ?? null })
|
||||
})
|
||||
|
||||
app.post('/session/:sessionId/end', { preHandler: [authenticate, resolveCustomer] }, async (request, reply) => {
|
||||
const session = await endSession(request.params.sessionId, EndedBy.CUSTOMER, request.customer.id)
|
||||
return reply.send({ success: true, data: session })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { authenticate } from '../../plugins/auth.js'
|
||||
import { getMitraByFirebaseUid } from '../../services/mitra.service.js'
|
||||
import { acceptPairingRequest, declinePairingRequest } from '../../services/pairing.service.js'
|
||||
import { getActiveSessionsByMitra, endSession, getMitraHistory } from '../../services/session.service.js'
|
||||
import { getActiveSessionsByMitra, getActiveSessionsByMitraWithUnread, endSession, getMitraHistory } from '../../services/session.service.js'
|
||||
import { respondToExtension } from '../../services/extension.service.js'
|
||||
import { EndedBy } from '../../constants.js'
|
||||
|
||||
@@ -38,6 +38,11 @@ export const mitraChatRoutes = async (app) => {
|
||||
return reply.send({ success: true, data: sessions })
|
||||
})
|
||||
|
||||
app.get('/sessions/active-with-unread', { preHandler: [authenticate, resolveMitra] }, async (request, reply) => {
|
||||
const sessions = await getActiveSessionsByMitraWithUnread(request.mitra.id)
|
||||
return reply.send({ success: true, data: sessions })
|
||||
})
|
||||
|
||||
app.post('/sessions/:sessionId/end', { preHandler: [authenticate, resolveMitra] }, async (request, reply) => {
|
||||
const session = await endSession(request.params.sessionId, EndedBy.MITRA, request.mitra.id)
|
||||
return reply.send({ success: true, data: session })
|
||||
|
||||
Reference in New Issue
Block a user