From c69fd84fddd68354c0801997fbee4c57cd5d5599 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Mon, 2 Feb 2026 14:16:08 +0100 Subject: [PATCH] implement proper sever route handling --- pkg/web/components/issuelist.templ | 19 +++++ pkg/web/components/issuelist_templ.go | 111 ++++++++++++++++++++++++++ pkg/web/handler/handler.go | 21 +++++ pkg/web/handler/issues.go | 66 ++++++++++++--- pkg/web/handler/pages.go | 46 +++++++++++ pkg/web/routes/index.templ | 13 ++- pkg/web/routes/index_templ.go | 16 +++- pkg/web/server/routes.go | 12 +-- pkg/web/server/server.go | 8 +- pkg/web/web.go | 13 +-- 10 files changed, 290 insertions(+), 35 deletions(-) create mode 100644 pkg/web/components/issuelist.templ create mode 100644 pkg/web/components/issuelist_templ.go create mode 100644 pkg/web/handler/handler.go create mode 100644 pkg/web/handler/pages.go diff --git a/pkg/web/components/issuelist.templ b/pkg/web/components/issuelist.templ new file mode 100644 index 0000000..cc97491 --- /dev/null +++ b/pkg/web/components/issuelist.templ @@ -0,0 +1,19 @@ +package components + +import "github.com/LazyBachelor/LazyPM/internal/models" + +type IssueListProps struct { + Issues []*models.Issue +} + +templ IssueList(props IssueListProps) { + +} + +templ IssueListItem(issue *models.Issue) { +
  • Issue ID: { issue.ID }, Title: { issue.Title }
  • +} diff --git a/pkg/web/components/issuelist_templ.go b/pkg/web/components/issuelist_templ.go new file mode 100644 index 0000000..860dc69 --- /dev/null +++ b/pkg/web/components/issuelist_templ.go @@ -0,0 +1,111 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "github.com/LazyBachelor/LazyPM/internal/models" + +type IssueListProps struct { + Issues []*models.Issue +} + +func IssueList(props IssueListProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func IssueListItem(issue *models.Issue) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var2 := templ.GetChildren(ctx) + if templ_7745c5c3_Var2 == nil { + templ_7745c5c3_Var2 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
  • Issue ID: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(issue.ID) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issuelist.templ`, Line: 18, Col: 25} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, ", Title: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(issue.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issuelist.templ`, Line: 18, Col: 49} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/web/handler/handler.go b/pkg/web/handler/handler.go new file mode 100644 index 0000000..597333c --- /dev/null +++ b/pkg/web/handler/handler.go @@ -0,0 +1,21 @@ +package handler + +import ( + "net/http" + + "github.com/LazyBachelor/LazyPM/internal/service" +) + +type Route struct { + Pattern string + Handler http.Handler +} + +func GetRoutes(svc *service.Services) []Route { + var routes []Route + + routes = append(routes, PagesRoutes(svc)...) + routes = append(routes, IssuesRoutes(svc)...) + + return routes +} diff --git a/pkg/web/handler/issues.go b/pkg/web/handler/issues.go index d89e213..6beff6a 100644 --- a/pkg/web/handler/issues.go +++ b/pkg/web/handler/issues.go @@ -1,22 +1,66 @@ package handler import ( - "github.com/LazyBachelor/LazyPM/internal/service" + "encoding/json" "net/http" + + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/internal/service" ) -type IssuesHandler struct { - Services *service.Services -} - -func NewIssuesHandler(services *service.Services) *IssuesHandler { - return &IssuesHandler{ - Services: services, +func IssuesRoutes(svc *service.Services) []Route { + return []Route{ + {Pattern: "/issues", Handler: GetAllIssues(svc)}, + {Pattern: "POST /create-issue", Handler: CreateIssue(svc)}, } } -func (h *IssuesHandler) HandleGetAllIssues() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +func CreateIssue(svc *service.Services) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } - }) + var issue models.Issue + + err := json.NewDecoder(r.Body).Decode(&issue) + if err != nil { + http.Error(w, "Invalid request payload", http.StatusBadRequest) + return + } + + defer r.Body.Close() + + err = svc.Beads.CreateIssue(r.Context(), &issue, "") + if err != nil { + http.Error(w, "Failed to create issue: "+err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(issue) + } +} + +func GetAllIssues(svc *service.Services) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + issues, err := svc.Beads.AllIssues(r.Context()) + + if err != nil { + http.Error(w, "Failed to retrieve issues", http.StatusInternalServerError) + return + } + + jsonData, err := json.Marshal(issues) + + if err != nil { + http.Error(w, "Failed to marshal issues", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(jsonData) + } } diff --git a/pkg/web/handler/pages.go b/pkg/web/handler/pages.go new file mode 100644 index 0000000..0eea015 --- /dev/null +++ b/pkg/web/handler/pages.go @@ -0,0 +1,46 @@ +package handler + +import ( + "net/http" + + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/web/components" + "github.com/LazyBachelor/LazyPM/pkg/web/routes" +) + +func PagesRoutes(svc *service.Services) []Route { + return []Route{ + {Pattern: "/", Handler: IndexHandler(svc)}, + } +} + +func IndexHandler(svc *service.Services) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + if r.URL.Path != "/" { + handleNotFound(w, r) + return + } + + issues, err := svc.Beads.AllIssues(r.Context()) + + if err != nil { + http.Error(w, "Failed to retrieve issues", + http.StatusInternalServerError) + return + } + + props := routes.IndexProps{ + Issues: issues, + IssueList: components.IssueListProps{ + Issues: issues, + }, + } + + routes.Index(props).Render(r.Context(), w) + } +} + +func handleNotFound(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "Page not found", http.StatusNotFound) +} diff --git a/pkg/web/routes/index.templ b/pkg/web/routes/index.templ index 2216e9c..73423e2 100644 --- a/pkg/web/routes/index.templ +++ b/pkg/web/routes/index.templ @@ -1,8 +1,19 @@ package routes -templ Index() { +import ( + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/pkg/web/components" +) + +type IndexProps struct { + Issues []*models.Issue + IssueList components.IssueListProps +} + +templ Index(props IndexProps) { @BaseLayout() {

    Welcome to the Beads Test Application

    Here are current issues:

    + @components.IssueList(props.IssueList) } } diff --git a/pkg/web/routes/index_templ.go b/pkg/web/routes/index_templ.go index 1103fc6..0600679 100644 --- a/pkg/web/routes/index_templ.go +++ b/pkg/web/routes/index_templ.go @@ -8,7 +8,17 @@ package routes import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -func Index() templ.Component { +import ( + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/pkg/web/components" +) + +type IndexProps struct { + Issues []*models.Issue + IssueList components.IssueListProps +} + +func Index(props IndexProps) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -45,6 +55,10 @@ func Index() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + templ_7745c5c3_Err = components.IssueList(props.IssueList).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } return nil }) templ_7745c5c3_Err = BaseLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) diff --git a/pkg/web/server/routes.go b/pkg/web/server/routes.go index bf02c5f..94e439a 100644 --- a/pkg/web/server/routes.go +++ b/pkg/web/server/routes.go @@ -6,22 +6,18 @@ import ( "strings" "time" + "github.com/LazyBachelor/LazyPM/pkg/web/handler" "github.com/NYTimes/gziphandler" "github.com/rs/cors" ) -type Route struct { - Pattern string - Handeler http.Handler -} - -func (s *Server) RegisterRoutes(assets embed.FS, routes []Route) http.Handler { +func (s *Server) RegisterRoutes(assets embed.FS) http.Handler { mux := http.NewServeMux() s.handleAssets(mux, assets) - for _, route := range routes { - mux.Handle(route.Pattern, route.Handeler) + for _, route := range handler.GetRoutes(s.Services) { + mux.Handle(route.Pattern, route.Handler) } handler := cors.Default().Handler(mux) diff --git a/pkg/web/server/server.go b/pkg/web/server/server.go index b473ca9..9a5553b 100644 --- a/pkg/web/server/server.go +++ b/pkg/web/server/server.go @@ -1,16 +1,16 @@ package server import ( - "github.com/LazyBachelor/LazyPM/internal/service" "embed" "net/http" "time" + + "github.com/LazyBachelor/LazyPM/internal/service" ) type Server struct { Address string Assets embed.FS - Routes []Route Services *service.Services } @@ -20,9 +20,11 @@ func NewServer(props Server) *http.Server { props.Address = "localhost:8080" } + handler := props.RegisterRoutes(props.Assets) + return &http.Server{ Addr: props.Address, - Handler: props.RegisterRoutes(props.Assets, props.Routes), + Handler: handler, IdleTimeout: time.Minute, ReadTimeout: time.Second * 10, WriteTimeout: time.Second * 10, diff --git a/pkg/web/web.go b/pkg/web/web.go index b62360d..0670da8 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -1,15 +1,12 @@ package web import ( - "github.com/LazyBachelor/LazyPM/internal/service" - "github.com/LazyBachelor/LazyPM/pkg/web/handler" - "github.com/LazyBachelor/LazyPM/pkg/web/routes" - "github.com/LazyBachelor/LazyPM/pkg/web/server" "context" "embed" "fmt" - "github.com/a-h/templ" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/web/server" ) type WebConfig = service.Config @@ -25,16 +22,10 @@ func Run(ctx context.Context, config WebConfig) error { defer cleanup() - handler := handler.NewIssuesHandler(svc) - server := server.NewServer(server.Server{ Address: config.WebAddress, Assets: assets, Services: svc, - Routes: []server.Route{ - {Pattern: "/", Handeler: templ.Handler(routes.Index())}, - {Pattern: "/issues", Handeler: handler.HandleGetAllIssues()}, - }, }) fmt.Printf("Starting web server on %s...\n", config.WebAddress)