feat: implement reading progress tracking system
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>
This commit is contained in:
113
app/api/user/reading-progress/route.ts
Normal file
113
app/api/user/reading-progress/route.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user