Compare commits
No commits in common. "df1d63019eb79ebab0644440c62750c28e6a75d9" and "444c867ae8b3893ffc3d97e6a45c40ad18beb316" have entirely different histories.
df1d63019e
...
444c867ae8
|
|
@ -21,7 +21,6 @@ O sistema realiza o roteamento automático do frontend via `src/Root.tsx`:
|
||||||
|
|
||||||
Para evitar conflitos com outros bancos de dados ativos na VPS (como o PostgreSQL do AgendaPRO na porta `5432`), o MicrotecFlix utiliza uma instância isolada e dedicada de PostgreSQL:
|
Para evitar conflitos com outros bancos de dados ativos na VPS (como o PostgreSQL do AgendaPRO na porta `5432`), o MicrotecFlix utiliza uma instância isolada e dedicada de PostgreSQL:
|
||||||
|
|
||||||
* **Tecnologia ORM**: Prisma ORM (`prisma/schema.prisma`), que governa 100% da persistência de dados. O backend (`server-prisma.ts`) está inteiramente refatorado, sem utilizar mais arquivos `db.json` e nem funções locais (como `loadDb()`). Todas as alterações agora salvam em tabelas.
|
|
||||||
* **Container**: `postgres-microtecflix`
|
* **Container**: `postgres-microtecflix`
|
||||||
* **Imagem**: `postgres:16-alpine`
|
* **Imagem**: `postgres:16-alpine`
|
||||||
* **Porta Host**: `5435:5432` (evita colisão com a porta 5432 original)
|
* **Porta Host**: `5435:5432` (evita colisão com a porta 5432 original)
|
||||||
|
|
|
||||||
11
memory.md
11
memory.md
|
|
@ -27,21 +27,14 @@ O **MicrotecFlix** é uma plataforma EAD / Streaming de cursos de informática e
|
||||||
|
|
||||||
## 2. Esquema de Entidades (`prisma/schema.prisma` / PostgreSQL)
|
## 2. Esquema de Entidades (`prisma/schema.prisma` / PostgreSQL)
|
||||||
|
|
||||||
Todo o sistema foi **100% migrado para o PostgreSQL** utilizando o **Prisma ORM**. O antigo arquivo `db.json` e a função genérica `loadDb()` foram completamente descartados e removidos da base de código backend (`server-prisma.ts`).
|
|
||||||
|
|
||||||
1. **`User`**: Usuários da plataforma (`admin` ou `student`) com status de assinatura (`ACTIVE`, `OVERDUE`, `CANCELED`, `TRIAL`).
|
1. **`User`**: Usuários da plataforma (`admin` ou `student`) com status de assinatura (`ACTIVE`, `OVERDUE`, `CANCELED`, `TRIAL`).
|
||||||
2. **`Course`**: Cursos cadastrados com título, descrição, thumbnail, categoria e preço avulso/bloqueio.
|
2. **`Course`**: Cursos cadastrados com título, descrição, thumbnail, categoria e preço.
|
||||||
3. **`Module`**: Módulos organizados sequencialmente por curso.
|
3. **`Module`**: Módulos organizados sequencialmente por curso.
|
||||||
4. **`Lesson`**: Aulas vinculadas ao módulo com URL de vídeo (`youtube` ou `direct`) e conteúdo complementar.
|
4. **`Lesson`**: Aulas vinculadas ao módulo com URL de vídeo (`youtube` ou `direct`) e conteúdo complementar.
|
||||||
5. **`Note`**: Anotações pessoais feitas pelos alunos por aula.
|
5. **`Note`**: Anotações pessoais feitas pelos alunos por aula.
|
||||||
6. **`Progress`**: Histórico de progresso do aluno por aula (% assistida e `completed`).
|
6. **`Progress`**: Histórico de progresso do aluno por aula (% assistida e `completed`).
|
||||||
7. **`Payment` (SubscriptionPayment)**: Histórico de faturas e cobranças registradas no gateway de pagamentos, linkado a usuários, cursos e planos.
|
7. **`Payment`**: Histórico de faturas e cobranças registradas no gateway de pagamentos.
|
||||||
8. **`WebhookLog`**: Registro auditável de webhooks recebidos do Asaas.
|
8. **`WebhookLog`**: Registro auditável de webhooks recebidos do Asaas.
|
||||||
9. **`ModuleActivity` / `ModuleGrade`**: Questionários/Múltipla escolha e pontuações dos alunos, substituindo a antiga matriz JSON de atividades.
|
|
||||||
10. **`Certificate`**: Certificados ganhos pelos alunos (por Módulo ou por Curso) gerados automaticamente.
|
|
||||||
11. **`Plan`**: Planos de assinatura (Combos vitálcios, Assinaturas Mensais, Anuais).
|
|
||||||
12. **`Notification`**: Alertas e avisos aos usuários, gerenciados em tabela relacional em tempo real.
|
|
||||||
13. **`SystemSettings`**: Configurações dinâmicas do sistema, chaves do Asaas e texto de ajuda.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
"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",
|
||||||
|
|
@ -36,7 +35,6 @@
|
||||||
"@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",
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,6 @@ 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[]
|
||||||
|
|
@ -65,7 +60,6 @@ model User {
|
||||||
payments Payment[]
|
payments Payment[]
|
||||||
moduleGrades ModuleGrade[]
|
moduleGrades ModuleGrade[]
|
||||||
certificates Certificate[]
|
certificates Certificate[]
|
||||||
unlockedCourses String[] @default([]) @map("unlocked_courses")
|
|
||||||
|
|
||||||
@@map("users")
|
@@map("users")
|
||||||
}
|
}
|
||||||
|
|
@ -77,8 +71,6 @@ 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[]
|
||||||
|
|
@ -155,9 +147,6 @@ 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)
|
||||||
|
|
@ -216,29 +205,3 @@ 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")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
1760
server-json.ts
1760
server-json.ts
File diff suppressed because it is too large
Load Diff
|
|
@ -32,7 +32,7 @@ function CoursePricingCard({ course, saveLoading, onSave }: { course: Course, sa
|
||||||
localLocked ? 'bg-amber-500/20 text-amber-400 border border-amber-500/30' : 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
|
localLocked ? 'bg-amber-500/20 text-amber-400 border border-amber-500/30' : 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{localLocked ? <span>💰 Pago (Bloqueado)</span> : <span>🔓 Gratuito (Livre)</span>}
|
{localLocked ? '💰 Pago (Bloqueado)' : '🔓 Gratuito (Livre)'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ function CoursePricingCard({ course, saveLoading, onSave }: { course: Course, sa
|
||||||
) : (
|
) : (
|
||||||
<Save className="w-4 h-4" />
|
<Save className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
<span>Salvar Alteração</span>
|
Salvar Alteração
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -82,7 +82,7 @@ export const AdminPricingManager: React.FC<AdminPricingManagerProps> = ({ course
|
||||||
const [showPlanModal, setShowPlanModal] = useState(false);
|
const [showPlanModal, setShowPlanModal] = useState(false);
|
||||||
const [planForm, setPlanForm] = useState<Partial<Plan>>({ cycle: 'LIFETIME', active: true, includedCourses: [] });
|
const [planForm, setPlanForm] = useState<Partial<Plan>>({ cycle: 'LIFETIME', active: true, includedCourses: [] });
|
||||||
|
|
||||||
const token = localStorage.getItem('devflix_admin_token');
|
const token = localStorage.getItem('devflix_token');
|
||||||
|
|
||||||
const fetchPlans = async () => {
|
const fetchPlans = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
|
||||||
|
|
@ -220,13 +220,6 @@ export default function Navbar({ user, activeView, setView, onLogout }: NavbarPr
|
||||||
<User className="w-4 h-4" />
|
<User className="w-4 h-4" />
|
||||||
<span>Meus dados</span>
|
<span>Meus dados</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
onClick={() => { setView('certificates'); setShowProfileMenu(false); }}
|
|
||||||
className="w-full px-4 py-2 text-left flex items-center space-x-2 text-sm text-zinc-300 hover:bg-zinc-800 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
<GraduationCap className="w-4 h-4" />
|
|
||||||
<span>Meu Certificado</span>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => { setView('help'); setShowProfileMenu(false); }}
|
onClick={() => { setView('help'); setShowProfileMenu(false); }}
|
||||||
className="w-full px-4 py-2 text-left flex items-center space-x-2 text-sm text-zinc-300 hover:bg-zinc-800 hover:text-white transition-colors"
|
className="w-full px-4 py-2 text-left flex items-center space-x-2 text-sm text-zinc-300 hover:bg-zinc-800 hover:text-white transition-colors"
|
||||||
|
|
|
||||||
|
|
@ -1,242 +0,0 @@
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
export const prisma = new PrismaClient();
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
import { Router, Request, Response } from 'express';
|
|
||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import multer from 'multer';
|
|
||||||
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
||||||
|
|
||||||
// Mock types since we will split these out
|
|
||||||
interface AuthenticatedRequest extends Request {
|
|
||||||
user?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
const upload = multer({ storage: multer.memoryStorage() });
|
|
||||||
const s3 = new S3Client({
|
|
||||||
region: 'us-east-1',
|
|
||||||
endpoint: 'https://s3-estudo.microtecinformaticacurso.com.br',
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: 'microtecflix_admin',
|
|
||||||
secretAccessKey: 'MicrotecFlixS3SecurePass2026!'
|
|
||||||
},
|
|
||||||
forcePathStyle: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const authenticateToken = (req: any, res: any, next: any) => {
|
|
||||||
// Logic will be in server.ts or a middleware file, assuming it's passed down
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
|
|
||||||
router.put('/profile', async (req: AuthenticatedRequest, res: Response) => {
|
|
||||||
const { name, whatsapp, birthDate, cpf } = req.body;
|
|
||||||
const userId = req.user!.id;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const user = await prisma.user.update({
|
|
||||||
where: { id: userId },
|
|
||||||
data: {
|
|
||||||
...(name !== undefined && { name }),
|
|
||||||
...(whatsapp !== undefined && { whatsapp }),
|
|
||||||
...(birthDate !== undefined && { birthDate }),
|
|
||||||
...(cpf !== undefined && { cpf })
|
|
||||||
}
|
|
||||||
});
|
|
||||||
res.json({ success: true, user });
|
|
||||||
} catch (error) {
|
|
||||||
res.status(404).json({ error: 'Usuário não encontrado' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/avatar', upload.single('avatar'), async (req: AuthenticatedRequest, res: Response) => {
|
|
||||||
const userId = req.user!.id;
|
|
||||||
if (!req.file) {
|
|
||||||
res.status(400).json({ error: 'Nenhum arquivo enviado' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ext = req.file.originalname.split('.').pop();
|
|
||||||
const filename = `avatars/${userId}_${Date.now()}.${ext}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await s3.send(new PutObjectCommand({
|
|
||||||
Bucket: 'microtecflix',
|
|
||||||
Key: filename,
|
|
||||||
Body: req.file.buffer,
|
|
||||||
ContentType: req.file.mimetype
|
|
||||||
}));
|
|
||||||
|
|
||||||
const avatarUrl = `https://s3-estudo.microtecinformaticacurso.com.br/microtecflix/${filename}`;
|
|
||||||
|
|
||||||
await prisma.user.update({
|
|
||||||
where: { id: userId },
|
|
||||||
data: { avatarUrl }
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json({ success: true, avatarUrl });
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error('Erro no upload da foto:', err);
|
|
||||||
res.status(500).json({ error: 'Erro ao salvar foto no MinIO' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
Loading…
Reference in New Issue