From 0dcfe3989d9d2ceb4870482c613f6ae6bd544c35 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Mon, 2 Feb 2026 11:22:20 +0100 Subject: [PATCH] refine architecture change module name to repo --- .gitignore | 4 +- Makefile | 5 + cmd/cli/main.go | 26 +- cmd/survey.go | 39 +++ cmd/web/main.go | 57 +---- cmd/web/routes/index.templ | 8 - go.mod | 2 +- internal/models/beads.go | 73 ++++++ internal/service/beads.go | 13 +- internal/service/service.go | 63 +++++ internal/service/statistics.go | 4 +- internal/storage/storage.go | 4 +- main.go | 4 +- pkg/cli/cli.go | 25 ++ {cmd => pkg}/cli/commands/create.go | 10 +- {cmd => pkg}/cli/commands/root.go | 8 +- pkg/survey.go | 18 ++ pkg/tui/tui.go | 18 ++ {cmd => pkg}/web/assets/css/styles.css | 0 {cmd => pkg}/web/assets/js/ajax.min.js | 0 {cmd => pkg}/web/assets/js/htmx.min.js | 0 {cmd => pkg}/web/assets/manifest.json | 0 {cmd => pkg}/web/components/layout.templ | 0 pkg/web/components/layout_templ.go | 312 +++++++++++++++++++++++ pkg/web/handler/issues.go | 22 ++ pkg/web/routes/index.templ | 8 + pkg/web/routes/index_templ.go | 58 +++++ {cmd => pkg}/web/routes/layout.templ | 4 +- pkg/web/routes/layout_templ.go | 77 ++++++ {cmd => pkg}/web/server/routes.go | 7 +- {cmd => pkg}/web/server/server.go | 20 +- pkg/web/web.go | 47 ++++ 32 files changed, 822 insertions(+), 114 deletions(-) create mode 100644 cmd/survey.go delete mode 100644 cmd/web/routes/index.templ create mode 100644 internal/models/beads.go create mode 100644 internal/service/service.go create mode 100644 pkg/cli/cli.go rename {cmd => pkg}/cli/commands/create.go (72%) rename {cmd => pkg}/cli/commands/root.go (66%) create mode 100644 pkg/survey.go create mode 100644 pkg/tui/tui.go rename {cmd => pkg}/web/assets/css/styles.css (100%) rename {cmd => pkg}/web/assets/js/ajax.min.js (100%) rename {cmd => pkg}/web/assets/js/htmx.min.js (100%) rename {cmd => pkg}/web/assets/manifest.json (100%) rename {cmd => pkg}/web/components/layout.templ (100%) create mode 100644 pkg/web/components/layout_templ.go create mode 100644 pkg/web/handler/issues.go create mode 100644 pkg/web/routes/index.templ create mode 100644 pkg/web/routes/index_templ.go rename {cmd => pkg}/web/routes/layout.templ (85%) create mode 100644 pkg/web/routes/layout_templ.go rename {cmd => pkg}/web/server/routes.go (88%) rename {cmd => pkg}/web/server/server.go (54%) create mode 100644 pkg/web/web.go diff --git a/.gitignore b/.gitignore index 629516a..62a01e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .pm *.db -*_templ.go -*.ext \ No newline at end of file +*.ext +bin \ No newline at end of file diff --git a/Makefile b/Makefile index 31a1718..53da244 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,11 @@ tidy: clean: go clean +build: + go build -o ./bin/cli ./cmd/cli + go build -o ./bin/tui ./cmd/tui + go build -o ./bin/web ./cmd/web + cli: go run ./cmd/cli diff --git a/cmd/cli/main.go b/cmd/cli/main.go index f085447..54e7634 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -1,31 +1,21 @@ package main import ( - "beadstest/cmd/cli/commands" - "beadstest/internal/service" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/cli" "context" "fmt" "os" - - "github.com/steveyegge/beads" ) func main() { - ctx := context.Background() + config := service.Config{ + IssuePrefix: "pm", + BeadsDBPath: "./.pm/db.db", + StatisticsStoragePath: "./.pm/stats.json", + } - store, err := beads.NewSQLiteStorage(ctx, "./db.db") - handleError(err) - defer store.Close() - - svc, err := service.NewService(ctx, store, "pm") - handleError(err) - defer svc.Close() - - handleError(commands.Execute(svc)) -} - -func handleError(err error) { - if err != nil { + if err := cli.Run(context.Background(), config); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } diff --git a/cmd/survey.go b/cmd/survey.go new file mode 100644 index 0000000..47da94a --- /dev/null +++ b/cmd/survey.go @@ -0,0 +1,39 @@ +package main + +import ( + "github.com/LazyBachelor/LazyPM/pkg" + "github.com/LazyBachelor/LazyPM/pkg/cli" + "github.com/LazyBachelor/LazyPM/pkg/tui" + "github.com/LazyBachelor/LazyPM/pkg/web" + "context" + "fmt" + "os" +) + +func main() { + config := pkg.SurveyConfig{ + WebAddress: "localhost:8080", + IssuePrefix: "pm", + BeadsDBPath: "./.pm/db.db", + StatisticsStoragePath: "./.pm/stats.json", + } + + ctx := context.Background() + var err error + + switch os.Args[1] { + case "tui": + err = tui.Run(ctx, config) + case "cli": + err = cli.Run(ctx, config) + case "web": + err = web.Run(ctx, config) + default: + err = fmt.Errorf("unknown command: %s", os.Args[1]) + } + + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} diff --git a/cmd/web/main.go b/cmd/web/main.go index eea75e1..bf8542b 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -1,60 +1,23 @@ package main import ( - "beadstest/cmd/web/routes" - "beadstest/cmd/web/server" - "beadstest/internal/models" - "beadstest/internal/service" - "beadstest/internal/storage" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/web" "context" - "embed" "fmt" "os" - "time" - - "github.com/google/uuid" - "github.com/steveyegge/beads" ) -//go:embed assets/* -var assets embed.FS - func main() { - ctx := context.Background() + config := service.Config{ + WebAddress: "localhost:8080", + BeadsDBPath: "./.pm/db.db", + IssuePrefix: "pm", + StatisticsStoragePath: "./.pm/stats.json", + } - beadStore, err := beads.NewSQLiteStorage(ctx, "./.pm/db.db") - handleError(err, "Error initializing Beads storage") - defer beadStore.Close() - - beadSvc, err := service.NewService(ctx, beadStore, "pm") - handleError(err, "Error initializing Beads service") - defer beadSvc.Close() - - statStore := storage.NewJsonStorage("./.pm/stats.json", &models.Statistics{ - ID: uuid.New(), - StartTime: time.Now(), - }) - - statSvc, err := service.NewStatisticsService(statStore) - handleError(err, "Error initializing Statistics service") - - server := server.NewServer(server.Server{ - Port: 8080, - Assets: assets, - Service: beadSvc, - StatisticsService: statSvc, - Routes: []server.Route{ - {Pattern: "/", Component: routes.Index()}, - }, - }) - - fmt.Printf("Starting web server on port %s...\n", server.Addr) - handleError(server.ListenAndServe(), "Server closed") -} - -func handleError(err error, msg string) { - if err != nil { - fmt.Fprintf(os.Stderr, "%s: %v\n", msg, err) + if err := web.Run(context.Background(), config); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } } diff --git a/cmd/web/routes/index.templ b/cmd/web/routes/index.templ deleted file mode 100644 index 2af0540..0000000 --- a/cmd/web/routes/index.templ +++ /dev/null @@ -1,8 +0,0 @@ -package routes - -templ Index(){ - @BaseLayout(){ -

