chore: cleanup and route refactoring

Signed-off-by: allanice001 <allanice001@gmail.com>
This commit is contained in:
allanice001
2025-11-14 06:12:59 +00:00
parent b358911b1b
commit fc1c83ba18
51 changed files with 4099 additions and 539 deletions

View File

@@ -0,0 +1,26 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/api/httpmiddleware"
"github.com/glueops/autoglue/internal/bg"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountAdminRoutes(r chi.Router, db *gorm.DB, jobs *bg.Jobs, authUser func(http.Handler) http.Handler) {
r.Route("/admin", func(admin chi.Router) {
admin.Route("/archer", func(archer chi.Router) {
archer.Use(authUser)
archer.Use(httpmiddleware.RequirePlatformAdmin())
archer.Get("/jobs", handlers.AdminListArcherJobs(db))
archer.Post("/jobs", handlers.AdminEnqueueArcherJob(db, jobs))
archer.Post("/jobs/{id}/retry", handlers.AdminRetryArcherJob(db))
archer.Post("/jobs/{id}/cancel", handlers.AdminCancelArcherJob(db))
archer.Get("/queues", handlers.AdminListArcherQueues(db))
})
})
}

View File

@@ -0,0 +1,20 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountAnnotationRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/annotations", func(a chi.Router) {
a.Use(authOrg)
a.Get("/", handlers.ListAnnotations(db))
a.Post("/", handlers.CreateAnnotation(db))
a.Get("/{id}", handlers.GetAnnotation(db))
a.Patch("/{id}", handlers.UpdateAnnotation(db))
a.Delete("/{id}", handlers.DeleteAnnotation(db))
})
}

View File

@@ -0,0 +1,37 @@
package api
import (
"github.com/glueops/autoglue/internal/api/httpmiddleware"
"github.com/glueops/autoglue/internal/bg"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountAPIRoutes(r chi.Router, db *gorm.DB, jobs *bg.Jobs) {
r.Route("/api", func(api chi.Router) {
api.Route("/v1", func(v1 chi.Router) {
authUser := httpmiddleware.AuthMiddleware(db, false)
authOrg := httpmiddleware.AuthMiddleware(db, true)
// shared basics
mountMetaRoutes(v1)
mountAuthRoutes(v1, db)
// admin
mountAdminRoutes(v1, db, jobs, authUser)
// user/org scoped
mountMeRoutes(v1, db, authUser)
mountOrgRoutes(v1, db, authUser, authOrg)
mountCredentialRoutes(v1, db, authOrg)
mountSSHRoutes(v1, db, authOrg)
mountServerRoutes(v1, db, authOrg)
mountTaintRoutes(v1, db, authOrg)
mountLabelRoutes(v1, db, authOrg)
mountAnnotationRoutes(v1, db, authOrg)
mountNodePoolRoutes(v1, db, authOrg)
mountDNSRoutes(v1, db, authOrg)
})
})
}

View File

@@ -0,0 +1,16 @@
package api
import (
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountAuthRoutes(r chi.Router, db *gorm.DB) {
r.Route("/auth", func(a chi.Router) {
a.Post("/{provider}/start", handlers.AuthStart(db))
a.Get("/{provider}/callback", handlers.AuthCallback(db))
a.Post("/refresh", handlers.Refresh(db))
a.Post("/logout", handlers.Logout(db))
})
}

View File

@@ -0,0 +1,21 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountCredentialRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/credentials", func(c chi.Router) {
c.Use(authOrg)
c.Get("/", handlers.ListCredentials(db))
c.Post("/", handlers.CreateCredential(db))
c.Get("/{id}", handlers.GetCredential(db))
c.Patch("/{id}", handlers.UpdateCredential(db))
c.Delete("/{id}", handlers.DeleteCredential(db))
c.Post("/{id}/reveal", handlers.RevealCredential(db))
})
}

View File

