house/backend/router/route.go
2025-06-14 23:47:44 -04:00

204 lines
4.5 KiB
Go

package router
import (
"context"
"database/sql"
"encoding/json"
"net/http"
"net/url"
"time"
"git.red-panda.pet/pandaware/house/backend/db"
"github.com/charmbracelet/log"
)
type IRequestContext interface {
Search(key string) string
Parameter(key string) string
Cookie(name string) (*http.Cookie, error)
SetCookie(cookie *http.Cookie)
JSON(statusCode int, body any) error
Redirect(statusCode int, url string) error
Text(statusCode int, body string) error
Bytes(statusCode int, body []byte) error
With(key any, value any)
}
type RequestError struct {
Inner error `json:"-"`
RequestID string `json:"requestID"`
StatusCode int `json:"statusCode"`
Message string `json:"message"`
}
// Error implements error.
func (r *RequestError) Error() string {
return r.Inner.Error()
}
var _ error = new(RequestError)
type Context struct {
id string
statusCode int
start time.Time
log *log.Logger
context context.Context
search url.Values
resp http.ResponseWriter
Request *http.Request
DB *sql.Conn
Query *db.Queries
}
// Redirect implements IRequestContext.
func (r *Context) Redirect(statusCode int, url string) error {
http.Redirect(r, r.Request, url, statusCode)
return nil
}
// With implements IRequestContext.
func (r *Context) With(key, value any) {
r.context = context.WithValue(r.context, key, value)
}
// Bytes implements IRequestContext.
func (r *Context) Bytes(statusCode int, body []byte) error {
r.WriteHeader(statusCode)
_, err := r.Write(body)
return err
}
// Text implements IRequestContext.
func (r *Context) Text(statusCode int, body string) error {
r.Header().Set("Content-Type", "text/plain")
return r.Bytes(statusCode, []byte(body))
}
// JSON implements IRequestContext.
func (r *Context) JSON(statusCode int, body any) error {
enc := json.NewEncoder(r)
r.Header().Set("Content-Type", "application/json")
r.WriteHeader(statusCode)
return enc.Encode(body)
}
func (r *Context) Error(err error, statusCode int, message string) error {
return &RequestError{
Inner: err,
RequestID: r.id,
StatusCode: statusCode,
Message: message,
}
}
func (r *Context) GenericError(err error, statusCode int) error {
message := "Error"
switch statusCode {
case http.StatusBadRequest:
message = "Bad request"
case http.StatusUnauthorized:
message = "Unauthorized"
case http.StatusNotFound:
message = "Not found"
case http.StatusInternalServerError:
message = "Internal server error"
}
return r.Error(err, statusCode, message)
}
// Cookie implements IRequestContext.
func (r *Context) Cookie(name string) (*http.Cookie, error) {
return r.Request.Cookie(name)
}
// Parameter implements IRequestContext.
func (r *Context) Parameter(key string) string {
return r.Request.PathValue(key)
}
// Search implements IRequestContext.
func (r *Context) Search(key string) string {
if r.search == nil {
r.search = r.Request.URL.Query()
}
return r.search.Get(key)
}
// SetCookie implements IRequestContext.
func (r *Context) SetCookie(cookie *http.Cookie) {
http.SetCookie(r, cookie)
}
// Deadline implements context.Context.
func (r *Context) Deadline() (deadline time.Time, ok bool) {
return r.context.Deadline()
}
// Done implements context.Context.
func (r *Context) Done() <-chan struct{} {
return r.context.Done()
}
// Err implements context.Context.
func (r *Context) Err() error {
return r.context.Err()
}
// Value implements context.Context.
func (r *Context) Value(key any) any {
return r.context.Value(key)
}
// Header implements http.ResponseWriter.
func (r *Context) Header() http.Header {
return r.resp.Header()
}
// Write implements http.ResponseWriter.
func (r *Context) Write(bs []byte) (int, error) {
if r.statusCode == 0 {
r.statusCode = 200
}
r.log.Helper()
r.log.Info("",
"id", r.id,
"duration", time.Since(r.start),
"status", r.statusCode,
)
return r.resp.Write(bs)
}
// WriteHeader implements http.ResponseWriter.
func (r *Context) WriteHeader(statusCode int) {
r.statusCode = statusCode
r.resp.WriteHeader(statusCode)
}
var _ context.Context = new(Context)
var _ http.ResponseWriter = new(Context)
var _ IRequestContext = new(Context)
type Route interface {
Handle(ctx *Context) error
}
type AuthorizedRoute interface {
Authorize(ctx *Context) error
}
type ValidatedSearchRoute interface {
ValidateSearch(ctx *Context) error
}
type ValidatedBodyRoute interface {
ValidateBody(ctx *Context) error
}
type ValidatedParamRoute interface {
ValidateParams(ctx *Context) error
}