From 114867bcb5ff666728435fee64001bb64224c49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Fri, 29 May 2026 15:23:01 -0300 Subject: [PATCH] Extract backend provisioning service --- backend/index.js | 290 +------------------------------ backend/services/provisioning.js | 273 +++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+), 288 deletions(-) create mode 100644 backend/services/provisioning.js diff --git a/backend/index.js b/backend/index.js index 3e05a74..ff1d370 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,12 +1,12 @@ require('dotenv').config(); const express = require('express'); const path = require('path'); -const crypto = require('crypto'); const multer = require('multer'); const { v4: uuidv4 } = require('uuid'); const fs = require('fs'); const pool = require('./db'); const transporter = require('./services/mailer'); +const { provisionSuperAdmin } = require('./services/provisioning'); const { createCorsMiddleware } = require('./config/cors'); const { allowedOrigins, getBaseUrl, getStartupBaseUrl, isProduction, jwtSecret: JWT_SECRET, port: PORT } = require('./config/runtime'); const { authenticateToken } = require('./middleware/auth'); @@ -110,293 +110,7 @@ if (process.env.NODE_ENV === 'production') { }); } -// Auto-provision Super Admin -const provisionSuperAdmin = async (retries = 10, delay = 10000) => { - const email = 'suporte@blyzer.com.br'; - - for (let i = 0; i < retries; i++) { - try { - // Test connection first - const connection = await pool.getConnection(); - - // Auto-create missing tables to prevent issues with outdated Docker configs/volumes - await connection.query(` - CREATE TABLE IF NOT EXISTS password_resets ( - email varchar(255) NOT NULL, - token varchar(255) NOT NULL, - expires_at timestamp NOT NULL, - created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (token), - KEY email (email) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - `); - - await connection.query(` - CREATE TABLE IF NOT EXISTS pending_registrations ( - email varchar(255) NOT NULL, - password_hash varchar(255) NOT NULL, - full_name varchar(255) NOT NULL, - organization_name varchar(255) NOT NULL, - verification_code varchar(10) NOT NULL, - expires_at timestamp NOT NULL, - created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (email) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - `); - - await connection.query(` - CREATE TABLE IF NOT EXISTS notifications ( - id varchar(36) NOT NULL, - user_id varchar(36) NOT NULL, - type enum('success', 'info', 'warning', 'error') DEFAULT 'info', - title varchar(255) NOT NULL, - message text NOT NULL, - link varchar(255) DEFAULT NULL, - is_read boolean DEFAULT false, - created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY user_id (user_id), - KEY created_at (created_at) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - `); - - // Add slug column if it doesn't exist - try { - await connection.query('ALTER TABLE users ADD COLUMN slug VARCHAR(255) UNIQUE DEFAULT NULL'); - } catch (err) { - // Ignore error if column already exists (ER_DUP_FIELDNAME) - if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (slug):', err.message); - } - - // Populate empty slugs - try { - await connection.query(`UPDATE users SET slug = CONCAT(LOWER(REPLACE(name, ' ', '-')), '-', SUBSTRING(MD5(RAND()), 1, 8)) WHERE slug IS NULL`); - } catch (err) { - console.log('Schema update note (populate slugs):', err.message); - } - - // Update origin to VARCHAR for custom origins - try { - await connection.query("ALTER TABLE attendances MODIFY COLUMN origin VARCHAR(255) NOT NULL"); - } catch (err) { - console.log('Schema update note (origin):', err.message); - } - - // Convert funnel_stage to VARCHAR for custom funnels - try { - await connection.query("ALTER TABLE attendances MODIFY COLUMN funnel_stage VARCHAR(255) NOT NULL"); - } catch (err) { - console.log('Schema update note (funnel_stage):', err.message); - } - - // Add full_summary column for detailed AI analysis - try { - await connection.query("ALTER TABLE attendances ADD COLUMN full_summary TEXT DEFAULT NULL"); - } catch (err) { - if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (full_summary):', err.message); - } - - // Create origin_groups table - await connection.query(` - CREATE TABLE IF NOT EXISTS origin_groups ( - id varchar(36) NOT NULL, - tenant_id varchar(36) NOT NULL, - name varchar(255) NOT NULL, - created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY tenant_id (tenant_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - `); - - // Create origin_items table - await connection.query(` - CREATE TABLE IF NOT EXISTS origin_items ( - id varchar(36) NOT NULL, - origin_group_id varchar(36) NOT NULL, - name varchar(255) NOT NULL, - color_class varchar(255) DEFAULT 'bg-zinc-100 text-zinc-800 border-zinc-200', - created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY origin_group_id (origin_group_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - `); - - // Attempt to add color_class if table already existed without it - try { - await connection.query("ALTER TABLE origin_items ADD COLUMN color_class VARCHAR(255) DEFAULT 'bg-zinc-100 text-zinc-800 border-zinc-200'"); - } catch (err) { - if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (origin_items.color_class):', err.message); - } - - // Add origin_group_id to teams - try { - await connection.query("ALTER TABLE teams ADD COLUMN origin_group_id VARCHAR(36) DEFAULT NULL"); - } catch (err) { - if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (teams.origin_group_id):', err.message); - } - - // Rename summary to title - try { - await connection.query("ALTER TABLE attendances RENAME COLUMN summary TO title"); - } catch (err) { - if (err.code !== 'ER_BAD_FIELD_ERROR' && err.code !== 'ER_DUP_FIELDNAME') { - // If RENAME COLUMN fails (older mysql), try CHANGE - try { - await connection.query("ALTER TABLE attendances CHANGE COLUMN summary title TEXT"); - } catch (e) { - console.log('Schema update note (summary to title):', e.message); - } - } - } - - // Create funnels table - await connection.query(` - CREATE TABLE IF NOT EXISTS funnels ( - id varchar(36) NOT NULL, - tenant_id varchar(36) NOT NULL, - name varchar(255) NOT NULL, - created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY tenant_id (tenant_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - `); - - // Create funnel_stages table - await connection.query(` - CREATE TABLE IF NOT EXISTS funnel_stages ( - id varchar(36) NOT NULL, - funnel_id varchar(36) NOT NULL, - name varchar(255) NOT NULL, - color_class varchar(255) DEFAULT 'bg-zinc-100 text-zinc-800 border-zinc-200', - order_index int DEFAULT 0, - created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY funnel_id (funnel_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - `); - - // Create api_keys table for external integrations (n8n) - await connection.query(` - CREATE TABLE IF NOT EXISTS api_keys ( - id varchar(36) NOT NULL, - tenant_id varchar(36) NOT NULL, - name varchar(255) NOT NULL, - secret_key varchar(255) NOT NULL, - secret_hash varchar(64) DEFAULT NULL, - created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, - last_used_at timestamp NULL DEFAULT NULL, - PRIMARY KEY (id), - UNIQUE KEY secret_key (secret_key), - UNIQUE KEY secret_hash (secret_hash), - KEY tenant_id (tenant_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - `); - - try { - await connection.query("ALTER TABLE api_keys ADD COLUMN secret_hash VARCHAR(64) DEFAULT NULL"); - } catch (err) { - if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (api_keys.secret_hash):', err.message); - } - - try { - await connection.query("ALTER TABLE api_keys ADD UNIQUE KEY secret_hash (secret_hash)"); - } catch (err) { - if (err.code !== 'ER_DUP_KEYNAME') console.log('Schema update note (api_keys.secret_hash index):', err.message); - } - - // Create refresh_tokens table for persistent sessions - await connection.query(` - CREATE TABLE IF NOT EXISTS refresh_tokens ( - id varchar(36) NOT NULL, - user_id varchar(36) NOT NULL, - token varchar(255) NOT NULL, - token_hash varchar(64) DEFAULT NULL, - expires_at timestamp NOT NULL, - created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY token (token), - UNIQUE KEY token_hash (token_hash), - KEY user_id (user_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - `); - - try { - await connection.query("ALTER TABLE refresh_tokens ADD COLUMN token_hash VARCHAR(64) DEFAULT NULL"); - } catch (err) { - if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (refresh_tokens.token_hash):', err.message); - } - - try { - await connection.query("ALTER TABLE refresh_tokens ADD UNIQUE KEY token_hash (token_hash)"); - } catch (err) { - if (err.code !== 'ER_DUP_KEYNAME') console.log('Schema update note (refresh_tokens.token_hash index):', err.message); - } - - // Add funnel_id to teams - try { - await connection.query("ALTER TABLE teams ADD COLUMN funnel_id VARCHAR(36) DEFAULT NULL"); - } catch (err) { - if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (teams.funnel_id):', err.message); - } - - connection.release(); - // Ensure system tenant exists - await pool.query('INSERT IGNORE INTO tenants (id, name, slug, admin_email, status) VALUES (?, ?, ?, ?, ?)', ['system', 'System Admin', 'system', email, 'active']); - - const [existing] = await pool.query('SELECT id, password_hash FROM users WHERE email = ?', [email]); - if (existing.length === 0 || existing[0].password_hash === 'pending_setup') { - console.log('Provisioning default super_admin or resending email...'); - - if (existing.length === 0) { - const uid = `u_${crypto.randomUUID().split('-')[0]}`; - const placeholderHash = 'pending_setup'; - const superAdminSlug = 'suporte-blyzer'; - - await pool.query( - 'INSERT INTO users (id, tenant_id, name, email, password_hash, slug, role, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', - [uid, 'system', 'Blyzer Suporte', email, placeholderHash, superAdminSlug, 'super_admin', 'active'] - ); - } - - const token = crypto.randomBytes(32).toString('hex'); - // Delete any old unused tokens for this email to prevent buildup - await pool.query('DELETE FROM password_resets WHERE email = ?', [email]); - - await pool.query( - 'INSERT INTO password_resets (email, token, expires_at) VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 15 MINUTE))', - [email, token] - ); - - const setupLink = `${getStartupBaseUrl()}/#/setup-account?token=${token}`; - console.log(`\n\n=== SUPER ADMIN SETUP LINK ===\n${setupLink}\n==============================\n\n`); - - await transporter.sendMail({ - from: `"Fasto" <${process.env.MAIL_FROM || 'nao-responda@blyzer.com.br'}>`, - to: email, - subject: 'Conta Super Admin Criada - Fasto', - html: ` -
-

