fix(backend): fix production crash with vite, make asaas async and add prisma db push retries
Build and Deploy (Gitea) / build-and-deploy (push) Failing after 44s Details

This commit is contained in:
Sidney Gomes 2026-07-22 22:27:19 -03:00
parent 159ae1e8db
commit fa180dee0c
3 changed files with 51 additions and 35 deletions

View File

@ -24,6 +24,7 @@
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.3",
"lucide-react": "^0.546.0", "lucide-react": "^0.546.0",
"motion": "^12.23.24", "motion": "^12.23.24",
"prisma": "^6.4.1",
"react": "^19.0.1", "react": "^19.0.1",
"react-dom": "^19.0.1", "react-dom": "^19.0.1",
"recharts": "^2.15.0", "recharts": "^2.15.0",
@ -37,10 +38,8 @@
"@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"
"vite": "^6.2.3"
} }
} }

View File

@ -6,7 +6,6 @@ import multer from 'multer';
import axios from 'axios'; import axios from 'axios';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { S3Client, PutObjectCommand, CreateBucketCommand, HeadBucketCommand, PutBucketPolicyCommand } from '@aws-sdk/client-s3'; import { S3Client, PutObjectCommand, CreateBucketCommand, HeadBucketCommand, PutBucketPolicyCommand } from '@aws-sdk/client-s3';
import { createServer as createViteServer } from 'vite';
import { prisma } from './src/db/prisma'; import { prisma } from './src/db/prisma';
import { AsaasService } from './src/services/asaas'; import { AsaasService } from './src/services/asaas';
import { User, Course, Module, Lesson, Note, LessonProgress, WebhookLog, SubscriptionPayment, SubscriptionStatus } from './src/types'; 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 { try {
let customerId = user.asaasCustomerId; 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 || ''))) { if (apiKey && (customerId?.startsWith('cus_sim_') || customerId?.startsWith('cus_fallback_') || ['cus_test', 'cus_00001', 'cus_00002', 'cus_00003', 'cus_00004'].includes(customerId || ''))) {
customerId = null; customerId = null;
} }
@ -659,7 +658,7 @@ app.post('/api/subscription/subscribe', authenticateToken, async (req: Authentic
try { try {
let customerId = user.asaasCustomerId; 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 || ''))) { if (apiKey && (customerId?.startsWith('cus_sim_') || customerId?.startsWith('cus_fallback_') || ['cus_test', 'cus_00001', 'cus_00002', 'cus_00003', 'cus_00004'].includes(customerId || ''))) {
customerId = null; customerId = null;
} }
@ -1827,8 +1826,24 @@ app.put('/api/admin/courses/:id/price', authenticateToken, requireAdmin, async (
async function startServer() { async function startServer() {
try { try {
console.log('🔄 Sincronizando banco de dados com Prisma...'); console.log('🔄 Sincronizando banco de dados com Prisma...');
execSync('npx prisma db push --accept-data-loss', { stdio: 'inherit' }); let retries = 5;
console.log('✅ Banco de dados sincronizado!'); 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 // Seed test users if DB is empty
const adminCount = await prisma.user.count({ where: { email: 'sidney.gomes1989@gmail.com' } }); const adminCount = await prisma.user.count({ where: { email: 'sidney.gomes1989@gmail.com' } });
@ -1891,6 +1906,7 @@ async function startServer() {
} }
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
const { createServer: createViteServer } = await import('vite');
const vite = await createViteServer({ const vite = await createViteServer({
server: { middlewareMode: true }, server: { middlewareMode: true },
appType: 'spa', appType: 'spa',

View File

@ -1,20 +1,20 @@
import { SubscriptionPayment } from '../types'; import { SubscriptionPayment } from '../types';
import { loadDb } from '../db/db'; import { prisma } from '../db/prisma';
export class AsaasService { export class AsaasService {
public static getApiKey(): string | null { public static async getApiKey(): Promise<string | null> {
const db = loadDb(); const db = await prisma.systemSettings.findUnique({ where: { id: 'default' } });
return db.settings?.asaasApiKey || process.env.ASAAS_API_KEY || null; return db?.asaasApiKey || process.env.ASAAS_API_KEY || null;
} }
private static getApiUrl(): string { private static async getApiUrl(): Promise<string> {
const db = loadDb(); const db = await prisma.systemSettings.findUnique({ where: { id: 'default' } });
const env = db.settings?.asaasEnvironment || 'sandbox'; const env = db?.asaasEnvironment || 'sandbox';
return env === 'production' ? 'https://api.asaas.com/v3' : 'https://sandbox.asaas.com/api/v3'; return env === 'production' ? 'https://api.asaas.com/v3' : 'https://sandbox.asaas.com/api/v3';
} }
private static getHeaders() { private static async getHeaders(): Promise<Record<string, string>> {
const key = this.getApiKey(); const key = await this.getApiKey();
return { return {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'access_token': key || '' 'access_token': key || ''
@ -25,8 +25,9 @@ export class AsaasService {
* Test the connection to Asaas API * Test the connection to Asaas API
*/ */
public static async testConnection(customApiKey?: string, customEnv?: string): Promise<{ success: boolean; message: string; environment: string }> { public static async testConnection(customApiKey?: string, customEnv?: string): Promise<{ success: boolean; message: string; environment: string }> {
const apiKey = customApiKey || this.getApiKey(); const apiKey = customApiKey || await this.getApiKey();
const env = customEnv || loadDb().settings?.asaasEnvironment || 'sandbox'; 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'; const apiUrl = env === 'production' ? 'https://api.asaas.com/v3' : 'https://sandbox.asaas.com/v3';
if (!apiKey || apiKey.trim() === '') { if (!apiKey || apiKey.trim() === '') {
@ -74,7 +75,7 @@ export class AsaasService {
* Create a customer in Asaas * Create a customer in Asaas
*/ */
public static async createCustomer(name: string, email: string, cpfCnpj?: string): Promise<string> { public static async createCustomer(name: string, email: string, cpfCnpj?: string): Promise<string> {
const apiKey = this.getApiKey(); const apiKey = await this.getApiKey();
if (!apiKey) { if (!apiKey) {
console.warn('ASAAS_API_KEY not configured. Simulating customer creation.'); console.warn('ASAAS_API_KEY not configured. Simulating customer creation.');
return `cus_sim_${Math.random().toString(36).substring(2, 9)}`; return `cus_sim_${Math.random().toString(36).substring(2, 9)}`;
@ -91,9 +92,9 @@ export class AsaasService {
} }
try { try {
const response = await fetch(`${this.getApiUrl()}/customers`, { const response = await fetch(`${await this.getApiUrl()}/customers`, {
method: 'POST', method: 'POST',
headers: this.getHeaders(), headers: await this.getHeaders(),
body: JSON.stringify(payload) body: JSON.stringify(payload)
}); });
@ -122,7 +123,7 @@ export class AsaasService {
cycle: 'MONTHLY' | 'YEARLY' = 'MONTHLY', cycle: 'MONTHLY' | 'YEARLY' = 'MONTHLY',
description: string = 'Assinatura Mensal DevFlix' description: string = 'Assinatura Mensal DevFlix'
): Promise<{ subscriptionId: string; payment: Partial<SubscriptionPayment> }> { ): Promise<{ subscriptionId: string; payment: Partial<SubscriptionPayment> }> {
const apiKey = this.getApiKey(); const apiKey = await this.getApiKey();
if (!apiKey) { 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.'); 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; payload.creditCardHolderInfo = creditCardInfo.creditCardHolderInfo;
} }
const response = await fetch(`${this.getApiUrl()}/subscriptions`, { const response = await fetch(`${await this.getApiUrl()}/subscriptions`, {
method: 'POST', method: 'POST',
headers: this.getHeaders(), headers: await this.getHeaders(),
body: JSON.stringify(payload) body: JSON.stringify(payload)
}); });
@ -192,7 +193,7 @@ export class AsaasService {
* Fetch PIX QR Code for a given payment ID * Fetch PIX QR Code for a given payment ID
*/ */
public static async getPixQrCode(paymentId: string): Promise<{ encodedImage: string, payload: string } | null> { 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`; const fallbackPayload = `00020126580014br.gov.bcb.pix0136${paymentId}_microtecflix520400005303986540549.905802BR5915MicrotecFlix6009Sao Paulo62070503***6304`;
if (!apiKey || paymentId.startsWith('pay_sim_') || paymentId.startsWith('pay_fb_')) { if (!apiKey || paymentId.startsWith('pay_sim_') || paymentId.startsWith('pay_fb_')) {
@ -203,9 +204,9 @@ export class AsaasService {
} }
try { try {
const response = await fetch(`${this.getApiUrl()}/payments/${paymentId}/pixQrCode`, { const response = await fetch(`${await this.getApiUrl()}/payments/${paymentId}/pixQrCode`, {
method: 'GET', method: 'GET',
headers: this.getHeaders() headers: await this.getHeaders()
}); });
if (!response.ok) { if (!response.ok) {
@ -237,7 +238,7 @@ export class AsaasService {
creditCard: any, creditCard: any,
creditCardHolderInfo: any creditCardHolderInfo: any
): Promise<{ creditCardToken: string; creditCardNumber: string; creditCardBrand: string }> { ): Promise<{ creditCardToken: string; creditCardNumber: string; creditCardBrand: string }> {
const apiKey = this.getApiKey(); const apiKey = await this.getApiKey();
if (!apiKey) { if (!apiKey) {
return { return {
creditCardToken: `tok_sim_${Math.random().toString(36).substring(2, 9)}`, creditCardToken: `tok_sim_${Math.random().toString(36).substring(2, 9)}`,
@ -247,9 +248,9 @@ export class AsaasService {
} }
try { try {
const response = await fetch(`${this.getApiUrl()}/creditCard/tokenize`, { const response = await fetch(`${await this.getApiUrl()}/creditCard/tokenize`, {
method: 'POST', method: 'POST',
headers: this.getHeaders(), headers: await this.getHeaders(),
body: JSON.stringify({ body: JSON.stringify({
customer: customerId, customer: customerId,
creditCard, creditCard,
@ -280,7 +281,7 @@ export class AsaasService {
creditCardInfo?: any, creditCardInfo?: any,
installmentCount?: number installmentCount?: number
): Promise<any> { ): Promise<any> {
const apiKey = this.getApiKey(); const apiKey = await this.getApiKey();
const futureDueDate = new Date(Date.now() + 3 * 24 * 3600 * 1000).toISOString().split('T')[0]; const futureDueDate = new Date(Date.now() + 3 * 24 * 3600 * 1000).toISOString().split('T')[0];
if (!apiKey) { 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', method: 'POST',
headers: this.getHeaders(), headers: await this.getHeaders(),
body: JSON.stringify(payload) body: JSON.stringify(payload)
}); });