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:
Trey t
2025-12-16 13:52:08 -06:00
parent c51f1ce34a
commit 6dac34e373
98 changed files with 8209 additions and 4425 deletions

View File

@@ -8,7 +8,7 @@ import (
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/labstack/echo/v4"
"github.com/gorilla/websocket"
"github.com/rs/zerolog/log"
)
@@ -38,11 +38,10 @@ func NewHandler(logBuffer *LogBuffer, statsStore *StatsStore) *Handler {
// GetLogs returns filtered log entries
// GET /api/admin/monitoring/logs
func (h *Handler) GetLogs(c *gin.Context) {
func (h *Handler) GetLogs(c echo.Context) error {
var filters LogFilters
if err := c.ShouldBindQuery(&filters); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid filters"})
return
if err := c.Bind(&filters); err != nil {
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "Invalid filters"})
}
limit := filters.GetLimit()
@@ -51,8 +50,7 @@ func (h *Handler) GetLogs(c *gin.Context) {
entries, err := h.logBuffer.GetRecent(limit * 2)
if err != nil {
log.Error().Err(err).Msg("Failed to get logs from buffer")
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve logs"})
return
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to retrieve logs"})
}
// Apply filters
@@ -83,7 +81,7 @@ func (h *Handler) GetLogs(c *gin.Context) {
}
}
c.JSON(http.StatusOK, gin.H{
return c.JSON(http.StatusOK, map[string]interface{}{
"logs": filtered,
"total": len(filtered),
})
@@ -91,41 +89,38 @@ func (h *Handler) GetLogs(c *gin.Context) {
// GetStats returns system statistics for all processes
// GET /api/admin/monitoring/stats
func (h *Handler) GetStats(c *gin.Context) {
func (h *Handler) GetStats(c echo.Context) error {
allStats, err := h.statsStore.GetAllStats()
if err != nil {
log.Error().Err(err).Msg("Failed to get stats from store")
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve stats"})
return
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to retrieve stats"})
}
c.JSON(http.StatusOK, allStats)
return c.JSON(http.StatusOK, allStats)
}
// ClearLogs clears all logs from the buffer
// DELETE /api/admin/monitoring/logs
func (h *Handler) ClearLogs(c *gin.Context) {
func (h *Handler) ClearLogs(c echo.Context) error {
if err := h.logBuffer.Clear(); err != nil {
log.Error().Err(err).Msg("Failed to clear logs")
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear logs"})
return
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to clear logs"})
}
c.JSON(http.StatusOK, gin.H{"message": "Logs cleared"})
return c.JSON(http.StatusOK, map[string]interface{}{"message": "Logs cleared"})
}
// WebSocket handles real-time log streaming
// GET /api/admin/monitoring/ws
func (h *Handler) WebSocket(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
func (h *Handler) WebSocket(c echo.Context) error {
conn, err := upgrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
log.Error().Err(err).Msg("Failed to upgrade WebSocket connection")
return
}
defer conn.Close()
// Create context that cancels when connection closes
ctx, cancel := context.WithCancel(c.Request.Context())
ctx, cancel := context.WithCancel(c.Request().Context())
defer cancel()
// Subscribe to Redis pubsub for logs
@@ -139,7 +134,6 @@ func (h *Handler) WebSocket(c *gin.Context) {
_, _, err := conn.ReadMessage()
if err != nil {
cancel()
return
}
}
}()
@@ -173,7 +167,6 @@ func (h *Handler) WebSocket(c *gin.Context) {
if err != nil {
log.Debug().Err(err).Msg("WebSocket write error")
return
}
case <-statsTicker.C:
@@ -181,7 +174,6 @@ func (h *Handler) WebSocket(c *gin.Context) {
h.sendStats(conn, &wsMu)
case <-ctx.Done():
return
}
}
}
@@ -189,7 +181,6 @@ func (h *Handler) WebSocket(c *gin.Context) {
func (h *Handler) sendStats(conn *websocket.Conn, mu *sync.Mutex) {
allStats, err := h.statsStore.GetAllStats()
if err != nil {
return
}
wsMsg := WSMessage{