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 //}