a1d93f7a8e
Go backend with Gin, pgx, Valkey (go-valkey), and PostGIS. Domains: - Market search with PostGIS geo-queries (ST_DWithin, ST_Distance), German full-text search (tsvector + ILIKE fallback for compound words), date range filtering, pagination, and slug-based detail endpoint - Auth with email+password (bcrypt), JWT access tokens (15min), session tokens (30d, dual Valkey+Postgres storage), OAuth (Google/GitHub/Facebook), magic links, and TOTP 2FA - User profile with CRUD, soft-delete (30d grace), and restore Infrastructure: - 6 database migrations (users, sessions, oauth_accounts, magic_links, markets with PostGIS+FTS, totp_secrets) - Middleware: recovery, request ID, structured logging (slog), CORS, per-IP rate limiting, JWT auth - Seed data: 10 medieval markets across DACH region - Docker Compose (PostGIS 17 + Valkey 8), multi-stage Dockerfile, Woodpecker CI pipeline, Kubernetes manifests - Justfile, golangci-lint config, env example
83 lines
1.8 KiB
Go
83 lines
1.8 KiB
Go
package apierror
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
type Error struct {
|
|
Status int `json:"-"`
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func (e *Error) Error() string {
|
|
return fmt.Sprintf("[%d] %s: %s", e.Status, e.Code, e.Message)
|
|
}
|
|
|
|
type Response struct {
|
|
Error *ErrorBody `json:"error"`
|
|
}
|
|
|
|
type ErrorBody struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func NewResponse(e *Error) Response {
|
|
return Response{
|
|
Error: &ErrorBody{
|
|
Code: e.Code,
|
|
Message: e.Message,
|
|
},
|
|
}
|
|
}
|
|
|
|
func BadRequest(code, message string) *Error {
|
|
return &Error{Status: http.StatusBadRequest, Code: code, Message: message}
|
|
}
|
|
|
|
func Unauthorized(message string) *Error {
|
|
return &Error{Status: http.StatusUnauthorized, Code: "unauthorized", Message: message}
|
|
}
|
|
|
|
func Forbidden(message string) *Error {
|
|
return &Error{Status: http.StatusForbidden, Code: "forbidden", Message: message}
|
|
}
|
|
|
|
func NotFound(resource string) *Error {
|
|
return &Error{
|
|
Status: http.StatusNotFound,
|
|
Code: "not_found",
|
|
Message: fmt.Sprintf("%s not found", resource),
|
|
}
|
|
}
|
|
|
|
func Conflict(message string) *Error {
|
|
return &Error{Status: http.StatusConflict, Code: "conflict", Message: message}
|
|
}
|
|
|
|
func TooManyRequests() *Error {
|
|
return &Error{
|
|
Status: http.StatusTooManyRequests,
|
|
Code: "rate_limited",
|
|
Message: "too many requests, please try again later",
|
|
}
|
|
}
|
|
|
|
func Internal(message string) *Error {
|
|
return &Error{
|
|
Status: http.StatusInternalServerError,
|
|
Code: "internal_error",
|
|
Message: message,
|
|
}
|
|
}
|
|
|
|
func Validation(message string) *Error {
|
|
return &Error{Status: http.StatusUnprocessableEntity, Code: "validation_error", Message: message}
|
|
}
|
|
|
|
func Gone(message string) *Error {
|
|
return &Error{Status: http.StatusGone, Code: "gone", Message: message}
|
|
}
|