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>
98 lines
2.1 KiB
Go
98 lines
2.1 KiB
Go
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
|
|
}
|