🚀 Initial commit - PDIMaker v1.0.0
Sistema completo de gestão de PDI com: - Autenticação com email/senha e Google OAuth - Workspaces privados isolados - Sistema de convites com código único - Interface profissional com Next.js 14 - Backend NestJS com PostgreSQL - Docker com Nginx e SSL Desenvolvido por Sergio Correa
This commit is contained in:
8
frontend/app/api/auth/[...nextauth]/route.ts
Normal file
8
frontend/app/api/auth/[...nextauth]/route.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// app/api/auth/[...nextauth]/route.ts
|
||||
import NextAuth from "next-auth"
|
||||
import { authOptions } from "@/lib/auth/config"
|
||||
|
||||
const handler = NextAuth(authOptions)
|
||||
|
||||
export { handler as GET, handler as POST }
|
||||
|
||||
15
frontend/app/api/auth/logout/route.ts
Normal file
15
frontend/app/api/auth/logout/route.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// app/api/auth/logout/route.ts
|
||||
import { NextRequest, NextResponse } from "next/server"
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const response = NextResponse.redirect(new URL("/login", request.url))
|
||||
|
||||
// Limpar todos os cookies de autenticação
|
||||
response.cookies.delete("next-auth.session-token")
|
||||
response.cookies.delete("__Secure-next-auth.session-token")
|
||||
response.cookies.delete("next-auth.csrf-token")
|
||||
response.cookies.delete("__Host-next-auth.csrf-token")
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
51
frontend/app/api/auth/register/route.ts
Normal file
51
frontend/app/api/auth/register/route.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
// app/api/auth/register/route.ts
|
||||
import { NextRequest, NextResponse } from "next/server"
|
||||
import { createUser } from "@/lib/auth/credentials"
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { email, password, name } = await request.json()
|
||||
|
||||
// Validações
|
||||
if (!email || !password || !name) {
|
||||
return NextResponse.json(
|
||||
{ error: "Todos os campos são obrigatórios" },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return NextResponse.json(
|
||||
{ error: "A senha deve ter pelo menos 6 caracteres" },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Criar usuário
|
||||
const user = await createUser(email, password, name)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error("Erro ao registrar:", error)
|
||||
|
||||
if (error.message === "Email já cadastrado") {
|
||||
return NextResponse.json(
|
||||
{ error: "Este email já está cadastrado" },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: "Erro ao criar conta" },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
84
frontend/app/api/invites/accept/route.ts
Normal file
84
frontend/app/api/invites/accept/route.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
// app/api/invites/accept/route.ts
|
||||
import { NextRequest, NextResponse } from "next/server"
|
||||
import { getServerSession } from "next-auth"
|
||||
import { authOptions } from "@/lib/auth/config"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions)
|
||||
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autenticado" }, { status: 401 })
|
||||
}
|
||||
|
||||
const { inviteCode } = await request.json()
|
||||
|
||||
if (!inviteCode) {
|
||||
return NextResponse.json({ error: "Código de convite inválido" }, { status: 400 })
|
||||
}
|
||||
|
||||
// Buscar convite
|
||||
const invite = await prisma.invite.findUnique({
|
||||
where: { token: inviteCode }
|
||||
})
|
||||
|
||||
if (!invite) {
|
||||
return NextResponse.json({ error: "Convite não encontrado" }, { status: 404 })
|
||||
}
|
||||
|
||||
// Buscar workspace
|
||||
const workspace = invite.workspaceId
|
||||
? await prisma.workspace.findUnique({ where: { id: invite.workspaceId } })
|
||||
: null
|
||||
|
||||
if (!workspace) {
|
||||
return NextResponse.json({ error: "Workspace não encontrado" }, { status: 404 })
|
||||
}
|
||||
|
||||
// Verificar se está expirado
|
||||
if (invite.expiresAt < new Date()) {
|
||||
return NextResponse.json({ error: "Convite expirado" }, { status: 400 })
|
||||
}
|
||||
|
||||
// Verificar se já foi aceito
|
||||
if (invite.status !== "PENDING") {
|
||||
return NextResponse.json({ error: "Convite já foi utilizado" }, { status: 400 })
|
||||
}
|
||||
|
||||
// Verificar se o email confere
|
||||
if (invite.email !== session.user.email) {
|
||||
return NextResponse.json({ error: "Este convite não é para você" }, { status: 403 })
|
||||
}
|
||||
|
||||
// Atualizar convite
|
||||
await prisma.invite.update({
|
||||
where: { id: invite.id },
|
||||
data: {
|
||||
status: "ACCEPTED",
|
||||
acceptedAt: new Date()
|
||||
}
|
||||
})
|
||||
|
||||
// Ativar workspace
|
||||
await prisma.workspace.update({
|
||||
where: { id: invite.workspaceId! },
|
||||
data: { status: "ACTIVE" }
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
workspace: {
|
||||
id: workspace.id,
|
||||
slug: workspace.slug
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error("Erro ao aceitar convite:", error)
|
||||
return NextResponse.json(
|
||||
{ error: "Erro ao aceitar convite" },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
97
frontend/app/api/workspaces/create/route.ts
Normal file
97
frontend/app/api/workspaces/create/route.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
// app/api/workspaces/create/route.ts
|
||||
import { NextRequest, NextResponse } from "next/server"
|
||||
import { getServerSession } from "next-auth"
|
||||
import { authOptions } from "@/lib/auth/config"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
import { generateInviteCode, generateWorkspaceSlug } from "@/lib/utils/invite-code"
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions)
|
||||
|
||||
if (!session || !session.user) {
|
||||
return NextResponse.json({ error: "Não autenticado" }, { status: 401 })
|
||||
}
|
||||
|
||||
const { email, inviteRole } = await request.json()
|
||||
|
||||
console.log("Criando workspace:", { email, inviteRole, userId: session.user.id })
|
||||
|
||||
// Validar email
|
||||
if (!email || !email.includes("@")) {
|
||||
return NextResponse.json({ error: "Email inválido" }, { status: 400 })
|
||||
}
|
||||
|
||||
// Buscar ou criar o usuário convidado
|
||||
let invitedUser = await prisma.user.findUnique({ where: { email } })
|
||||
|
||||
if (!invitedUser) {
|
||||
// Criar registro temporário do usuário convidado
|
||||
invitedUser = await prisma.user.create({
|
||||
data: {
|
||||
email,
|
||||
name: email.split("@")[0],
|
||||
role: inviteRole === "MANAGER" ? "MANAGER" : "EMPLOYEE"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Determinar quem é employee e quem é manager
|
||||
const employeeId = inviteRole === "MANAGER" ? session.user.id : invitedUser.id
|
||||
const managerId = inviteRole === "MANAGER" ? invitedUser.id : session.user.id
|
||||
|
||||
// Buscar nomes para o slug
|
||||
const employee = await prisma.user.findUnique({ where: { id: employeeId } })
|
||||
const manager = await prisma.user.findUnique({ where: { id: managerId } })
|
||||
|
||||
if (!employee || !manager) {
|
||||
return NextResponse.json({ error: "Erro ao buscar usuários" }, { status: 400 })
|
||||
}
|
||||
|
||||
// Gerar slug único
|
||||
const slug = generateWorkspaceSlug(employee.name, manager.name)
|
||||
|
||||
// Criar workspace
|
||||
const workspace = await prisma.workspace.create({
|
||||
data: {
|
||||
slug,
|
||||
employeeId,
|
||||
managerId,
|
||||
status: "PENDING_INVITE"
|
||||
}
|
||||
})
|
||||
|
||||
// Criar convite
|
||||
const inviteCode = generateInviteCode()
|
||||
const expiresAt = new Date()
|
||||
expiresAt.setDate(expiresAt.getDate() + 7) // Expira em 7 dias
|
||||
|
||||
await prisma.invite.create({
|
||||
data: {
|
||||
email: invitedUser.email,
|
||||
role: inviteRole === "MANAGER" ? "MANAGER" : "EMPLOYEE",
|
||||
token: inviteCode,
|
||||
invitedBy: session.user.id,
|
||||
workspaceId: workspace.id,
|
||||
expiresAt
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: Enviar email com o código de convite
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
workspace: { id: workspace.id, slug: workspace.slug },
|
||||
inviteCode
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error("Erro ao criar workspace:", error)
|
||||
console.error("Stack:", error.stack)
|
||||
console.error("Message:", error.message)
|
||||
return NextResponse.json(
|
||||
{ error: error.message || "Erro ao criar workspace" },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user