1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package shared provides common interfaces, types, and helpers for MCP tools.
6package shared
7
8import (
9 "errors"
10 "fmt"
11 "time"
12)
13
14// ErrTimezoneNotConfigured is returned when the timezone config value is empty.
15var ErrTimezoneNotConfigured = errors.New(
16 "timezone is not configured; please set the 'timezone' value in your " +
17 "config file (e.g. 'UTC' or 'America/New_York')",
18)
19
20// AreaProvider defines the interface for accessing area data.
21type AreaProvider interface {
22 GetName() string
23 GetID() string
24 GetKey() string
25 GetGoals() []GoalProvider
26}
27
28// GoalProvider defines the interface for accessing goal data.
29//
30//nolint:iface // semantically distinct from HabitProvider
31type GoalProvider interface {
32 GetName() string
33 GetID() string
34 GetKey() string
35}
36
37// HabitProvider defines the interface for accessing habit data.
38//
39//nolint:iface // semantically distinct from GoalProvider
40type HabitProvider interface {
41 GetName() string
42 GetID() string
43 GetKey() string
44}
45
46// Config holds the necessary configuration for tool handlers.
47type Config struct {
48 AccessToken string
49 Timezone string
50 Areas []AreaProvider
51 Habits []HabitProvider
52}
53
54// LoadLocation loads a timezone location string, returning a *time.Location or error.
55func LoadLocation(timezone string) (*time.Location, error) {
56 if timezone == "" {
57 return nil, ErrTimezoneNotConfigured
58 }
59
60 loc, err := time.LoadLocation(timezone)
61 if err != nil {
62 return nil, fmt.Errorf("could not load timezone '%s': %w", timezone, err)
63 }
64
65 return loc, nil
66}
67
68// FindArea finds an area by ID or key from the list of providers. Returns nil if not found.
69func FindArea(areas []AreaProvider, idOrKey string) AreaProvider {
70 for _, ap := range areas {
71 if ap.GetID() == idOrKey || ap.GetKey() == idOrKey {
72 return ap
73 }
74 }
75
76 return nil
77}
78
79// FindGoalInArea checks if a goal exists within an area by ID or key.
80// Returns true if found.
81func FindGoalInArea(area AreaProvider, idOrKey string) bool {
82 for _, goal := range area.GetGoals() {
83 if goal.GetID() == idOrKey || goal.GetKey() == idOrKey {
84 return true
85 }
86 }
87
88 return false
89}
90
91// GetGoalInArea finds a goal within an area by ID or key.
92// Returns nil if not found.
93func GetGoalInArea(area AreaProvider, idOrKey string) GoalProvider {
94 for _, goal := range area.GetGoals() {
95 if goal.GetID() == idOrKey || goal.GetKey() == idOrKey {
96 return goal
97 }
98 }
99
100 return nil
101}
102
103// FindHabit finds a habit by ID or key from the list of providers. Returns nil if not found.
104func FindHabit(habits []HabitProvider, idOrKey string) HabitProvider {
105 for _, hp := range habits {
106 if hp.GetID() == idOrKey || hp.GetKey() == idOrKey {
107 return hp
108 }
109 }
110
111 return nil
112}