refactor services to use Interface and change name to app

This commit is contained in:
Robin Olsen
2026-02-22 16:16:09 +01:00
parent dbf619a054
commit 118e67dbf4
36 changed files with 247 additions and 219 deletions

View File

@@ -8,8 +8,8 @@ import (
"github.com/LazyBachelor/LazyPM/pkg/cli/commands"
)
// CLIConfig is an alias for service.Config, used to configure the CLI.
type CLIConfig = service.Config
// Config is an alias for service.Config, used to configure the CLI.
type Config = service.Config
type CLI struct{}
@@ -18,15 +18,15 @@ func NewCli() *CLI {
}
// Run initializes the services and executes the CLI commands.
func (c *CLI) Run(ctx context.Context, config CLIConfig) error {
svc, cleanup, err := service.NewServices(ctx, config)
func (c *CLI) Run(ctx context.Context, config Config) error {
app, cleanup, err := service.NewServices(ctx, config)
if err != nil {
return err
}
defer cleanup()
commands.SetServices(svc)
commands.SetApp(app)
if err := commands.Execute(); err != nil {
return err
@@ -36,15 +36,15 @@ func (c *CLI) Run(ctx context.Context, config CLIConfig) error {
}
// RunWithArgs initializes the services and executes the CLI commands with the provided arguments.
func (c *CLI) RunWithArgs(ctx context.Context, config CLIConfig, args []string) error {
svc, cleanup, err := service.NewServices(ctx, config)
func (c *CLI) RunWithArgs(ctx context.Context, config Config, args []string) error {
app, cleanup, err := service.NewServices(ctx, config)
if err != nil {
return err
}
defer cleanup()
commands.SetServices(svc)
commands.SetApp(app)
if err := commands.ExecuteArgs(args); err != nil {
return err

View File

@@ -30,8 +30,10 @@ func runCloseCmd(cmd *cobra.Command, args []string) error {
return fmt.Errorf("issue ID cannot be empty")
}
app := AppFromContext(cmd.Context())
// Fetch the issue to ensure it exists before closing.
issue, err := svc.Beads.GetIssue(cmd.Context(), closeID)
issue, err := app.Issues.GetIssue(cmd.Context(), closeID)
if err != nil {
return fmt.Errorf("error fetching issue: %w", err)
}
@@ -47,7 +49,7 @@ func runCloseCmd(cmd *cobra.Command, args []string) error {
}
// Close the issue.
err = svc.Beads.CloseIssue(cmd.Context(), closeID, issue.CloseReason, "", "")
err = app.Issues.CloseIssue(cmd.Context(), closeID, issue.CloseReason, "", "")
if err != nil {
return fmt.Errorf("error closing issue: %w", err)
}

View File

@@ -34,11 +34,12 @@ func completeIssues(cmd *cobra.Command, args []string, toComplete string) ([]str
// GetIssueCompletions fetches issues matching the toComplete string for shell completion.
func GetIssueCompletions(ctx context.Context, toComplete string) ([]models.Issue, cobra.ShellCompDirective) {
if svc == nil {
app := AppFromContext(ctx)
if app == nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
issues, err := svc.Beads.AllIssues(ctx)
issues, err := app.Issues.AllIssues(ctx)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}

View File

@@ -54,7 +54,8 @@ func runCreateCmd(cmd *cobra.Command, args []string) error {
}
// Create the issue using the service layer.
err := svc.Beads.CreateIssue(cmd.Context(), issue, "test_actor")
app := AppFromContext(cmd.Context())
err := app.Issues.CreateIssue(cmd.Context(), issue, "test_actor")
if err != nil {
return fmt.Errorf("error creating issue: %w", err)
}

View File

@@ -46,8 +46,10 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error {
return fmt.Errorf("issue ID cannot be empty")
}
app := AppFromContext(cmd.Context())
// Fetch the issue to ensure it exists before deletion.
issue, err := svc.Beads.GetIssue(cmd.Context(), deleteID)
issue, err := app.Issues.GetIssue(cmd.Context(), deleteID)
if err != nil {
return fmt.Errorf("error fetching issue: %w", err)
}
@@ -70,7 +72,7 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error {
}
// Delete the issue.
err = svc.Beads.DeleteIssue(cmd.Context(), deleteID)
err = app.Issues.DeleteIssue(cmd.Context(), deleteID)
if err != nil {
return fmt.Errorf("error deleting issue: %w", err)
}
@@ -83,9 +85,10 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error {
// runDeleteInteractive runs the interactive mode for deleting issues,
// allowing users to select multiple issues for deletion.
func runDeleteInteractive(ctx context.Context) error {
app := AppFromContext(ctx)
options := []huh.Option[string]{}
issues, err := svc.Beads.SearchIssues(ctx, "", models.IssueFilter{})
issues, err := app.Issues.SearchIssues(ctx, "", models.IssueFilter{})
if err != nil {
return fmt.Errorf("error fetching issues: %w", err)
}
@@ -110,7 +113,7 @@ func runDeleteInteractive(ctx context.Context) error {
}
for _, id := range deleteIDs {
err := svc.Beads.DeleteIssue(ctx, id)
err := app.Issues.DeleteIssue(ctx, id)
if err != nil {
return fmt.Errorf("error deleting issue with ID %s: %w", id, err)
}

View File

@@ -55,7 +55,8 @@ func runGetIssuesCmd(cmd *cobra.Command, args []string) error {
}
// Fetch issues based on the search query and filters.
issuesPtr, err := svc.Beads.SearchIssues(cmd.Context(), queryArg, filter)
app := AppFromContext(cmd.Context())
issuesPtr, err := app.Issues.SearchIssues(cmd.Context(), queryArg, filter)
if err != nil {
return err
}

View File

@@ -24,7 +24,8 @@ func runGetCmd(cmd *cobra.Command, args []string) error {
issueID := args[0]
// Fetch the issue details using the service layer.
issuePtr, err := svc.Beads.GetIssue(cmd.Context(), issueID)
app := AppFromContext(cmd.Context())
issuePtr, err := app.Issues.GetIssue(cmd.Context(), issueID)
if err != nil {
return err
}

View File

@@ -10,9 +10,12 @@ import (
"github.com/spf13/cobra"
)
// svc is a global variable that holds beads, config and stats services.
// Must be called before executing any commands to ensure services are available.
var svc *service.Services
type contextKey string
const appKey contextKey = "app"
// app is a package-level variable used during command setup
var app *service.App
// Flags struct to hold command-line flag values for issues.
type Flags struct {
@@ -30,13 +33,28 @@ type Flags struct {
var rootCmd = &cobra.Command{
Short: "Project Management CLI",
Long: `Project Management CLI for managing issues and tasks.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// Inject app into context for all commands
if app != nil {
cmd.SetContext(context.WithValue(cmd.Context(), appKey, app))
}
},
}
// SetServices sets the global services variable for use in command execution.
// SetApp sets the app variable for use in command execution.
// Must be called before executing any commands to ensure services are available.
func SetServices(services *service.Services) {
svc = services
rootCmd.Use = svc.Config.RootCmd
func SetApp(application *service.App) {
app = application
rootCmd.Use = app.Config.RootCmd
}
// AppFromContext retrieves the App from the command context
func AppFromContext(ctx context.Context) *service.App {
if a, ok := ctx.Value(appKey).(*service.App); ok {
return a
}
// Fallback to package-level app (for testing or edge cases)
return app
}
// Execute executes the root command using the fang library.

View File

@@ -23,7 +23,9 @@ var updateCmd = &cobra.Command{
func runUpdateCmd(cmd *cobra.Command, args []string) error {
issueID := args[0]
issue, err := svc.Beads.GetIssue(cmd.Context(), issueID)
app := AppFromContext(cmd.Context())
issue, err := app.Issues.GetIssue(cmd.Context(), issueID)
if err != nil {
return fmt.Errorf("error getting issue: %w", err)
}
@@ -37,12 +39,12 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error {
return fmt.Errorf("error getting update values: %w", err)
}
err = svc.Beads.UpdateIssue(cmd.Context(), issueID, updates, "test_actor")
err = app.Issues.UpdateIssue(cmd.Context(), issueID, updates, "test_actor")
if err != nil {
return fmt.Errorf("error updating issue: %w", err)
}
updatedIssue, err := svc.Beads.GetIssue(cmd.Context(), issueID)
updatedIssue, err := app.Issues.GetIssue(cmd.Context(), issueID)
if err != nil {
return fmt.Errorf("error getting updated issue: %w", err)
}

View File

@@ -37,7 +37,7 @@ func NewRepl() *REPL {
}
// Run starts the interactive Read-Eval-Print Loop for the PM CLI.
func (r *REPL) Run(ctx context.Context, config cli.CLIConfig) error {
func (r *REPL) Run(ctx context.Context, config cli.Config) error {
// Set terminal to raw mode to capture input properly in the REPL.
// This allows us to handle input character by character and provide a better user experience.
// We also ensure that the terminal state is restored when the REPL exits, even if an error occurs.
@@ -48,14 +48,14 @@ func (r *REPL) Run(ctx context.Context, config cli.CLIConfig) error {
defer term.Restore(int(os.Stdin.Fd()), oldState)
// Initialize services for beads, config and stats.
svc, cleanup, err := service.NewServices(ctx, config)
app, cleanup, err := service.NewServices(ctx, config)
if err != nil {
return fmt.Errorf("failed to initialize services: %w", err)
}
defer cleanup()
// Make sure to set services, to ensure they are available.
commands.SetServices(svc)
// Make sure to set app, to ensure they are available.
commands.SetApp(app)
// Set the REPL instance so status command can access it
commands.SetRepl(r)