vibeStonk/server/routes/server.go
2025-06-12 16:57:42 -04:00

156 lines
3.5 KiB
Go

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