mirror of
https://github.com/Tony0410/nextstep.git
synced 2026-05-25 22:01:39 +08:00
Add 11 major features for caregiver health management
Features added: - Emergency Info Card: Full-screen emergency view with patient info - Refill Tracker: Track pill counts with auto-decrement on dose - Activity Feed: View caregiver activity with filtering - Symptom Tracker: Log symptoms with severity and offline sync - Print Views: Daily meds, appointments, doctor visit summaries - iCal Export: Calendar subscription for appointments - PDF Export: Medical summary for doctor visits - Calendar View: Monthly calendar for appointments - Appointment Preparation: Checklist for upcoming appointments - Medication Reminders: PWA push notifications with quiet hours Bug fixes: - Fix invite workflow: Register/login now properly redirect back - Add undo for doctor questions (can unmark "asked" questions) - Fix API route type annotations for Next.js 14 compatibility - Add Suspense boundary for useSearchParams in login/register Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
48
src/app/api/notifications/send/route.ts
Normal file
48
src/app/api/notifications/send/route.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { sendDueNotifications } from '@/lib/notifications/scheduler'
|
||||
|
||||
// This endpoint should be called by a cron job every minute
|
||||
// You can set up a cron service like:
|
||||
// - Vercel Cron Jobs
|
||||
// - AWS EventBridge
|
||||
// - A simple setInterval in a long-running process
|
||||
|
||||
// POST /api/notifications/send - Trigger sending due notifications
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
// Verify cron secret to prevent unauthorized access
|
||||
const authHeader = req.headers.get('authorization')
|
||||
const cronSecret = process.env.CRON_SECRET
|
||||
|
||||
// If CRON_SECRET is set, verify it
|
||||
if (cronSecret && authHeader !== `Bearer ${cronSecret}`) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const { sent, failed } = await sendDueNotifications()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
sent,
|
||||
failed,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Send notifications error:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to send notifications' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// GET endpoint for health checks
|
||||
export async function GET() {
|
||||
return NextResponse.json({
|
||||
status: 'ok',
|
||||
message: 'Notification sender is ready',
|
||||
})
|
||||
}
|
||||
106
src/app/api/notifications/subscribe/route.ts
Normal file
106
src/app/api/notifications/subscribe/route.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/db/prisma'
|
||||
import { checkWorkspaceAccess } from '@/lib/db/workspace-access'
|
||||
import { withAuth, type AuthenticatedRequest } from '@/lib/auth'
|
||||
import { getPublicVAPIDKey } from '@/lib/notifications/push'
|
||||
|
||||
// GET /api/notifications/subscribe - Get VAPID public key
|
||||
export const GET = withAuth(async () => {
|
||||
const publicKey = getPublicVAPIDKey()
|
||||
|
||||
if (!publicKey) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Push notifications not configured' },
|
||||
{ status: 503 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({ publicKey })
|
||||
})
|
||||
|
||||
// POST /api/notifications/subscribe - Subscribe to push notifications
|
||||
export const POST = withAuth(async (req: AuthenticatedRequest) => {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { subscription, workspaceId } = body
|
||||
|
||||
if (!subscription || !subscription.endpoint || !subscription.keys) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid subscription data' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const access = await checkWorkspaceAccess(workspaceId, req.session.user.id)
|
||||
if (!access) {
|
||||
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
||||
}
|
||||
|
||||
// Upsert the subscription (update if exists, create if not)
|
||||
const existing = await prisma.pushSubscription.findFirst({
|
||||
where: {
|
||||
endpoint: subscription.endpoint,
|
||||
userId: req.session.user.id,
|
||||
},
|
||||
})
|
||||
|
||||
if (existing) {
|
||||
await prisma.pushSubscription.update({
|
||||
where: { id: existing.id },
|
||||
data: {
|
||||
p256dh: subscription.keys.p256dh,
|
||||
auth: subscription.keys.auth,
|
||||
workspaceId,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
await prisma.pushSubscription.create({
|
||||
data: {
|
||||
userId: req.session.user.id,
|
||||
workspaceId,
|
||||
endpoint: subscription.endpoint,
|
||||
p256dh: subscription.keys.p256dh,
|
||||
auth: subscription.keys.auth,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
console.error('Subscribe error:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to subscribe' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// DELETE /api/notifications/subscribe - Unsubscribe from push notifications
|
||||
export const DELETE = withAuth(async (req: AuthenticatedRequest) => {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url)
|
||||
const endpoint = searchParams.get('endpoint')
|
||||
|
||||
if (!endpoint) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Endpoint required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
await prisma.pushSubscription.deleteMany({
|
||||
where: {
|
||||
endpoint,
|
||||
userId: req.session.user.id,
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
console.error('Unsubscribe error:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to unsubscribe' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user