package handler import ( "fmt" "strings" "time" "github.com/gin-gonic/gin" "github.com/muyuqingfeng/iloom/auth-service/internal/repository" "github.com/muyuqingfeng/iloom/auth-service/internal/storage" "github.com/muyuqingfeng/iloom/shared/pkg/response" ) type CommonHandler struct { shareLinkRepo *repository.ShareLinkRepo auditLogRepo *repository.AuditLogRepo storage *storage.MinIOStorage } func NewCommonHandler(slRepo *repository.ShareLinkRepo, alRepo *repository.AuditLogRepo, st *storage.MinIOStorage) *CommonHandler { return &CommonHandler{shareLinkRepo: slRepo, auditLogRepo: alRepo, storage: st} } type createShareLinkReq struct { ResourceType string `json:"resource_type" binding:"required"` ResourceID string `json:"resource_id" binding:"required"` ExpiresIn int `json:"expires_in"` // seconds, 0 = no expiry } func (h *CommonHandler) CreateShareLink(c *gin.Context) { var req createShareLinkReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "invalid request: "+err.Error()) return } userID := c.GetHeader("X-User-ID") companyID := c.GetHeader("X-Company-ID") var expiresAt *time.Time if req.ExpiresIn > 0 { t := time.Now().Add(time.Duration(req.ExpiresIn) * time.Second) expiresAt = &t } link, err := h.shareLinkRepo.Create(c.Request.Context(), companyID, userID, req.ResourceType, req.ResourceID, expiresAt) if err != nil { response.InternalError(c, err.Error()) return } response.OK(c, link) } func (h *CommonHandler) GetShareLink(c *gin.Context) { code := c.Param("code") link, err := h.shareLinkRepo.GetByCode(c.Request.Context(), code) if err != nil { response.InternalError(c, err.Error()) return } if link == nil { response.NotFound(c, "share link not found") return } // check expiry if link.ExpiresAt != nil && link.ExpiresAt.Before(time.Now()) { response.BadRequest(c, "share link expired") return } response.OK(c, link) } type feedbackReq struct { Content string `json:"content" binding:"required"` Type string `json:"type"` } func (h *CommonHandler) Feedback(c *gin.Context) { var req feedbackReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "invalid request: "+err.Error()) return } userID := c.GetHeader("X-User-ID") companyID := c.GetHeader("X-Company-ID") feedbackType := req.Type if feedbackType == "" { feedbackType = "general" } err := h.auditLogRepo.Create( c.Request.Context(), userID, companyID, "feedback", feedbackType, "", c.ClientIP(), map[string]string{"content": req.Content}, ) if err != nil { response.InternalError(c, err.Error()) return } response.OK(c, gin.H{"message": "feedback received"}) } type crashReportReq struct { Error string `json:"error" binding:"required"` StackTrace string `json:"stack_trace"` UserAgent string `json:"user_agent"` URL string `json:"url"` } func (h *CommonHandler) CrashReport(c *gin.Context) { var req crashReportReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "invalid request: "+err.Error()) return } userID := c.GetHeader("X-User-ID") companyID := c.GetHeader("X-Company-ID") err := h.auditLogRepo.Create( c.Request.Context(), userID, companyID, "crash_report", "frontend", "", c.ClientIP(), req, ) if err != nil { response.InternalError(c, err.Error()) return } response.OK(c, gin.H{"message": "crash report received"}) } const maxUploadSize = 10 << 20 // 10 MB func (h *CommonHandler) Upload(c *gin.Context) { if h.storage == nil { response.InternalError(c, "file storage not configured") return } if err := c.Request.ParseMultipartForm(maxUploadSize); err != nil { response.BadRequest(c, fmt.Sprintf("file too large (max %dMB)", maxUploadSize>>20)) return } file, header, err := c.Request.FormFile("file") if err != nil { response.BadRequest(c, "missing file field") return } defer file.Close() // Validate content type contentType := header.Header.Get("Content-Type") if !strings.HasPrefix(contentType, "image/") { response.BadRequest(c, "only image files are allowed") return } // Validate size if header.Size > maxUploadSize { response.BadRequest(c, fmt.Sprintf("file too large (max %dMB)", maxUploadSize>>20)) return } pathPrefix := c.DefaultPostForm("path_prefix", "products") objectPath, err := h.storage.Upload(c.Request.Context(), pathPrefix, header.Filename, file, header.Size, contentType) if err != nil { response.InternalError(c, "upload failed: "+err.Error()) return } url := "/uploads/" + objectPath response.OK(c, gin.H{"url": url}) }