Backend: - AccountDeletionController com 4 endpoints principais - requestDeletionCode: Envia código de 6 dígitos por email (válido 10min) - exportBackup: Exporta todos os dados do usuário em JSON - executeHardDelete: Deleta permanentemente conta e dados com validação de código - importBackup: Importa backup completo com mapeamento de IDs Frontend: - FactoryResetWizard: Wizard de 4 etapas (Warning → Backup → Code → Confirmation) - ImportBackupModal: Drag & drop para importar backup JSON - Integração na página Profile com seção de Gerenciamento de Dados - accountDeletionService: Serviços API completos Email: - Template HTML para código de confirmação - Avisos visuais sobre irreversibilidade da ação i18n: - Traduções completas em pt-BR, es, en - 50+ strings de tradução adicionadas - Avisos e mensagens de erro traduzidos Funcionalidades: ✅ Hard delete com confirmação dupla (código + texto DELETAR) ✅ Backup completo em JSON (transações, contas, categorias, etc) ✅ Importação de backup com mapeamento inteligente de IDs ✅ Email com código de segurança ✅ Wizard responsivo com 4 etapas ✅ Validação de arquivos e tamanho (max 50MB) ✅ Drag & drop para upload ✅ Estatísticas de importação ✅ Logout automático após delete
527 lines
22 KiB
PHP
527 lines
22 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Mail\AccountDeletionConfirmation;
|
|
use App\Models\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
|
|
class AccountDeletionController extends Controller
|
|
{
|
|
/**
|
|
* Solicitar código de confirmação por email
|
|
*/
|
|
public function requestDeletionCode(Request $request)
|
|
{
|
|
try {
|
|
$user = Auth::user();
|
|
|
|
// Gerar código de 6 dígitos
|
|
$code = str_pad(random_int(100000, 999999), 6, '0', STR_PAD_LEFT);
|
|
|
|
// Armazenar no cache por 10 minutos
|
|
$cacheKey = "account_deletion_code_{$user->id}";
|
|
Cache::put($cacheKey, $code, now()->addMinutes(10));
|
|
|
|
// Enviar email
|
|
Mail::to($user->email)->send(new AccountDeletionConfirmation($code, $user->name));
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Código de confirmação enviado para seu email',
|
|
'expires_at' => now()->addMinutes(10)->toISOString()
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Erro ao enviar código de confirmação',
|
|
'error' => $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exportar backup completo dos dados do usuário
|
|
*/
|
|
public function exportBackup(Request $request)
|
|
{
|
|
try {
|
|
$user = Auth::user();
|
|
|
|
// Coletar todos os dados do usuário
|
|
$backup = [
|
|
'version' => '1.0',
|
|
'exported_at' => now()->toISOString(),
|
|
'user' => [
|
|
'name' => $user->name,
|
|
'email' => $user->email,
|
|
],
|
|
'accounts' => $user->accounts()->with('currency')->get(),
|
|
'asset_accounts' => $user->assetAccounts()->with('assetType')->get(),
|
|
'categories' => $user->categories()->get(),
|
|
'cost_centers' => $user->costCenters()->with('keywords')->where('is_system', false)->get(),
|
|
'credit_cards' => $user->creditCards()->with('account')->get(),
|
|
'liability_accounts' => $user->liabilityAccounts()->get(),
|
|
'budgets' => $user->budgets()->with(['category', 'subcategory'])->get(),
|
|
'goals' => $user->goals()->get(),
|
|
'investments' => $user->investments()->with(['assetAccount', 'investmentType', 'priceHistories'])->get(),
|
|
'transactions' => $user->transactions()
|
|
->with([
|
|
'account',
|
|
'category',
|
|
'subcategory',
|
|
'costCenter',
|
|
'creditCard',
|
|
'liabilityAccount'
|
|
])
|
|
->get(),
|
|
];
|
|
|
|
// Gerar nome do arquivo
|
|
$fileName = 'webmoney_backup_' . $user->id . '_' . now()->format('Y-m-d_His') . '.json';
|
|
|
|
// Salvar temporariamente
|
|
$filePath = 'backups/' . $fileName;
|
|
Storage::disk('local')->put($filePath, json_encode($backup, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
|
|
|
// Retornar URL para download
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Backup criado com sucesso',
|
|
'download_url' => route('download.backup', ['file' => $fileName]),
|
|
'file_size' => Storage::disk('local')->size($filePath),
|
|
'expires_in' => '24 horas'
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Erro ao criar backup',
|
|
'error' => $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Download do arquivo de backup
|
|
*/
|
|
public function downloadBackup($file)
|
|
{
|
|
try {
|
|
$filePath = 'backups/' . $file;
|
|
|
|
if (!Storage::disk('local')->exists($filePath)) {
|
|
abort(404, 'Arquivo não encontrado');
|
|
}
|
|
|
|
return Storage::disk('local')->download($filePath);
|
|
} catch (\Exception $e) {
|
|
abort(500, 'Erro ao baixar backup');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validar código e executar hard delete
|
|
*/
|
|
public function executeHardDelete(Request $request)
|
|
{
|
|
$request->validate([
|
|
'code' => 'required|string|size:6',
|
|
'confirmation_text' => 'required|string|in:DELETAR'
|
|
]);
|
|
|
|
try {
|
|
$user = Auth::user();
|
|
$cacheKey = "account_deletion_code_{$user->id}";
|
|
|
|
// Verificar código
|
|
$storedCode = Cache::get($cacheKey);
|
|
|
|
if (!$storedCode) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Código expirado ou inválido'
|
|
], 400);
|
|
}
|
|
|
|
if ($storedCode !== $request->code) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Código incorreto'
|
|
], 400);
|
|
}
|
|
|
|
// Limpar cache
|
|
Cache::forget($cacheKey);
|
|
|
|
// Executar hard delete em transação
|
|
DB::beginTransaction();
|
|
|
|
try {
|
|
// Deletar em ordem (respeitando foreign keys)
|
|
|
|
// 1. Transações
|
|
$user->transactions()->delete();
|
|
|
|
// 2. Orçamentos
|
|
$user->budgets()->delete();
|
|
|
|
// 3. Histórico de preços de investimentos
|
|
DB::table('investment_price_histories')
|
|
->whereIn('investment_id', $user->investments()->pluck('id'))
|
|
->delete();
|
|
|
|
// 4. Investimentos
|
|
$user->investments()->delete();
|
|
|
|
// 5. Cartões de crédito
|
|
$user->creditCards()->delete();
|
|
|
|
// 6. Contas de passivos
|
|
$user->liabilityAccounts()->delete();
|
|
|
|
// 7. Contas de ativos
|
|
$user->assetAccounts()->delete();
|
|
|
|
// 8. Contas bancárias
|
|
$user->accounts()->delete();
|
|
|
|
// 9. Palavras-chave de centros de custo
|
|
$costCenterIds = $user->costCenters()->pluck('id');
|
|
DB::table('cost_center_keywords')->whereIn('cost_center_id', $costCenterIds)->delete();
|
|
|
|
// 10. Centros de custo (não-sistema)
|
|
$user->costCenters()->where('is_system', false)->delete();
|
|
|
|
// 11. Categorias customizadas
|
|
$user->categories()->where('is_custom', true)->delete();
|
|
|
|
// 12. Objetivos
|
|
$user->goals()->delete();
|
|
|
|
// 13. Tokens de autenticação
|
|
$user->tokens()->delete();
|
|
|
|
// 14. Usuário
|
|
$userName = $user->name;
|
|
$userEmail = $user->email;
|
|
$user->delete();
|
|
|
|
DB::commit();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Conta deletada permanentemente',
|
|
'deleted_user' => [
|
|
'name' => $userName,
|
|
'email' => $userEmail
|
|
]
|
|
]);
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
throw $e;
|
|
}
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Erro ao executar exclusão',
|
|
'error' => $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Importar backup para conta atual
|
|
*/
|
|
public function importBackup(Request $request)
|
|
{
|
|
$request->validate([
|
|
'backup_file' => 'required|file|mimes:json|max:51200' // Max 50MB
|
|
]);
|
|
|
|
try {
|
|
$user = Auth::user();
|
|
|
|
// Ler arquivo
|
|
$content = file_get_contents($request->file('backup_file')->getRealPath());
|
|
$backup = json_decode($content, true);
|
|
|
|
if (!$backup || !isset($backup['version'])) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Arquivo de backup inválido'
|
|
], 400);
|
|
}
|
|
|
|
DB::beginTransaction();
|
|
|
|
try {
|
|
$stats = [
|
|
'accounts' => 0,
|
|
'asset_accounts' => 0,
|
|
'categories' => 0,
|
|
'cost_centers' => 0,
|
|
'credit_cards' => 0,
|
|
'liability_accounts' => 0,
|
|
'budgets' => 0,
|
|
'goals' => 0,
|
|
'investments' => 0,
|
|
'transactions' => 0,
|
|
];
|
|
|
|
// Mapear IDs antigos -> novos IDs
|
|
$accountMap = [];
|
|
$categoryMap = [];
|
|
$subcategoryMap = [];
|
|
$costCenterMap = [];
|
|
$creditCardMap = [];
|
|
$liabilityAccountMap = [];
|
|
$assetAccountMap = [];
|
|
|
|
// 1. Importar contas bancárias
|
|
if (isset($backup['accounts'])) {
|
|
foreach ($backup['accounts'] as $account) {
|
|
$oldId = $account['id'];
|
|
unset($account['id'], $account['user_id'], $account['created_at'], $account['updated_at']);
|
|
|
|
$newAccount = $user->accounts()->create($account);
|
|
$accountMap[$oldId] = $newAccount->id;
|
|
$stats['accounts']++;
|
|
}
|
|
}
|
|
|
|
// 2. Importar contas de ativos
|
|
if (isset($backup['asset_accounts'])) {
|
|
foreach ($backup['asset_accounts'] as $assetAccount) {
|
|
$oldId = $assetAccount['id'];
|
|
unset($assetAccount['id'], $assetAccount['user_id'], $assetAccount['created_at'], $assetAccount['updated_at'], $assetAccount['asset_type']);
|
|
|
|
$newAssetAccount = $user->assetAccounts()->create($assetAccount);
|
|
$assetAccountMap[$oldId] = $newAssetAccount->id;
|
|
$stats['asset_accounts']++;
|
|
}
|
|
}
|
|
|
|
// 3. Importar categorias customizadas
|
|
if (isset($backup['categories'])) {
|
|
foreach ($backup['categories'] as $category) {
|
|
if (!$category['is_custom']) continue;
|
|
|
|
$oldId = $category['id'];
|
|
$oldParentId = $category['parent_id'] ?? null;
|
|
|
|
unset($category['id'], $category['user_id'], $category['created_at'], $category['updated_at']);
|
|
|
|
// Se tem parent_id, precisa esperar para mapear depois
|
|
if ($oldParentId) {
|
|
$category['parent_id'] = null; // Temporário
|
|
}
|
|
|
|
$newCategory = $user->categories()->create($category);
|
|
|
|
if ($oldParentId) {
|
|
$subcategoryMap[$oldId] = ['new_id' => $newCategory->id, 'old_parent_id' => $oldParentId];
|
|
} else {
|
|
$categoryMap[$oldId] = $newCategory->id;
|
|
}
|
|
|
|
$stats['categories']++;
|
|
}
|
|
}
|
|
|
|
// Atualizar parent_id das subcategorias
|
|
foreach ($subcategoryMap as $oldId => $data) {
|
|
if (isset($categoryMap[$data['old_parent_id']])) {
|
|
DB::table('categories')
|
|
->where('id', $data['new_id'])
|
|
->update(['parent_id' => $categoryMap[$data['old_parent_id']]]);
|
|
$categoryMap[$oldId] = $data['new_id']; // Adicionar ao mapa principal
|
|
}
|
|
}
|
|
|
|
// 4. Importar centros de custo
|
|
if (isset($backup['cost_centers'])) {
|
|
foreach ($backup['cost_centers'] as $costCenter) {
|
|
$oldId = $costCenter['id'];
|
|
$keywords = $costCenter['keywords'] ?? [];
|
|
|
|
unset($costCenter['id'], $costCenter['user_id'], $costCenter['created_at'], $costCenter['updated_at'], $costCenter['keywords']);
|
|
|
|
$newCostCenter = $user->costCenters()->create($costCenter);
|
|
$costCenterMap[$oldId] = $newCostCenter->id;
|
|
|
|
// Importar keywords
|
|
foreach ($keywords as $keyword) {
|
|
unset($keyword['id'], $keyword['cost_center_id'], $keyword['created_at'], $keyword['updated_at']);
|
|
$newCostCenter->keywords()->create($keyword);
|
|
}
|
|
|
|
$stats['cost_centers']++;
|
|
}
|
|
}
|
|
|
|
// 5. Importar cartões de crédito
|
|
if (isset($backup['credit_cards'])) {
|
|
foreach ($backup['credit_cards'] as $creditCard) {
|
|
$oldId = $creditCard['id'];
|
|
$oldAccountId = $creditCard['account_id'];
|
|
|
|
unset($creditCard['id'], $creditCard['user_id'], $creditCard['created_at'], $creditCard['updated_at'], $creditCard['account']);
|
|
|
|
// Mapear account_id
|
|
if (isset($accountMap[$oldAccountId])) {
|
|
$creditCard['account_id'] = $accountMap[$oldAccountId];
|
|
} else {
|
|
continue; // Pular se conta não foi importada
|
|
}
|
|
|
|
$newCreditCard = $user->creditCards()->create($creditCard);
|
|
$creditCardMap[$oldId] = $newCreditCard->id;
|
|
$stats['credit_cards']++;
|
|
}
|
|
}
|
|
|
|
// 6. Importar contas de passivos
|
|
if (isset($backup['liability_accounts'])) {
|
|
foreach ($backup['liability_accounts'] as $liabilityAccount) {
|
|
$oldId = $liabilityAccount['id'];
|
|
unset($liabilityAccount['id'], $liabilityAccount['user_id'], $liabilityAccount['created_at'], $liabilityAccount['updated_at']);
|
|
|
|
$newLiabilityAccount = $user->liabilityAccounts()->create($liabilityAccount);
|
|
$liabilityAccountMap[$oldId] = $newLiabilityAccount->id;
|
|
$stats['liability_accounts']++;
|
|
}
|
|
}
|
|
|
|
// 7. Importar orçamentos
|
|
if (isset($backup['budgets'])) {
|
|
foreach ($backup['budgets'] as $budget) {
|
|
$oldCategoryId = $budget['category_id'];
|
|
$oldSubcategoryId = $budget['subcategory_id'] ?? null;
|
|
|
|
unset($budget['id'], $budget['user_id'], $budget['created_at'], $budget['updated_at'], $budget['category'], $budget['subcategory']);
|
|
|
|
// Mapear IDs
|
|
if (isset($categoryMap[$oldCategoryId])) {
|
|
$budget['category_id'] = $categoryMap[$oldCategoryId];
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
if ($oldSubcategoryId && isset($categoryMap[$oldSubcategoryId])) {
|
|
$budget['subcategory_id'] = $categoryMap[$oldSubcategoryId];
|
|
}
|
|
|
|
$user->budgets()->create($budget);
|
|
$stats['budgets']++;
|
|
}
|
|
}
|
|
|
|
// 8. Importar objetivos
|
|
if (isset($backup['goals'])) {
|
|
foreach ($backup['goals'] as $goal) {
|
|
unset($goal['id'], $goal['user_id'], $goal['created_at'], $goal['updated_at']);
|
|
$user->goals()->create($goal);
|
|
$stats['goals']++;
|
|
}
|
|
}
|
|
|
|
// 9. Importar investimentos
|
|
if (isset($backup['investments'])) {
|
|
foreach ($backup['investments'] as $investment) {
|
|
$oldId = $investment['id'];
|
|
$oldAssetAccountId = $investment['asset_account_id'];
|
|
$priceHistories = $investment['price_histories'] ?? [];
|
|
|
|
unset($investment['id'], $investment['user_id'], $investment['created_at'], $investment['updated_at'], $investment['asset_account'], $investment['investment_type'], $investment['price_histories']);
|
|
|
|
// Mapear asset_account_id
|
|
if (isset($assetAccountMap[$oldAssetAccountId])) {
|
|
$investment['asset_account_id'] = $assetAccountMap[$oldAssetAccountId];
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
$newInvestment = $user->investments()->create($investment);
|
|
|
|
// Importar histórico de preços
|
|
foreach ($priceHistories as $history) {
|
|
unset($history['id'], $history['investment_id'], $history['created_at'], $history['updated_at']);
|
|
$newInvestment->priceHistories()->create($history);
|
|
}
|
|
|
|
$stats['investments']++;
|
|
}
|
|
}
|
|
|
|
// 10. Importar transações
|
|
if (isset($backup['transactions'])) {
|
|
foreach ($backup['transactions'] as $transaction) {
|
|
$oldAccountId = $transaction['account_id'] ?? null;
|
|
$oldCategoryId = $transaction['category_id'] ?? null;
|
|
$oldSubcategoryId = $transaction['subcategory_id'] ?? null;
|
|
$oldCostCenterId = $transaction['cost_center_id'] ?? null;
|
|
$oldCreditCardId = $transaction['credit_card_id'] ?? null;
|
|
$oldLiabilityAccountId = $transaction['liability_account_id'] ?? null;
|
|
|
|
unset($transaction['id'], $transaction['user_id'], $transaction['created_at'], $transaction['updated_at'], $transaction['account'], $transaction['category'], $transaction['subcategory'], $transaction['cost_center'], $transaction['credit_card'], $transaction['liability_account']);
|
|
|
|
// Mapear IDs
|
|
if ($oldAccountId && isset($accountMap[$oldAccountId])) {
|
|
$transaction['account_id'] = $accountMap[$oldAccountId];
|
|
} elseif ($oldAccountId) {
|
|
continue;
|
|
}
|
|
|
|
if ($oldCategoryId && isset($categoryMap[$oldCategoryId])) {
|
|
$transaction['category_id'] = $categoryMap[$oldCategoryId];
|
|
}
|
|
|
|
if ($oldSubcategoryId && isset($categoryMap[$oldSubcategoryId])) {
|
|
$transaction['subcategory_id'] = $categoryMap[$oldSubcategoryId];
|
|
}
|
|
|
|
if ($oldCostCenterId && isset($costCenterMap[$oldCostCenterId])) {
|
|
$transaction['cost_center_id'] = $costCenterMap[$oldCostCenterId];
|
|
}
|
|
|
|
if ($oldCreditCardId && isset($creditCardMap[$oldCreditCardId])) {
|
|
$transaction['credit_card_id'] = $creditCardMap[$oldCreditCardId];
|
|
}
|
|
|
|
if ($oldLiabilityAccountId && isset($liabilityAccountMap[$oldLiabilityAccountId])) {
|
|
$transaction['liability_account_id'] = $liabilityAccountMap[$oldLiabilityAccountId];
|
|
}
|
|
|
|
$user->transactions()->create($transaction);
|
|
$stats['transactions']++;
|
|
}
|
|
}
|
|
|
|
DB::commit();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Backup importado com sucesso',
|
|
'stats' => $stats
|
|
]);
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
throw $e;
|
|
}
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Erro ao importar backup',
|
|
'error' => $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
}
|