Welcome to the Beads Test Application

-

This is the home page.

- } -} \ No newline at end of file diff --git a/go.mod b/go.mod index c5acea7..dd36aa8 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module beadstest +module github.com/LazyBachelor/LazyPM go 1.25.6 diff --git a/internal/models/beads.go b/internal/models/beads.go new file mode 100644 index 0000000..d009e86 --- /dev/null +++ b/internal/models/beads.go @@ -0,0 +1,73 @@ +package models + +import "github.com/steveyegge/beads" + +type ( + Issue = beads.Issue + Status = beads.Status + IssueType = beads.IssueType + Dependency = beads.Dependency + DependencyType = beads.DependencyType + Label = beads.Label + Comment = beads.Comment + Event = beads.Event + EventType = beads.EventType + BlockedIssue = beads.BlockedIssue + TreeNode = beads.TreeNode + IssueFilter = beads.IssueFilter + WorkFilter = beads.WorkFilter + StaleFilter = beads.StaleFilter + DependencyCounts = beads.DependencyCounts + IssueWithCounts = beads.IssueWithCounts + SortPolicy = beads.SortPolicy + EpicStatus = beads.EpicStatus +) + +// Status constants +const ( + StatusOpen = beads.StatusOpen + StatusInProgress = beads.StatusInProgress + StatusBlocked = beads.StatusBlocked + StatusDeferred = beads.StatusDeferred + StatusClosed = beads.StatusClosed +) + +// IssueType constants +const ( + TypeBug = beads.TypeBug + TypeFeature = beads.TypeFeature + TypeTask = beads.TypeTask + TypeEpic = beads.TypeEpic + TypeChore = beads.TypeChore +) + +// DependencyType constants +const ( + DepBlocks = beads.DepBlocks + DepRelated = beads.DepRelated + DepParentChild = beads.DepParentChild + DepDiscoveredFrom = beads.DepDiscoveredFrom + DepConditionalBlocks = beads.DepConditionalBlocks // B runs only if A fails (bd-kzda) +) + +// SortPolicy constants +const ( + SortPolicyHybrid = beads.SortPolicyHybrid + SortPolicyPriority = beads.SortPolicyPriority + SortPolicyOldest = beads.SortPolicyOldest +) + +// EventType constants +const ( + EventCreated = beads.EventCreated + EventUpdated = beads.EventUpdated + EventStatusChanged = beads.EventStatusChanged + EventCommented = beads.EventCommented + EventClosed = beads.EventClosed + EventReopened = beads.EventReopened + EventDependencyAdded = beads.EventDependencyAdded + EventDependencyRemoved = beads.EventDependencyRemoved + EventLabelAdded = beads.EventLabelAdded + EventLabelRemoved = beads.EventLabelRemoved + EventCompacted = beads.EventCompacted +) diff --git a/internal/service/beads.go b/internal/service/beads.go index 20c73f6..4751ca4 100644 --- a/internal/service/beads.go +++ b/internal/service/beads.go @@ -1,17 +1,18 @@ package service import ( + "github.com/LazyBachelor/LazyPM/internal/models" "context" "fmt" "github.com/steveyegge/beads" ) -type Service struct { +type BeadsService struct { beads.Storage } -func NewService(ctx context.Context, storage beads.Storage, prefix string) (*Service, error) { +func NewBeadsService(ctx context.Context, storage beads.Storage, prefix string) (*BeadsService, error) { issue_prefix, err := storage.GetConfig(ctx, "issue_prefix") if err != nil || issue_prefix == "" { if err := storage.SetConfig(ctx, "issue_prefix", prefix); err != nil { @@ -20,7 +21,11 @@ func NewService(ctx context.Context, storage beads.Storage, prefix string) (*Ser fmt.Println("Initialized with prefix:", prefix) } - return &Service{ - Storage: storage, + return &BeadsService{ + Storage: storage, }, nil } + +func (s *BeadsService) AllIssues(ctx context.Context) ([]*models.Issue, error) { + return s.Storage.SearchIssues(ctx, "", models.IssueFilter{}) +} diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 0000000..344ac92 --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1,63 @@ +package service + +import ( + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/internal/storage" + "context" + "time" + + "github.com/google/uuid" + "github.com/steveyegge/beads" +) + +type Config struct { + WebAddress string + BeadsDBPath string + IssuePrefix string + StatisticsStoragePath string +} + +type Services struct { + Config Config + Beads *BeadsService + Statistics *StatisticsService +} + +func NewServices(ctx context.Context, config Config) (*Services, func(), error) { + var cleanupFuncs []func() + + store, err := beads.NewSQLiteStorage(ctx, config.BeadsDBPath) + if err != nil { + return nil, nil, err + } + cleanupFuncs = append(cleanupFuncs, func() { store.Close() }) + + beadsSvc, err := NewBeadsService(ctx, store, config.IssuePrefix) + if err != nil { + return nil, nil, err + } + cleanupFuncs = append(cleanupFuncs, func() { beadsSvc.Close() }) + + statStore := storage.NewJsonStorage(config.StatisticsStoragePath, &models.Statistics{ + ID: uuid.New(), + StartTime: time.Now(), + }) + + statSvc, err := NewStatisticsService(statStore) + if err != nil { + return nil, nil, err + } + + return &Services{ + Beads: beadsSvc, + Statistics: statSvc, + Config: config, + }, func() { runCleanup(cleanupFuncs) }, nil + +} + +func runCleanup(funcs []func()) { + for _, fn := range funcs { + fn() + } +} diff --git a/internal/service/statistics.go b/internal/service/statistics.go index 1ac87be..3005ded 100644 --- a/internal/service/statistics.go +++ b/internal/service/statistics.go @@ -1,8 +1,8 @@ package service import ( - "beadstest/internal/models" - "beadstest/internal/storage" + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/internal/storage" "errors" ) diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 1099d7d..9ed52a9 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -7,8 +7,8 @@ import ( ) type Storage[T any] struct { - path string mu sync.RWMutex + path string Data *T } @@ -51,4 +51,4 @@ func (s *Storage[T]) Init() error { return s.Save() } return s.Load() -} \ No newline at end of file +} diff --git a/main.go b/main.go index 5bbfbad..1ca39d7 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,8 @@ package main import ( - "beadstest/internal/models" - "beadstest/internal/storage" + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/internal/storage" "fmt" "os" "time" diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go new file mode 100644 index 0000000..13b0161 --- /dev/null +++ b/pkg/cli/cli.go @@ -0,0 +1,25 @@ +package cli + +import ( + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/cli/commands" + "context" +) + +type CLIConfig = service.Config + +func Run(ctx context.Context, config CLIConfig) error { + svc, cleanup, err := service.NewServices(ctx, config) + + if err != nil { + return err + } + + defer cleanup() + + if err := commands.Execute(svc); err != nil { + return err + } + + return nil +} diff --git a/cmd/cli/commands/create.go b/pkg/cli/commands/create.go similarity index 72% rename from cmd/cli/commands/create.go rename to pkg/cli/commands/create.go index f76f3ba..7b8704d 100644 --- a/cmd/cli/commands/create.go +++ b/pkg/cli/commands/create.go @@ -1,11 +1,11 @@ package commands import ( + "github.com/LazyBachelor/LazyPM/internal/models" "fmt" "os" "github.com/spf13/cobra" - "github.com/steveyegge/beads" ) var createCmd = &cobra.Command{ @@ -16,15 +16,15 @@ var createCmd = &cobra.Command{ } func runCreateCmd(cmd *cobra.Command, args []string) error { - issue := &beads.Issue{ + issue := &models.Issue{ Title: "test", Description: "This is a test issue created by the CLI.", - Status: beads.StatusOpen, - IssueType: beads.TypeBug, + Status: models.StatusOpen, + IssueType: models.TypeBug, Priority: 0, } - err := svc.CreateIssue(cmd.Context(), issue, "test_actor") + err := svc.Beads.CreateIssue(cmd.Context(), issue, "test_actor") if err != nil { fmt.Fprintf(os.Stderr, "Error creating issue: %v\n", err) os.Exit(1) diff --git a/cmd/cli/commands/root.go b/pkg/cli/commands/root.go similarity index 66% rename from cmd/cli/commands/root.go rename to pkg/cli/commands/root.go index 7441605..9cfa819 100644 --- a/cmd/cli/commands/root.go +++ b/pkg/cli/commands/root.go @@ -1,12 +1,12 @@ package commands import ( - "beadstest/internal/service" + "github.com/LazyBachelor/LazyPM/internal/service" "github.com/spf13/cobra" ) -var svc *service.Service +var svc *service.Services var rootCmd = &cobra.Command{ Use: "pm", @@ -14,8 +14,8 @@ var rootCmd = &cobra.Command{ Long: `Project Management CLI for managing issues and tasks.`, } -func Execute(beadsService *service.Service) error { - svc = beadsService +func Execute(services *service.Services) error { + svc = services return rootCmd.Execute() } diff --git a/pkg/survey.go b/pkg/survey.go new file mode 100644 index 0000000..56d1948 --- /dev/null +++ b/pkg/survey.go @@ -0,0 +1,18 @@ +package pkg + +import ( + "github.com/LazyBachelor/LazyPM/internal/service" + "context" +) + +type SurveyConfig = service.Config + +func Run(ctx context.Context, config SurveyConfig) error { + _, cleanup, err := service.NewServices(ctx, config) + if err != nil { + return err + } + defer cleanup() + + return nil +} diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go new file mode 100644 index 0000000..91f6988 --- /dev/null +++ b/pkg/tui/tui.go @@ -0,0 +1,18 @@ +package tui + +import ( + "github.com/LazyBachelor/LazyPM/internal/service" + "context" +) + +type TUIConfig = service.Config + +func Run(ctx context.Context, config TUIConfig) error { + _, cleanup, err := service.NewServices(ctx, config) + if err != nil { + return err + } + defer cleanup() + + return nil +} diff --git a/cmd/web/assets/css/styles.css b/pkg/web/assets/css/styles.css similarity index 100% rename from cmd/web/assets/css/styles.css rename to pkg/web/assets/css/styles.css diff --git a/cmd/web/assets/js/ajax.min.js b/pkg/web/assets/js/ajax.min.js similarity index 100% rename from cmd/web/assets/js/ajax.min.js rename to pkg/web/assets/js/ajax.min.js diff --git a/cmd/web/assets/js/htmx.min.js b/pkg/web/assets/js/htmx.min.js similarity index 100% rename from cmd/web/assets/js/htmx.min.js rename to pkg/web/assets/js/htmx.min.js diff --git a/cmd/web/assets/manifest.json b/pkg/web/assets/manifest.json similarity index 100% rename from cmd/web/assets/manifest.json rename to pkg/web/assets/manifest.json diff --git a/cmd/web/components/layout.templ b/pkg/web/components/layout.templ similarity index 100% rename from cmd/web/components/layout.templ rename to pkg/web/components/layout.templ diff --git a/pkg/web/components/layout_templ.go b/pkg/web/components/layout_templ.go new file mode 100644 index 0000000..3b5aa99 --- /dev/null +++ b/pkg/web/components/layout_templ.go @@ -0,0 +1,312 @@ +// 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" + +func Layout(props LayoutProps) 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 + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(props.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/layout.templ`, Line: 8, Col: 27} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = Head(props.Head).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = Header(props.Header).Render(ctx, templ_7745c5c3_Buffer) + 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 + } + templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = Footer(props.Footer).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +type LayoutProps struct { + Title string + Description string + Head HeadProps + Header HeaderProps + Footer FooterProps +} + +func Head(props HeadProps) 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_Var4 := templ.GetChildren(ctx) + if templ_7745c5c3_Var4 == nil { + templ_7745c5c3_Var4 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + for _, link := range props.Links { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + for _, script := range props.Scripts { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +type HeadProps struct { + Links []Link + Scripts []Script +} + +type Link struct { + Href string + Rel string +} + +type Script struct { + Src string + Defer bool +} + +func Header(props HeaderProps) 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_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +type HeaderProps struct { + Routes []NavRoute +} + +type NavRoute struct { + Name string + Path string +} + +func Footer(props FooterProps) 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_Var12 := templ.GetChildren(ctx) + if templ_7745c5c3_Var12 == nil { + templ_7745c5c3_Var12 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + return nil + }) +} + +type FooterProps struct { +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/web/handler/issues.go b/pkg/web/handler/issues.go new file mode 100644 index 0000000..d89e213 --- /dev/null +++ b/pkg/web/handler/issues.go @@ -0,0 +1,22 @@ +package handler + +import ( + "github.com/LazyBachelor/LazyPM/internal/service" + "net/http" +) + +type IssuesHandler struct { + Services *service.Services +} + +func NewIssuesHandler(services *service.Services) *IssuesHandler { + return &IssuesHandler{ + Services: services, + } +} + +func (h *IssuesHandler) HandleGetAllIssues() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + }) +} diff --git a/pkg/web/routes/index.templ b/pkg/web/routes/index.templ new file mode 100644 index 0000000..2216e9c --- /dev/null +++ b/pkg/web/routes/index.templ @@ -0,0 +1,8 @@ +package routes + +templ Index() { + @BaseLayout() { +

