package routes import ( "errors" "fmt" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/labstack/gommon/log" "net/http" "vibeStonk/server/repository" "vibeStonk/server/services" ) type ApiServer interface { AddRoutesBulk(routeProviders []Provider) error AddRoute(routeProvider Provider) error GetSystemServices() *services.SystemServices GetCommonMiddleware() *SystemMiddleware Start() error } type SystemMiddleware struct { UserAuth echo.MiddlewareFunc } func NewAPIServer(config *repository.Config) (ApiServer, error) { e := echo.New() e.HideBanner = true // Middleware e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.Use(middleware.CORS()) svcs, err := services.NewSystemServices(config) if err != nil { return nil, fmt.Errorf("failed to initialize server's system services: %w", err) } server := &apiServer{ e: e, config: config, Services: svcs, } // initialize common mware utilities mware := &SystemMiddleware{ UserAuth: server.AuthenticationMiddleware, } server.Middleware = mware server.developmentNonsense() return server, nil } type apiServer struct { e *echo.Echo config *repository.Config Services *services.SystemServices Middleware *SystemMiddleware } func (a *apiServer) addRoute(route *Route) error { var handler func(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route switch route.Method { case http.MethodPost: handler = a.e.POST case http.MethodGet: handler = a.e.GET case http.MethodPut: handler = a.e.PUT case http.MethodDelete: handler = a.e.DELETE default: return fmt.Errorf("failed to add Route; unknown method: %s", route.Method) } log.Infof("Registering route[%s]{%s}\n", route.Endpoint.String(), route.Method) handler(route.Endpoint.String(), route.HandlerFn, route.Middleware...) return nil } func (a *apiServer) AddRoute(provider Provider) error { for _, route := range provider.Provide(a.Middleware) { err := a.addRoute(route) if err != nil { return fmt.Errorf("failed to add route[%s]: %w", route.Endpoint.String(), err) } } return nil } func (a *apiServer) AddRoutesBulk(routeProviders []Provider) error { for _, rp := range routeProviders { err := a.AddRoute(rp) if err != nil { return err } } return nil } func (a *apiServer) developmentNonsense() { // this is here until we get a hosting | ci/cd pipeline established a.e.Static("/", "./content/") } func (a *apiServer) Start() error { return a.e.Start(a.config.ListenAddress) } func (a *apiServer) GetSystemServices() *services.SystemServices { return a.Services } func (a *apiServer) GetCommonMiddleware() *SystemMiddleware { return a.Middleware } // region system-level middleware const authHeader = "Bearer" var ( ErrNoAuthToken = errors.New("no 'Bearer' token header") ) func (a *apiServer) AuthenticationMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { token := c.Request().Header.Get(authHeader) if len(token) == 0 { c.Response().Status = http.StatusUnauthorized _, err := c.Response().Write([]byte(ErrNoAuthToken.Error())) if err != nil { return fmt.Errorf("failed to write error response: %w", err) } return nil } user, err := a.Services.AuthService.AuthenticateToken(token) if err != nil { c.Response().Status = http.StatusUnauthorized return fmt.Errorf("failed to authenticate user's bearer token: %w", err) } newRequestContext(c, user) return next(c) } } // endregion