package handlers import ( "net/http" "regexp" "strconv" "strings" "github.com/gin-gonic/gin" "github.com/imc-vibe/backend/internal/db" ) type DomainHandler struct { db *db.DB } func NewDomainHandler(database *db.DB) *DomainHandler { return &DomainHandler{db: database} } type CreateDomainRequest struct { Name string `json:"name" binding:"required"` } type DomainPermissions struct { DomainID uint `json:"domainId"` DomainName string `json:"domainName"` UserID uint `json:"userId"` CanManage bool `json:"canManage"` } func (h *DomainHandler) List(c *gin.Context) { authCtx := GetAuthContext(c) if authCtx == nil { Error(c, http.StatusUnauthorized, "authentication required") return } isAdmin := authCtx.IsAdmin() domains, err := h.db.GetUserAccessibleDomains(authCtx.UserID, isAdmin) if err != nil { Error(c, http.StatusInternalServerError, "database error") return } domainStats := make([]db.DomainStats, len(domains)) for i, d := range domains { var userCount, aliasCount int64 h.db.Model(&db.User{}).Where("domain_id = ?", d.ID).Count(&userCount) h.db.Model(&db.Alias{}).Where("domain_id = ?", d.ID).Count(&aliasCount) domainStats[i] = db.DomainStats{ ID: d.ID, Name: d.Name, UserCount: userCount, AliasCount: aliasCount, } } Success(c, domainStats) } func (h *DomainHandler) Get(c *gin.Context) { domainName := c.Param("name") if domainName == "" { Error(c, http.StatusBadRequest, "domain name required") return } domain, err := h.db.GetDomainByName(domainName) if err != nil { Error(c, http.StatusNotFound, "domain not found") return } authCtx := GetAuthContext(c) if authCtx == nil { Error(c, http.StatusUnauthorized, "authentication required") return } canAccess, _ := h.db.CanAccessDomain(authCtx.UserID, domainName, authCtx.IsAdmin()) if !canAccess { Error(c, http.StatusForbidden, "access denied") return } Success(c, domain) } func (h *DomainHandler) Create(c *gin.Context) { authCtx := GetAuthContext(c) if authCtx == nil || !authCtx.IsAdmin() { Error(c, http.StatusForbidden, "admin access required") return } var req CreateDomainRequest if err := c.ShouldBindJSON(&req); err != nil { Error(c, http.StatusBadRequest, "invalid request body") return } name := strings.ToLower(strings.TrimSpace(req.Name)) if err := validateDomainName(name); err != nil { Error(c, http.StatusBadRequest, err.Error()) return } existing, err := h.db.GetDomainByName(name) if err == nil && existing != nil { Error(c, http.StatusConflict, "domain already exists") return } domain, err := h.db.CreateDomain(name) if err != nil { Error(c, http.StatusInternalServerError, "failed to create domain") return } Created(c, domain) } func validateDomainName(name string) error { if len(name) < 3 || len(name) > 253 { return &ValidationError{Message: "domain name must be between 3 and 253 characters"} } if strings.Contains(name, "..") { return &ValidationError{Message: "domain name cannot contain consecutive dots"} } if strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") { return &ValidationError{Message: "domain name cannot start or end with a dot"} } if strings.HasPrefix(name, "-") || strings.HasSuffix(name, "-") { return &ValidationError{Message: "domain name cannot start or end with a hyphen"} } domainRegex := regexp.MustCompile(`^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$`) if !domainRegex.MatchString(name) { return &ValidationError{Message: "domain name can only contain letters, numbers, dots, and hyphens"} } labels := strings.Split(name, ".") for _, label := range labels { if len(label) < 1 || len(label) > 63 { return &ValidationError{Message: "each part of the domain name must be between 1 and 63 characters"} } } return nil } type ValidationError struct { Message string } func (e *ValidationError) Error() string { return e.Message } func (h *DomainHandler) Delete(c *gin.Context) { authCtx := GetAuthContext(c) if authCtx == nil || !authCtx.IsAdmin() { Error(c, http.StatusForbidden, "admin access required") return } domainName := c.Param("name") if domainName == "" { Error(c, http.StatusBadRequest, "domain name required") return } domain, err := h.db.GetDomainByName(domainName) if err != nil { Error(c, http.StatusNotFound, "domain not found") return } if err := h.db.DeleteDomain(domain.ID); err != nil { Error(c, http.StatusInternalServerError, "failed to delete domain") return } NoContent(c) } func (h *DomainHandler) GetPermissions(c *gin.Context) { authCtx := GetAuthContext(c) if authCtx == nil { Error(c, http.StatusUnauthorized, "authentication required") return } if !authCtx.IsAdmin() { Error(c, http.StatusForbidden, "admin access required") return } domainName := c.Param("name") if domainName == "" { Error(c, http.StatusBadRequest, "domain name required") return } domain, err := h.db.GetDomainByName(domainName) if err != nil { Error(c, http.StatusNotFound, "domain not found") return } users, err := h.db.GetUsersForDomain(domain.ID) if err != nil { Error(c, http.StatusInternalServerError, "database error") return } permissions := make([]DomainPermissions, len(users)) for i, u := range users { permissions[i] = DomainPermissions{ DomainID: domain.ID, DomainName: domain.Name, UserID: u.ID, CanManage: true, } } Success(c, permissions) } func (h *DomainHandler) AddPermission(c *gin.Context) { authCtx := GetAuthContext(c) if authCtx == nil || !authCtx.IsAdmin() { Error(c, http.StatusForbidden, "admin access required") return } domainName := c.Param("name") if domainName == "" { Error(c, http.StatusBadRequest, "domain name required") return } domain, err := h.db.GetDomainByName(domainName) if err != nil { Error(c, http.StatusNotFound, "domain not found") return } var req struct { UserID uint `json:"userId" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { Error(c, http.StatusBadRequest, "invalid request") return } if err := h.db.AddUserToDomain(req.UserID, domain.ID); err != nil { Error(c, http.StatusInternalServerError, "failed to add user to domain") return } Success(c, map[string]string{"message": "user added to domain"}) } func (h *DomainHandler) RemovePermission(c *gin.Context) { authCtx := GetAuthContext(c) if authCtx == nil || !authCtx.IsAdmin() { Error(c, http.StatusForbidden, "admin access required") return } domainName := c.Param("name") if domainName == "" { Error(c, http.StatusBadRequest, "domain name required") return } domain, err := h.db.GetDomainByName(domainName) if err != nil { Error(c, http.StatusNotFound, "domain not found") return } userIDStr := c.Param("userId") userID, err := strconv.ParseUint(userIDStr, 10, 64) if err != nil { Error(c, http.StatusBadRequest, "invalid user id") return } if err := h.db.RemoveUserFromDomain(uint(userID), domain.ID); err != nil { Error(c, http.StatusInternalServerError, "failed to remove user from domain") return } NoContent(c) }