204 lines
4.5 KiB
Go
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
|
|
}
|