Files
biblical-guide.com/app/api/user/reading-plans/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

182 lines
4.3 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
* Get all reading plans for the authenticated user
*/
export async function GET(request: Request) {
try {
// 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 url = new URL(request.url)
const status = url.searchParams.get('status') || 'ACTIVE'
const userPlans = await prisma.userReadingPlan.findMany({
where: {
userId: user.id,
...(status !== 'ALL' && { status: status as any })
},
include: {
plan: {
select: {
id: true,
name: true,
description: true,
duration: true,
difficulty: true,
type: true
}
}
},
orderBy: {
createdAt: 'desc'
}
})
return NextResponse.json({
success: true,
plans: userPlans
})
} catch (error) {
console.error('User reading plans fetch error:', error)
return NextResponse.json(
{ error: 'Failed to fetch reading plans' },
{ status: 500 }
)
}
}
/**
* POST /api/user/reading-plans
* Enroll user in a reading plan
*/
export async function POST(request: Request) {
try {
// 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 { planId, startDate, customSchedule, name } = body
// Validate input
if (!planId && !customSchedule) {
return NextResponse.json(
{ error: 'Either planId or customSchedule is required' },
{ status: 400 }
)
}
let planData: any = {}
let duration = 365 // Default duration
if (planId) {
// Enrolling in a predefined plan
const plan = await prisma.readingPlan.findUnique({
where: { id: planId }
})
if (!plan) {
return NextResponse.json(
{ error: 'Reading plan not found' },
{ status: 404 }
)
}
if (!plan.isActive) {
return NextResponse.json(
{ error: 'This reading plan is no longer available' },
{ status: 400 }
)
}
duration = plan.duration
planData = {
planId: plan.id,
name: plan.name
}
} else {
// Creating a custom plan
if (!name) {
return NextResponse.json(
{ error: 'Name is required for custom plans' },
{ status: 400 }
)
}
if (!customSchedule || !Array.isArray(customSchedule)) {
return NextResponse.json(
{ error: 'Valid customSchedule is required' },
{ status: 400 }
)
}
duration = customSchedule.length
planData = {
name,
customSchedule
}
}
// Calculate target end date
const start = startDate ? new Date(startDate) : new Date()
const targetEnd = new Date(start)
targetEnd.setDate(targetEnd.getDate() + duration)
// Create user reading plan
const userPlan = await prisma.userReadingPlan.create({
data: {
userId: user.id,
startDate: start,
targetEndDate: targetEnd,
...planData
},
include: {
plan: true
}
})
return NextResponse.json({
success: true,
message: 'Successfully enrolled in reading plan',
plan: userPlan
})
} catch (error) {
console.error('Reading plan enrollment error:', error)
return NextResponse.json(
{ error: 'Failed to enroll in reading plan' },
{ status: 500 }
)
}
}