Initial commit

This commit is contained in:
2025-09-06 21:35:45 -04:00
commit b02525e28a
32 changed files with 1478 additions and 0 deletions

192
pkg/router/router.go Normal file
View File

@@ -0,0 +1,192 @@
package router
import (
"addrss/pkg/auth"
"addrss/pkg/config"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
)
var routeMap = map[string][]Route{}
type router struct{}
func Serve(addr string) error {
if len(routeMap) == 0 {
return errors.New("no registered routes")
}
return http.ListenAndServe(addr, router{})
}
func (r router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
route, ok := getRoute(req)
if !ok {
// See if the request is for a file that exists in the content directory
file := filepath.Join("content", req.URL.Path)
if info, err := os.Stat(file); err == nil && !info.IsDir() {
http.ServeFile(w, req, file)
return
}
w.WriteHeader(http.StatusNotFound)
return
}
ctx := Context{
Request: Request{req, map[string]any{}},
Response: Response{w},
Claims: auth.AccessClaims{},
route: route,
}
defer func() {
if rec := recover(); rec != nil {
err := fmt.Errorf("recovered from panic: %v", rec)
ctx.Response.InternalServerError(err)
}
}()
originHeader := req.Header.Get("Origin")
if originHeader != "" {
o, err := config.Get("cors.origins")
if err != nil {
panic(err)
}
origins := o.([]any)
for _, origin := range origins {
if origin.(string) == originHeader {
w.Header().Add("Access-Control-Allow-Origin", origin.(string))
}
}
}
if req.Method == http.MethodOptions {
acrh := req.Header.Get("Access-Control-Request-Headers")
acrm := req.Header.Get("Access-Control-Request-Method")
if route.method != acrm {
w.WriteHeader(http.StatusForbidden)
return
}
// Congrats, you get whatever headers you want
w.Header().Add("Access-Control-Allow-Headers", acrh)
w.Header().Add("Access-Control-Allow-Methods", acrm)
w.WriteHeader(http.StatusNoContent)
return
}
if !route.anonymous {
authSegments := strings.Split(req.Header.Get("Authorization"), " ")
if len(authSegments) != 2 || authSegments[0] != "Bearer" {
w.WriteHeader(http.StatusUnauthorized)
return
}
if err := auth.ValidateJwtToken(authSegments[1], &ctx.Claims); err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
}
if route.scope != "" {
if !strings.Contains(ctx.Claims.Scopes, route.scope) {
w.WriteHeader(http.StatusForbidden)
return
}
}
route.handler(&ctx)
}
func AddGet(pattern string, handler func(*Context)) Route {
return addRoute(http.MethodGet, pattern, handler)
}
func AddPost(pattern string, handler func(*Context)) Route {
return addRoute(http.MethodPost, pattern, handler)
}
func AddPut(pattern string, handler func(*Context)) Route {
return addRoute(http.MethodPut, pattern, handler)
}
func AddPatch(pattern string, handler func(*Context)) Route {
return addRoute(http.MethodPatch, pattern, handler)
}
func AddDelete(pattern string, handler func(*Context)) Route {
return addRoute(http.MethodDelete, pattern, handler)
}
func addRoute(method string, path string, handler func(*Context)) Route {
route := Route{
handler: handler,
method: method,
path: path,
}
if routeMap[method] == nil {
routeMap[method] = []Route{}
}
routeMap[method] = append(routeMap[method], route)
return route
}
func getRoute(req *http.Request) (Route, bool) {
method := req.Method
if method == http.MethodOptions {
method = req.Header.Get("Access-Control-Request-Method")
}
req.URL.Path = strings.TrimSuffix(req.URL.Path, "/")
for _, route := range routeMap[method] {
//if strings.Contains(route.path, ":") {
// rte, ok := regexMatchRoute(route, req.URL.Path)
// if ok {
// return rte, true
// }
//} else {
if route.path == req.URL.Path {
return route, true
}
//}
}
return Route{}, false
}
//func regexMatchRoute(route Route, path string) (Route, bool) {
// regex := regexp.MustCompile(`:[a-zA-Z]+`)
// matchPath := regex.ReplaceAllString(route.path, `([A-Za-z0-9\-_.]+)`)
// matchPath += "$"
// regex = regexp.MustCompile(matchPath)
//
// // matchPath := route.path
// if matches := regex.FindStringSubmatch(path); len(matches) > 0 {
// if len(matches) > 1 {
// route.params = map[string]any{}
//
// values := strings.Split(path, "/")
// for i, param := range strings.Split(route.path, "/") {
// if strings.Contains(param, ":") {
// route.params[param[1:]] = values[i]
// }
// }
// }
//
// return route, true
// }
//
// return route, false
//}