@@ -0,0 +1,53 @@
package api
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
pgapi "github.com/sosedoff/pgweb/pkg/api"
pgclient "github.com/sosedoff/pgweb/pkg/client"
pgcmd "github.com/sosedoff/pgweb/pkg/command"
)
func PgwebHandler(dbURL, prefix string, readonly bool) (http.Handler, error) {
// Normalize prefix for pgweb:
// - no leading slash
// - always trailing slash if not empty
prefix = strings.Trim(prefix, "/")
if prefix != "" {
prefix = prefix + "/"
}
pgcmd.Opts = pgcmd.Options{
URL: dbURL,
Prefix: prefix, // e.g. "db-studio/"
ReadOnly: readonly,
Sessions: false,
LockSession: true,
SkipOpen: true,
}
cli, err := pgclient.NewFromUrl(dbURL, nil)
if err != nil {
return nil, err
}
if readonly {
_ = cli.SetReadOnlyMode()
}
if err := cli.Test(); err != nil {
return nil, err
}
pgapi.DbClient = cli
gin.SetMode(gin.ReleaseMode)
g := gin.New()
g.Use(gin.Recovery())
pgapi.SetupRoutes(g)
pgapi.SetupMetrics(g)
return g, nil
}

View File

@@ -0,0 +1,26 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountDNSRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/dns", func(d chi.Router) {
d.Use(authOrg)
d.Get("/domains", handlers.ListDomains(db))
d.Post("/domains", handlers.CreateDomain(db))
d.Get("/domains/{id}", handlers.GetDomain(db))
d.Patch("/domains/{id}", handlers.UpdateDomain(db))
d.Delete("/domains/{id}", handlers.DeleteDomain(db))
d.Get("/domains/{domain_id}/records", handlers.ListRecordSets(db))
d.Post("/domains/{domain_id}/records", handlers.CreateRecordSet(db))
d.Patch("/records/{id}", handlers.UpdateRecordSet(db))
d.Delete("/records/{id}", handlers.DeleteRecordSet(db))
})
}

View File

@@ -0,0 +1,20 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountLabelRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/labels", func(l chi.Router) {
l.Use(authOrg)
l.Get("/", handlers.ListLabels(db))
l.Post("/", handlers.CreateLabel(db))
l.Get("/{id}", handlers.GetLabel(db))
l.Patch("/{id}", handlers.UpdateLabel(db))
l.Delete("/{id}", handlers.DeleteLabel(db))
})
}

View File

@@ -0,0 +1,22 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountMeRoutes(r chi.Router, db *gorm.DB, authUser func(http.Handler) http.Handler) {
r.Route("/me", func(me chi.Router) {
me.Use(authUser)
me.Get("/", handlers.GetMe(db))
me.Patch("/", handlers.UpdateMe(db))
me.Get("/api-keys", handlers.ListUserAPIKeys(db))
me.Post("/api-keys", handlers.CreateUserAPIKey(db))
me.Delete("/api-keys/{id}", handlers.DeleteUserAPIKey(db))
})
}

View File

@@ -0,0 +1,13 @@
package api
import (
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
)
func mountMetaRoutes(r chi.Router) {
// Versioned JWKS for swagger
r.Get("/.well-known/jwks.json", handlers.JWKSHandler)
r.Get("/healthz", handlers.HealthCheck)
r.Get("/version", handlers.Version)
}

View File

@@ -0,0 +1,40 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountNodePoolRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/node-pools", func(n chi.Router) {
n.Use(authOrg)
n.Get("/", handlers.ListNodePools(db))
n.Post("/", handlers.CreateNodePool(db))
n.Get("/{id}", handlers.GetNodePool(db))
n.Patch("/{id}", handlers.UpdateNodePool(db))
n.Delete("/{id}", handlers.DeleteNodePool(db))
// Servers
n.Get("/{id}/servers", handlers.ListNodePoolServers(db))
n.Post("/{id}/servers", handlers.AttachNodePoolServers(db))
n.Delete("/{id}/servers/{serverId}", handlers.DetachNodePoolServer(db))
// Taints
n.Get("/{id}/taints", handlers.ListNodePoolTaints(db))
n.Post("/{id}/taints", handlers.AttachNodePoolTaints(db))
n.Delete("/{id}/taints/{taintId}", handlers.DetachNodePoolTaint(db))
// Labels
n.Get("/{id}/labels", handlers.ListNodePoolLabels(db))
n.Post("/{id}/labels", handlers.AttachNodePoolLabels(db))
n.Delete("/{id}/labels/{labelId}", handlers.DetachNodePoolLabel(db))
// Annotations
n.Get("/{id}/annotations", handlers.ListNodePoolAnnotations(db))
n.Post("/{id}/annotations", handlers.AttachNodePoolAnnotations(db))
n.Delete("/{id}/annotations/{annotationId}", handlers.DetachNodePoolAnnotation(db))
})
}

