diff --git a/frontend/src/app/admin/layout.tsx b/frontend/src/app/admin/layout.tsx index 810d714..1588f44 100644 --- a/frontend/src/app/admin/layout.tsx +++ b/frontend/src/app/admin/layout.tsx @@ -87,8 +87,8 @@ export default function AdminLayout({ useEffect(() => { setMounted(true); const fetchUser = async () => { - // Pular verificação para a rota de emergência (suporta com ou sem prefixo de idioma) - if (pathname?.endsWith('/admin/backup/emergency')) { + // Pular verificação para rotas de emergência + if (pathname?.endsWith('/admin/backup/emergency') || pathname?.endsWith('/admin/rescue')) { setIsLoading(false); return; } @@ -177,8 +177,8 @@ export default function AdminLayout({ ); } - // Se for a rota de emergência, renderiza apenas o conteúdo - if (pathname?.endsWith('/admin/backup/emergency')) { + // Se for uma rota de emergência, renderiza apenas o conteúdo + if (pathname?.endsWith('/admin/backup/emergency') || pathname?.endsWith('/admin/rescue')) { return (
{children} diff --git a/frontend/src/app/admin/rescue/page.tsx b/frontend/src/app/admin/rescue/page.tsx new file mode 100644 index 0000000..60fb500 --- /dev/null +++ b/frontend/src/app/admin/rescue/page.tsx @@ -0,0 +1,103 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; + +export default function RescuePage() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [name, setName] = useState(''); + const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle'); + const [message, setMessage] = useState(''); + const router = useRouter(); + + const handleCreate = async (e: React.FormEvent) => { + e.preventDefault(); + setStatus('loading'); + + try { + const res = await fetch('/api/admin/rescue', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password, name }), + }); + + if (res.ok) { + setStatus('success'); + setMessage('Usuário administrador criado com sucesso! Redirecionando...'); + setTimeout(() => router.push('/acesso'), 2000); + } else { + const error = await res.json(); + setStatus('error'); + setMessage(error.error || 'Erro ao criar usuário.'); + } + } catch (err) { + setStatus('error'); + setMessage('Erro de conexão.'); + } + }; + + return ( +
+
+
+ +

Resgate de Emergência

+
+ +

Use esta página apenas se você foi bloqueado do sistema. Ela criará um novo administrador.

+ +
+
+ + setName(e.target.value)} + placeholder="Ex: Erik" + /> +
+
+ + setEmail(e.target.value)} + placeholder="seu@email.com" + /> +
+
+ + setPassword(e.target.value)} + placeholder="••••••••" + /> +
+ + + + {message && ( +
+ {message} +
+ )} +
+
+
+ ); +} diff --git a/frontend/src/app/api/admin/backup/full/route.ts b/frontend/src/app/api/admin/backup/full/route.ts index 7eaf787..9fedfac 100644 --- a/frontend/src/app/api/admin/backup/full/route.ts +++ b/frontend/src/app/api/admin/backup/full/route.ts @@ -146,15 +146,23 @@ export async function POST(req: NextRequest) { // Importação atômica com timeout estendido para 120 segundos console.log('🔄 Iniciando transação no banco de dados...'); await prisma.$transaction(async (tx) => { - console.log('🧹 Limpando tabelas atuais...'); + console.log('🧹 Limpando tabelas atuais (exceto usuários se o backup estiver incompleto)...'); await tx.message.deleteMany(); await tx.project.deleteMany(); await tx.service.deleteMany(); await tx.pageContent.deleteMany(); - await tx.user.deleteMany(); + + // Só apaga os usuários se viermos com novos usuários no ZIP + const hasUsersInBackup = data.users && data.users.length > 0; + if (hasUsersInBackup) { + console.log('👥 Backup contém usuários. Atualizando tabela de usuários...'); + await tx.user.deleteMany(); + await tx.user.createMany({ data: data.users }); + } else { + console.warn('⚠️ Backup NÃO contém usuários. Preservando usuários atuais para evitar bloqueio.'); + } console.log('📝 Inserindo novos dados...'); - if (data.users?.length > 0) await tx.user.createMany({ data: data.users }); if (data.projects?.length > 0) await tx.project.createMany({ data: data.projects }); if (data.services?.length > 0) await tx.service.createMany({ data: data.services }); if (data.pageContents?.length > 0) await tx.pageContent.createMany({ data: data.pageContents }); diff --git a/frontend/src/app/api/admin/rescue/route.ts b/frontend/src/app/api/admin/rescue/route.ts new file mode 100644 index 0000000..7d6017a --- /dev/null +++ b/frontend/src/app/api/admin/rescue/route.ts @@ -0,0 +1,35 @@ +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/prisma'; +import bcrypt from 'bcryptjs'; + +export async function POST(req: NextRequest) { + try { + const { email, password, name } = await req.json(); + + if (!email || !password) { + return NextResponse.json({ error: 'Email e senha são obrigatórios' }, { status: 400 }); + } + + // Criptografar senha + const hashedPassword = await bcrypt.hash(password, 10); + + // Criar ou atualizar usuário + const user = await prisma.user.upsert({ + where: { email }, + update: { + password: hashedPassword, + name: name || undefined + }, + create: { + email, + password: hashedPassword, + name: name || 'Administrador' + } + }); + + return NextResponse.json({ message: 'Usuário configurado com sucesso' }); + } catch (error) { + console.error('Erro no resgate de usuário:', error); + return NextResponse.json({ error: 'Erro interno no servidor' }, { status: 500 }); + } +}