dukcapil/internal/handler/letter_handler.go
2025-10-30 11:36:49 +07:00

510 lines
13 KiB
Go

package handler
import (
"context"
"eslogad-be/internal/appcontext"
"fmt"
"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
BulkSoftDeleteIncomingLetters(ctx context.Context, ids []uuid.UUID) error
BulkArchiveIncomingLetters(ctx context.Context, letterIDs []uuid.UUID) (*contract.BulkArchiveLettersResponse, error)
ArchiveIncomingLetter(ctx context.Context, letterID uuid.UUID) 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) BulkDeleteIncomingLetters(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{
Message: "Invalid request body",
Error: err.Error(),
})
return
}
if err := h.svc.BulkSoftDeleteIncomingLetters(c.Request.Context(), req.IDs); err != nil {
h.handleServiceError(c, err)
return
}
h.respondSuccess(c, http.StatusOK, &contract.SuccessResponse{
Message: fmt.Sprintf("%d letters deleted successfully", len(req.IDs)),
})
}
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)
}
func (h *LetterHandler) ArchiveIncomingLetter(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
h.respondError(c, http.StatusBadRequest, "invalid id")
return
}
err = h.svc.ArchiveIncomingLetter(c.Request.Context(), id)
if err != nil {
h.handleServiceError(c, err)
return
}
h.respondSuccess(c, http.StatusOK, &contract.SuccessResponse{Message: "archived"})
}