package handler import ( "context" "eslogad-be/internal/appcontext" "net/http" "strconv" "strings" "eslogad-be/internal/contract" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type LetterService interface { CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error) ListIncomingLetters(ctx context.Context, req *contract.ListIncomingLettersRequest) (*contract.ListIncomingLettersResponse, error) SearchIncomingLetters(ctx context.Context, req *contract.SearchIncomingLettersRequest) (*contract.SearchIncomingLettersResponse, error) GetLetterUnreadCounts(ctx context.Context) (*contract.LetterUnreadCountResponse, error) MarkIncomingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error) MarkOutgoingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error) UpdateIncomingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) SoftDeleteIncomingLetter(ctx context.Context, id uuid.UUID) error BulkArchiveIncomingLetters(ctx context.Context, letterIDs []uuid.UUID) (*contract.BulkArchiveLettersResponse, error) CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error) GetEnhancedDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListEnhancedDispositionsResponse, error) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) GetDepartmentDispositionStatus(ctx context.Context, req *contract.GetDepartmentDispositionStatusRequest) (*contract.ListDepartmentDispositionStatusResponse, error) UpdateDispositionStatus(ctx context.Context, req *contract.UpdateDispositionStatusRequest) (*contract.DepartmentDispositionStatusResponse, error) GetLetterCTA(ctx context.Context, letterID uuid.UUID) (*contract.LetterCTAResponse, error) } type LetterHandler struct { svc LetterService } func NewLetterHandler(svc LetterService) *LetterHandler { return &LetterHandler{svc: svc} } // Helper functions for common patterns func (h *LetterHandler) parseUUID(c *gin.Context, param string) (uuid.UUID, bool) { id, err := uuid.Parse(c.Param(param)) if err != nil { h.respondError(c, http.StatusBadRequest, "invalid "+param) return uuid.Nil, false } return id, true } func (h *LetterHandler) bindJSON(c *gin.Context, req interface{}) bool { if err := c.ShouldBindJSON(req); err != nil { h.respondError(c, http.StatusBadRequest, "invalid request body") return false } return true } func (h *LetterHandler) bindQuery(c *gin.Context, req interface{}) bool { if err := c.ShouldBindQuery(req); err != nil { h.respondError(c, http.StatusBadRequest, "invalid query parameters") return false } return true } func (h *LetterHandler) respondError(c *gin.Context, code int, message string) { c.JSON(code, &contract.ErrorResponse{ Error: message, Code: code, }) } func (h *LetterHandler) respondSuccess(c *gin.Context, code int, data interface{}) { c.JSON(code, contract.BuildSuccessResponse(data)) } func (h *LetterHandler) handleServiceError(c *gin.Context, err error) { if err != nil { h.respondError(c, http.StatusInternalServerError, err.Error()) } } func (h *LetterHandler) CreateIncomingLetter(c *gin.Context) { var req contract.CreateIncomingLetterRequest if !h.bindJSON(c, &req) { return } resp, err := h.svc.CreateIncomingLetter(c.Request.Context(), &req) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusCreated, resp) } func (h *LetterHandler) GetIncomingLetter(c *gin.Context) { id, ok := h.parseUUID(c, "id") if !ok { return } resp, err := h.svc.GetIncomingLetterByID(c.Request.Context(), id) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) ListIncomingLetters(c *gin.Context) { req := h.parseListRequest(c) resp, err := h.svc.ListIncomingLetters(c.Request.Context(), req) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) GetLetterUnreadCounts(c *gin.Context) { resp, err := h.svc.GetLetterUnreadCounts(c.Request.Context()) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) MarkIncomingLetterAsRead(c *gin.Context) { id, ok := h.parseUUID(c, "id") if !ok { return } resp, err := h.svc.MarkIncomingLetterAsRead(c.Request.Context(), id) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) MarkOutgoingLetterAsRead(c *gin.Context) { id, ok := h.parseUUID(c, "id") if !ok { return } resp, err := h.svc.MarkOutgoingLetterAsRead(c.Request.Context(), id) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) parseListRequest(c *gin.Context) *contract.ListIncomingLettersRequest { //appCtx := appcontext.FromGinContext(c) //departmentID := appCtx.DepartmentID page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) // Ensure valid pagination values if page < 1 { page = 1 } if limit < 1 { limit = 10 } if limit > 100 { limit = 100 } req := &contract.ListIncomingLettersRequest{ Page: page, Limit: limit, } if status := c.Query("status"); status != "" { req.Status = &status } if query := c.Query("q"); query != "" { req.Query = &query } // Parse is_read filter if isReadStr := c.Query("is_read"); isReadStr != "" { isRead := isReadStr == "true" || isReadStr == "1" req.IsRead = &isRead } // Parse priority_ids filter if priorityIDsStr := c.QueryArray("priority_ids[]"); len(priorityIDsStr) > 0 { priorityIDs := make([]uuid.UUID, 0, len(priorityIDsStr)) for _, idStr := range priorityIDsStr { if id, err := uuid.Parse(idStr); err == nil { priorityIDs = append(priorityIDs, id) } } req.PriorityIDs = priorityIDs } else if priorityIDStr := c.Query("priority_ids"); priorityIDStr != "" { // Also support comma-separated format idStrs := strings.Split(priorityIDStr, ",") priorityIDs := make([]uuid.UUID, 0, len(idStrs)) for _, idStr := range idStrs { if id, err := uuid.Parse(strings.TrimSpace(idStr)); err == nil { priorityIDs = append(priorityIDs, id) } } req.PriorityIDs = priorityIDs } // Parse is_dispositioned filter if isDispositionedStr := c.Query("is_dispositioned"); isDispositionedStr != "" { isDispositioned := isDispositionedStr == "true" || isDispositionedStr == "1" req.IsDispositioned = &isDispositioned } // Parse is_archived filter if isArchivedStr := c.Query("is_archived"); isArchivedStr != "" { isArchived := isArchivedStr == "true" || isArchivedStr == "1" req.IsArchived = &isArchived } //req.DepartmentID = &departmentID return req } func (h *LetterHandler) UpdateIncomingLetter(c *gin.Context) { id, ok := h.parseUUID(c, "id") if !ok { return } var req contract.UpdateIncomingLetterRequest if !h.bindJSON(c, &req) { return } resp, err := h.svc.UpdateIncomingLetter(c.Request.Context(), id, &req) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) DeleteIncomingLetter(c *gin.Context) { id, ok := h.parseUUID(c, "id") if !ok { return } if err := h.svc.SoftDeleteIncomingLetter(c.Request.Context(), id); err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, &contract.SuccessResponse{ Message: "Letter deleted successfully", }) } func (h *LetterHandler) CreateDispositions(c *gin.Context) { var req contract.CreateLetterDispositionRequest if !h.bindJSON(c, &req) { return } appCtx := appcontext.FromGinContext(c.Request.Context()) req.FromDepartment = appCtx.DepartmentID resp, err := h.svc.CreateDispositions(c.Request.Context(), &req) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusCreated, resp) } func (h *LetterHandler) GetEnhancedDispositionsByLetter(c *gin.Context) { letterID, ok := h.parseUUID(c, "letter_id") if !ok { return } resp, err := h.svc.GetEnhancedDispositionsByLetter(c.Request.Context(), letterID) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) CreateDiscussion(c *gin.Context) { letterID, ok := h.parseUUID(c, "letter_id") if !ok { return } var req contract.CreateLetterDiscussionRequest if !h.bindJSON(c, &req) { return } resp, err := h.svc.CreateDiscussion(c.Request.Context(), letterID, &req) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusCreated, resp) } func (h *LetterHandler) UpdateDiscussion(c *gin.Context) { letterID, ok := h.parseUUID(c, "letter_id") if !ok { return } discussionID, ok := h.parseUUID(c, "discussion_id") if !ok { return } var req contract.UpdateLetterDiscussionRequest if !h.bindJSON(c, &req) { return } resp, err := h.svc.UpdateDiscussion(c.Request.Context(), letterID, discussionID, &req) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) SearchIncomingLetters(c *gin.Context) { var req contract.SearchIncomingLettersRequest if !h.bindQuery(c, &req) { return } if req.Page <= 0 { req.Page = 1 } if req.Limit <= 0 { req.Limit = 10 } if req.SortOrder == "" { req.SortOrder = "desc" } if req.SortBy == "" { req.SortBy = "created_at" } resp, err := h.svc.SearchIncomingLetters(c.Request.Context(), &req) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) GetDepartmentDispositionStatus(c *gin.Context) { letterID, ok := h.parseUUID(c, "letter_id") if !ok { return } departmentID := appcontext.FromGinContext(c.Request.Context()).DepartmentID req := &contract.GetDepartmentDispositionStatusRequest{ LetterIncomingID: letterID, DepartmentID: departmentID, } resp, err := h.svc.GetDepartmentDispositionStatus(c.Request.Context(), req) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) UpdateDispositionStatus(c *gin.Context) { letterID, ok := h.parseUUID(c, "letter_id") if !ok { return } var req contract.UpdateDispositionStatusRequest if !h.bindJSON(c, &req) { return } req.LetterIncomingID = letterID resp, err := h.svc.UpdateDispositionStatus(c.Request.Context(), &req) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) GetLetterCTA(c *gin.Context) { letterID, ok := h.parseUUID(c, "letter_id") if !ok { return } resp, err := h.svc.GetLetterCTA(c.Request.Context(), letterID) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) } func (h *LetterHandler) BulkArchiveIncomingLetters(c *gin.Context) { var req contract.BulkArchiveLettersRequest if !h.bindJSON(c, &req) { return } if len(req.LetterIDs) == 0 { h.respondError(c, http.StatusBadRequest, "at least one letter ID is required") return } resp, err := h.svc.BulkArchiveIncomingLetters(c.Request.Context(), req.LetterIDs) if err != nil { h.handleServiceError(c, err) return } h.respondSuccess(c, http.StatusOK, resp) }