feat(db): Initial Prisma setup and PostgreSQL migration script
This commit is contained in:
parent
444c867ae8
commit
320b627a91
|
|
@ -13,6 +13,7 @@
|
|||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.700.0",
|
||||
"@google/genai": "^2.4.0",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
|
|
@ -35,6 +36,7 @@
|
|||
"@types/node": "^22.14.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"esbuild": "^0.25.0",
|
||||
"prisma": "^6.4.1",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "~5.8.2",
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ model User {
|
|||
role Role @default(student)
|
||||
subscriptionStatus SubscriptionStatus @default(TRIAL) @map("subscription_status")
|
||||
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")
|
||||
|
||||
notes Note[]
|
||||
|
|
@ -60,6 +65,7 @@ model User {
|
|||
payments Payment[]
|
||||
moduleGrades ModuleGrade[]
|
||||
certificates Certificate[]
|
||||
unlockedCourses String[] @default([]) @map("unlocked_courses")
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
|
@ -71,6 +77,8 @@ model Course {
|
|||
thumbnail String
|
||||
category String
|
||||
certificateMode CertificateMode @default(FULL_COURSE) @map("certificate_mode")
|
||||
isLocked Boolean @default(false) @map("is_locked")
|
||||
price Float?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
modules Module[]
|
||||
|
|
@ -147,6 +155,9 @@ model Payment {
|
|||
invoiceUrl String @map("invoice_url") @db.Text
|
||||
dueDate DateTime @map("due_date") @db.Date
|
||||
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")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
|
@ -205,3 +216,29 @@ model Certificate {
|
|||
|
||||
@@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")
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
export const prisma = new PrismaClient();
|
||||
Loading…
Reference in New Issue