Files
inventory-plus-plus/server/server.go
T
angelandClaude Sonnet 5 c41d5953c5
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s
Tests / Go tests (push) Successful in 34s
auth: split dev-mode auth constructor and wire up dev-login/logout UI
Squeamish about New()'s empty-domain-string sentinel for "dev mode, skip
OIDC discovery" - split into New (always makes a real OIDC discovery
call, all params required) and NewDev (no ctx/domain/credentials at all,
since none are used). main.go now branches on cfg.DevAuthEnabled to pick
the right constructor instead of main.go/config.go coordinating on when
it's safe to pass empty strings.

Also finishes out the dev-auth flow this enables: config.Load reads a
DEV_AUTH_ENABLED-aware env file and only requires Auth0 vars when dev
auth is off; a PORT config var replaces the hardcoded :8082; and the nav
UI (layout/index templates, ui router) points login/logout links at
/api/auth/dev-login and a new /api/auth/dev-logout route when dev auth
is enabled, so the whole login/logout loop works locally without a real
Auth0 app.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 00:24:04 -06:00

134 lines
2.6 KiB
Go

package server
import (
"context"
"net/http"
"path"
"strings"
"github.com/gin-gonic/gin"
"ruben/inventory2/domains/accounts"
"ruben/inventory2/domains/authentication"
etsy_platform "ruben/inventory2/domains/platforms/etsy"
"ruben/inventory2/domains/raw_events"
"ruben/inventory2/domains/reports"
"ruben/inventory2/logging"
"ruben/inventory2/server/api"
"ruben/inventory2/server/auth"
"ruben/inventory2/server/response"
"ruben/inventory2/server/sse"
"ruben/inventory2/server/ui"
)
type Router struct {
*gin.Engine
sse *sse.Queue
}
func NewRouter(
logger *logging.Logger,
contentDir string,
rawEvents *raw_events.Store,
accts *accounts.Store,
reps *reports.Store,
etsy *etsy_platform.Platform,
authr *authentication.Authenticator,
devAuthEnabled bool,
) *Router {
authM := auth.NewService(
logger.WithGroup("auth-middleware"),
authr,
accts,
)
r := gin.Default()
r.Use(
authM.Identify,
response.HandleResponses,
response.HandleErrors,
)
// webpage content
// top level GET is assumed to be for the home page.
r.GET("/", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/ui")
})
ui.Routes(
logger.WithGroup("ui"),
r.Group("/ui"),
"/ui",
rawEvents,
accts,
reps,
etsy,
authM.Authenticate(),
devAuthEnabled,
)
// non-html content: scripts, styles, images, etc
r.Use(fileServer("/scripts", contentDir+"/scripts", func(c *gin.Context) {
w := c.Writer
w.Header().Set("Content-Type", "text/javascript")
if path.Ext(c.Request.URL.Path) == ".gz" {
w.Header().Set("Content-Encoding", "gzip")
}
}))
r.Static("/styles", "./styles")
r.Static("/favicon", "./favicon")
r.Static("/images", "./images")
// api endpoints
// sse setup
sq := sse.NewQueue()
unp := sq.NewUpdateNotificationPublisher(
logger.WithGroup("update.notification.publisher"),
func(c *gin.Context) int64 {
return auth.GetIdentity(c).Account.AccountID
},
).Trim("/api")
api.Routes(
r.Group("/api"),
logger.WithGroup("/api"),
authM,
sq,
accts,
reps,
unp,
rawEvents,
etsy,
devAuthEnabled,
)
return &Router{
Engine: r,
sse: sq,
}
}
func (r *Router) RunSSE(ctx context.Context) error {
return r.sse.Start(ctx)
}
func (r *Router) GetSSEQueue() *sse.Queue {
return r.sse
}
func fileServer(urlPrefix, dir string, beforeServe func(c *gin.Context)) gin.HandlerFunc {
scfs := http.StripPrefix(urlPrefix, http.FileServer(http.Dir(dir)))
return func(c *gin.Context) {
r := c.Request
if r.URL.Path == urlPrefix || strings.HasPrefix(r.URL.Path, path.Join(urlPrefix, "/")) {
if beforeServe != nil {
beforeServe(c)
}
scfs.ServeHTTP(c.Writer, r)
c.Abort()
}
}
}