Database & API: - Enhanced ReadingHistory model with versionId field and unique constraint per user/version - Created /api/user/reading-progress endpoint (GET/POST) for saving and retrieving progress - Upsert operation ensures one reading position per user per Bible version Bible Reader Features: - Auto-save reading position after 2 seconds of inactivity - Auto-restore last reading position on page load (respects URL parameters) - Visual progress bar showing completion percentage based on chapters read - Calculate progress across entire Bible (current chapter / total chapters) - Client-side only loading to prevent hydration mismatches Bug Fixes: - Remove 200 version limit when loading "all versions" - now loads ALL versions - Fix version selection resetting to favorite when user manually selects different version - Transform books API response to include chaptersCount property - Update service worker cache version to force client updates - Add comprehensive SEO URL logging for debugging 404 issues 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
113 lines
3.0 KiB
TypeScript
113 lines
3.0 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
import { prisma } from '@/lib/db'
|
|
import jwt from 'jsonwebtoken'
|
|
|
|
export const runtime = 'nodejs'
|
|
|
|
// Get user's reading progress for a specific version
|
|
export async function GET(request: Request) {
|
|
try {
|
|
const authHeader = request.headers.get('authorization')
|
|
if (!authHeader?.startsWith('Bearer ')) {
|
|
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
|
}
|
|
|
|
const token = authHeader.substring(7)
|
|
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { userId: string }
|
|
|
|
const { searchParams } = new URL(request.url)
|
|
const versionId = searchParams.get('versionId')
|
|
|
|
if (!versionId) {
|
|
// Get all reading progress for user
|
|
const allProgress = await prisma.readingHistory.findMany({
|
|
where: { userId: decoded.userId },
|
|
orderBy: { viewedAt: 'desc' }
|
|
})
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
progress: allProgress
|
|
})
|
|
}
|
|
|
|
// Get reading progress for specific version
|
|
const progress = await prisma.readingHistory.findUnique({
|
|
where: {
|
|
userId_versionId: {
|
|
userId: decoded.userId,
|
|
versionId: versionId
|
|
}
|
|
}
|
|
})
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
progress: progress || null
|
|
})
|
|
} catch (error) {
|
|
console.error('Error getting reading progress:', error)
|
|
return NextResponse.json(
|
|
{ success: false, error: 'Failed to get reading progress' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
|
|
// Save/update user's reading progress
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const authHeader = request.headers.get('authorization')
|
|
if (!authHeader?.startsWith('Bearer ')) {
|
|
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
|
}
|
|
|
|
const token = authHeader.substring(7)
|
|
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { userId: string }
|
|
|
|
const body = await request.json()
|
|
const { versionId, bookId, chapterNum, verseNum } = body
|
|
|
|
if (!versionId || !bookId || chapterNum === undefined) {
|
|
return NextResponse.json(
|
|
{ success: false, error: 'versionId, bookId, and chapterNum are required' },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
|
|
// Upsert reading progress (update if exists, create if not)
|
|
const progress = await prisma.readingHistory.upsert({
|
|
where: {
|
|
userId_versionId: {
|
|
userId: decoded.userId,
|
|
versionId: versionId
|
|
}
|
|
},
|
|
update: {
|
|
bookId,
|
|
chapterNum,
|
|
verseNum,
|
|
viewedAt: new Date()
|
|
},
|
|
create: {
|
|
userId: decoded.userId,
|
|
versionId,
|
|
bookId,
|
|
chapterNum,
|
|
verseNum
|
|
}
|
|
})
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: 'Reading progress saved',
|
|
progress
|
|
})
|
|
} catch (error) {
|
|
console.error('Error saving reading progress:', error)
|
|
return NextResponse.json(
|
|
{ success: false, error: 'Failed to save reading progress' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
} |