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, }) } }