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
Build and Deploy (Gitea) / build-and-deploy (push) Failing after 44s
Details
This commit is contained in:
parent
159ae1e8db
commit
fa180dee0c
|
|
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
server.ts
28
server.ts
|
|
@ -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,9 +1826,25 @@ 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' } });
|
||||||
if (adminCount === 0) {
|
if (adminCount === 0) {
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue