From 8236ade11af5584a1792ff591adc3062dbcba176 Mon Sep 17 00:00:00 2001 From: Sidney Gomes Date: Wed, 22 Jul 2026 21:26:17 -0300 Subject: [PATCH] feat(db): Migrate Admin endpoints to Prisma --- server-prisma.ts | 1045 ++++++++++++++++++++++------------------------ 1 file changed, 494 insertions(+), 551 deletions(-) diff --git a/server-prisma.ts b/server-prisma.ts index 6436931..bc32762 100644 --- a/server-prisma.ts +++ b/server-prisma.ts @@ -779,44 +779,43 @@ app.post('/api/subscription/subscribe', authenticateToken, async (req: Authentic // --- ADMIN API ENDPOINTS --- // 1. Dashboard statistics -app.get('/api/admin/dashboard', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); +// 1. Dashboard statistics +app.get('/api/admin/dashboard', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { + const users = await prisma.user.findMany({ where: { role: 'student' } }); + + const totalActive = users.filter(u => u.subscriptionStatus === 'ACTIVE').length; + const totalOverdue = users.filter(u => u.subscriptionStatus === 'OVERDUE').length; + const totalCanceled = users.filter(u => u.subscriptionStatus === 'CANCELED').length; + const totalTrial = users.filter(u => u.subscriptionStatus === 'TRIAL').length; - const totalActive = db.users.filter(u => u.role === 'student' && u.subscriptionStatus === 'ACTIVE').length; - const totalOverdue = db.users.filter(u => u.role === 'student' && u.subscriptionStatus === 'OVERDUE').length; - const totalCanceled = db.users.filter(u => u.role === 'student' && u.subscriptionStatus === 'CANCELED').length; - const totalTrial = db.users.filter(u => u.role === 'student' && u.subscriptionStatus === 'TRIAL').length; + const activeUsersIds = users.filter(u => u.subscriptionStatus === 'ACTIVE').map(u => u.id); + + const allPayments = await prisma.subscriptionPayment.findMany({ + include: { + user: true, + course: true, + plan: true + }, + orderBy: { createdAt: 'desc' } + }); // MRR Calculation let mrr = 0; - const activeUsers = db.users.filter(u => u.role === 'student' && u.subscriptionStatus === 'ACTIVE'); - for (const u of activeUsers) { - const studentPayments = db.payments - .filter(p => p.userId === u.id && (p.status === 'CONFIRMED' || p.status === 'RECEIVED')) - .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); - + for (const userId of activeUsersIds) { + const studentPayments = allPayments.filter(p => p.userId === userId && (p.status === 'CONFIRMED' || p.status === 'RECEIVED')); if (studentPayments.length > 0) { const latest = studentPayments[0]; - if (latest.planId) { - const plan = db.plans?.find(p => p.id === latest.planId); - if (plan) { - if (plan.cycle === 'MONTHLY') mrr += plan.price; - else if (plan.cycle === 'YEARLY') mrr += (plan.price / 12); - // LIFETIME does not add to MRR - } else { - mrr += latest.value; // Fallback - } - } else if (latest.courseId) { - // Avulso, 0 MRR - } else { - // Legacy payment, assume monthly + if (latest.planId && latest.plan) { + if (latest.plan.cycle === 'MONTHLY') mrr += latest.plan.price; + else if (latest.plan.cycle === 'YEARLY') mrr += (latest.plan.price / 12); + } else if (!latest.courseId) { mrr += latest.value; } } } // Total Revenue Calculation - const totalRevenue = db.payments + const totalRevenue = allPayments .filter(p => p.status === 'CONFIRMED' || p.status === 'RECEIVED') .reduce((sum, p) => sum + p.value, 0); @@ -824,24 +823,22 @@ app.get('/api/admin/dashboard', authenticateToken, requireAdmin, (req: Authentic const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); - const recentConfirmedPayments = db.payments.filter( - p => (p.status === 'CONFIRMED' || p.status === 'RECEIVED') && new Date(p.createdAt) >= thirtyDaysAgo + const recentConfirmedPayments = allPayments.filter( + p => (p.status === 'CONFIRMED' || p.status === 'RECEIVED') && p.createdAt >= thirtyDaysAgo ); const revenueByDay: Record = {}; const categoryRevenue: Record = {}; for (const pay of recentConfirmedPayments) { - const dateStr = new Date(pay.createdAt).toISOString().split('T')[0]; + const dateStr = pay.createdAt.toISOString().split('T')[0]; revenueByDay[dateStr] = (revenueByDay[dateStr] || 0) + pay.value; let category = 'Desconhecido'; - if (pay.courseId) { - const c = db.courses.find(c => c.id === pay.courseId); - category = c ? `Curso: ${c.title}` : 'Avulso'; - } else if (pay.planId) { - const p = db.plans?.find(p => p.id === pay.planId); - category = p ? `Plano: ${p.title}` : 'Assinatura'; + if (pay.courseId && pay.course) { + category = `Curso: ${pay.course.title}`; + } else if (pay.planId && pay.plan) { + category = `Plano: ${pay.plan.title}`; } else { category = 'Assinatura Legado'; } @@ -858,29 +855,35 @@ app.get('/api/admin/dashboard', authenticateToken, requireAdmin, (req: Authentic value: categoryRevenue[name] })); - // Let's format recent invoice history for logs/charts - const recentPayments = db.payments + const recentPaymentsFormatted = allPayments + .slice(0, 15) .map(p => { - const student = db.users.find(u => u.id === p.userId); let desc = ''; - if (p.courseId) { - desc = `Curso: ${db.courses.find(c => c.id === p.courseId)?.title || p.courseId}`; - } else if (p.planId) { - desc = `Plano: ${db.plans?.find(pl => pl.id === p.planId)?.title || p.planId}`; - } + if (p.course) desc = `Curso: ${p.course.title}`; + else if (p.plan) desc = `Plano: ${p.plan.title}`; + return { - ...p, - studentName: student ? student.name : 'Aluno Removido', - studentEmail: student ? student.email : '', + id: p.id, + userId: p.userId, + value: p.value, + status: p.status, + billingType: p.billingType, + invoiceUrl: p.invoiceUrl, + dueDate: p.dueDate, + paidAt: p.paidAt, + createdAt: p.createdAt, + courseId: p.courseId, + planId: p.planId, + studentName: p.user?.name || 'Aluno Removido', + studentEmail: p.user?.email || '', description: desc }; - }) - .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) - .slice(0, 15); + }); - const webhookLogs = db.webhookLogs - .sort((a, b) => new Date(b.receivedAt).getTime() - new Date(a.receivedAt).getTime()) - .slice(0, 15); + const webhookLogs = await prisma.notification.findMany({ + orderBy: { createdAt: 'desc' }, + take: 15 + }); res.json({ stats: { @@ -893,104 +896,115 @@ app.get('/api/admin/dashboard', authenticateToken, requireAdmin, (req: Authentic }, revenueData, categoryData, - recentPayments, - webhookLogs + recentPayments: recentPaymentsFormatted, + webhookLogs: webhookLogs.map(w => ({ + id: w.id, + event: w.type, + paymentId: '', + customer: w.userId, + receivedAt: w.createdAt, + status: w.read ? 'READ' : 'UNREAD' + })) }); }); // 2. Students & status management -app.get('/api/admin/students', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - const students = db.users - .filter(u => u.role === 'student') - .map(u => { - const studentPayments = db.payments.filter(p => p.userId === u.id); - const studentGrades = db.moduleGrades.filter(g => g.userId === u.id); - const studentCertificates = db.certificates.filter(c => c.userId === u.id); - return { - id: u.id, - name: u.name, - email: u.email, - subscriptionStatus: u.subscriptionStatus, - asaasCustomerId: u.asaasCustomerId, - createdAt: u.createdAt, - birthDate: u.birthDate, - whatsapp: u.whatsapp, - trialEndsAt: u.trialEndsAt, - totalPaid: studentPayments - .filter(p => p.status === 'CONFIRMED' || p.status === 'RECEIVED') - .reduce((acc, curr) => acc + curr.value, 0), - unlockedCourses: u.unlockedCourses || [], - moduleGrades: studentGrades, - certificates: studentCertificates, - payments: studentPayments - }; - }); +app.get('/api/admin/students', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { + const students = await prisma.user.findMany({ + where: { role: 'student' }, + include: { + payments: true, + moduleGrades: true, + certificates: true + } + }); - res.json(students); + const studentsFormatted = students.map(u => { + return { + id: u.id, + name: u.name, + email: u.email, + subscriptionStatus: u.subscriptionStatus, + asaasCustomerId: u.asaasCustomerId, + createdAt: u.createdAt, + birthDate: u.birthDate, + whatsapp: u.whatsapp, + trialEndsAt: u.trialEndsAt, + totalPaid: u.payments + .filter(p => p.status === 'CONFIRMED' || p.status === 'RECEIVED') + .reduce((acc, curr) => acc + curr.value, 0), + unlockedCourses: u.unlockedCourses || [], + moduleGrades: u.moduleGrades, + certificates: u.certificates, + payments: u.payments + }; + }); + + res.json(studentsFormatted); }); // Admin change student status manually -app.post('/api/admin/students/:id/status', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.post('/api/admin/students/:id/status', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const { status } = req.body; const studentId = req.params.id; - const allowedStatuses: SubscriptionStatus[] = ['ACTIVE', 'OVERDUE', 'CANCELED', 'TRIAL']; - if (!status || !allowedStatuses.includes(status)) { + const allowedStatuses = ['ACTIVE', 'OVERDUE', 'CANCELED', 'TRIAL']; + if (!status || !allowedStatuses.includes(status as any)) { res.status(400).json({ error: 'Status de assinatura inválido' }); return; } - const db = loadDb(); - const student = db.users.find(u => u.id === studentId && u.role === 'student'); - - if (!student) { + try { + const student = await prisma.user.update({ + where: { id: studentId, role: 'student' }, + data: { subscriptionStatus: status as any } + }); + res.json({ success: true, status: student.subscriptionStatus }); + } catch (error) { res.status(404).json({ error: 'Estudante não encontrado' }); - return; } - - student.subscriptionStatus = status; - saveDb(db); - - res.json({ success: true, status: student.subscriptionStatus }); }); // Student / Public System Settings -app.get('/api/settings', authenticateToken, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - const settings = db.settings || {}; +app.get('/api/settings', authenticateToken, async (req: AuthenticatedRequest, res: Response) => { + const settings = await prisma.systemSettings.findFirst(); res.json({ - trialDurationDays: settings.trialDurationDays || 7, - helpText: settings.helpText || '', - enableBoleto: settings.enableBoleto !== false + trialDurationDays: settings?.trialDurationDays || 7, + helpText: settings?.helpText || '', + enableBoleto: settings?.enableBoleto !== false }); }); // Admin System Settings -app.get('/api/admin/settings', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - const settings = db.settings || {}; +app.get('/api/admin/settings', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { + const settings = await prisma.systemSettings.findFirst(); res.json({ - trialDurationDays: settings.trialDurationDays || 7, - asaasEnvironment: settings.asaasEnvironment || 'sandbox', - asaasApiKey: settings.asaasApiKey || '', - asaasWebhookUrl: settings.asaasWebhookUrl || 'https://admin-estudo.microtecinformaticacurso.com.br/api/webhooks/asaas', - asaasWebhookSecret: settings.asaasWebhookSecret || 'devflix_webhook_secret_key', - helpText: settings.helpText || '' + trialDurationDays: settings?.trialDurationDays || 7, + asaasEnvironment: settings?.asaasEnvironment || 'sandbox', + asaasApiKey: settings?.asaasApiKey || '', + asaasWebhookUrl: settings?.asaasWebhookUrl || 'https://admin-estudo.microtecinformaticacurso.com.br/api/webhooks/asaas', + asaasWebhookSecret: settings?.asaasWebhookSecret || 'devflix_webhook_secret_key', + helpText: settings?.helpText || '' }); }); -app.post('/api/admin/settings', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.post('/api/admin/settings', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const newSettings = req.body; - const db = loadDb(); - db.settings = { - ...db.settings, - ...newSettings - }; + const settings = await prisma.systemSettings.findFirst(); + if (settings) { + await prisma.systemSettings.update({ + where: { id: settings.id }, + data: newSettings + }); + } else { + await prisma.systemSettings.create({ + data: newSettings + }); + } - saveDb(db); - res.json({ success: true, settings: db.settings }); + const updatedSettings = await prisma.systemSettings.findFirst(); + res.json({ success: true, settings: updatedSettings }); }); app.post('/api/admin/test-asaas', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { @@ -1004,7 +1018,7 @@ app.post('/api/admin/test-asaas', authenticateToken, requireAdmin, async (req: A }); // Admin manually unlock or lock a course for a student -app.post('/api/admin/students/:id/unlock-course', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.post('/api/admin/students/:id/unlock-course', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const { courseId, unlocked } = req.body; const studentId = req.params.id; @@ -1013,32 +1027,33 @@ app.post('/api/admin/students/:id/unlock-course', authenticateToken, requireAdmi return; } - const db = loadDb(); - const student = db.users.find(u => u.id === studentId && u.role === 'student'); + const student = await prisma.user.findUnique({ where: { id: studentId, role: 'student' } }); if (!student) { res.status(404).json({ error: 'Estudante não encontrado' }); return; } - if (!student.unlockedCourses) { - student.unlockedCourses = []; - } + let newUnlocked = student.unlockedCourses || []; if (unlocked) { - if (!student.unlockedCourses.includes(courseId)) { - student.unlockedCourses.push(courseId); + if (!newUnlocked.includes(courseId)) { + newUnlocked.push(courseId); } } else { - student.unlockedCourses = student.unlockedCourses.filter(id => id !== courseId); + newUnlocked = newUnlocked.filter(id => id !== courseId); } - saveDb(db); - res.json({ success: true, unlockedCourses: student.unlockedCourses }); + await prisma.user.update({ + where: { id: studentId }, + data: { unlockedCourses: newUnlocked } + }); + + res.json({ success: true, unlockedCourses: newUnlocked }); }); // 3. CRUD Courses -app.post('/api/admin/courses', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.post('/api/admin/courses', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const { title, description, thumbnail, category, isLocked, price } = req.body; if (!title || !description || !thumbnail || !category) { @@ -1046,75 +1061,56 @@ app.post('/api/admin/courses', authenticateToken, requireAdmin, (req: Authentica return; } - const db = loadDb(); - const newCourse: Course = { - id: `crs_${Math.random().toString(36).substring(2, 9)}`, - title, - description, - thumbnail, - category, - isLocked: isLocked === true || isLocked === 'true', - price: price ? Number(price) : 0, - createdAt: new Date().toISOString() - }; - - db.courses.push(newCourse); - saveDb(db); + const newCourse = await prisma.course.create({ + data: { + title, + description, + thumbnailUrl: thumbnail, + category, + isLocked: isLocked === true || isLocked === 'true', + price: price ? Number(price) : 0, + order: await prisma.course.count() + 1 + } + }); res.status(201).json(newCourse); }); -app.put('/api/admin/courses/:id', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.put('/api/admin/courses/:id', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const { title, description, thumbnail, category, isLocked, price } = req.body; const courseId = req.params.id; - const db = loadDb(); - const idx = db.courses.findIndex(c => c.id === courseId); - - if (idx < 0) { + try { + const course = await prisma.course.update({ + where: { id: courseId }, + data: { + ...(title && { title }), + ...(description && { description }), + ...(thumbnail && { thumbnailUrl: thumbnail }), + ...(category && { category }), + ...(isLocked !== undefined && { isLocked: isLocked === true || isLocked === 'true' }), + ...(price !== undefined && { price: Number(price) }) + } + }); + res.json(course); + } catch (error) { res.status(404).json({ error: 'Curso não encontrado' }); - return; } - - db.courses[idx] = { - ...db.courses[idx], - title: title || db.courses[idx].title, - description: description || db.courses[idx].description, - thumbnail: thumbnail || db.courses[idx].thumbnail, - category: category || db.courses[idx].category, - isLocked: isLocked !== undefined ? (isLocked === true || isLocked === 'true') : db.courses[idx].isLocked, - price: price !== undefined ? Number(price) : db.courses[idx].price - }; - - saveDb(db); - res.json(db.courses[idx]); }); -app.delete('/api/admin/courses/:id', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.delete('/api/admin/courses/:id', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const courseId = req.params.id; - const db = loadDb(); - const idx = db.courses.findIndex(c => c.id === courseId); - - if (idx < 0) { + try { + await prisma.course.delete({ where: { id: courseId } }); + res.json({ success: true }); + } catch (error) { res.status(404).json({ error: 'Curso não encontrado' }); - return; } - - // Delete modules, lessons, and course - db.courses.splice(idx, 1); - const affectedModules = db.modules.filter(m => m.courseId === courseId); - const affectedModuleIds = affectedModules.map(m => m.id); - - db.modules = db.modules.filter(m => m.courseId !== courseId); - db.lessons = db.lessons.filter(l => !affectedModuleIds.includes(l.moduleId)); - - saveDb(db); - res.json({ success: true }); }); // 4. CRUD Modules -app.post('/api/admin/modules', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.post('/api/admin/modules', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const { courseId, title, order } = req.body; if (!courseId || !title) { @@ -1122,62 +1118,50 @@ app.post('/api/admin/modules', authenticateToken, requireAdmin, (req: Authentica return; } - const db = loadDb(); - const newModule: Module = { - id: `mdl_${Math.random().toString(36).substring(2, 9)}`, - courseId, - title, - order: order !== undefined ? Number(order) : db.modules.filter(m => m.courseId === courseId).length + 1, - createdAt: new Date().toISOString() - }; - - db.modules.push(newModule); - saveDb(db); + const count = await prisma.module.count({ where: { courseId } }); + + const newModule = await prisma.module.create({ + data: { + courseId, + title, + order: order !== undefined ? Number(order) : count + 1 + } + }); res.status(201).json(newModule); }); -app.put('/api/admin/modules/:id', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.put('/api/admin/modules/:id', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const { title, order } = req.body; const moduleId = req.params.id; - const db = loadDb(); - const idx = db.modules.findIndex(m => m.id === moduleId); - - if (idx < 0) { + try { + const updatedModule = await prisma.module.update({ + where: { id: moduleId }, + data: { + ...(title && { title }), + ...(order !== undefined && { order: Number(order) }) + } + }); + res.json(updatedModule); + } catch (error) { res.status(404).json({ error: 'Módulo não encontrado' }); - return; } - - db.modules[idx].title = title || db.modules[idx].title; - if (order !== undefined) { - db.modules[idx].order = Number(order); - } - - saveDb(db); - res.json(db.modules[idx]); }); -app.delete('/api/admin/modules/:id', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.delete('/api/admin/modules/:id', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const moduleId = req.params.id; - const db = loadDb(); - const idx = db.modules.findIndex(m => m.id === moduleId); - - if (idx < 0) { + try { + await prisma.module.delete({ where: { id: moduleId } }); + res.json({ success: true }); + } catch (error) { res.status(404).json({ error: 'Módulo não encontrado' }); - return; } - - db.modules.splice(idx, 1); - db.lessons = db.lessons.filter(l => l.moduleId !== moduleId); - - saveDb(db); - res.json({ success: true }); }); // 5. CRUD Lessons -app.post('/api/admin/lessons', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.post('/api/admin/lessons', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const { moduleId, title, videoUrl, videoType, order, content } = req.body; if (!moduleId || !title || !videoUrl || !videoType) { @@ -1185,123 +1169,103 @@ app.post('/api/admin/lessons', authenticateToken, requireAdmin, (req: Authentica return; } - const db = loadDb(); - const newLesson: Lesson = { - id: `lsn_${Math.random().toString(36).substring(2, 9)}`, - moduleId, - title, - videoUrl, - videoType, - order: order !== undefined ? Number(order) : db.lessons.filter(l => l.moduleId === moduleId).length + 1, - content: content || '', - createdAt: new Date().toISOString() - }; + const count = await prisma.lesson.count({ where: { moduleId } }); - db.lessons.push(newLesson); - saveDb(db); + const newLesson = await prisma.lesson.create({ + data: { + moduleId, + title, + videoUrl, + videoType, + content: content || '', + order: order !== undefined ? Number(order) : count + 1 + } + }); res.status(201).json(newLesson); }); -app.put('/api/admin/lessons/:id', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.put('/api/admin/lessons/:id', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const { title, videoUrl, videoType, order, content } = req.body; const lessonId = req.params.id; - const db = loadDb(); - const idx = db.lessons.findIndex(l => l.id === lessonId); - - if (idx < 0) { + try { + const updatedLesson = await prisma.lesson.update({ + where: { id: lessonId }, + data: { + ...(title && { title }), + ...(videoUrl && { videoUrl }), + ...(videoType && { videoType }), + ...(content !== undefined && { content }), + ...(order !== undefined && { order: Number(order) }) + } + }); + res.json(updatedLesson); + } catch (error) { res.status(404).json({ error: 'Aula não encontrada' }); - return; } - - db.lessons[idx] = { - ...db.lessons[idx], - title: title || db.lessons[idx].title, - videoUrl: videoUrl || db.lessons[idx].videoUrl, - videoType: videoType || db.lessons[idx].videoType, - content: content !== undefined ? content : db.lessons[idx].content - }; - - if (order !== undefined) { - db.lessons[idx].order = Number(order); - } - - saveDb(db); - res.json(db.lessons[idx]); }); -app.delete('/api/admin/lessons/:id', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.delete('/api/admin/lessons/:id', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const lessonId = req.params.id; - const db = loadDb(); - const idx = db.lessons.findIndex(l => l.id === lessonId); - - if (idx < 0) { + try { + await prisma.lesson.delete({ where: { id: lessonId } }); + res.json({ success: true }); + } catch (error) { res.status(404).json({ error: 'Aula não encontrada' }); - return; } - - db.lessons.splice(idx, 1); - // Also clean up notes and progress - db.notes = db.notes.filter(n => n.lessonId !== lessonId); - db.progress = db.progress.filter(p => p.lessonId !== lessonId); - - saveDb(db); - res.json({ success: true }); }); // Reorder modules or lessons (Drag-and-drop or batch reorder helper) -app.post('/api/admin/reorder', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const { type, items } = req.body; // type: 'modules' | 'lessons', items: Array of { id, order } +app.post('/api/admin/reorder', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { + const { type, items } = req.body; if (!type || !items || !Array.isArray(items)) { res.status(400).json({ error: 'Parâmetros inválidos' }); return; } - const db = loadDb(); - - if (type === 'modules') { - items.forEach((item: { id: string; order: number }) => { - const idx = db.modules.findIndex(m => m.id === item.id); - if (idx >= 0) { - db.modules[idx].order = Number(item.order); + try { + if (type === 'modules') { + for (const item of items) { + await prisma.module.update({ + where: { id: item.id }, + data: { order: Number(item.order) } + }); } - }); - } else if (type === 'lessons') { - items.forEach((item: { id: string; order: number }) => { - const idx = db.lessons.findIndex(l => l.id === item.id); - if (idx >= 0) { - db.lessons[idx].order = Number(item.order); + } else if (type === 'lessons') { + for (const item of items) { + await prisma.lesson.update({ + where: { id: item.id }, + data: { order: Number(item.order) } + }); } - }); + } + res.json({ success: true }); + } catch (error) { + console.error('Reorder error:', error); + res.status(500).json({ error: 'Erro ao reordenar' }); } - - saveDb(db); - res.json({ success: true }); }); // --- ASAAS WEBHOOK HANDLER --- -app.post('/api/webhooks/asaas', (req: Request, res: Response) => { - const db = loadDb(); +app.post('/api/webhooks/asaas', async (req: Request, res: Response) => { const { event, payment } = req.body; - // 1. Logging Antecipado (para debuggar mesmo se der 401) - const logId = `wh_log_${Math.random().toString(36).substring(2, 9)}`; if (event) { - db.webhookLogs.push({ - id: logId, - event, - payload: JSON.stringify(req.body), - receivedAt: new Date().toISOString() + await prisma.notification.create({ + data: { + userId: 'ALL', // Log events to ALL as webhookLogs + type: event, + message: JSON.stringify(req.body), + read: false + } }); - saveDb(db); // Salva o log imediatamente para não perdê-lo se der return early } - // 2. Validação de Segurança - const configuredSecret = db.settings?.asaasWebhookSecret || process.env.ASAAS_WEBHOOK_TOKEN || 'devflix_webhook_secret_key'; - // O Asaas usa asaas-access-token por padrão + const settings = await prisma.systemSettings.findFirst(); + const configuredSecret = settings?.asaasWebhookSecret || process.env.ASAAS_WEBHOOK_TOKEN || 'devflix_webhook_secret_key'; const tokenHeader = req.headers['asaas-access-token'] || req.headers['asaas-token'] || req.headers['authorization']; if (configuredSecret && tokenHeader !== configuredSecret) { @@ -1318,14 +1282,10 @@ app.post('/api/webhooks/asaas', (req: Request, res: Response) => { return; } - // 3. Processamento do Pagamento const customerId = payment.customer; - const user = db.users.find(u => u.asaasCustomerId === customerId); + const user = await prisma.user.findFirst({ where: { asaasCustomerId: customerId } }); if (user) { - // Busca APENAS pelo ID exato da fatura - let userPaymentIdx = db.payments.findIndex(p => p.id === payment.id); - const isPaid = event === 'PAYMENT_RECEIVED' || event === 'PAYMENT_CONFIRMED'; const isOverdue = event === 'PAYMENT_OVERDUE'; const isRefunded = event === 'PAYMENT_REFUNDED' || event === 'PAYMENT_DELETED'; @@ -1335,53 +1295,68 @@ app.post('/api/webhooks/asaas', (req: Request, res: Response) => { isRefunded ? 'REFUNDED' : event === 'PAYMENT_RESTORED' ? 'PENDING' : null; - if (userPaymentIdx >= 0) { + let existingPayment = await prisma.subscriptionPayment.findUnique({ where: { id: payment.id } }); + + if (existingPayment) { if (newStatus) { - db.payments[userPaymentIdx].status = newStatus as any; - } - if (isPaid) { - db.payments[userPaymentIdx].paidAt = new Date().toISOString(); + await prisma.subscriptionPayment.update({ + where: { id: payment.id }, + data: { + status: newStatus as any, + ...(isPaid && { paidAt: new Date() }) + } + }); } } else if (!isRefunded) { - // Se não achou e não é estorno/deleção, registra a fatura enviada pelo Asaas - // Tenta achar o plano/curso da última compra do usuário para manter o histórico - const lastPayment = db.payments.filter(p => p.userId === user.id).sort((a,b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0]; + const lastPayment = await prisma.subscriptionPayment.findFirst({ + where: { userId: user.id }, + orderBy: { createdAt: 'desc' } + }); - db.payments.push({ - id: payment.id, // Usa o ID exato - userId: user.id, - value: payment.value || 49.90, - status: (newStatus || 'PENDING') as any, - billingType: payment.billingType || 'PIX', - invoiceUrl: payment.invoiceUrl || `https://sandbox.asaas.com/i/${payment.id}`, - dueDate: payment.dueDate || new Date().toISOString().split('T')[0], - paidAt: isPaid ? new Date().toISOString() : null, - createdAt: new Date().toISOString(), - planId: lastPayment?.planId, - courseId: lastPayment?.courseId + await prisma.subscriptionPayment.create({ + data: { + id: payment.id, + userId: user.id, + value: payment.value || 49.90, + status: (newStatus || 'PENDING') as any, + billingType: payment.billingType || 'PIX', + invoiceUrl: payment.invoiceUrl || `https://sandbox.asaas.com/i/${payment.id}`, + dueDate: payment.dueDate ? new Date(payment.dueDate) : new Date(), + paidAt: isPaid ? new Date() : null, + planId: lastPayment?.planId, + courseId: lastPayment?.courseId + } }); } - // 4. Lógica de Liberação de Acesso / Curso const description = payment.description || ''; const isCoursePayment = description.startsWith('unlock_course_'); if (isPaid) { if (isCoursePayment) { const courseId = description.replace('unlock_course_', ''); - if (!user.unlockedCourses) user.unlockedCourses = []; - if (!user.unlockedCourses.includes(courseId)) { - user.unlockedCourses.push(courseId); + let newUnlocked = user.unlockedCourses || []; + if (!newUnlocked.includes(courseId)) { + newUnlocked.push(courseId); } + await prisma.user.update({ + where: { id: user.id }, + data: { unlockedCourses: newUnlocked } + }); console.log(`Webhook: Curso ${courseId} LIBERADO para usuário ${user.email}`); } else { - user.subscriptionStatus = 'ACTIVE'; + await prisma.user.update({ + where: { id: user.id }, + data: { subscriptionStatus: 'ACTIVE' } + }); console.log(`Webhook: Assinatura LIBERADA para usuário ${user.email}`); } } else if (isOverdue || isRefunded || event === 'SUBSCRIPTION_DELETED') { if (!isCoursePayment) { - // Bloqueia acesso da assinatura - user.subscriptionStatus = isOverdue ? 'OVERDUE' : 'CANCELED'; + await prisma.user.update({ + where: { id: user.id }, + data: { subscriptionStatus: isOverdue ? 'OVERDUE' : 'CANCELED' } + }); console.log(`Webhook: Assinatura BLOQUEADA para usuário ${user.email} - Motivo: ${event}`); } else if (isRefunded) { console.log(`Webhook: Pagamento de Curso REEMBOLSADO/DELETADO para usuário ${user.email}`); @@ -1391,14 +1366,13 @@ app.post('/api/webhooks/asaas', (req: Request, res: Response) => { console.warn(`Webhook: Cliente Asaas ${customerId} não encontrado no banco local.`); } - saveDb(db); res.status(200).json({ received: true }); }); // --- ACTIVITIES & CERTIFICATES API ENDPOINTS --- // Admin: Save or update module activity -app.post('/api/admin/modules/:id/activity', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.post('/api/admin/modules/:id/activity', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const moduleId = req.params.id; const { questions } = req.body; @@ -1407,39 +1381,37 @@ app.post('/api/admin/modules/:id/activity', authenticateToken, requireAdmin, (re return; } - const db = loadDb(); - let activity = db.activities.find(a => a.moduleId === moduleId); + const existingActivity = await prisma.moduleActivity.findUnique({ where: { moduleId } }); - if (activity) { - activity.questions = questions; + if (existingActivity) { + const updated = await prisma.moduleActivity.update({ + where: { moduleId }, + data: { questions: questions as any } + }); + res.json({ success: true, activity: updated }); } else { - activity = { - id: `act_${Math.random().toString(36).substring(2, 9)}`, - moduleId, - questions, - createdAt: new Date().toISOString() - }; - db.activities.push(activity); + const newActivity = await prisma.moduleActivity.create({ + data: { + moduleId, + questions: questions as any + } + }); + res.json({ success: true, activity: newActivity }); } - - saveDb(db); - res.json({ success: true, activity }); }); // Admin/Student: Get module activity -app.get('/api/modules/:id/activity', authenticateToken, (req: AuthenticatedRequest, res: Response) => { +app.get('/api/modules/:id/activity', authenticateToken, async (req: AuthenticatedRequest, res: Response) => { const moduleId = req.params.id; - const db = loadDb(); - const activity = db.activities.find(a => a.moduleId === moduleId); + const activity = await prisma.moduleActivity.findUnique({ where: { moduleId } }); if (!activity) { res.status(404).json({ error: 'Atividade não encontrada' }); return; } - // If student, remove correctIndex so they can't cheat easily by inspecting the network request if (req.user?.role === 'student') { - const safeQuestions = activity.questions.map((q: any) => ({ + const safeQuestions = (activity.questions as any[]).map((q: any) => ({ question: q.question, options: q.options })); @@ -1450,310 +1422,281 @@ app.get('/api/modules/:id/activity', authenticateToken, (req: AuthenticatedReque }); // Admin: Change Course Certificate Mode -app.post('/api/admin/courses/:id/certificate-mode', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.post('/api/admin/courses/:id/certificate-mode', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const courseId = req.params.id; - const { mode } = req.body; // 'PER_MODULE' or 'FULL_COURSE' + const { mode } = req.body; if (mode !== 'PER_MODULE' && mode !== 'FULL_COURSE') { res.status(400).json({ error: 'Modo inválido' }); return; } - const db = loadDb(); - const course = db.courses.find(c => c.id === courseId); - if (!course) { + try { + const course = await prisma.course.update({ + where: { id: courseId }, + data: { certificateMode: mode } + }); + res.json({ success: true, course }); + } catch (error) { res.status(404).json({ error: 'Curso não encontrado' }); - return; } - - course.certificateMode = mode; - saveDb(db); - res.json({ success: true, course }); }); // Admin: Manually Unlock Certificate -app.post('/api/admin/students/:userId/unlock-certificate', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { +app.post('/api/admin/students/:userId/unlock-certificate', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const { userId } = req.params; - const { type, targetId, courseName, moduleName } = req.body; // type: 'COURSE' or 'MODULE' + const { type, targetId, courseName, moduleName } = req.body; - const db = loadDb(); - - const certId = `cert_${Math.random().toString(36).substring(2, 9)}`; - const certificate = { - id: certId, - userId, - courseId: type === 'COURSE' ? targetId : null, - moduleId: type === 'MODULE' ? targetId : null, - certificateType: type, - unlockedAt: new Date().toISOString(), - finalGrade: 10, // Default manual grade - courseName, - moduleName: moduleName || null - }; - - db.certificates.push(certificate); - saveDb(db); + const certificate = await prisma.certificate.create({ + data: { + userId, + courseId: type === 'COURSE' ? targetId : null, + moduleId: type === 'MODULE' ? targetId : null, + certificateType: type, + finalGrade: 10, + courseName, + moduleName: moduleName || null + } + }); res.json({ success: true, certificate }); }); // Student: Submit Activity Answers -app.post('/api/modules/:id/submit-activity', authenticateToken, (req: AuthenticatedRequest, res: Response) => { +app.post('/api/modules/:id/submit-activity', authenticateToken, async (req: AuthenticatedRequest, res: Response) => { const moduleId = req.params.id; - const { answers } = req.body; // Array of selected indexes + const { answers } = req.body; const userId = req.user!.id; - const db = loadDb(); - const activity = db.activities.find(a => a.moduleId === moduleId); + const activity = await prisma.moduleActivity.findUnique({ where: { moduleId } }); if (!activity) { res.status(404).json({ error: 'Atividade não encontrada' }); return; } - // Calculate Grade let correctCount = 0; - const totalQuestions = activity.questions.length; + const questions = activity.questions as any[]; + const totalQuestions = questions.length; - activity.questions.forEach((q: any, index: number) => { + questions.forEach((q: any, index: number) => { if (answers[index] === q.correctIndex) { correctCount++; } }); const score = totalQuestions > 0 ? (correctCount / totalQuestions) * 10 : 0; - const passed = score >= 6; // Configurable passing score - - let gradeIdx = db.moduleGrades.findIndex(g => g.userId === userId && g.activityId === activity.id); - if (gradeIdx >= 0) { - db.moduleGrades[gradeIdx].score = score; - db.moduleGrades[gradeIdx].passed = passed; - } else { - db.moduleGrades.push({ - id: `grd_${Math.random().toString(36).substring(2, 9)}`, - userId, - activityId: activity.id, - score, - passed, - createdAt: new Date().toISOString() - }); - } + const passed = score >= 6; - // --- Automatic Certificate Generation Logic --- - const moduleInfo = db.modules.find(m => m.id === moduleId); - if (passed && moduleInfo) { - const course = db.courses.find(c => c.id === moduleInfo.courseId); - if (course) { - const mode = course.certificateMode || 'FULL_COURSE'; - - if (mode === 'PER_MODULE') { - // Check if certificate already exists - const exists = db.certificates.find(c => c.userId === userId && c.moduleId === moduleId && c.certificateType === 'MODULE'); - if (!exists) { - db.certificates.push({ - id: `cert_${Math.random().toString(36).substring(2, 9)}`, + await prisma.moduleGrade.upsert({ + where: { + userId_activityId: { userId, activityId: activity.id } + }, + update: { score, passed }, + create: { userId, activityId: activity.id, score, passed } + }); + + const moduleInfo = await prisma.module.findUnique({ where: { id: moduleId }, include: { course: true } }); + if (passed && moduleInfo && moduleInfo.course) { + const course = moduleInfo.course; + const mode = course.certificateMode || 'FULL_COURSE'; + + if (mode === 'PER_MODULE') { + const exists = await prisma.certificate.findFirst({ + where: { userId, moduleId, certificateType: 'MODULE' } + }); + if (!exists) { + await prisma.certificate.create({ + data: { userId, courseId: course.id, moduleId: moduleInfo.id, certificateType: 'MODULE', - unlockedAt: new Date().toISOString(), finalGrade: score, courseName: course.title, moduleName: moduleInfo.title - }); - } - } else { - // FULL_COURSE mode - // Check if student has passed all modules of this course - const allCourseModules = db.modules.filter(m => m.courseId === course.id); - let allPassed = true; - let totalScore = 0; - let evaluatedModules = 0; - - for (const m of allCourseModules) { - const act = db.activities.find(a => a.moduleId === m.id); - if (act) { - const grade = db.moduleGrades.find(g => g.activityId === act.id && g.userId === userId); - if (!grade || !grade.passed) { - allPassed = false; - break; - } - totalScore += grade.score; - evaluatedModules++; - } else { - // If a module has no activity, we might consider it "passed" automatically, but let's just ignore it for grades - // Or require manual progress? Let's assume if it has no activity, we just skip grade check for it - // Actually, wait, let's require 100% watched percentage for modules without activities. - const lessons = db.lessons.filter(l => l.moduleId === m.id); - for(const lsn of lessons) { - const prog = db.progress.find(p => p.lessonId === lsn.id && p.userId === userId); - if (!prog || !prog.completed) { - allPassed = false; - break; - } - } } + }); + } + } else { + const allCourseModules = await prisma.module.findMany({ where: { courseId: course.id } }); + let allPassed = true; + let totalScore = 0; + let evaluatedModules = 0; + + for (const m of allCourseModules) { + const act = await prisma.moduleActivity.findUnique({ where: { moduleId: m.id } }); + if (act) { + const grade = await prisma.moduleGrade.findUnique({ + where: { userId_activityId: { userId, activityId: act.id } } + }); + if (!grade || !grade.passed) { + allPassed = false; + break; + } + totalScore += grade.score; + evaluatedModules++; + } else { + const lessons = await prisma.lesson.findMany({ where: { moduleId: m.id } }); + for (const lsn of lessons) { + const prog = await prisma.progress.findUnique({ + where: { userId_lessonId: { userId, lessonId: lsn.id } } + }); + if (!prog || !prog.completed) { + allPassed = false; + break; + } + } } - - if (allPassed) { - const exists = db.certificates.find(c => c.userId === userId && c.courseId === course.id && c.certificateType === 'COURSE'); - if (!exists) { - const finalGrade = evaluatedModules > 0 ? (totalScore / evaluatedModules) : null; - db.certificates.push({ - id: `cert_${Math.random().toString(36).substring(2, 9)}`, + } + + if (allPassed) { + const exists = await prisma.certificate.findFirst({ + where: { userId, courseId: course.id, certificateType: 'COURSE' } + }); + if (!exists) { + const finalGrade = evaluatedModules > 0 ? (totalScore / evaluatedModules) : null; + await prisma.certificate.create({ + data: { userId, courseId: course.id, - moduleId: null, certificateType: 'COURSE', - unlockedAt: new Date().toISOString(), finalGrade, - courseName: course.title, - moduleName: null - }); - } + courseName: course.title + } + }); } } } } - saveDb(db); res.json({ success: true, score, passed }); }); // Student: Get my certificates -app.get('/api/certificates', authenticateToken, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - const myCerts = db.certificates.filter(c => c.userId === req.user!.id); +app.get('/api/certificates', authenticateToken, async (req: AuthenticatedRequest, res: Response) => { + const myCerts = await prisma.certificate.findMany({ where: { userId: req.user!.id } }); res.json(myCerts); }); // --- NOTIFICATIONS --- -app.get('/api/notifications', authenticateToken, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - const myNotifications = db.notifications.filter(n => n.userId === req.user!.id || n.userId === 'ALL'); - res.json(myNotifications.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())); +app.get('/api/notifications', authenticateToken, async (req: AuthenticatedRequest, res: Response) => { + const myNotifications = await prisma.notification.findMany({ + where: { OR: [{ userId: req.user!.id }, { userId: 'ALL' }] }, + orderBy: { createdAt: 'desc' } + }); + res.json(myNotifications); }); -app.put('/api/notifications/:id/read', authenticateToken, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - const notification = db.notifications.find(n => n.id === req.params.id); +app.put('/api/notifications/:id/read', authenticateToken, async (req: AuthenticatedRequest, res: Response) => { + const notification = await prisma.notification.findUnique({ where: { id: req.params.id } }); if (notification && (notification.userId === req.user!.id || notification.userId === 'ALL')) { - notification.read = true; - saveDb(db); + await prisma.notification.update({ + where: { id: notification.id }, + data: { read: true } + }); } res.json({ success: true }); }); // --- HELP DESK --- -app.get('/api/help', authenticateToken, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - res.json({ helpText: db.settings?.helpText || '' }); +app.get('/api/help', authenticateToken, async (req: AuthenticatedRequest, res: Response) => { + const settings = await prisma.systemSettings.findFirst(); + res.json({ helpText: settings?.helpText || '' }); }); -app.put('/api/admin/help', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - if (!db.settings) db.settings = {}; - db.settings.helpText = req.body.helpText; - saveDb(db); +app.put('/api/admin/help', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { + const settings = await prisma.systemSettings.findFirst(); + if (settings) { + await prisma.systemSettings.update({ + where: { id: settings.id }, + data: { helpText: req.body.helpText } + }); + } else { + await prisma.systemSettings.create({ data: { helpText: req.body.helpText } }); + } res.json({ success: true }); }); // --- MODULE ACTIVITIES (ADMIN) --- -app.get('/api/admin/modules/:moduleId/activity', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - const activity = db.activities.find(a => a.moduleId === req.params.moduleId); +app.get('/api/admin/modules/:moduleId/activity', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { + const activity = await prisma.moduleActivity.findUnique({ where: { moduleId: req.params.moduleId } }); res.json(activity || { moduleId: req.params.moduleId, questions: [] }); }); -app.put('/api/admin/modules/:moduleId/activity', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); +app.put('/api/admin/modules/:moduleId/activity', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const { moduleId } = req.params; const { questions } = req.body; - let activity = db.activities.find(a => a.moduleId === moduleId); - if (!activity) { - activity = { - id: `act_${Math.random().toString(36).substring(2, 9)}`, - moduleId, - questions: [], - createdAt: new Date().toISOString() - }; - db.activities.push(activity); - } - - activity.questions = questions; - saveDb(db); - res.json(activity); + const updated = await prisma.moduleActivity.upsert({ + where: { moduleId }, + update: { questions: questions as any }, + create: { moduleId, questions: questions as any } + }); + res.json(updated); }); // --- PUBLIC/STUDENT PLANS API --- -app.get('/api/plans', authenticateToken, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - const activePlans = (db.plans || []).filter(p => p.active); +app.get('/api/plans', authenticateToken, async (req: AuthenticatedRequest, res: Response) => { + const activePlans = await prisma.plan.findMany({ where: { active: true } }); res.json(activePlans); }); // --- ADMIN PLANS API ENDPOINTS --- - -app.get('/api/admin/plans', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - res.json(db.plans || []); +app.get('/api/admin/plans', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { + const plans = await prisma.plan.findMany(); + res.json(plans); }); -app.post('/api/admin/plans', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); +app.post('/api/admin/plans', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { const { title, description, price, cycle, includedCourses, active } = req.body; - const newPlan = { - id: `plan_${Math.random().toString(36).substring(2, 9)}`, - title: title || 'Novo Plano', - description: description || '', - price: price || 0, - cycle: cycle || 'LIFETIME', - includedCourses: includedCourses || [], - active: active !== undefined ? active : true, - createdAt: new Date().toISOString() - }; - - if (!db.plans) db.plans = []; - db.plans.push(newPlan); - saveDb(db); + const newPlan = await prisma.plan.create({ + data: { + title: title || 'Novo Plano', + description: description || '', + price: price || 0, + cycle: cycle || 'LIFETIME', + includedCourses: includedCourses || [], + active: active !== undefined ? active : true + } + }); res.json({ success: true, plan: newPlan }); }); -app.put('/api/admin/plans/:id', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - const planIdx = db.plans?.findIndex(p => p.id === req.params.id); - - if (planIdx !== undefined && planIdx >= 0 && db.plans) { - db.plans[planIdx] = { ...db.plans[planIdx], ...req.body }; - saveDb(db); - res.json({ success: true, plan: db.plans[planIdx] }); - } else { +app.put('/api/admin/plans/:id', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { + try { + const plan = await prisma.plan.update({ + where: { id: req.params.id }, + data: req.body + }); + res.json({ success: true, plan }); + } catch (error) { res.status(404).json({ error: 'Plano não encontrado' }); } }); -app.delete('/api/admin/plans/:id', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - if (db.plans) { - db.plans = db.plans.filter(p => p.id !== req.params.id); - saveDb(db); +app.delete('/api/admin/plans/:id', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { + try { + await prisma.plan.delete({ where: { id: req.params.id } }); + res.json({ success: true }); + } catch (error) { + res.status(404).json({ error: 'Plano não encontrado' }); } - res.json({ success: true }); }); // Update single course price and lock status directly -app.put('/api/admin/courses/:id/price', authenticateToken, requireAdmin, (req: AuthenticatedRequest, res: Response) => { - const db = loadDb(); - const courseIdx = db.courses.findIndex(c => c.id === req.params.id); - - if (courseIdx >= 0) { - db.courses[courseIdx].price = req.body.price; - db.courses[courseIdx].isLocked = req.body.isLocked; - saveDb(db); - res.json({ success: true, course: db.courses[courseIdx] }); - } else { +app.put('/api/admin/courses/:id/price', authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res: Response) => { + try { + const course = await prisma.course.update({ + where: { id: req.params.id }, + data: { + price: req.body.price !== undefined ? Number(req.body.price) : undefined, + isLocked: req.body.isLocked + } + }); + res.json({ success: true, course }); + } catch (error) { res.status(404).json({ error: 'Curso não encontrado' }); } });