View File

@@ -0,0 +1,35 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountOrgRoutes(r chi.Router, db *gorm.DB, authUser, authOrg func(http.Handler) http.Handler) {
r.Route("/orgs", func(o chi.Router) {
o.Use(authUser)
o.Get("/", handlers.ListMyOrgs(db))
o.Post("/", handlers.CreateOrg(db))
o.Group(func(og chi.Router) {
og.Use(authOrg)
og.Get("/{id}", handlers.GetOrg(db))
og.Patch("/{id}", handlers.UpdateOrg(db))
og.Delete("/{id}", handlers.DeleteOrg(db))
// members
og.Get("/{id}/members", handlers.ListMembers(db))
og.Post("/{id}/members", handlers.AddOrUpdateMember(db))
og.Delete("/{id}/members/{user_id}", handlers.RemoveMember(db))
// org-scoped key/secret pair
og.Get("/{id}/api-keys", handlers.ListOrgKeys(db))
og.Post("/{id}/api-keys", handlers.CreateOrgKey(db))
og.Delete("/{id}/api-keys/{key_id}", handlers.DeleteOrgKey(db))
})
})
}

View File

@@ -0,0 +1,24 @@
package api
import (
httpPprof "net/http/pprof"
"github.com/go-chi/chi/v5"
)
func mountPprofRoutes(r chi.Router) {
r.Route("/debug/pprof", func(pr chi.Router) {
pr.Get("/", httpPprof.Index)
pr.Get("/cmdline", httpPprof.Cmdline)
pr.Get("/profile", httpPprof.Profile)
pr.Get("/symbol", httpPprof.Symbol)
pr.Get("/trace", httpPprof.Trace)
pr.Handle("/allocs", httpPprof.Handler("allocs"))
pr.Handle("/block", httpPprof.Handler("block"))
pr.Handle("/goroutine", httpPprof.Handler("goroutine"))
pr.Handle("/heap", httpPprof.Handler("heap"))
pr.Handle("/mutex", httpPprof.Handler("mutex"))
pr.Handle("/threadcreate", httpPprof.Handler("threadcreate"))
})
}

View File

@@ -0,0 +1,21 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountServerRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/servers", func(s chi.Router) {
s.Use(authOrg)
s.Get("/", handlers.ListServers(db))
s.Post("/", handlers.CreateServer(db))
s.Get("/{id}", handlers.GetServer(db))
s.Patch("/{id}", handlers.UpdateServer(db))
s.Delete("/{id}", handlers.DeleteServer(db))
s.Post("/{id}/reset-hostkey", handlers.ResetServerHostKey(db))
})
}

View File

@@ -0,0 +1,20 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountSSHRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/ssh", func(s chi.Router) {
s.Use(authOrg)
s.Get("/", handlers.ListPublicSshKeys(db))
s.Post("/", handlers.CreateSSHKey(db))
s.Get("/{id}", handlers.GetSSHKey(db))
s.Delete("/{id}", handlers.DeleteSSHKey(db))
s.Get("/{id}/download", handlers.DownloadSSHKey(db))
})
}

View File

@@ -0,0 +1,15 @@
package api
import (
"github.com/glueops/autoglue/docs"
"github.com/go-chi/chi/v5"
httpSwagger "github.com/swaggo/http-swagger/v2"
)
func mountSwaggerRoutes(r chi.Router) {
r.Get("/swagger/*", httpSwagger.Handler(
httpSwagger.URL("swagger.json"),
))
r.Get("/swagger/swagger.json", serveSwaggerFromEmbed(docs.SwaggerJSON, "application/json"))
r.Get("/swagger/swagger.yaml", serveSwaggerFromEmbed(docs.SwaggerYAML, "application/x-yaml"))
}

View File

