From fa180dee0c7df113d259d19d5de8849fe67a9c6a Mon Sep 17 00:00:00 2001 From: Sidney Gomes Date: Wed, 22 Jul 2026 22:27:19 -0300 Subject: [PATCH] fix(backend): fix production crash with vite, make asaas async and add prisma db push retries --- package.json | 5 ++-- server.ts | 28 ++++++++++++++++++----- src/services/asaas.ts | 53 ++++++++++++++++++++++--------------------- 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index a9eb7eb..9f7f002 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "jsonwebtoken": "^9.0.3", "lucide-react": "^0.546.0", "motion": "^12.23.24", + "prisma": "^6.4.1", "react": "^19.0.1", "react-dom": "^19.0.1", "recharts": "^2.15.0", @@ -37,10 +38,8 @@ "@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", - "vite": "^6.2.3" + "typescript": "~5.8.2" } } diff --git a/server.ts b/server.ts index b24304b..8f594e0 100644 --- a/server.ts +++ b/server.ts @@ -6,7 +6,6 @@ import multer from 'multer'; import axios from 'axios'; import { execSync } from 'child_process'; import { S3Client, PutObjectCommand, CreateBucketCommand, HeadBucketCommand, PutBucketPolicyCommand } from '@aws-sdk/client-s3'; -import { createServer as createViteServer } from 'vite'; import { prisma } from './src/db/prisma'; import { AsaasService } from './src/services/asaas'; import { User, Course, Module, Lesson, Note, LessonProgress, WebhookLog, SubscriptionPayment, SubscriptionStatus } from './src/types'; @@ -461,7 +460,7 @@ app.post('/api/courses/:id/unlock', authenticateToken, checkoutRateLimiter, asyn try { let customerId = user.asaasCustomerId; - const apiKey = AsaasService.getApiKey(); + const apiKey = await AsaasService.getApiKey(); if (apiKey && (customerId?.startsWith('cus_sim_') || customerId?.startsWith('cus_fallback_') || ['cus_test', 'cus_00001', 'cus_00002', 'cus_00003', 'cus_00004'].includes(customerId || ''))) { customerId = null; } @@ -659,7 +658,7 @@ app.post('/api/subscription/subscribe', authenticateToken, async (req: Authentic try { let customerId = user.asaasCustomerId; - const apiKey = AsaasService.getApiKey(); + const apiKey = await AsaasService.getApiKey(); if (apiKey && (customerId?.startsWith('cus_sim_') || customerId?.startsWith('cus_fallback_') || ['cus_test', 'cus_00001', 'cus_00002', 'cus_00003', 'cus_00004'].includes(customerId || ''))) { customerId = null; } @@ -1827,9 +1826,25 @@ app.put('/api/admin/courses/:id/price', authenticateToken, requireAdmin, async ( async function startServer() { try { console.log('🔄 Sincronizando banco de dados com Prisma...'); - execSync('npx prisma db push --accept-data-loss', { stdio: 'inherit' }); - console.log('✅ Banco de dados sincronizado!'); - + let retries = 5; + while (retries > 0) { + try { + execSync('npx prisma db push --accept-data-loss', { stdio: 'inherit' }); + console.log('✅ Banco de dados sincronizado!'); + break; + } catch (err) { + console.error(`⚠️ Erro ao sincronizar banco (tentativas restantes: ${retries - 1})... Aguardando Postgres...`); + retries -= 1; + if (retries === 0) { + console.error('❌ Falha definitiva ao inicializar banco de dados:', err); + // Don't throw, let it attempt anyway (might just be a timeout issue, though count() will probably fail) + } else { + // Wait 3 seconds synchronously using a busy wait or just use a helper + await new Promise(res => setTimeout(res, 3000)); + } + } + } + // Seed test users if DB is empty const adminCount = await prisma.user.count({ where: { email: 'sidney.gomes1989@gmail.com' } }); if (adminCount === 0) { @@ -1891,6 +1906,7 @@ async function startServer() { } if (process.env.NODE_ENV !== 'production') { + const { createServer: createViteServer } = await import('vite'); const vite = await createViteServer({ server: { middlewareMode: true }, appType: 'spa', diff --git a/src/services/asaas.ts b/src/services/asaas.ts index 1857e95..329031b 100644 --- a/src/services/asaas.ts +++ b/src/services/asaas.ts @@ -1,20 +1,20 @@ import { SubscriptionPayment } from '../types'; -import { loadDb } from '../db/db'; +import { prisma } from '../db/prisma'; export class AsaasService { - public static getApiKey(): string | null { - const db = loadDb(); - return db.settings?.asaasApiKey || process.env.ASAAS_API_KEY || null; + public static async getApiKey(): Promise { + const db = await prisma.systemSettings.findUnique({ where: { id: 'default' } }); + return db?.asaasApiKey || process.env.ASAAS_API_KEY || null; } - private static getApiUrl(): string { - const db = loadDb(); - const env = db.settings?.asaasEnvironment || 'sandbox'; + private static async getApiUrl(): Promise { + const db = await prisma.systemSettings.findUnique({ where: { id: 'default' } }); + const env = db?.asaasEnvironment || 'sandbox'; return env === 'production' ? 'https://api.asaas.com/v3' : 'https://sandbox.asaas.com/api/v3'; } - private static getHeaders() { - const key = this.getApiKey(); + private static async getHeaders(): Promise> { + const key = await this.getApiKey(); return { 'Content-Type': 'application/json', 'access_token': key || '' @@ -25,8 +25,9 @@ export class AsaasService { * Test the connection to Asaas API */ public static async testConnection(customApiKey?: string, customEnv?: string): Promise<{ success: boolean; message: string; environment: string }> { - const apiKey = customApiKey || this.getApiKey(); - const env = customEnv || loadDb().settings?.asaasEnvironment || 'sandbox'; + const apiKey = customApiKey || await this.getApiKey(); + const db = await prisma.systemSettings.findUnique({ where: { id: 'default' } }); + const env = customEnv || db?.asaasEnvironment || 'sandbox'; const apiUrl = env === 'production' ? 'https://api.asaas.com/v3' : 'https://sandbox.asaas.com/v3'; if (!apiKey || apiKey.trim() === '') { @@ -74,7 +75,7 @@ export class AsaasService { * Create a customer in Asaas */ public static async createCustomer(name: string, email: string, cpfCnpj?: string): Promise { - const apiKey = this.getApiKey(); + const apiKey = await this.getApiKey(); if (!apiKey) { console.warn('ASAAS_API_KEY not configured. Simulating customer creation.'); return `cus_sim_${Math.random().toString(36).substring(2, 9)}`; @@ -91,9 +92,9 @@ export class AsaasService { } try { - const response = await fetch(`${this.getApiUrl()}/customers`, { + const response = await fetch(`${await this.getApiUrl()}/customers`, { method: 'POST', - headers: this.getHeaders(), + headers: await this.getHeaders(), body: JSON.stringify(payload) }); @@ -122,7 +123,7 @@ export class AsaasService { cycle: 'MONTHLY' | 'YEARLY' = 'MONTHLY', description: string = 'Assinatura Mensal DevFlix' ): Promise<{ subscriptionId: string; payment: Partial }> { - const apiKey = this.getApiKey(); + const apiKey = await this.getApiKey(); if (!apiKey) { throw new Error('A Chave de API do Asaas (API Key) não foi configurada. Acesse o Painel Admin -> Configurações Globais, cole a sua chave do Asaas Sandbox e clique em Salvar Configurações.'); } @@ -142,9 +143,9 @@ export class AsaasService { payload.creditCardHolderInfo = creditCardInfo.creditCardHolderInfo; } - const response = await fetch(`${this.getApiUrl()}/subscriptions`, { + const response = await fetch(`${await this.getApiUrl()}/subscriptions`, { method: 'POST', - headers: this.getHeaders(), + headers: await this.getHeaders(), body: JSON.stringify(payload) }); @@ -192,7 +193,7 @@ export class AsaasService { * Fetch PIX QR Code for a given payment ID */ public static async getPixQrCode(paymentId: string): Promise<{ encodedImage: string, payload: string } | null> { - const apiKey = this.getApiKey(); + const apiKey = await this.getApiKey(); const fallbackPayload = `00020126580014br.gov.bcb.pix0136${paymentId}_microtecflix520400005303986540549.905802BR5915MicrotecFlix6009Sao Paulo62070503***6304`; if (!apiKey || paymentId.startsWith('pay_sim_') || paymentId.startsWith('pay_fb_')) { @@ -203,9 +204,9 @@ export class AsaasService { } try { - const response = await fetch(`${this.getApiUrl()}/payments/${paymentId}/pixQrCode`, { + const response = await fetch(`${await this.getApiUrl()}/payments/${paymentId}/pixQrCode`, { method: 'GET', - headers: this.getHeaders() + headers: await this.getHeaders() }); if (!response.ok) { @@ -237,7 +238,7 @@ export class AsaasService { creditCard: any, creditCardHolderInfo: any ): Promise<{ creditCardToken: string; creditCardNumber: string; creditCardBrand: string }> { - const apiKey = this.getApiKey(); + const apiKey = await this.getApiKey(); if (!apiKey) { return { creditCardToken: `tok_sim_${Math.random().toString(36).substring(2, 9)}`, @@ -247,9 +248,9 @@ export class AsaasService { } try { - const response = await fetch(`${this.getApiUrl()}/creditCard/tokenize`, { + const response = await fetch(`${await this.getApiUrl()}/creditCard/tokenize`, { method: 'POST', - headers: this.getHeaders(), + headers: await this.getHeaders(), body: JSON.stringify({ customer: customerId, creditCard, @@ -280,7 +281,7 @@ export class AsaasService { creditCardInfo?: any, installmentCount?: number ): Promise { - const apiKey = this.getApiKey(); + const apiKey = await this.getApiKey(); const futureDueDate = new Date(Date.now() + 3 * 24 * 3600 * 1000).toISOString().split('T')[0]; if (!apiKey) { @@ -310,9 +311,9 @@ export class AsaasService { } } - const response = await fetch(`${this.getApiUrl()}/payments`, { + const response = await fetch(`${await this.getApiUrl()}/payments`, { method: 'POST', - headers: this.getHeaders(), + headers: await this.getHeaders(), body: JSON.stringify(payload) });