environment.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5package cli
  6
  7import (
  8	"context"
  9	"errors"
 10	"fmt"
 11
 12	"git.secluded.site/np/internal/config"
 13	"git.secluded.site/np/internal/db"
 14	"git.secluded.site/np/internal/event"
 15	"git.secluded.site/np/internal/goal"
 16	"git.secluded.site/np/internal/i18n"
 17	"git.secluded.site/np/internal/session"
 18	"git.secluded.site/np/internal/task"
 19	"git.secluded.site/np/internal/timeutil"
 20)
 21
 22// Environment aggregates long-lived services needed by CLI commands.
 23type Environment struct {
 24	DB           *db.Database
 25	Clock        timeutil.Clock
 26	Config       config.Config
 27	Localizer    *i18n.Localizer
 28	SessionStore *session.Store
 29	GoalStore    *goal.Store
 30	TaskStore    *task.Store
 31	EventStore   *event.Store
 32}
 33
 34// OpenEnvironment initialises storage and domain stores for CLI commands.
 35func OpenEnvironment(opts db.Options, clock timeutil.Clock, cfg config.Config, localizer *i18n.Localizer) (*Environment, error) {
 36	database, err := db.Open(opts)
 37	if err != nil {
 38		return nil, fmt.Errorf("cli: open database: %w", err)
 39	}
 40
 41	if clock == nil {
 42		clock = timeutil.UTCClock{}
 43	}
 44
 45	env := &Environment{
 46		DB:           database,
 47		Clock:        clock,
 48		Config:       cfg,
 49		Localizer:    localizer,
 50		SessionStore: session.NewStore(database, clock),
 51		GoalStore:    goal.NewStore(database, clock),
 52		TaskStore:    task.NewStore(database, clock),
 53		EventStore:   event.NewStore(database, clock),
 54	}
 55	return env, nil
 56}
 57
 58// Close releases resources allocated by the environment.
 59func (e *Environment) Close() error {
 60	if e == nil || e.DB == nil {
 61		return nil
 62	}
 63	return e.DB.Close()
 64}
 65
 66// ActiveSession resolves the active session for path (or parent paths).
 67func (e *Environment) ActiveSession(ctx context.Context, path string) (session.Document, bool, error) {
 68	if e == nil || e.SessionStore == nil {
 69		return session.Document{}, false, errors.New("cli: environment not initialised")
 70	}
 71	return e.SessionStore.ActiveByPath(ctx, path)
 72}
 73
 74// LoadGoal retrieves the goal for sid. When absent, ok is false.
 75func (e *Environment) LoadGoal(ctx context.Context, sid string) (goal.Document, bool, error) {
 76	if e == nil || e.GoalStore == nil {
 77		return goal.Document{}, false, errors.New("cli: environment not initialised")
 78	}
 79	doc, err := e.GoalStore.Get(ctx, sid)
 80	if err != nil {
 81		if errors.Is(err, goal.ErrNotFound) {
 82			return goal.Document{}, false, nil
 83		}
 84		return goal.Document{}, false, err
 85	}
 86	return doc, true, nil
 87}
 88
 89// LoadTasks returns tasks for sid sorted by creation order.
 90func (e *Environment) LoadTasks(ctx context.Context, sid string) ([]task.Task, error) {
 91	if e == nil || e.TaskStore == nil {
 92		return nil, errors.New("cli: environment not initialised")
 93	}
 94	return e.TaskStore.List(ctx, sid)
 95}
 96
 97// LoadTasksByStatus returns tasks filtered by status. When status is empty,
 98// all tasks are returned.
 99func (e *Environment) LoadTasksByStatus(ctx context.Context, sid string, status task.Status) ([]task.Task, error) {
100	if e == nil || e.TaskStore == nil {
101		return nil, errors.New("cli: environment not initialised")
102	}
103	if status == "" {
104		return e.TaskStore.List(ctx, sid)
105	}
106	return e.TaskStore.ListByStatus(ctx, sid, status)
107}
108
109type contextKey string
110
111const environmentContextKey contextKey = "git.secluded.site/np/internal/cli/environment"
112
113// WithEnvironment stores env inside ctx for retrieval by subcommands.
114func WithEnvironment(ctx context.Context, env *Environment) context.Context {
115	return context.WithValue(ctx, environmentContextKey, env)
116}
117
118// FromContext extracts an Environment that was previously embedded with WithEnvironment.
119func FromContext(ctx context.Context) (*Environment, bool) {
120	if ctx == nil {
121		return nil, false
122	}
123	env, ok := ctx.Value(environmentContextKey).(*Environment)
124	if !ok || env == nil {
125		return nil, false
126	}
127	return env, true
128}