edtech / apps /api /src /routes /student.ts
CognxSafeTrack
feat(prod): implementation of missing features for production readiness
b150436
import { FastifyInstance } from 'fastify';
import { prisma } from '../services/prisma';
import { z } from 'zod';
/**
* Student-facing public routes (query by phone number).
* Protected by ADMIN_API_KEY in the guarded scope.
*/
export async function studentRoutes(fastify: FastifyInstance) {
// GET /v1/student/me?phone=221771234567
// Returns user profile + enrollments + generated documents
fastify.get('/me', async (req, reply) => {
const query = req.query as { phone?: string };
const phoneSchema = z.string().min(7);
const phoneResult = phoneSchema.safeParse(query.phone);
if (!phoneResult.success) {
return reply.code(400).send({ error: 'phone query param is required' });
}
const phone = phoneResult.data.replace(/\s+/g, '').replace(/^\+/, '');
const user = await prisma.user.findUnique({
where: { phone },
include: {
enrollments: {
include: {
track: {
include: { days: { orderBy: { dayNumber: 'asc' }, take: 1 } }
}
},
orderBy: { startedAt: 'desc' }
},
payments: {
where: { status: 'COMPLETED' },
orderBy: { createdAt: 'desc' }
},
progress: {
orderBy: { lastInteraction: 'desc' }
}
}
});
if (!user) {
return reply.code(404).send({ error: 'User not found. Send INSCRIPTION on WhatsApp to register.' });
}
return {
id: user.id,
phone: user.phone,
name: user.name,
language: user.language,
activity: user.activity,
createdAt: user.createdAt,
enrollments: user.enrollments.map(e => ({
id: e.id,
trackId: e.trackId,
trackTitle: e.track.title,
status: e.status,
currentDay: e.currentDay,
totalDays: e.track.duration,
progressPercent: Math.round((e.currentDay / e.track.duration) * 100),
startedAt: e.startedAt,
completedAt: e.completedAt,
})),
payments: user.payments,
// R2 document URLs are stored as Payment metadata (future enhancement)
};
});
}