@@ -0,0 +1,20 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountTaintRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/taints", func(t chi.Router) {
t.Use(authOrg)
t.Get("/", handlers.ListTaints(db))
t.Post("/", handlers.CreateTaint(db))
t.Get("/{id}", handlers.GetTaint(db))
t.Patch("/{id}", handlers.UpdateTaint(db))
t.Delete("/{id}", handlers.DeleteTaint(db))
})
}

View File

@@ -36,7 +36,7 @@ func SecurityHeaders(next http.Handler) http.Handler {
// Google font files
"font-src 'self' data: https://fonts.gstatic.com",
// HMR connections
"connect-src 'self' http://localhost:5173 ws://localhost:5173 ws://localhost:8080",
"connect-src 'self' http://localhost:5173 ws://localhost:5173 ws://localhost:8080 https://api.github.com",
"frame-ancestors 'none'",
}, "; "))
} else {
@@ -53,7 +53,7 @@ func SecurityHeaders(next http.Handler) http.Handler {
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: blob:",
"font-src 'self' data: https://fonts.gstatic.com",
"connect-src 'self'",
"connect-src 'self' ws://localhost:8080 https://api.github.com",
"frame-ancestors 'none'",
}, "; "))
}

View File

@@ -3,11 +3,10 @@ package api
import (
"fmt"
"net/http"
httpPprof "net/http/pprof"
"os"
"strings"
"time"
"github.com/glueops/autoglue/docs"
"github.com/glueops/autoglue/internal/api/httpmiddleware"
"github.com/glueops/autoglue/internal/bg"
"github.com/glueops/autoglue/internal/config"
@@ -23,8 +22,6 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
httpSwagger "github.com/swaggo/http-swagger/v2"
)
func NewRouter(db *gorm.DB, jobs *bg.Jobs, studio http.Handler) http.Handler {
@@ -38,7 +35,6 @@ func NewRouter(db *gorm.DB, jobs *bg.Jobs, studio http.Handler) http.Handler {
r.Use(middleware.RealIP)
r.Use(zeroLogMiddleware())
r.Use(middleware.Recoverer)
// r.Use(middleware.RedirectSlashes)
r.Use(SecurityHeaders)
r.Use(requestBodyLimit(10 << 20))
r.Use(httprate.LimitByIP(100, 1*time.Minute))
@@ -60,198 +56,44 @@ func NewRouter(db *gorm.DB, jobs *bg.Jobs, studio http.Handler) http.Handler {
MaxAge: 600,
}))
r.Use(middleware.AllowContentType("application/json"))
r.Use(middleware.Maybe(
middleware.AllowContentType("application/json"),
func(r *http.Request) bool {
// return true => run AllowContentType
// return false => skip AllowContentType for this request
return !strings.HasPrefix(r.URL.Path, "/db-studio")
}))
//r.Use(middleware.AllowContentType("application/json"))
// Unversioned, non-auth endpoints
r.Get("/.well-known/jwks.json", handlers.JWKSHandler)
r.Route("/api", func(api chi.Router) {
api.Route("/v1", func(v1 chi.Router) {
authUser := httpmiddleware.AuthMiddleware(db, false)
authOrg := httpmiddleware.AuthMiddleware(db, true)
// Also serving a versioned JWKS for swagger, which uses BasePath
v1.Get("/.well-known/jwks.json", handlers.JWKSHandler)
v1.Get("/healthz", handlers.HealthCheck)
v1.Get("/version", handlers.Version)
v1.Route("/auth", func(a chi.Router) {
a.Post("/{provider}/start", handlers.AuthStart(db))
a.Get("/{provider}/callback", handlers.AuthCallback(db))
a.Post("/refresh", handlers.Refresh(db))
a.Post("/logout", handlers.Logout(db))
})
v1.Route("/admin", func(admin chi.Router) {
admin.Route("/archer", func(archer chi.Router) {
archer.Use(authUser)
archer.Use(httpmiddleware.RequirePlatformAdmin())
archer.Get("/jobs", handlers.AdminListArcherJobs(db))
archer.Post("/jobs", handlers.AdminEnqueueArcherJob(db, jobs))
archer.Post("/jobs/{id}/retry", handlers.AdminRetryArcherJob(db))
archer.Post("/jobs/{id}/cancel", handlers.AdminCancelArcherJob(db))
archer.Get("/queues", handlers.AdminListArcherQueues(db))
})
})
v1.Route("/me", func(me chi.Router) {
me.Use(authUser)
me.Get("/", handlers.GetMe(db))
me.Patch("/", handlers.UpdateMe(db))
me.Get("/api-keys", handlers.ListUserAPIKeys(db))
me.Post("/api-keys", handlers.CreateUserAPIKey(db))
me.Delete("/api-keys/{id}", handlers.DeleteUserAPIKey(db))
})
v1.Route("/orgs", func(o chi.Router) {
o.Use(authUser)
o.Get("/", handlers.ListMyOrgs(db))
o.Post("/", handlers.CreateOrg(db))
o.Group(func(og chi.Router) {
og.Use(authOrg)
og.Get("/{id}", handlers.GetOrg(db))
og.Patch("/{id}", handlers.UpdateOrg(db))
og.Delete("/{id}", handlers.DeleteOrg(db))
// members
og.Get("/{id}/members", handlers.ListMembers(db))
og.Post("/{id}/members", handlers.AddOrUpdateMember(db))
og.Delete("/{id}/members/{user_id}", handlers.RemoveMember(db))
// org-scoped key/secret pair
og.Get("/{id}/api-keys", handlers.ListOrgKeys(db))
og.Post("/{id}/api-keys", handlers.CreateOrgKey(db))
og.Delete("/{id}/api-keys/{key_id}", handlers.DeleteOrgKey(db))
})
})
v1.Route("/credentials", func(c chi.Router) {
c.Use(authOrg)
c.Get("/", handlers.ListCredentials(db))
c.Post("/", handlers.CreateCredential(db))
c.Get("/{id}", handlers.GetCredential(db))
c.Patch("/{id}", handlers.UpdateCredential(db))
c.Delete("/{id}", handlers.DeleteCredential(db))
c.Post("/{id}/reveal", handlers.RevealCredential(db))
})
v1.Route("/ssh", func(s chi.Router) {
s.Use(authOrg)
s.Get("/", handlers.ListPublicSshKeys(db))
s.Post("/", handlers.CreateSSHKey(db))
s.Get("/{id}", handlers.GetSSHKey(db))
s.Delete("/{id}", handlers.DeleteSSHKey(db))
s.Get("/{id}/download", handlers.DownloadSSHKey(db))
})
v1.Route("/servers", func(s chi.Router) {
s.Use(authOrg)
s.Get("/", handlers.ListServers(db))
s.Post("/", handlers.CreateServer(db))
s.Get("/{id}", handlers.GetServer(db))
s.Patch("/{id}", handlers.UpdateServer(db))
s.Delete("/{id}", handlers.DeleteServer(db))
})
v1.Route("/taints", func(s chi.Router) {
s.Use(authOrg)
s.Get("/", handlers.ListTaints(db))
s.Post("/", handlers.CreateTaint(db))
s.Get("/{id}", handlers.GetTaint(db))
s.Patch("/{id}", handlers.UpdateTaint(db))
s.Delete("/{id}", handlers.DeleteTaint(db))
})
v1.Route("/labels", func(l chi.Router) {
l.Use(authOrg)
l.Get("/", handlers.ListLabels(db))
l.Post("/", handlers.CreateLabel(db))
l.Get("/{id}", handlers.GetLabel(db))
l.Patch("/{id}", handlers.UpdateLabel(db))
l.Delete("/{id}", handlers.DeleteLabel(db))
})
v1.Route("/annotations", func(a chi.Router) {
a.Use(authOrg)
a.Get("/", handlers.ListAnnotations(db))
a.Post("/", handlers.CreateAnnotation(db))
a.Get("/{id}", handlers.GetAnnotation(db))
a.Patch("/{id}", handlers.UpdateAnnotation(db))
a.Delete("/{id}", handlers.DeleteAnnotation(db))
})
v1.Route("/node-pools", func(n chi.Router) {
n.Use(authOrg)
n.Get("/", handlers.ListNodePools(db))
n.Post("/", handlers.CreateNodePool(db))
n.Get("/{id}", handlers.GetNodePool(db))
n.Patch("/{id}", handlers.UpdateNodePool(db))
n.Delete("/{id}", handlers.DeleteNodePool(db))
// Servers
n.Get("/{id}/servers", handlers.ListNodePoolServers(db))
n.Post("/{id}/servers", handlers.AttachNodePoolServers(db))
n.Delete("/{id}/servers/{serverId}", handlers.DetachNodePoolServer(db))
// Taints
n.Get("/{id}/taints", handlers.ListNodePoolTaints(db))
n.Post("/{id}/taints", handlers.AttachNodePoolTaints(db))
n.Delete("/{id}/taints/{taintId}", handlers.DetachNodePoolTaint(db))
// Labels
n.Get("/{id}/labels", handlers.ListNodePoolLabels(db))
n.Post("/{id}/labels", handlers.AttachNodePoolLabels(db))
n.Delete("/{id}/labels/{labelId}", handlers.DetachNodePoolLabel(db))
// Annotations
n.Get("/{id}/annotations", handlers.ListNodePoolAnnotations(db))
n.Post("/{id}/annotations", handlers.AttachNodePoolAnnotations(db))
n.Delete("/{id}/annotations/{annotationId}", handlers.DetachNodePoolAnnotation(db))
})
})
})
// Versioned API
mountAPIRoutes(r, db, jobs)
// Optional DB studio
if studio != nil {
r.Group(func(gr chi.Router) {
authUser := httpmiddleware.AuthMiddleware(db, false)
adminOnly := httpmiddleware.RequirePlatformAdmin()
gr.Use(authUser)
gr.Use(adminOnly)
gr.Use(authUser, adminOnly)
gr.Mount("/db-studio", studio)
})
}
// pprof
if config.IsDebug() {
r.Route("/debug/pprof", func(pr chi.Router) {
pr.Get("/", httpPprof.Index)
pr.Get("/cmdline", httpPprof.Cmdline)
pr.Get("/profile", httpPprof.Profile)
pr.Get("/symbol", httpPprof.Symbol)
pr.Get("/trace", httpPprof.Trace)
pr.Handle("/allocs", httpPprof.Handler("allocs"))
pr.Handle("/block", httpPprof.Handler("block"))
pr.Handle("/goroutine", httpPprof.Handler("goroutine"))
pr.Handle("/heap", httpPprof.Handler("heap"))
pr.Handle("/mutex", httpPprof.Handler("mutex"))
pr.Handle("/threadcreate", httpPprof.Handler("threadcreate"))
})
mountPprofRoutes(r)
}
// Swagger
if config.IsSwaggerEnabled() {
r.Get("/swagger/*", httpSwagger.Handler(
httpSwagger.URL("swagger.json"),
))
r.Get("/swagger/swagger.json", serveSwaggerFromEmbed(docs.SwaggerJSON, "application/json"))
r.Get("/swagger/swagger.yaml", serveSwaggerFromEmbed(docs.SwaggerYAML, "application/x-yaml"))
mountSwaggerRoutes(r)
}
// UI dev/prod
if config.IsUIDev() {
fmt.Println("Running in development mode")
// Dev: isolate proxy from chi middlewares so WS upgrade can hijack.
proxy, err := web.DevProxy("http://localhost:5173")
if err != nil {
log.Error().Err(err).Msg("dev proxy init failed")
@@ -259,23 +101,20 @@ func NewRouter(db *gorm.DB, jobs *bg.Jobs, studio http.Handler) http.Handler {
}
mux := http.NewServeMux()
// Send API/Swagger/pprof to chi
mux.Handle("/api/", r)
mux.Handle("/api", r)
mux.Handle("/swagger/", r)
mux.Handle("/db-studio/", r)
mux.Handle("/debug/pprof/", r)
// Everything else (/, /brand-preview, assets) → proxy (no middlewares)
mux.Handle("/", proxy)
return mux
}
fmt.Println("Running in production mode")
if h, err := web.SPAHandler(); err == nil {
r.NotFound(h.ServeHTTP)
} else {
fmt.Println("Running in production mode")
if h, err := web.SPAHandler(); err == nil {
r.NotFound(h.ServeHTTP)
} else {
log.Error().Err(err).Msg("spa handler init failed")
}
log.Error().Err(err).Msg("spa handler init failed")
}
return r