Welcome to the Beads Test Application

+

Here are current issues:

+ } +} diff --git a/pkg/web/routes/index_templ.go b/pkg/web/routes/index_templ.go new file mode 100644 index 0000000..1103fc6 --- /dev/null +++ b/pkg/web/routes/index_templ.go @@ -0,0 +1,58 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package routes + +//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" + +func Index() 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_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + 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_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Welcome to the Beads Test Application

Here are current issues:

") + 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) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/cmd/web/routes/layout.templ b/pkg/web/routes/layout.templ similarity index 85% rename from cmd/web/routes/layout.templ rename to pkg/web/routes/layout.templ index 1e5e1c4..4aa4d3d 100644 --- a/cmd/web/routes/layout.templ +++ b/pkg/web/routes/layout.templ @@ -1,6 +1,6 @@ package routes -import "beadstest/cmd/web/components" +import "github.com/LazyBachelor/LazyPM/pkg/web/components" var baseLayout = components.LayoutProps{ Title: "Beads Test Application", @@ -16,10 +16,8 @@ var baseLayout = components.LayoutProps{ }, }, Header: components.HeaderProps{ - // Add header properties if needed }, Footer: components.FooterProps{ - // Add footer properties if needed }, } diff --git a/pkg/web/routes/layout_templ.go b/pkg/web/routes/layout_templ.go new file mode 100644 index 0000000..3440109 --- /dev/null +++ b/pkg/web/routes/layout_templ.go @@ -0,0 +1,77 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package routes + +//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/pkg/web/components" + +var baseLayout = components.LayoutProps{ + Title: "Beads Test Application", + Description: "A sample application using Beads storage service", + Head: components.HeadProps{ + Links: []components.Link{ + {Href: "/assets/css/styles.css", Rel: "stylesheet"}, + {Href: "/assets/manifest.json", Rel: "manifest"}, + }, + Scripts: []components.Script{ + {Src: "/assets/js/htmx.min.js", Defer: true}, + {Src: "/assets/js/ajax.min.js", Defer: true}, + }, + }, + Header: components.HeaderProps{}, + Footer: components.FooterProps{}, +} + +func BaseLayout() 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_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + 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_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = components.Layout(baseLayout).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/cmd/web/server/routes.go b/pkg/web/server/routes.go similarity index 88% rename from cmd/web/server/routes.go rename to pkg/web/server/routes.go index 6a497b7..bf02c5f 100644 --- a/cmd/web/server/routes.go +++ b/pkg/web/server/routes.go @@ -7,13 +7,12 @@ import ( "time" "github.com/NYTimes/gziphandler" - "github.com/a-h/templ" "github.com/rs/cors" ) type Route struct { - Pattern string - Component templ.Component + Pattern string + Handeler http.Handler } func (s *Server) RegisterRoutes(assets embed.FS, routes []Route) http.Handler { @@ -22,7 +21,7 @@ func (s *Server) RegisterRoutes(assets embed.FS, routes []Route) http.Handler { s.handleAssets(mux, assets) for _, route := range routes { - mux.Handle(route.Pattern, templ.Handler(route.Component)) + mux.Handle(route.Pattern, route.Handeler) } handler := cors.Default().Handler(mux) diff --git a/cmd/web/server/server.go b/pkg/web/server/server.go similarity index 54% rename from cmd/web/server/server.go rename to pkg/web/server/server.go index 5a79d60..b473ca9 100644 --- a/cmd/web/server/server.go +++ b/pkg/web/server/server.go @@ -1,31 +1,27 @@ package server import ( - "beadstest/internal/service" + "github.com/LazyBachelor/LazyPM/internal/service" "embed" - "fmt" "net/http" "time" - - "github.com/steveyegge/beads" ) type Server struct { - Port int - Assets embed.FS - Routes []Route - Service beads.Storage - StatisticsService *service.StatisticsService + Address string + Assets embed.FS + Routes []Route + Services *service.Services } // NewServer creates and configures a new HTTP server instance. func NewServer(props Server) *http.Server { - if props.Port == 0 { - props.Port = 8080 + if props.Address == "" { + props.Address = "localhost:8080" } return &http.Server{ - Addr: fmt.Sprintf(":%d", props.Port), + Addr: props.Address, Handler: props.RegisterRoutes(props.Assets, props.Routes), IdleTimeout: time.Minute, ReadTimeout: time.Second * 10, diff --git a/pkg/web/web.go b/pkg/web/web.go new file mode 100644 index 0000000..b62360d --- /dev/null +++ b/pkg/web/web.go @@ -0,0 +1,47 @@ +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" +) + +type WebConfig = service.Config + +//go:embed assets/* +var assets embed.FS + +func Run(ctx context.Context, config WebConfig) error { + svc, cleanup, err := service.NewServices(ctx, config) + if err != nil { + return err + } + + 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) + + if err := server.ListenAndServe(); err != nil { + return err + } + + return nil +}