subrouters
This commit is contained in:
+108
-125
@@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log/slog"
|
||||
@@ -12,11 +13,13 @@ import (
|
||||
|
||||
"github.com/angelbeltran/templater"
|
||||
|
||||
"ruben/inventory2/internal/consts"
|
||||
"ruben/inventory2/internal/domains/accounts"
|
||||
"ruben/inventory2/internal/domains/authentication"
|
||||
etsy_platform "ruben/inventory2/internal/domains/platforms/etsy"
|
||||
"ruben/inventory2/internal/domains/raw_events"
|
||||
accounts_api "ruben/inventory2/internal/server/api/accounts"
|
||||
auth_api "ruben/inventory2/internal/server/api/auth"
|
||||
templates_api "ruben/inventory2/internal/server/api/templates"
|
||||
"ruben/inventory2/internal/server/middleware"
|
||||
"ruben/inventory2/internal/server/response"
|
||||
"ruben/inventory2/internal/server/router"
|
||||
@@ -26,7 +29,7 @@ import (
|
||||
|
||||
type Server struct {
|
||||
log *slog.Logger
|
||||
http.Handler
|
||||
*router.Mux
|
||||
contentDir string
|
||||
templater *templater.Templater
|
||||
rawEvents *raw_events.Store
|
||||
@@ -45,97 +48,124 @@ func NewServer(
|
||||
etsy *etsy_platform.Platform,
|
||||
auth *authentication.Authenticator,
|
||||
) *Server {
|
||||
mux := router.NewMux(middleware.LogRequests(ctx, logger.WithGroup("request")))
|
||||
mux := router.NewMux(
|
||||
logger,
|
||||
middleware.LogRequests(ctx, logger.WithGroup("request")),
|
||||
)
|
||||
|
||||
tmpl := templater.NewTemplater(
|
||||
contentDir+"/templates",
|
||||
func() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
// params
|
||||
"addPathParam": func(k string, v any, args map[string]any) (map[string]any, error) {
|
||||
pathParams, ok := args["PathParams"].(map[string]string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("PathParams no set are args: %v", args)
|
||||
}
|
||||
|
||||
pathParams[k] = fmt.Sprint(v)
|
||||
|
||||
return args, nil
|
||||
},
|
||||
|
||||
// parsing
|
||||
"parseInt": func(s string) (int, error) {
|
||||
return strconv.Atoi(s)
|
||||
},
|
||||
"parseInt64": func(s string) (int64, error) {
|
||||
return strconv.ParseInt(s, 10, 64)
|
||||
},
|
||||
"parsePlatform": func(s string) (accounts.Platform, error) {
|
||||
return accounts.NewPlatform(s)
|
||||
},
|
||||
|
||||
// arithmetic
|
||||
"addInt": func(a, b int) int {
|
||||
return a + b
|
||||
},
|
||||
"subInt": func(a, b int) int {
|
||||
return a - b
|
||||
},
|
||||
"multInt": func(a, b int) int {
|
||||
return a * b
|
||||
},
|
||||
|
||||
// json
|
||||
"prettyPrintJSON": func(j json.RawMessage) string {
|
||||
b, err := json.MarshalIndent(j, " ", "")
|
||||
if err != nil {
|
||||
return string(j)
|
||||
}
|
||||
return string(b)
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
s := &Server{
|
||||
// TODO: eliminate fields that aren't needed anymore
|
||||
log: logger,
|
||||
Handler: mux,
|
||||
Mux: mux,
|
||||
contentDir: contentDir,
|
||||
templater: templater.NewTemplater(
|
||||
contentDir+"/templates",
|
||||
func() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
// params
|
||||
"addPathParam": func(k string, v any, args map[string]any) (map[string]any, error) {
|
||||
pathParams, ok := args["PathParams"].(map[string]string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("PathParams no set are args: %v", args)
|
||||
}
|
||||
|
||||
pathParams[k] = fmt.Sprint(v)
|
||||
|
||||
return args, nil
|
||||
},
|
||||
|
||||
// parsing
|
||||
"parseInt": func(s string) (int, error) {
|
||||
return strconv.Atoi(s)
|
||||
},
|
||||
"parseInt64": func(s string) (int64, error) {
|
||||
return strconv.ParseInt(s, 10, 64)
|
||||
},
|
||||
"parsePlatform": func(s string) (accounts.Platform, error) {
|
||||
return accounts.NewPlatform(s)
|
||||
},
|
||||
|
||||
// arithmetic
|
||||
"addInt": func(a, b int) int {
|
||||
return a + b
|
||||
},
|
||||
"subInt": func(a, b int) int {
|
||||
return a - b
|
||||
},
|
||||
"multInt": func(a, b int) int {
|
||||
return a * b
|
||||
},
|
||||
|
||||
// json
|
||||
"prettyPrintJSON": func(j json.RawMessage) string {
|
||||
b, err := json.MarshalIndent(j, " ", "")
|
||||
if err != nil {
|
||||
return string(j)
|
||||
}
|
||||
return string(b)
|
||||
},
|
||||
}
|
||||
},
|
||||
),
|
||||
rawEvents: rawEvents,
|
||||
accts: accts,
|
||||
etsy: etsy,
|
||||
auth: auth,
|
||||
templater: tmpl,
|
||||
rawEvents: rawEvents,
|
||||
accts: accts,
|
||||
etsy: etsy,
|
||||
auth: auth,
|
||||
// TODO: shouldn't this ACTUALLY be a middleware?
|
||||
// - only try to make this an actual middleware AFTER all the routers are broken out, so that way how the middleware is supposed to work can be known
|
||||
authMiddleware: middleware.NewAuth(
|
||||
logger.WithGroup("auth-middleware"),
|
||||
auth,
|
||||
newLoginURL,
|
||||
auth_api.NewLoginURL,
|
||||
accts,
|
||||
),
|
||||
}
|
||||
|
||||
withAuth := func(fn response.HandlerFunc) response.HandlerFunc {
|
||||
return s.authMiddleware.AuthenticateAndAddIdentity(fn)
|
||||
}
|
||||
// kind of a dumb way to capture routes for webpages
|
||||
wpr := templates_api.NewWebpageRouter(
|
||||
s.log.WithGroup("templates"),
|
||||
contentDir,
|
||||
tmpl,
|
||||
rawEvents,
|
||||
accts,
|
||||
etsy,
|
||||
s.authMiddleware,
|
||||
)
|
||||
mux.AddMiddleware(
|
||||
func(fn response.HandlerFunc) response.HandlerFunc {
|
||||
return func(r *http.Request) (response.Response, error) {
|
||||
res, err := fn(r)
|
||||
if err == nil || !errors.Is(err, router.ErrHandlerNotFound) {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// login
|
||||
renderWebPage, pathParams, found := wpr.Handler(r)
|
||||
if !found {
|
||||
return nil, response.NotFound()
|
||||
}
|
||||
|
||||
// TODO: consider a separate '/login or /auth' router
|
||||
mux.Handle("GET /login", s.loginPage)
|
||||
mux.Handle("GET /login/callback", s.loginCallback)
|
||||
mux.Handle("GET /logout", s.logoutPage)
|
||||
for k, v := range pathParams {
|
||||
r.SetPathValue(k, v)
|
||||
}
|
||||
|
||||
// TODO: consider a separate '/accounts' router
|
||||
// /accounts
|
||||
return renderWebPage(r)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
mux.Handle("POST /accounts", withAuth(s.createAccount))
|
||||
mux.Handle("POST /accounts/{acctID}/inventory/sync-groups/draft/listings", withAuth(s.createSyncGroupListingDraft))
|
||||
mux.Handle("PUT /accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}/shop", withAuth(s.setShopInSyncGroupListingDraft))
|
||||
mux.Handle("PUT /accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}/listing", withAuth(s.setListingInSyncGroupListingDraft))
|
||||
mux.Handle("DELETE /accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}", withAuth(s.deleteSyncGroupListingDraft))
|
||||
mux.Handle("POST /accounts/{acctID}/inventory/sync-groups", withAuth(s.saveNewSyncGroup))
|
||||
// api endpoints
|
||||
|
||||
mux.Route("/auth", auth_api.NewLoginSubrouter(s.log.WithGroup("/auth"), auth))
|
||||
mux.Route("/accounts", accounts_api.NewAccountSubrouter(
|
||||
s.log.WithGroup("/accounts"),
|
||||
s.accts,
|
||||
s.authMiddleware,
|
||||
))
|
||||
|
||||
// api webhooks (TODO: make a router for these)
|
||||
|
||||
// TODO: consider a webhook separate router
|
||||
// webhooks
|
||||
webhookHandler := http.StripPrefix("/webhooks", webhooks.New(
|
||||
logger.WithGroup("webhooks"),
|
||||
rawEvents,
|
||||
@@ -150,8 +180,6 @@ func NewServer(
|
||||
mux.Mux.Handle("POST /webhooks/", webhookHandler)
|
||||
mux.Mux.Handle("PUT /webhooks/", webhookHandler)
|
||||
|
||||
// TODO: consider a separate webpage router
|
||||
|
||||
// webpage content
|
||||
|
||||
// non-html content: scripts, styles, images, etc
|
||||
@@ -165,56 +193,11 @@ func NewServer(
|
||||
scfs.ServeHTTP(w, r)
|
||||
})))
|
||||
mux.Mux.Handle("GET /styles/", http.StripPrefix("/styles", http.FileServer(http.Dir(contentDir+"/styles"))))
|
||||
mux.Mux.Handle("GET /favicon/", http.StripPrefix("/favicon", http.FileServer(http.Dir(contentDir+"/favicon"))))
|
||||
|
||||
// html
|
||||
|
||||
// non-authenticated
|
||||
mux.Handle("GET /{$}", s.authMiddleware.AddIdentity(s.serveTemplates))
|
||||
// authenticated
|
||||
mux.Handle("GET /", withAuth(s.serveTemplates))
|
||||
mux.Route("/", wpr)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func mapConstantErrorsToHTTPErrors(err error) error {
|
||||
cerr := err
|
||||
for cerr != nil {
|
||||
switch cerr {
|
||||
case consts.ErrNotFound:
|
||||
return response.NotFound()
|
||||
case consts.ErrConflict:
|
||||
return response.Conflict()
|
||||
}
|
||||
|
||||
uerr, ok := cerr.(interface {
|
||||
Unwrap() error
|
||||
})
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
cerr = uerr.Unwrap()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func newRequestIDProvider(ctx context.Context) <-chan int {
|
||||
reqIDCh := make(chan int)
|
||||
|
||||
go func() {
|
||||
defer close(reqIDCh)
|
||||
|
||||
nextReqID := 1
|
||||
|
||||
for {
|
||||
select {
|
||||
case reqIDCh <- nextReqID:
|
||||
nextReqID += 1
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return reqIDCh
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user