Spaces:
Running
Running
| const express = require('express'); | |
| const { collections, FieldValue, docToObject } = require('../db/firestore'); | |
| const authMiddleware = require('../middleware/auth'); | |
| const router = express.Router(); | |
| router.use(authMiddleware); | |
| function enrollmentDocId(userId, programId) { | |
| return `${userId}_${programId}`; | |
| } | |
| async function annotateEnrollment(programDocs, userId) { | |
| if (programDocs.length === 0) return []; | |
| const enrollSnaps = await Promise.all( | |
| programDocs.map((d) => | |
| collections.userPrograms.doc(enrollmentDocId(userId, d.id)).get() | |
| ) | |
| ); | |
| return programDocs.map((doc, i) => { | |
| const program = docToObject(doc); | |
| const e = enrollSnaps[i]; | |
| program.is_enrolled = e.exists; | |
| if (e.exists) { | |
| const ed = e.data(); | |
| program.current_week = ed.current_week ?? 1; | |
| program.is_active = ed.is_active ?? true; | |
| program.enrolled_at = ed.enrolled_at?.toDate?.().toISOString() ?? null; | |
| } else { | |
| program.current_week = null; | |
| program.is_active = null; | |
| } | |
| return program; | |
| }); | |
| } | |
| // GET /api/programs/me/active — must come before /:id | |
| router.get('/me/active', async (req, res) => { | |
| try { | |
| const enrollSnap = await collections.userPrograms | |
| .where('userId', '==', req.userId) | |
| .where('is_active', '==', true) | |
| .get(); | |
| const programs = await Promise.all( | |
| enrollSnap.docs.map(async (e) => { | |
| const ed = e.data(); | |
| const p = await collections.programs.doc(ed.programId).get(); | |
| if (!p.exists) return null; | |
| const program = docToObject(p); | |
| program.current_week = ed.current_week ?? 1; | |
| program.enrolled_at = ed.enrolled_at?.toDate?.().toISOString() ?? null; | |
| return program; | |
| }) | |
| ); | |
| res.json({ programs: programs.filter(Boolean) }); | |
| } catch (err) { | |
| console.error(err); | |
| res.status(500).json({ message: 'Server error' }); | |
| } | |
| }); | |
| // GET /api/programs | |
| router.get('/', async (req, res) => { | |
| const { difficulty, category } = req.query; | |
| try { | |
| let query = collections.programs; | |
| if (difficulty) query = query.where('difficulty', '==', difficulty); | |
| // Firestore can't do case-insensitive substring; for category we filter exact match. | |
| if (category) query = query.where('category', '==', category); | |
| const snap = await query.get(); | |
| const sorted = snap.docs.slice().sort((a, b) => { | |
| const ad = a.data(), bd = b.data(); | |
| if ((bd.is_featured ? 1 : 0) !== (ad.is_featured ? 1 : 0)) | |
| return (bd.is_featured ? 1 : 0) - (ad.is_featured ? 1 : 0); | |
| const at = ad.created_at?.toMillis?.() ?? 0; | |
| const bt = bd.created_at?.toMillis?.() ?? 0; | |
| return bt - at; | |
| }); | |
| const programs = await annotateEnrollment(sorted, req.userId); | |
| res.json({ programs }); | |
| } catch (err) { | |
| console.error(err); | |
| res.status(500).json({ message: 'Server error' }); | |
| } | |
| }); | |
| // GET /api/programs/:id | |
| router.get('/:id', async (req, res) => { | |
| try { | |
| const snap = await collections.programs.doc(req.params.id).get(); | |
| if (!snap.exists) return res.status(404).json({ message: 'Program not found' }); | |
| const [program] = await annotateEnrollment([snap], req.userId); | |
| res.json({ program }); | |
| } catch (err) { | |
| console.error(err); | |
| res.status(500).json({ message: 'Server error' }); | |
| } | |
| }); | |
| // POST /api/programs/:id/enroll | |
| router.post('/:id/enroll', async (req, res) => { | |
| try { | |
| const enrollRef = collections.userPrograms.doc(enrollmentDocId(req.userId, req.params.id)); | |
| const programRef = collections.programs.doc(req.params.id); | |
| const enrollSnap = await enrollRef.get(); | |
| const wasEnrolled = enrollSnap.exists; | |
| await enrollRef.set( | |
| { | |
| userId: req.userId, | |
| programId: req.params.id, | |
| enrolled_at: enrollSnap.exists ? enrollSnap.data().enrolled_at : FieldValue.serverTimestamp(), | |
| current_week: 1, | |
| is_active: true, | |
| completed_at: null, | |
| }, | |
| { merge: true } | |
| ); | |
| if (!wasEnrolled) { | |
| await programRef.update({ total_enrolled: FieldValue.increment(1) }); | |
| } | |
| res.json({ message: 'Enrolled successfully' }); | |
| } catch (err) { | |
| console.error(err); | |
| res.status(500).json({ message: 'Server error' }); | |
| } | |
| }); | |
| // DELETE /api/programs/:id/enroll | |
| router.delete('/:id/enroll', async (req, res) => { | |
| try { | |
| const enrollRef = collections.userPrograms.doc(enrollmentDocId(req.userId, req.params.id)); | |
| await enrollRef.set({ is_active: false }, { merge: true }); | |
| res.json({ message: 'Unenrolled' }); | |
| } catch (err) { | |
| console.error(err); | |
| res.status(500).json({ message: 'Server error' }); | |
| } | |
| }); | |
| module.exports = router; | |