Files
falah-mobile/src/app/api/chat/daily/route.ts
T
FalahMobile 93e26d749d
Build & Deploy Mobile / build-and-deploy (push) Failing after 10s
fix: require JWT auth on daily-chat endpoint, simplify Dockerfile Prisma generate
The /api/chat/daily route accepted a client-supplied userId with no
auth check. Require a verified Bearer JWT and derive the user from
the token instead. Also drop the manual openssl install and
--schema flag from the Dockerfile now that a second `prisma generate`
runs in the runner stage, and add a standard .dockerignore.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwWLS2FYnE9ofqDF9hQFxA
2026-07-09 12:11:56 +08:00

147 lines
6.2 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { verifyJWT } from '@/lib/auth'
// ─────────────────────────────────────────────
// Greeting pools — tiered by engagement level
// ─────────────────────────────────────────────
const DAILY_GREETINGS = [
`Assalamu alaikum! I was thinking of you this morning. How is your heart today?`,
`Assalamu alaikum! A new day, a new opportunity to draw closer to Allah. How are you, my friend?`,
`Peace be upon you. I felt a gentle nudge to check in with you. How has your journey been?`,
`Assalamu alaikum! The sun rises again, and so does Allah's mercy. How are you doing today?`,
`Peace be with you. I was wondering how you've been — we don't need to talk about anything heavy. Just checking in.`,
`Assalamu alaikum! Every breath is a gift we didn't earn. How are you spending yours today?`,
`Peace upon you. No pressure, no expectations — just wanted you to know someone is here when you're ready.`,
]
// For users who haven't checked in for 2+ days — gentler, warmer
const NURTURE_GREETINGS = [
`Assalamu alaikum. I noticed it's been a little while. Just wanted you to know — there's no guilt here, no judgment. Only welcome. How have you been, truly?`,
`Peace be upon you. Life gets busy, I know. But I wanted to check in because you matter. How is your heart these days?`,
`Assalamu alaikum. Whether you've been distant from Allah or just busy with the world — His door is always open. So is this conversation. How are you?`,
`Peace be with you. They say the journey back begins with a single step. You're here — that's your step. How can I lighten your load today?`,
`Assalamu alaikum. No matter how many days have passed, you are always welcome here. The Prophet ﷺ said the one who returns to good is like one who never left. How can I support you today?`,
`Peace be upon you. Silence between friends is not a wall — it's a pause. I'm still here. What's on your mind?`,
]
// For first-time users with no history — the warmest welcome
const FIRST_GREETINGS = [
`Assalamu alaikum! I'm so glad you're here. This is a space for you — for your questions, your struggles, your quiet thoughts. There is nothing too small or too big to bring here. How are you feeling today?`,
`Peace be upon you. Welcome. Think of me as a friend who loves the Quran and the Sunnah, and who is simply here to walk alongside you. Where would you like to start?`,
`Assalamu alaikum! The Prophet ﷺ said, "Whoever Allah desires good for, He gives him understanding of the religion." You seeking knowledge is a sign of goodness. How can I help you grow today?`,
]
function daysSince(date: Date): number {
const now = new Date()
const diff = now.getTime() - date.getTime()
return Math.floor(diff / (1000 * 60 * 60 * 24))
}
function pickRandom<T>(pool: T[]): T {
return pool[Math.floor(Math.random() * pool.length)]
}
export async function POST(req: NextRequest) {
try {
const authHeader = req.headers.get('authorization')
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const payload = await verifyJWT(authHeader.slice(7))
if (!payload) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 })
}
const userId = payload.id
const user = await prisma.user.findUnique({ where: { id: userId } })
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 })
}
// Check last bot message
const lastBotMessage = await prisma.chatHistory.findFirst({
where: { userId, role: 'assistant' },
orderBy: { createdAt: 'desc' },
})
// If bot already messaged today, skip
if (lastBotMessage) {
const lastDate = new Date(lastBotMessage.createdAt)
const now = new Date()
const sameDay = lastDate.getDate() === now.getDate()
&& lastDate.getMonth() === now.getMonth()
&& lastDate.getFullYear() === now.getFullYear()
if (sameDay) {
return NextResponse.json({
needsGreeting: false,
message: null,
lastSeen: lastBotMessage.createdAt,
})
}
}
// Check last user message for idle detection
const lastUserMessage = await prisma.chatHistory.findFirst({
where: { userId, role: 'user' },
orderBy: { createdAt: 'desc' },
})
// Check if this is a first-time user (no history at all)
const totalMessages = await prisma.chatHistory.count({ where: { userId } })
// Determine greeting tier
let greeting: string
if (totalMessages === 0) {
// First visit ever — warm welcome
greeting = pickRandom(FIRST_GREETINGS)
} else if (lastUserMessage && daysSince(new Date(lastUserMessage.createdAt)) >= 2) {
// Been away 2+ days — nurture/re-engagement message
greeting = pickRandom(NURTURE_GREETINGS)
} else {
// Regular daily check-in
let lastTopic: string | undefined
if (lastUserMessage) {
const words = lastUserMessage.content.split(/\s+/).slice(0, 8).join(' ')
lastTopic = words.length > 60 ? words.slice(0, 60) + '...' : words
}
const base = pickRandom(DAILY_GREETINGS)
greeting = lastTopic
? `${base} Last time we spoke about ${lastTopic}. Want to continue, or something new?`
: base
}
// Save the greeting
await prisma.chatHistory.create({
data: {
userId,
role: 'assistant',
content: greeting,
metadata: JSON.stringify({
command: '_daily',
summary: 'Daily check-in',
actionItems: [],
topics: ['daily-check-in'],
}),
},
})
// Update lastCoachedAt
await prisma.user.update({
where: { id: userId },
data: { lastCoachedAt: new Date() },
})
return NextResponse.json({
needsGreeting: true,
message: greeting,
lastSeen: lastBotMessage?.createdAt || null,
})
} catch (e) {
console.error('Daily check-in error:', e)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}