refine architecture

change module name to repo
This commit is contained in:
Robin Olsen
2026-02-02 11:22:20 +01:00
parent 4225d202bb
commit 0dcfe3989d
32 changed files with 822 additions and 114 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
.pm
*.db
*_templ.go
*.ext
bin

View File

@@ -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

View File

@@ -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)
}

39
cmd/survey.go Normal file
View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -1,8 +0,0 @@
package routes
templ Index(){
@BaseLayout(){
<h1>Welcome to the Beads Test Application</h1>
<p>This is the home page.</p>
}
}

2
go.mod
View File

@@ -1,4 +1,4 @@
module beadstest
module github.com/LazyBachelor/LazyPM
go 1.25.6

73
internal/models/beads.go Normal file
View File

@@ -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
)

View File

@@ -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{
return &BeadsService{
Storage: storage,
}, nil
}
func (s *BeadsService) AllIssues(ctx context.Context) ([]*models.Issue, error) {
return s.Storage.SearchIssues(ctx, "", models.IssueFilter{})
}

View File

@@ -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()
}
}

View File

@@ -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"
)

View File

@@ -7,8 +7,8 @@ import (
)
type Storage[T any] struct {
path string
mu sync.RWMutex
path string
Data *T
}

View File

@@ -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"

25
pkg/cli/cli.go Normal file
View File

@@ -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
}

View File

@@ -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)

View File

@@ -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()
}

18
pkg/survey.go Normal file
View File

@@ -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
}

18
pkg/tui/tui.go Normal file
View File

@@ -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
}

View File

@@ -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, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>")
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, "</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(props.Description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/layout.templ`, Line: 10, Col: 59}
}
_, 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, 3, "\">")
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, "</head><body>")
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, "<main>")
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, "</main>")
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, "</body></html>")
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, "<link href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.SafeURL
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(link.Href)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/layout.templ`, Line: 33, Col: 30}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" rel=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(link.Rel)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/layout.templ`, Line: 33, Col: 47}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
for _, script := range props.Scripts {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<script src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(script.Src)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/layout.templ`, Line: 37, Col: 32}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" defer=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(script.Defer)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/layout.templ`, Line: 37, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\"></script>")
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, "<header><nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, route := range props.Routes {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 templ.SafeURL
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(route.Path)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/layout.templ`, Line: 60, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(route.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/layout.templ`, Line: 60, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</nav></header>")
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

22
pkg/web/handler/issues.go Normal file
View File

@@ -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) {
})
}

View File

@@ -0,0 +1,8 @@
package routes
templ Index() {
@BaseLayout() {
<h1>Welcome to the Beads Test Application</h1>
<p>Here are current issues:</p>
}
}

View File

@@ -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, "<h1>Welcome to the Beads Test Application</h1><p>Here are current issues:</p>")
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

View File

@@ -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
},
}

View File

@@ -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

View File

@@ -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
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)

View File

@@ -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
Address string
Assets embed.FS
Routes []Route
Service beads.Storage
StatisticsService *service.StatisticsService
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,

47
pkg/web/web.go Normal file
View File

@@ -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
}