Migrate from Gin to Echo framework and add comprehensive integration tests
Major changes: - Migrate all handlers from Gin to Echo framework - Add new apperrors, echohelpers, and validator packages - Update middleware for Echo compatibility - Add ArchivedHandler to task categorization chain (archived tasks go to cancelled_tasks column) - Add 6 new integration tests: - RecurringTaskLifecycle: NextDueDate advancement for weekly/monthly tasks - MultiUserSharing: Complex sharing with user removal - TaskStateTransitions: All state transitions and kanban column changes - DateBoundaryEdgeCases: Threshold boundary testing - CascadeOperations: Residence deletion cascade effects - MultiUserOperations: Shared residence collaboration - Add single-purpose repository functions for kanban columns (GetOverdueTasks, GetDueSoonTasks, etc.) - Fix RemoveUser route param mismatch (userId -> user_id) - Fix determineExpectedColumn helper to correctly prioritize in_progress over overdue 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,9 @@ package handlers
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/i18n"
|
||||
"github.com/treytartt/casera-api/internal/apperrors"
|
||||
"github.com/treytartt/casera-api/internal/services"
|
||||
)
|
||||
|
||||
@@ -21,77 +21,72 @@ func NewUploadHandler(storageService *services.StorageService) *UploadHandler {
|
||||
|
||||
// UploadImage handles POST /api/uploads/image
|
||||
// Accepts multipart/form-data with "file" field
|
||||
func (h *UploadHandler) UploadImage(c *gin.Context) {
|
||||
func (h *UploadHandler) UploadImage(c echo.Context) error {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": i18n.LocalizedMessage(c, "error.no_file_provided")})
|
||||
return
|
||||
return apperrors.BadRequest("error.no_file_provided")
|
||||
}
|
||||
|
||||
// Get category from query param (default: images)
|
||||
category := c.DefaultQuery("category", "images")
|
||||
category := c.QueryParam("category")
|
||||
if category == "" {
|
||||
category = "images"
|
||||
}
|
||||
|
||||
result, err := h.storageService.Upload(file, category)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
return c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// UploadDocument handles POST /api/uploads/document
|
||||
// Accepts multipart/form-data with "file" field
|
||||
func (h *UploadHandler) UploadDocument(c *gin.Context) {
|
||||
func (h *UploadHandler) UploadDocument(c echo.Context) error {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": i18n.LocalizedMessage(c, "error.no_file_provided")})
|
||||
return
|
||||
return apperrors.BadRequest("error.no_file_provided")
|
||||
}
|
||||
|
||||
result, err := h.storageService.Upload(file, "documents")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
return c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// UploadCompletion handles POST /api/uploads/completion
|
||||
// For task completion photos
|
||||
func (h *UploadHandler) UploadCompletion(c *gin.Context) {
|
||||
func (h *UploadHandler) UploadCompletion(c echo.Context) error {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": i18n.LocalizedMessage(c, "error.no_file_provided")})
|
||||
return
|
||||
return apperrors.BadRequest("error.no_file_provided")
|
||||
}
|
||||
|
||||
result, err := h.storageService.Upload(file, "completions")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
return c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// DeleteFile handles DELETE /api/uploads
|
||||
// Expects JSON body with "url" field
|
||||
func (h *UploadHandler) DeleteFile(c *gin.Context) {
|
||||
func (h *UploadHandler) DeleteFile(c echo.Context) error {
|
||||
var req struct {
|
||||
URL string `json:"url" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return apperrors.BadRequest("error.invalid_request")
|
||||
}
|
||||
|
||||
if err := h.storageService.Delete(req.URL); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": i18n.LocalizedMessage(c, "message.file_deleted")})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "File deleted successfully"})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user