Conta Super Admin Gerada

-

Sua conta de suporte (super_admin) foi criada no Fasto.

-
- Finalizar Cadastro -
-

Este link expira em 15 minutos.

-
- ` - }).catch(err => console.error("Failed to send super_admin email:", err)); - } - return; // Success, exit the retry loop - } catch (error) { - console.error(`Failed to provision super_admin (Attempt ${i + 1}/${retries}):`, error.message); - if (i < retries - 1) { - await new Promise(res => setTimeout(res, delay)); - } - } - } -}; - app.listen(PORT, async () => { - await provisionSuperAdmin(); + await provisionSuperAdmin({ pool, transporter, getStartupBaseUrl }); console.log(`🚀 Servidor Backend rodando em http://localhost:${PORT}`); }); diff --git a/backend/services/provisioning.js b/backend/services/provisioning.js new file mode 100644 index 0000000..c77ddd7 --- /dev/null +++ b/backend/services/provisioning.js @@ -0,0 +1,273 @@ +const crypto = require('crypto'); + +const provisionSuperAdmin = async ({ pool, transporter, getStartupBaseUrl, retries = 10, delay = 10000 }) => { + const email = 'suporte@blyzer.com.br'; + + for (let i = 0; i < retries; i++) { + let connection; + try { + connection = await pool.getConnection(); + + await connection.query(` + CREATE TABLE IF NOT EXISTS password_resets ( + email varchar(255) NOT NULL, + token varchar(255) NOT NULL, + expires_at timestamp NOT NULL, + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (token), + KEY email (email) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `); + + await connection.query(` + CREATE TABLE IF NOT EXISTS pending_registrations ( + email varchar(255) NOT NULL, + password_hash varchar(255) NOT NULL, + full_name varchar(255) NOT NULL, + organization_name varchar(255) NOT NULL, + verification_code varchar(10) NOT NULL, + expires_at timestamp NOT NULL, + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (email) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `); + + await connection.query(` + CREATE TABLE IF NOT EXISTS notifications ( + id varchar(36) NOT NULL, + user_id varchar(36) NOT NULL, + type enum('success', 'info', 'warning', 'error') DEFAULT 'info', + title varchar(255) NOT NULL, + message text NOT NULL, + link varchar(255) DEFAULT NULL, + is_read boolean DEFAULT false, + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY user_id (user_id), + KEY created_at (created_at) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `); + + try { + await connection.query('ALTER TABLE users ADD COLUMN slug VARCHAR(255) UNIQUE DEFAULT NULL'); + } catch (err) { + if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (slug):', err.message); + } + + try { + await connection.query(`UPDATE users SET slug = CONCAT(LOWER(REPLACE(name, ' ', '-')), '-', SUBSTRING(MD5(RAND()), 1, 8)) WHERE slug IS NULL`); + } catch (err) { + console.log('Schema update note (populate slugs):', err.message); + } + + try { + await connection.query("ALTER TABLE attendances MODIFY COLUMN origin VARCHAR(255) NOT NULL"); + } catch (err) { + console.log('Schema update note (origin):', err.message); + } + + try { + await connection.query("ALTER TABLE attendances MODIFY COLUMN funnel_stage VARCHAR(255) NOT NULL"); + } catch (err) { + console.log('Schema update note (funnel_stage):', err.message); + } + + try { + await connection.query("ALTER TABLE attendances ADD COLUMN full_summary TEXT DEFAULT NULL"); + } catch (err) { + if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (full_summary):', err.message); + } + + await connection.query(` + CREATE TABLE IF NOT EXISTS origin_groups ( + id varchar(36) NOT NULL, + tenant_id varchar(36) NOT NULL, + name varchar(255) NOT NULL, + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY tenant_id (tenant_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `); + + await connection.query(` + CREATE TABLE IF NOT EXISTS origin_items ( + id varchar(36) NOT NULL, + origin_group_id varchar(36) NOT NULL, + name varchar(255) NOT NULL, + color_class varchar(255) DEFAULT 'bg-zinc-100 text-zinc-800 border-zinc-200', + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY origin_group_id (origin_group_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `); + + try { + await connection.query("ALTER TABLE origin_items ADD COLUMN color_class VARCHAR(255) DEFAULT 'bg-zinc-100 text-zinc-800 border-zinc-200'"); + } catch (err) { + if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (origin_items.color_class):', err.message); + } + + try { + await connection.query("ALTER TABLE teams ADD COLUMN origin_group_id VARCHAR(36) DEFAULT NULL"); + } catch (err) { + if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (teams.origin_group_id):', err.message); + } + + try { + await connection.query("ALTER TABLE attendances RENAME COLUMN summary TO title"); + } catch (err) { + if (err.code !== 'ER_BAD_FIELD_ERROR' && err.code !== 'ER_DUP_FIELDNAME') { + try { + await connection.query("ALTER TABLE attendances CHANGE COLUMN summary title TEXT"); + } catch (e) { + console.log('Schema update note (summary to title):', e.message); + } + } + } + + await connection.query(` + CREATE TABLE IF NOT EXISTS funnels ( + id varchar(36) NOT NULL, + tenant_id varchar(36) NOT NULL, + name varchar(255) NOT NULL, + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY tenant_id (tenant_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `); + + await connection.query(` + CREATE TABLE IF NOT EXISTS funnel_stages ( + id varchar(36) NOT NULL, + funnel_id varchar(36) NOT NULL, + name varchar(255) NOT NULL, + color_class varchar(255) DEFAULT 'bg-zinc-100 text-zinc-800 border-zinc-200', + order_index int DEFAULT 0, + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY funnel_id (funnel_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `); + + await connection.query(` + CREATE TABLE IF NOT EXISTS api_keys ( + id varchar(36) NOT NULL, + tenant_id varchar(36) NOT NULL, + name varchar(255) NOT NULL, + secret_key varchar(255) NOT NULL, + secret_hash varchar(64) DEFAULT NULL, + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + last_used_at timestamp NULL DEFAULT NULL, + PRIMARY KEY (id), + UNIQUE KEY secret_key (secret_key), + UNIQUE KEY secret_hash (secret_hash), + KEY tenant_id (tenant_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `); + + try { + await connection.query("ALTER TABLE api_keys ADD COLUMN secret_hash VARCHAR(64) DEFAULT NULL"); + } catch (err) { + if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (api_keys.secret_hash):', err.message); + } + + try { + await connection.query("ALTER TABLE api_keys ADD UNIQUE KEY secret_hash (secret_hash)"); + } catch (err) { + if (err.code !== 'ER_DUP_KEYNAME') console.log('Schema update note (api_keys.secret_hash index):', err.message); + } + + await connection.query(` + CREATE TABLE IF NOT EXISTS refresh_tokens ( + id varchar(36) NOT NULL, + user_id varchar(36) NOT NULL, + token varchar(255) NOT NULL, + token_hash varchar(64) DEFAULT NULL, + expires_at timestamp NOT NULL, + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY token (token), + UNIQUE KEY token_hash (token_hash), + KEY user_id (user_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `); + + try { + await connection.query("ALTER TABLE refresh_tokens ADD COLUMN token_hash VARCHAR(64) DEFAULT NULL"); + } catch (err) { + if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (refresh_tokens.token_hash):', err.message); + } + + try { + await connection.query("ALTER TABLE refresh_tokens ADD UNIQUE KEY token_hash (token_hash)"); + } catch (err) { + if (err.code !== 'ER_DUP_KEYNAME') console.log('Schema update note (refresh_tokens.token_hash index):', err.message); + } + + try { + await connection.query("ALTER TABLE teams ADD COLUMN funnel_id VARCHAR(36) DEFAULT NULL"); + } catch (err) { + if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (teams.funnel_id):', err.message); + } + + connection.release(); + connection = null; + + await pool.query('INSERT IGNORE INTO tenants (id, name, slug, admin_email, status) VALUES (?, ?, ?, ?, ?)', ['system', 'System Admin', 'system', email, 'active']); + + const [existing] = await pool.query('SELECT id, password_hash FROM users WHERE email = ?', [email]); + if (existing.length === 0 || existing[0].password_hash === 'pending_setup') { + console.log('Provisioning default super_admin or resending email...'); + + if (existing.length === 0) { + const uid = `u_${crypto.randomUUID().split('-')[0]}`; + const placeholderHash = 'pending_setup'; + const superAdminSlug = 'suporte-blyzer'; + + await pool.query( + 'INSERT INTO users (id, tenant_id, name, email, password_hash, slug, role, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', + [uid, 'system', 'Blyzer Suporte', email, placeholderHash, superAdminSlug, 'super_admin', 'active'] + ); + } + + const token = crypto.randomBytes(32).toString('hex'); + await pool.query('DELETE FROM password_resets WHERE email = ?', [email]); + + await pool.query( + 'INSERT INTO password_resets (email, token, expires_at) VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 15 MINUTE))', + [email, token] + ); + + const setupLink = `${getStartupBaseUrl()}/#/setup-account?token=${token}`; + console.log(`\n\n=== SUPER ADMIN SETUP LINK ===\n${setupLink}\n==============================\n\n`); + + await transporter.sendMail({ + from: `"Fasto" <${process.env.MAIL_FROM || 'nao-responda@blyzer.com.br'}>`, + to: email, + subject: 'Conta Super Admin Criada - Fasto', + html: ` +
+

Conta Super Admin Gerada

+

Sua conta de suporte (super_admin) foi criada no Fasto.

+
+ Finalizar Cadastro +
+

Este link expira em 15 minutos.

+
+ ` + }).catch(err => console.error("Failed to send super_admin email:", err)); + } + return; + } catch (error) { + if (connection) connection.release(); + console.error(`Failed to provision super_admin (Attempt ${i + 1}/${retries}):`, error.message); + if (i < retries - 1) { + await new Promise(res => setTimeout(res, delay)); + } + } + } +}; + +module.exports = { + provisionSuperAdmin, +};