Files
biblical-guide.com/app/api/user/reading-plans/[id]/route.ts
Andrei 63082c825a feat: add user settings save and reading plans with progress tracking
User Settings:
- Add /api/user/settings endpoint for persisting theme and fontSize preferences
- Update settings page with working save functionality
- Add validation and localized error messages

Reading Plans:
- Add database schema with ReadingPlan, UserReadingPlan, and UserReadingProgress models
- Create CRUD API endpoints for reading plans and progress tracking
- Build UI for browsing available plans and managing user enrollments
- Implement progress tracking with daily reading schedule
- Add streak calculation and statistics display
- Create seed data with 5 predefined plans (Bible in 1 year, 90 days, NT 30 days, Psalms 30, Gospels 30)
- Add navigation link with internationalization support

Technical:
- Update to MUI v7 Grid API (using size prop instead of xs/sm/md and removing item prop)
- Fix Next.js 15 dynamic route params (await params pattern)
- Add translations for readingPlans in all languages (en, ro, es, it)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 23:07:47 +00:00

231 lines
5.2 KiB
TypeScript

import { NextResponse } from 'next/server'
import { getUserFromToken } from '@/lib/auth'
import { prisma } from '@/lib/db'
export const runtime = 'nodejs'
/**
* GET /api/user/reading-plans/[id]
* Get a specific reading plan with progress
*/
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
// Get token from authorization header
const authHeader = request.headers.get('authorization')
const token = authHeader?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Verify token and get user
const user = await getUserFromToken(token)
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const userPlan = await prisma.userReadingPlan.findUnique({
where: {
id,
userId: user.id
},
include: {
plan: {
select: {
id: true,
name: true,
description: true,
duration: true,
difficulty: true,
schedule: true,
type: true
}
},
progress: {
orderBy: {
planDay: 'asc'
}
}
}
})
if (!userPlan) {
return NextResponse.json(
{ error: 'Reading plan not found' },
{ status: 404 }
)
}
return NextResponse.json({
success: true,
plan: userPlan
})
} catch (error) {
console.error('Reading plan fetch error:', error)
return NextResponse.json(
{ error: 'Failed to fetch reading plan' },
{ status: 500 }
)
}
}
/**
* PUT /api/user/reading-plans/[id]
* Update a reading plan (pause, resume, complete, cancel)
*/
export async function PUT(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
// Get token from authorization header
const authHeader = request.headers.get('authorization')
const token = authHeader?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Verify token and get user
const user = await getUserFromToken(token)
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const { status, reminderEnabled, reminderTime } = body
// Verify plan belongs to user
const existingPlan = await prisma.userReadingPlan.findUnique({
where: {
id,
userId: user.id
}
})
if (!existingPlan) {
return NextResponse.json(
{ error: 'Reading plan not found' },
{ status: 404 }
)
}
const updateData: any = {}
if (status !== undefined) {
const validStatuses = ['ACTIVE', 'COMPLETED', 'PAUSED', 'CANCELLED']
if (!validStatuses.includes(status)) {
return NextResponse.json(
{ error: 'Invalid status' },
{ status: 400 }
)
}
updateData.status = status
// If completing, set actualEndDate
if (status === 'COMPLETED') {
updateData.actualEndDate = new Date()
}
}
if (reminderEnabled !== undefined) {
updateData.reminderEnabled = reminderEnabled
}
if (reminderTime !== undefined) {
updateData.reminderTime = reminderTime
}
const updatedPlan = await prisma.userReadingPlan.update({
where: { id },
data: updateData,
include: {
plan: true
}
})
return NextResponse.json({
success: true,
message: 'Reading plan updated successfully',
plan: updatedPlan
})
} catch (error) {
console.error('Reading plan update error:', error)
return NextResponse.json(
{ error: 'Failed to update reading plan' },
{ status: 500 }
)
}
}
/**
* DELETE /api/user/reading-plans/[id]
* Delete a reading plan
*/
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
// Get token from authorization header
const authHeader = request.headers.get('authorization')
const token = authHeader?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Verify token and get user
const user = await getUserFromToken(token)
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Verify plan belongs to user
const existingPlan = await prisma.userReadingPlan.findUnique({
where: {
id,
userId: user.id
}
})
if (!existingPlan) {
return NextResponse.json(
{ error: 'Reading plan not found' },
{ status: 404 }
)
}
// Delete the plan (cascade will delete progress too)
await prisma.userReadingPlan.delete({
where: { id }
})
return NextResponse.json({
success: true,
message: 'Reading plan deleted successfully'
})
} catch (error) {
console.error('Reading plan delete error:', error)
return NextResponse.json(
{ error: 'Failed to delete reading plan' },
{ status: 500 }
)
}
}