feat(db): Initial Prisma setup and PostgreSQL migration script

This commit is contained in:
Sidney Gomes 2026-07-22 21:07:17 -03:00
parent 444c867ae8
commit 320b627a91
5 changed files with 2014 additions and 0 deletions

View File

@ -13,6 +13,7 @@
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.700.0", "@aws-sdk/client-s3": "^3.700.0",
"@google/genai": "^2.4.0", "@google/genai": "^2.4.0",
"@prisma/client": "^6.4.1",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"@tailwindcss/vite": "^4.1.14", "@tailwindcss/vite": "^4.1.14",
"@vitejs/plugin-react": "^5.0.4", "@vitejs/plugin-react": "^5.0.4",
@ -35,6 +36,7 @@
"@types/node": "^22.14.0", "@types/node": "^22.14.0",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"prisma": "^6.4.1",
"tailwindcss": "^4.1.14", "tailwindcss": "^4.1.14",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "~5.8.2", "typescript": "~5.8.2",

View File

@ -53,6 +53,11 @@ model User {
role Role @default(student) role Role @default(student)
subscriptionStatus SubscriptionStatus @default(TRIAL) @map("subscription_status") subscriptionStatus SubscriptionStatus @default(TRIAL) @map("subscription_status")
asaasCustomerId String? @map("asaas_customer_id") asaasCustomerId String? @map("asaas_customer_id")
cpf String?
whatsapp String?
birthDate String? @map("birth_date")
avatarUrl String? @map("avatar_url") @db.Text
trialEndsAt DateTime? @map("trial_ends_at")
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
notes Note[] notes Note[]
@ -60,6 +65,7 @@ model User {
payments Payment[] payments Payment[]
moduleGrades ModuleGrade[] moduleGrades ModuleGrade[]
certificates Certificate[] certificates Certificate[]
unlockedCourses String[] @default([]) @map("unlocked_courses")
@@map("users") @@map("users")
} }
@ -71,6 +77,8 @@ model Course {
thumbnail String thumbnail String
category String category String
certificateMode CertificateMode @default(FULL_COURSE) @map("certificate_mode") certificateMode CertificateMode @default(FULL_COURSE) @map("certificate_mode")
isLocked Boolean @default(false) @map("is_locked")
price Float?
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
modules Module[] modules Module[]
@ -147,6 +155,9 @@ model Payment {
invoiceUrl String @map("invoice_url") @db.Text invoiceUrl String @map("invoice_url") @db.Text
dueDate DateTime @map("due_date") @db.Date dueDate DateTime @map("due_date") @db.Date
paidAt DateTime? @map("paid_at") paidAt DateTime? @map("paid_at")
planId String? @map("plan_id")
courseId String? @map("course_id")
description String? @db.Text
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@ -205,3 +216,29 @@ model Certificate {
@@map("certificates") @@map("certificates")
} }
model Plan {
id String @id @default(uuid())
title String
description String @db.Text
price Float
cycle String // 'MONTHLY', 'YEARLY', 'LIFETIME'
active Boolean @default(true)
includedCourses String[] @default([]) @map("included_courses")
createdAt DateTime @default(now()) @map("created_at")
@@map("plans")
}
model SystemSettings {
id String @id @default("default")
trialDurationDays Int @default(7) @map("trial_duration_days")
asaasEnvironment String @default("sandbox") @map("asaas_environment")
asaasApiKey String @default("") @map("asaas_api_key")
asaasWebhookUrl String @default("") @map("asaas_webhook_url")
asaasWebhookSecret String @default("") @map("asaas_webhook_secret")
helpText String @db.Text @default("# Central de Ajuda") @map("help_text")
@@map("system_settings")
}

1730
server-prisma.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,242 @@
import fs from 'fs';
import path from 'path';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const DATA_DIR = process.env.DATA_DIR || (fs.existsSync('/app/data') ? '/app/data' : process.cwd());
const DB_FILE = path.join(DATA_DIR, 'database.json');
async function main() {
console.log('🔄 Iniciando migração de JSON para PostgreSQL...');
if (!fs.existsSync(DB_FILE)) {
console.error('❌ Arquivo database.json não encontrado!');
return;
}
const data = JSON.parse(fs.readFileSync(DB_FILE, 'utf-8'));
// 1. Settings
console.log('Migrando Settings...');
if (data.settings) {
await prisma.systemSettings.upsert({
where: { id: 'default' },
update: {
trialDurationDays: data.settings.trialDurationDays || 7,
asaasEnvironment: data.settings.asaasEnvironment || 'sandbox',
asaasApiKey: data.settings.asaasApiKey || '',
asaasWebhookUrl: data.settings.asaasWebhookUrl || '',
asaasWebhookSecret: data.settings.asaasWebhookSecret || '',
helpText: data.settings.helpText || '# Central de Ajuda'
},
create: {
id: 'default',
trialDurationDays: data.settings.trialDurationDays || 7,
asaasEnvironment: data.settings.asaasEnvironment || 'sandbox',
asaasApiKey: data.settings.asaasApiKey || '',
asaasWebhookUrl: data.settings.asaasWebhookUrl || '',
asaasWebhookSecret: data.settings.asaasWebhookSecret || '',
helpText: data.settings.helpText || '# Central de Ajuda'
}
});
}
// 2. Users
console.log(`Migrando ${data.users?.length || 0} Users...`);
for (const user of data.users || []) {
await prisma.user.upsert({
where: { id: user.id },
update: {
name: user.name,
email: user.email,
password: user.password,
role: user.role === 'admin' ? 'admin' : 'student',
subscriptionStatus: user.subscriptionStatus,
asaasCustomerId: user.asaasCustomerId,
cpf: user.cpf,
whatsapp: user.whatsapp,
birthDate: user.birthDate,
avatarUrl: user.avatarUrl,
trialEndsAt: user.trialEndsAt ? new Date(user.trialEndsAt) : null,
unlockedCourses: user.unlockedCourses || [],
createdAt: new Date(user.createdAt)
},
create: {
id: user.id,
name: user.name,
email: user.email,
password: user.password,
role: user.role === 'admin' ? 'admin' : 'student',
subscriptionStatus: user.subscriptionStatus,
asaasCustomerId: user.asaasCustomerId,
cpf: user.cpf,
whatsapp: user.whatsapp,
birthDate: user.birthDate,
avatarUrl: user.avatarUrl,
trialEndsAt: user.trialEndsAt ? new Date(user.trialEndsAt) : null,
unlockedCourses: user.unlockedCourses || [],
createdAt: new Date(user.createdAt)
}
});
}
// 3. Courses
console.log(`Migrando ${data.courses?.length || 0} Courses...`);
for (const course of data.courses || []) {
await prisma.course.upsert({
where: { id: course.id },
update: {
title: course.title,
description: course.description,
thumbnail: course.thumbnail,
category: course.category,
certificateMode: course.certificateMode || 'FULL_COURSE',
isLocked: course.isLocked || false,
price: course.price || null,
createdAt: new Date(course.createdAt)
},
create: {
id: course.id,
title: course.title,
description: course.description,
thumbnail: course.thumbnail,
category: course.category,
certificateMode: course.certificateMode || 'FULL_COURSE',
isLocked: course.isLocked || false,
price: course.price || null,
createdAt: new Date(course.createdAt)
}
});
}
// 4. Modules
console.log(`Migrando ${data.modules?.length || 0} Modules...`);
for (const mod of data.modules || []) {
await prisma.module.upsert({
where: { id: mod.id },
update: {
courseId: mod.courseId,
title: mod.title,
order: mod.order,
createdAt: new Date(mod.createdAt)
},
create: {
id: mod.id,
courseId: mod.courseId,
title: mod.title,
order: mod.order,
createdAt: new Date(mod.createdAt)
}
});
}
// 5. Lessons
console.log(`Migrando ${data.lessons?.length || 0} Lessons...`);
for (const lesson of data.lessons || []) {
await prisma.lesson.upsert({
where: { id: lesson.id },
update: {
moduleId: lesson.moduleId,
title: lesson.title,
videoUrl: lesson.videoUrl,
videoType: lesson.videoType === 'youtube' ? 'youtube' : 'direct',
order: lesson.order,
content: lesson.content,
createdAt: new Date(lesson.createdAt)
},
create: {
id: lesson.id,
moduleId: lesson.moduleId,
title: lesson.title,
videoUrl: lesson.videoUrl,
videoType: lesson.videoType === 'youtube' ? 'youtube' : 'direct',
order: lesson.order,
content: lesson.content,
createdAt: new Date(lesson.createdAt)
}
});
}
// 6. Plans
console.log(`Migrando ${data.plans?.length || 0} Plans...`);
for (const plan of data.plans || []) {
await prisma.plan.upsert({
where: { id: plan.id },
update: {
title: plan.title,
description: plan.description || '',
price: plan.price,
cycle: plan.cycle,
active: plan.active,
includedCourses: plan.includedCourses || [],
createdAt: plan.createdAt ? new Date(plan.createdAt) : new Date()
},
create: {
id: plan.id,
title: plan.title,
description: plan.description || '',
price: plan.price,
cycle: plan.cycle,
active: plan.active,
includedCourses: plan.includedCourses || [],
createdAt: plan.createdAt ? new Date(plan.createdAt) : new Date()
}
});
}
// 7. Payments
console.log(`Migrando ${data.payments?.length || 0} Payments...`);
for (const p of data.payments || []) {
let billingType = 'PIX';
if (p.billingType === 'BOLETO') billingType = 'BOLETO';
if (p.billingType === 'CREDIT_CARD') billingType = 'CREDIT_CARD';
let status = 'PENDING';
if (p.status === 'RECEIVED') status = 'RECEIVED';
if (p.status === 'CONFIRMED') status = 'CONFIRMED';
if (p.status === 'OVERDUE') status = 'OVERDUE';
if (p.status === 'REFUNDED') status = 'REFUNDED';
await prisma.payment.upsert({
where: { id: p.id },
update: {
userId: p.userId,
value: p.value,
status: status as any,
billingType: billingType as any,
invoiceUrl: p.invoiceUrl || '',
dueDate: p.dueDate ? new Date(p.dueDate) : new Date(),
paidAt: p.paidAt ? new Date(p.paidAt) : null,
planId: p.planId || null,
courseId: p.courseId || null,
description: p.description || null,
createdAt: p.createdAt ? new Date(p.createdAt) : new Date()
},
create: {
id: p.id,
userId: p.userId,
value: p.value,
status: status as any,
billingType: billingType as any,
invoiceUrl: p.invoiceUrl || '',
dueDate: p.dueDate ? new Date(p.dueDate) : new Date(),
paidAt: p.paidAt ? new Date(p.paidAt) : null,
planId: p.planId || null,
courseId: p.courseId || null,
description: p.description || null,
createdAt: p.createdAt ? new Date(p.createdAt) : new Date()
}
});
}
console.log('✅ Migração finalizada com sucesso!');
}
main()
.catch(e => {
console.error('❌ Erro na migração:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

3
src/db/prisma.ts Normal file
View File

@ -0,0 +1,3 @@
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient();