243 lines
7.0 KiB
Go
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,
|
|
})
|
|
}
|
|
}
|