package handler import ( "context" "eslogad-be/internal/appcontext" "eslogad-be/internal/contract" "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" "gorm.io/gorm" ) type LetterOutgoingService interface { CreateOutgoingLetter(ctx context.Context, req *contract.CreateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*contract.OutgoingLetterResponse, error) ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error) SearchOutgoingLetters(ctx context.Context, req *contract.SearchOutgoingLettersRequest) (*contract.SearchOutgoingLettersResponse, error) UpdateOutgoingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error BulkDeleteOutgoingLetters(ctx context.Context, ids []uuid.UUID) error SubmitForApproval(ctx context.Context, letterID uuid.UUID) error ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error ReviseOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ReviseLetterRequest) error SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID) error AddRecipients(ctx context.Context, letterID uuid.UUID, req *contract.AddRecipientsRequest) error UpdateRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID, req *contract.UpdateRecipientRequest) error RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID) error CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateDiscussionRequest) (*contract.DiscussionResponse, error) UpdateDiscussion(ctx context.Context, discussionID uuid.UUID, req *contract.UpdateDiscussionRequest) error DeleteDiscussion(ctx context.Context, discussionID uuid.UUID) error GetLetterApprovalInfo(ctx context.Context, letterID uuid.UUID) (*contract.LetterApprovalInfoResponse, error) GetLetterApprovals(ctx context.Context, letterID uuid.UUID) (*contract.GetLetterApprovalsResponse, error) GetApprovalDiscussions(ctx context.Context, letterID uuid.UUID) (*contract.OutgoingLetterApprovalDiscussionsResponse, error) GetApprovalTimeline(ctx context.Context, letterID uuid.UUID) (*contract.ApprovalTimelineResponse, error) BulkArchiveOutgoingLetters(ctx context.Context, letterIDs []uuid.UUID) (*contract.BulkArchiveLettersResponse, error) } type LetterOutgoingHandler struct { svc LetterOutgoingService } func NewLetterOutgoingHandler(svc LetterOutgoingService) *LetterOutgoingHandler { return &LetterOutgoingHandler{svc: svc} } func (h *LetterOutgoingHandler) CreateOutgoingLetter(c *gin.Context) { var req contract.CreateOutgoingLetterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } ctx := c.Request.Context() req.UserID = appcontext.FromGinContext(ctx).UserID resp, err := h.svc.CreateOutgoingLetter(c.Request.Context(), &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp)) } func (h *LetterOutgoingHandler) GetOutgoingLetter(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } resp, err := h.svc.GetOutgoingLetterByID(c.Request.Context(), id) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } func (h *LetterOutgoingHandler) ListOutgoingLetters(c *gin.Context) { var req contract.ListOutgoingLettersRequest if err := c.ShouldBindQuery(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid query parameters", Code: http.StatusBadRequest}) return } if req.Page <= 0 { req.Page = 1 } if req.Limit <= 0 { req.Limit = 10 } if ids := c.QueryArray("priority_ids[]"); len(ids) > 0 { for _, s := range ids { if id, err := uuid.Parse(s); err == nil { req.PriorityIDs = append(req.PriorityIDs, id) } } } fmt.Printf("[DEBUG] request: %v\n", req) fmt.Printf("[DEBUG] Raw query: %v\n", c.Request.URL.RawQuery) fmt.Printf("[DEBUG] Parsed form: %v\n", c.Request.URL.Query()) resp, err := h.svc.ListOutgoingLetters(c.Request.Context(), &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } func (h *LetterOutgoingHandler) UpdateOutgoingLetter(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } var req contract.UpdateOutgoingLetterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } resp, err := h.svc.UpdateOutgoingLetter(c.Request.Context(), id, &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } func (h *LetterOutgoingHandler) DeleteOutgoingLetter(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } if err := h.svc.DeleteOutgoingLetter(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(&contract.SuccessResponse{Message: "deleted"})) } func (h *LetterOutgoingHandler) BulkDeleteOutgoingLetters(c *gin.Context) { var req struct { IDs []uuid.UUID `json:"ids" binding:"required,min=1"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ Error: "Invalid request body", Code: http.StatusBadRequest, }) return } if err := h.svc.BulkDeleteOutgoingLetters(c.Request.Context(), req.IDs); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{ Error: err.Error(), Code: http.StatusInternalServerError, }) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(&contract.SuccessResponse{ Message: fmt.Sprintf("%d letters deleted successfully", len(req.IDs)), })) } func (h *LetterOutgoingHandler) SubmitForApproval(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } if err := h.svc.SubmitForApproval(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "submitted for approval"}) } func (h *LetterOutgoingHandler) ApproveOutgoingLetter(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } var req contract.ApproveLetterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } if err := h.svc.ApproveOutgoingLetter(c.Request.Context(), id, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(&contract.SuccessResponse{Message: "approved"})) } func (h *LetterOutgoingHandler) RejectOutgoingLetter(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } var req contract.RejectLetterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } if err := h.svc.RejectOutgoingLetter(c.Request.Context(), id, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(&contract.SuccessResponse{Message: "rejected"})) } func (h *LetterOutgoingHandler) ReviseOutgoingLetter(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } var req contract.ReviseLetterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } if err := h.svc.ReviseOutgoingLetter(c.Request.Context(), id, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(&contract.SuccessResponse{Message: "revised"})) } func (h *LetterOutgoingHandler) SendOutgoingLetter(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } if err := h.svc.SendOutgoingLetter(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "sent"}) } func (h *LetterOutgoingHandler) ArchiveOutgoingLetter(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } if err := h.svc.ArchiveOutgoingLetter(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "archived"}) } func (h *LetterOutgoingHandler) AddRecipients(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } var req contract.AddRecipientsRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } if err := h.svc.AddRecipients(c.Request.Context(), id, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "recipients added"}) } func (h *LetterOutgoingHandler) UpdateRecipient(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest}) return } recipientID, err := uuid.Parse(c.Param("recipient_id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid recipient id", Code: http.StatusBadRequest}) return } var req contract.UpdateRecipientRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } if err := h.svc.UpdateRecipient(c.Request.Context(), id, recipientID, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "recipient updated"}) } func (h *LetterOutgoingHandler) RemoveRecipient(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest}) return } recipientID, err := uuid.Parse(c.Param("recipient_id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid recipient id", Code: http.StatusBadRequest}) return } if err := h.svc.RemoveRecipient(c.Request.Context(), id, recipientID); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "recipient removed"}) } func (h *LetterOutgoingHandler) AddAttachments(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } var req contract.AddAttachmentsRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } if err := h.svc.AddAttachments(c.Request.Context(), id, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "attachments added"}) } func (h *LetterOutgoingHandler) RemoveAttachment(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest}) return } attachmentID, err := uuid.Parse(c.Param("attachment_id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid attachment id", Code: http.StatusBadRequest}) return } if err := h.svc.RemoveAttachment(c.Request.Context(), id, attachmentID); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "attachment removed"}) } func (h *LetterOutgoingHandler) CreateDiscussion(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest}) return } var req contract.CreateDiscussionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } resp, err := h.svc.CreateDiscussion(c.Request.Context(), id, &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp)) } func (h *LetterOutgoingHandler) UpdateDiscussion(c *gin.Context) { discussionID, err := uuid.Parse(c.Param("discussion_id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid discussion id", Code: http.StatusBadRequest}) return } var req contract.UpdateDiscussionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } if err := h.svc.UpdateDiscussion(c.Request.Context(), discussionID, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "discussion updated"}) } func (h *LetterOutgoingHandler) DeleteDiscussion(c *gin.Context) { discussionID, err := uuid.Parse(c.Param("discussion_id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid discussion id", Code: http.StatusBadRequest}) return } if err := h.svc.DeleteDiscussion(c.Request.Context(), discussionID); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "discussion deleted"}) } func (h *LetterOutgoingHandler) SearchOutgoingLetters(c *gin.Context) { var req contract.SearchOutgoingLettersRequest if err := c.ShouldBindQuery(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid query parameters", Code: http.StatusBadRequest}) 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.SearchOutgoingLetters(c.Request.Context(), &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } func (h *LetterOutgoingHandler) GetLetterApprovalInfo(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } resp, err := h.svc.GetLetterApprovalInfo(c.Request.Context(), id) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } // GetLetterApprovals returns all approvals and their status for a letter func (h *LetterOutgoingHandler) GetLetterApprovals(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } resp, err := h.svc.GetLetterApprovals(c.Request.Context(), id) if err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, &contract.ErrorResponse{Error: "letter not found", Code: http.StatusNotFound}) return } c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } // GetApprovalDiscussions returns both approvals and discussions for an outgoing letter func (h *LetterOutgoingHandler) GetApprovalDiscussions(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } resp, err := h.svc.GetApprovalDiscussions(c.Request.Context(), id) if err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, &contract.ErrorResponse{Error: "letter not found", Code: http.StatusNotFound}) return } c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } // GetApprovalTimeline returns a chronological timeline of approval and discussion events func (h *LetterOutgoingHandler) GetApprovalTimeline(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } resp, err := h.svc.GetApprovalTimeline(c.Request.Context(), id) if err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, &contract.ErrorResponse{Error: "letter not found", Code: http.StatusNotFound}) return } c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } func (h *LetterOutgoingHandler) BulkArchiveOutgoingLetters(c *gin.Context) { var req contract.BulkArchiveLettersRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid request body", Code: http.StatusBadRequest}) return } if len(req.LetterIDs) == 0 { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "at least one letter ID is required", Code: http.StatusBadRequest}) return } resp, err := h.svc.BulkArchiveOutgoingLetters(c.Request.Context(), req.LetterIDs) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) }