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

@@ -5,7 +5,7 @@ import (
"os"
"time"
"github.com/gin-gonic/gin"
"github.com/labstack/echo/v4"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
@@ -57,66 +57,75 @@ func InitLoggerWithWriter(debug bool, additionalWriter io.Writer) {
log.Logger = zerolog.New(output).With().Timestamp().Caller().Logger()
}
// GinLogger returns a Gin middleware for request logging
func GinLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// EchoLogger returns an Echo middleware for request logging
func EchoLogger() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
start := time.Now()
req := c.Request()
path := req.URL.Path
raw := req.URL.RawQuery
// Process request
c.Next()
// Process request
err := next(c)
// Log after request
end := time.Now()
latency := end.Sub(start)
// Log after request
end := time.Now()
latency := end.Sub(start)
if raw != "" {
path = path + "?" + raw
}
msg := "Request"
if len(c.Errors) > 0 {
msg = c.Errors.String()
}
event := log.Info()
statusCode := c.Writer.Status()
if statusCode >= 400 && statusCode < 500 {
event = log.Warn()
} else if statusCode >= 500 {
event = log.Error()
}
event.
Str("method", c.Request.Method).
Str("path", path).
Int("status", statusCode).
Str("ip", c.ClientIP()).
Dur("latency", latency).
Str("user-agent", c.Request.UserAgent()).
Msg(msg)
}
}
// GinRecovery returns a Gin middleware for panic recovery
func GinRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
log.Error().
Interface("error", err).
Str("path", c.Request.URL.Path).
Str("method", c.Request.Method).
Msg("Panic recovered")
c.AbortWithStatusJSON(500, gin.H{
"error": "Internal server error",
})
if raw != "" {
path = path + "?" + raw
}
}()
c.Next()
res := c.Response()
statusCode := res.Status
msg := "Request"
if err != nil {
msg = err.Error()
}
event := log.Info()
if statusCode >= 400 && statusCode < 500 {
event = log.Warn()
} else if statusCode >= 500 {
event = log.Error()
}
event.
Str("method", req.Method).
Str("path", path).
Int("status", statusCode).
Str("ip", c.RealIP()).
Dur("latency", latency).
Str("user-agent", req.UserAgent()).
Msg(msg)
return err
}
}
}
// EchoRecovery returns an Echo middleware for panic recovery
func EchoRecovery() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
defer func() {
if err := recover(); err != nil {
log.Error().
Interface("error", err).
Str("path", c.Request().URL.Path).
Str("method", c.Request().Method).
Msg("Panic recovered")
c.JSON(500, map[string]interface{}{
"error": "Internal server error",
})
}
}()
return next(c)
}
}
}