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:
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/admin/dto"
|
||||
@@ -47,15 +47,14 @@ type UpdateCompletionImageRequest struct {
|
||||
}
|
||||
|
||||
// List handles GET /api/admin/completion-images
|
||||
func (h *AdminCompletionImageHandler) List(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) List(c echo.Context) error {
|
||||
var filters dto.PaginationParams
|
||||
if err := c.ShouldBindQuery(&filters); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
if err := c.Bind(&filters); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Optional completion_id filter
|
||||
completionIDStr := c.Query("completion_id")
|
||||
completionIDStr := c.QueryParam("completion_id")
|
||||
|
||||
var images []models.TaskCompletionImage
|
||||
var total int64
|
||||
@@ -90,8 +89,7 @@ func (h *AdminCompletionImageHandler) List(c *gin.Context) {
|
||||
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
|
||||
|
||||
if err := query.Find(&images).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion images"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completion images"})
|
||||
}
|
||||
|
||||
// Build response with task info
|
||||
@@ -100,47 +98,41 @@ func (h *AdminCompletionImageHandler) List(c *gin.Context) {
|
||||
responses[i] = h.toResponse(&image)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
return c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
|
||||
}
|
||||
|
||||
// Get handles GET /api/admin/completion-images/:id
|
||||
func (h *AdminCompletionImageHandler) Get(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) Get(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid image ID"})
|
||||
}
|
||||
|
||||
var image models.TaskCompletionImage
|
||||
if err := h.db.First(&image, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Completion image not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Completion image not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completion image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Create handles POST /api/admin/completion-images
|
||||
func (h *AdminCompletionImageHandler) Create(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) Create(c echo.Context) error {
|
||||
var req CreateCompletionImageRequest
|
||||
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 c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Verify completion exists
|
||||
var completion models.TaskCompletion
|
||||
if err := h.db.First(&completion, req.CompletionID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Task completion not found"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Task completion not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify completion"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to verify completion"})
|
||||
}
|
||||
|
||||
image := models.TaskCompletionImage{
|
||||
@@ -150,35 +142,30 @@ func (h *AdminCompletionImageHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Create(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to create completion image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, h.toResponse(&image))
|
||||
return c.JSON(http.StatusCreated, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Update handles PUT /api/admin/completion-images/:id
|
||||
func (h *AdminCompletionImageHandler) Update(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) Update(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid image ID"})
|
||||
}
|
||||
|
||||
var image models.TaskCompletionImage
|
||||
if err := h.db.First(&image, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Completion image not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Completion image not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completion image"})
|
||||
}
|
||||
|
||||
var req UpdateCompletionImageRequest
|
||||
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 c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if req.ImageURL != nil {
|
||||
@@ -189,53 +176,46 @@ func (h *AdminCompletionImageHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.db.Save(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to update completion image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
return c.JSON(http.StatusOK, h.toResponse(&image))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/admin/completion-images/:id
|
||||
func (h *AdminCompletionImageHandler) Delete(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) Delete(c echo.Context) error {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid image ID"})
|
||||
return
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid image ID"})
|
||||
}
|
||||
|
||||
var image models.TaskCompletionImage
|
||||
if err := h.db.First(&image, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Completion image not found"})
|
||||
return
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{"error": "Completion image not found"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to fetch completion image"})
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&image).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete completion image"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete completion image"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Completion image deleted successfully"})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Completion image deleted successfully"})
|
||||
}
|
||||
|
||||
// BulkDelete handles DELETE /api/admin/completion-images/bulk
|
||||
func (h *AdminCompletionImageHandler) BulkDelete(c *gin.Context) {
|
||||
func (h *AdminCompletionImageHandler) BulkDelete(c echo.Context) error {
|
||||
var req dto.BulkDeleteRequest
|
||||
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 c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
|
||||
if err := h.db.Where("id IN ?", req.IDs).Delete(&models.TaskCompletionImage{}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete completion images"})
|
||||
return
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete completion images"})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Completion images deleted successfully", "count": len(req.IDs)})
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Completion images deleted successfully", "count": len(req.IDs)})
|
||||
}
|
||||
|
||||
// toResponse converts a TaskCompletionImage model to AdminCompletionImageResponse
|
||||
|
||||
Reference in New Issue
Block a user