Files
Luciano Milani 5de37bb512 Inicial
2026-07-02 12:47:55 +01:00

243 lines
7.0 KiB
Go

package tenant
import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/techxcar/backend/internal/auth"
"github.com/techxcar/backend/internal/config"
"github.com/techxcar/backend/pkg/database"
)
func listTenantsHandler(repo *Repository) fiber.Handler {
return func(c *fiber.Ctx) error {
list, err := repo.ListTenants(c.Context())
if err != nil {
return fiber.NewError(500, "erro ao listar oficinas")
}
if list == nil {
list = []*Tenant{}
}
return c.JSON(fiber.Map{"data": list, "error": nil})
}
}
type createTenantRequest struct {
Name string `json:"name"`
Slug string `json:"slug"`
AdminEmail string `json:"admin_email"`
AdminPassword string `json:"admin_password"`
AdminName string `json:"admin_name"`
}
func createTenantHandler(repo *Repository, db *database.DB, cfg *config.Config) fiber.Handler {
return func(c *fiber.Ctx) error {
var req createTenantRequest
if err := c.BodyParser(&req); err != nil {
return fiber.NewError(400, "corpo do pedido inválido")
}
if req.Name == "" || req.Slug == "" || req.AdminEmail == "" || req.AdminPassword == "" || req.AdminName == "" {
return fiber.NewError(400, "todos os campos são obrigatórios")
}
existing, err := repo.GetTenantBySlug(c.Context(), req.Slug)
if err != nil {
return fiber.NewError(500, "erro interno")
}
if existing != nil {
return fiber.NewError(409, "slug já existe")
}
ten, err := repo.CreateTenant(c.Context(), req.Slug, req.Name)
if err != nil {
return fiber.NewError(500, "erro ao criar oficina")
}
if db != nil {
if err := db.ProvisionTenantSchema(c.Context(), ten.ID, "migrations/tenant"); err != nil {
return fiber.NewError(500, "erro ao provisionar schema")
}
}
hash, err := auth.HashPassword(req.AdminPassword)
if err != nil {
return fiber.NewError(500, "erro interno")
}
if db != nil {
if _, err := repo.CreateTenantUser(c.Context(), ten.ID, req.AdminEmail, hash, req.AdminName, "tenant_admin"); err != nil {
return fiber.NewError(500, "erro ao criar utilizador admin")
}
}
return c.Status(201).JSON(fiber.Map{"data": ten, "error": nil})
}
}
func generateInviteHandler(repo *Repository) fiber.Handler {
return func(c *fiber.Ctx) error {
tenantID := c.Params("id")
ten, err := repo.GetTenantByID(c.Context(), tenantID)
if err != nil || ten == nil {
return fiber.NewError(404, "oficina não encontrada")
}
invite, err := repo.CreateInvite(c.Context(), &ten.ID, 72*time.Hour)
if err != nil {
return fiber.NewError(500, "erro ao gerar convite")
}
return c.Status(201).JSON(fiber.Map{
"data": fiber.Map{"token": invite.Token, "expires_at": invite.ExpiresAt},
"error": nil,
})
}
}
func generatePlatformInviteHandler(repo *Repository) fiber.Handler {
return func(c *fiber.Ctx) error {
invite, err := repo.CreateInvite(c.Context(), nil, 72*time.Hour)
if err != nil {
return fiber.NewError(500, "erro ao gerar convite")
}
return c.Status(201).JSON(fiber.Map{
"data": fiber.Map{"token": invite.Token, "expires_at": invite.ExpiresAt},
"error": nil,
})
}
}
func getInviteHandler(repo *Repository) fiber.Handler {
return func(c *fiber.Ctx) error {
token := c.Params("token")
invite, err := repo.GetInviteByToken(c.Context(), token)
if err != nil {
return fiber.NewError(500, "erro interno")
}
if invite == nil {
return fiber.NewError(404, "convite não encontrado")
}
if invite.UsedAt != nil {
return fiber.NewError(410, "convite já foi utilizado")
}
if invite.ExpiresAt.Before(time.Now()) {
return fiber.NewError(410, "convite expirado")
}
return c.JSON(fiber.Map{"data": invite, "error": nil})
}
}
type redeemRequest struct {
TenantName string `json:"tenant_name"`
TenantSlug string `json:"tenant_slug"`
AdminEmail string `json:"admin_email"`
AdminPassword string `json:"admin_password"`
AdminName string `json:"admin_name"`
}
func tenantAccessHandler(repo *Repository, cfg *config.Config) fiber.Handler {
return func(c *fiber.Ctx) error {
claims, ok := c.Locals("claims").(*auth.Claims)
if !ok {
return fiber.NewError(401, "autenticação necessária")
}
tenantID := c.Params("id")
ten, err := repo.GetTenantByID(c.Context(), tenantID)
if err != nil {
return fiber.NewError(500, "erro interno")
}
if ten == nil {
return fiber.NewError(404, "oficina não encontrada")
}
if ten.Status != "active" {
return fiber.NewError(404, "oficina não encontrada ou inativa")
}
token, err := auth.GenerateAccessToken(claims.UserID, ten.ID, "tenant_admin", cfg.JWTSecret)
if err != nil {
return fiber.NewError(500, "erro ao gerar token")
}
return c.JSON(fiber.Map{
"data": fiber.Map{
"access_token": token,
"tenant": fiber.Map{
"id": ten.ID,
"name": ten.Name,
"slug": ten.Slug,
},
},
"error": nil,
})
}
}
func redeemInviteHandler(repo *Repository, db *database.DB, cfg *config.Config) fiber.Handler {
return func(c *fiber.Ctx) error {
token := c.Params("token")
invite, err := repo.GetInviteByToken(c.Context(), token)
if err != nil || invite == nil {
return fiber.NewError(404, "convite não encontrado")
}
if invite.UsedAt != nil || invite.ExpiresAt.Before(time.Now()) {
return fiber.NewError(410, "convite inválido ou expirado")
}
var req redeemRequest
if err := c.BodyParser(&req); err != nil {
return fiber.NewError(400, "corpo do pedido inválido")
}
if req.TenantName == "" || req.TenantSlug == "" || req.AdminEmail == "" || req.AdminPassword == "" || req.AdminName == "" {
return fiber.NewError(400, "todos os campos são obrigatórios")
}
if len(req.AdminPassword) < 8 {
return fiber.NewError(400, "password deve ter pelo menos 8 caracteres")
}
existing, _ := repo.GetTenantBySlug(c.Context(), req.TenantSlug)
if existing != nil {
return fiber.NewError(409, "slug já existe")
}
ten, err := repo.CreateTenant(c.Context(), req.TenantSlug, req.TenantName)
if err != nil {
return fiber.NewError(500, "erro ao criar oficina")
}
if db != nil {
if err := db.ProvisionTenantSchema(c.Context(), ten.ID, "migrations/tenant"); err != nil {
return fiber.NewError(500, "erro ao provisionar schema")
}
}
hash, err := auth.HashPassword(req.AdminPassword)
if err != nil {
return fiber.NewError(500, "erro interno")
}
var userID string
if db != nil {
user, err := repo.CreateTenantUser(c.Context(), ten.ID, req.AdminEmail, hash, req.AdminName, "tenant_admin")
if err != nil {
return fiber.NewError(500, "erro ao criar utilizador")
}
userID = user.ID
} else {
userID = "mock-user-id"
}
if err := repo.UseInvite(c.Context(), invite.ID); err != nil {
return fiber.NewError(500, "erro ao registar utilização do convite")
}
access, _ := auth.GenerateAccessToken(userID, ten.ID, "tenant_admin", cfg.JWTSecret)
refresh, _ := auth.GenerateRefreshToken(userID, ten.ID, "tenant_admin", cfg.JWTSecret)
auth.SetRefreshCookie(c, refresh, cfg)
return c.Status(201).JSON(fiber.Map{
"data": fiber.Map{"access_token": access, "tenant_slug": ten.Slug},
"error": nil,
})
}
}