summaryrefslogtreecommitdiff
path: root/api/serve.go
diff options
context:
space:
mode:
authorElizabeth Alexander Hunt <me@liz.coffee>2026-06-20 09:40:07 -0700
committerElizabeth Alexander Hunt <me@liz.coffee>2026-06-20 09:40:07 -0700
commitfb653292612eddca5b088ed88466c546d1597107 (patch)
tree900ed32e066812688858ed3bb15128c41bb78054 /api/serve.go
downloadpenguins.lan-fb653292612eddca5b088ed88466c546d1597107.tar.gz
penguins.lan-fb653292612eddca5b088ed88466c546d1597107.zip
Initial commit
Diffstat (limited to 'api/serve.go')
-rw-r--r--api/serve.go165
1 files changed, 165 insertions, 0 deletions
diff --git a/api/serve.go b/api/serve.go
new file mode 100644
index 0000000..0005369
--- /dev/null
+++ b/api/serve.go
@@ -0,0 +1,165 @@
+package api
+
+import (
+ "database/sql"
+ "fmt"
+ "log"
+ "net/http"
+ "time"
+
+ "penguins.lan/api/auth"
+ "penguins.lan/api/canvas"
+ "penguins.lan/api/minecraft"
+ "penguins.lan/api/presence"
+ "penguins.lan/api/template"
+ "penguins.lan/api/types"
+ "penguins.lan/api/ws"
+ "penguins.lan/args"
+ "penguins.lan/utils"
+)
+
+func LogRequestContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, _failure types.Continuation) types.ContinuationChain {
+ context.Start = time.Now()
+ context.Id = utils.RandomId()
+
+ log.Println(req.Method, req.URL.Path, req.RemoteAddr, context.Id)
+ return success(context, req, resp)
+ }
+}
+
+func LogExecutionTimeContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, _failure types.Continuation) types.ContinuationChain {
+ log.Println(context.Id, "took", time.Since(context.Start))
+ return success(context, req, resp)
+ }
+}
+
+func HealthCheckContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, _failure types.Continuation) types.ContinuationChain {
+ resp.WriteHeader(http.StatusOK)
+ resp.Write([]byte("healthy"))
+ return success(context, req, resp)
+ }
+}
+
+func FailurePassingContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(_success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ return failure(context, req, resp)
+ }
+}
+
+func IdContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, _failure types.Continuation) types.ContinuationChain {
+ return success(context, req, resp)
+ }
+}
+
+func CacheControlMiddleware(next http.Handler, maxAge int) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", maxAge))
+ next.ServeHTTP(w, r)
+ })
+}
+
+func MakeServer(argv *args.Arguments, dbConn *sql.DB) *http.Server {
+ mux := http.NewServeMux()
+
+ hub := ws.NewHub(dbConn)
+ go hub.Run()
+
+ canvasHub := canvas.NewHub(dbConn)
+ go canvasHub.Run()
+
+ makeRequestContext := func() *types.RequestContext {
+ return &types.RequestContext{
+ DBConn: dbConn,
+ Args: argv,
+ TemplateData: &map[string]interface{}{},
+ }
+ }
+
+ staticFileServer := http.FileServer(http.Dir(argv.StaticPath))
+ mux.Handle("GET /static/", http.StripPrefix("/static/", CacheControlMiddleware(staticFileServer, 3600)))
+
+ mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(HealthCheckContinuation, FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /portal", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(auth.ShowPortalContinuation, FailurePassingContinuation)(template.TemplateContinuation("portal.html", true), IdContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("POST /portal", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(auth.SetupUserContinuation, FailurePassingContinuation)(IdContinuation, template.TemplateContinuation("portal.html", true))(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /profile", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(template.TemplateContinuation("profile.html", true), IdContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /logout", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.LogoutContinuation, FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(presence.RosterContinuation(hub), FailurePassingContinuation)(auth.RequireUserContinuation, FailurePassingContinuation)(template.TemplateContinuation("home.html", true), auth.GoPortalContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("POST /rename", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(auth.RequireUserContinuation, FailurePassingContinuation)(presence.RosterContinuation(hub), auth.GoPortalContinuation)(auth.RenameContinuation, auth.GoPortalContinuation)(IdContinuation, template.TemplateContinuation("home.html", true))(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /waddlers", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(auth.RequireUserContinuation, FailurePassingContinuation)(presence.StatusContinuation(hub), auth.UnauthorizedContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /canvas", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(canvas.PageContinuation, FailurePassingContinuation)(auth.RequireUserContinuation, FailurePassingContinuation)(template.TemplateContinuation("canvas.html", true), auth.GoPortalContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /canvas/state", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(auth.RequireUserContinuation, FailurePassingContinuation)(canvas.StateContinuation, auth.UnauthorizedContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /canvas/ws", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(auth.RequireUserContinuation, FailurePassingContinuation)(canvas.ServeWSContinuation(canvasHub), auth.UnauthorizedContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /minecraft", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(auth.RequireUserContinuation, FailurePassingContinuation)(minecraft.StatusContinuation, auth.UnauthorizedContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /chat", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(auth.RequireUserContinuation, FailurePassingContinuation)(template.TemplateContinuation("chat.html", true), auth.GoPortalContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /ws", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(auth.RequireUserContinuation, FailurePassingContinuation)(ws.ServeWSContinuation(hub), auth.UnauthorizedContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /{template}", func(w http.ResponseWriter, r *http.Request) {
+ ctx := makeRequestContext()
+ templateFile := r.PathValue("template")
+ LogRequestContinuation(ctx, r, w)(auth.ResolveSessionContinuation, FailurePassingContinuation)(auth.RequireUserContinuation, FailurePassingContinuation)(template.TemplateContinuation(templateFile+".html", true), auth.GoPortalContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ return &http.Server{
+ Addr: ":" + fmt.Sprint(argv.Port),
+ Handler: mux,
+ }
+}