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 }