package handlers import ( "net/http" "strconv" "strings" "git.workaround.org/chaas/imc/backend/internal/db" "github.com/gin-gonic/gin" ) type AliasHandler struct { db *db.DB } func NewAliasHandler(database *db.DB) *AliasHandler { return &AliasHandler{db: database} } type CreateAliasRequest struct { Source string `json:"source" binding:"required"` Destination string `json:"destination" binding:"required"` } func (h *AliasHandler) List(c *gin.Context) { domainName := c.Param("name") if domainName == "" { Error(c, http.StatusBadRequest, "domain name required") return } authCtx := GetAuthContext(c) if authCtx == nil { Error(c, http.StatusUnauthorized, "authentication required") return } canAccess, _ := h.db.CanAccessDomain(c.Request.Context(), uint32(authCtx.UserID), domainName, authCtx.IsAdmin()) if !canAccess { Error(c, http.StatusForbidden, "access denied") return } domain, err := h.db.GetDomainByName(c.Request.Context(), domainName) if err != nil { Error(c, http.StatusNotFound, "domain not found") return } aliases, err := h.db.GetAliasesByDomain(c.Request.Context(), domain.ID) if err != nil { Error(c, http.StatusInternalServerError, "database error") return } Success(c, aliases) } func (h *AliasHandler) Create(c *gin.Context) { domainName := c.Param("name") if domainName == "" { Error(c, http.StatusBadRequest, "domain name required") return } authCtx := GetAuthContext(c) if authCtx == nil { Error(c, http.StatusUnauthorized, "authentication required") return } canAccess, _ := h.db.CanAccessDomain(c.Request.Context(), uint32(authCtx.UserID), domainName, authCtx.IsAdmin()) if !canAccess { Error(c, http.StatusForbidden, "access denied") return } domain, err := h.db.GetDomainByName(c.Request.Context(), domainName) if err != nil { Error(c, http.StatusNotFound, "domain not found") return } var req CreateAliasRequest if err := c.ShouldBindJSON(&req); err != nil { Error(c, http.StatusBadRequest, "invalid request body") return } // Validate source email format if err := validateEmailLocalPart(req.Source); err != nil { Error(c, http.StatusBadRequest, "source "+err.Error()) return } // Ensure source is in the correct domain if !strings.HasSuffix(req.Source, "@"+domainName) { Error(c, http.StatusBadRequest, "source must be in domain "+domainName) return } // Validate destination - can be single email or comma-separated list destinations := strings.Split(req.Destination, ",") for _, dest := range destinations { dest = strings.TrimSpace(dest) if dest == "" { Error(c, http.StatusBadRequest, "destination cannot be empty") return } // Destination doesn't need @domain validation since it can be external if strings.Contains(dest, "@") { if err := validateEmailLocalPart(dest); err != nil { Error(c, http.StatusBadRequest, "destination "+err.Error()) return } } } // Check for duplicate alias _, err = h.db.GetAliasBySource(c.Request.Context(), req.Source) if err == nil { Error(c, http.StatusConflict, "alias already exists") return } if err != nil && !strings.Contains(err.Error(), "sql: no rows") { // Only error if it's not "no rows" error } err = h.db.CreateAlias(c.Request.Context(), domain.ID, req.Source, req.Destination) if err != nil { Error(c, http.StatusInternalServerError, "failed to create alias") return } Created(c, map[string]string{"message": "alias created"}) } func (h *AliasHandler) Delete(c *gin.Context) { domainName := c.Param("name") idStr := c.Param("id") if domainName == "" || idStr == "" { Error(c, http.StatusBadRequest, "domain name and alias id required") return } authCtx := GetAuthContext(c) if authCtx == nil { Error(c, http.StatusUnauthorized, "authentication required") return } canAccess, _ := h.db.CanAccessDomain(c.Request.Context(), uint32(authCtx.UserID), domainName, authCtx.IsAdmin()) if !canAccess { Error(c, http.StatusForbidden, "access denied") return } id, err := strconv.ParseUint(idStr, 10, 64) if err != nil { Error(c, http.StatusBadRequest, "invalid alias id") return } err = h.db.DeleteAlias(c.Request.Context(), uint32(id)) if err != nil { Error(c, http.StatusInternalServerError, "failed to delete alias") return } NoContent(c) }