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:
97
internal/apperrors/errors.go
Normal file
97
internal/apperrors/errors.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package apperrors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// AppError represents an application error with HTTP status and i18n key
|
||||
type AppError struct {
|
||||
Code int // HTTP status code
|
||||
Message string // Default message (fallback if i18n key not found)
|
||||
MessageKey string // i18n key for localization
|
||||
Err error // Wrapped error (for internal errors)
|
||||
}
|
||||
|
||||
func (e *AppError) Error() string {
|
||||
if e.Err != nil {
|
||||
return fmt.Sprintf("%s: %v", e.Message, e.Err)
|
||||
}
|
||||
if e.Message != "" {
|
||||
return e.Message
|
||||
}
|
||||
return e.MessageKey
|
||||
}
|
||||
|
||||
func (e *AppError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NotFound creates a 404 Not Found error
|
||||
func NotFound(messageKey string) *AppError {
|
||||
return &AppError{
|
||||
Code: http.StatusNotFound,
|
||||
MessageKey: messageKey,
|
||||
}
|
||||
}
|
||||
|
||||
// Forbidden creates a 403 Forbidden error
|
||||
func Forbidden(messageKey string) *AppError {
|
||||
return &AppError{
|
||||
Code: http.StatusForbidden,
|
||||
MessageKey: messageKey,
|
||||
}
|
||||
}
|
||||
|
||||
// BadRequest creates a 400 Bad Request error
|
||||
func BadRequest(messageKey string) *AppError {
|
||||
return &AppError{
|
||||
Code: http.StatusBadRequest,
|
||||
MessageKey: messageKey,
|
||||
}
|
||||
}
|
||||
|
||||
// Unauthorized creates a 401 Unauthorized error
|
||||
func Unauthorized(messageKey string) *AppError {
|
||||
return &AppError{
|
||||
Code: http.StatusUnauthorized,
|
||||
MessageKey: messageKey,
|
||||
}
|
||||
}
|
||||
|
||||
// Conflict creates a 409 Conflict error
|
||||
func Conflict(messageKey string) *AppError {
|
||||
return &AppError{
|
||||
Code: http.StatusConflict,
|
||||
MessageKey: messageKey,
|
||||
}
|
||||
}
|
||||
|
||||
// TooManyRequests creates a 429 Too Many Requests error
|
||||
func TooManyRequests(messageKey string) *AppError {
|
||||
return &AppError{
|
||||
Code: http.StatusTooManyRequests,
|
||||
MessageKey: messageKey,
|
||||
}
|
||||
}
|
||||
|
||||
// Internal creates a 500 Internal Server Error, wrapping the original error
|
||||
func Internal(err error) *AppError {
|
||||
return &AppError{
|
||||
Code: http.StatusInternalServerError,
|
||||
MessageKey: "error.internal",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage adds a default message to the error (used when i18n key not found)
|
||||
func (e *AppError) WithMessage(msg string) *AppError {
|
||||
e.Message = msg
|
||||
return e
|
||||
}
|
||||
|
||||
// Wrap wraps an underlying error
|
||||
func (e *AppError) Wrap(err error) *AppError {
|
||||
e.Err = err
|
||||
return e
|
||||
}
|
||||
Reference in New Issue
Block a user