Files
ai-agent/internal/agents/task/store.go
2026-03-20 07:07:38 +01:00

177 lines
3.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// task/store.go JSON-Persistenz für Tasks (atomisches Schreiben)
package task
import (
"encoding/json"
"fmt"
"os"
"sync"
"time"
"my-brain-importer/internal/config"
)
// Task repräsentiert eine Aufgabe.
type Task struct {
ID string `json:"id"`
Text string `json:"text"`
Done bool `json:"done"`
CreatedAt time.Time `json:"created_at"`
DoneAt *time.Time `json:"done_at,omitempty"`
DueDate *time.Time `json:"due_date,omitempty"`
Priority string `json:"priority,omitempty"` // "hoch", "mittel", "niedrig"
}
// Store verwaltet tasks.json mit atomischen Schreiboperationen.
type Store struct {
mu sync.Mutex
path string
}
// NewStore erstellt einen Store mit dem Pfad aus der Config.
func NewStore() *Store {
path := config.Cfg.Tasks.StorePath
if path == "" {
path = "./tasks.json"
}
return &Store{path: path}
}
// Load liest alle Tasks aus der JSON-Datei.
func (s *Store) Load() ([]Task, error) {
s.mu.Lock()
defer s.mu.Unlock()
data, err := os.ReadFile(s.path)
if os.IsNotExist(err) {
return []Task{}, nil
}
if err != nil {
return nil, fmt.Errorf("tasks lesen: %w", err)
}
var tasks []Task
if err := json.Unmarshal(data, &tasks); err != nil {
return nil, fmt.Errorf("tasks parsen: %w", err)
}
return tasks, nil
}
// save schreibt alle Tasks atomisch. Muss unter mu aufgerufen werden.
func (s *Store) save(tasks []Task) error {
data, err := json.MarshalIndent(tasks, "", " ")
if err != nil {
return fmt.Errorf("tasks serialisieren: %w", err)
}
tmp := s.path + ".tmp"
if err := os.WriteFile(tmp, data, 0644); err != nil {
return fmt.Errorf("temp-datei schreiben: %w", err)
}
return os.Rename(tmp, s.path)
}
// Add fügt einen neuen Task hinzu.
func (s *Store) Add(text, priority string, dueDate *time.Time) (Task, error) {
s.mu.Lock()
defer s.mu.Unlock()
tasks, err := s.loadLocked()
if err != nil {
return Task{}, err
}
t := Task{
ID: fmt.Sprintf("%d", time.Now().UnixNano()),
Text: text,
Done: false,
CreatedAt: time.Now(),
Priority: priority,
DueDate: dueDate,
}
tasks = append(tasks, t)
if err := s.save(tasks); err != nil {
return Task{}, err
}
return t, nil
}
// MarkDone markiert einen Task als erledigt.
func (s *Store) MarkDone(id string) error {
s.mu.Lock()
defer s.mu.Unlock()
tasks, err := s.loadLocked()
if err != nil {
return err
}
found := false
now := time.Now()
for i, t := range tasks {
if t.ID == id {
tasks[i].Done = true
tasks[i].DoneAt = &now
found = true
break
}
}
if !found {
return fmt.Errorf("task %q nicht gefunden", id)
}
return s.save(tasks)
}
// Delete löscht einen Task.
func (s *Store) Delete(id string) error {
s.mu.Lock()
defer s.mu.Unlock()
tasks, err := s.loadLocked()
if err != nil {
return err
}
newTasks := tasks[:0]
for _, t := range tasks {
if t.ID != id {
newTasks = append(newTasks, t)
}
}
if len(newTasks) == len(tasks) {
return fmt.Errorf("task %q nicht gefunden", id)
}
return s.save(newTasks)
}
// OpenTasks gibt alle offenen Tasks zurück.
func (s *Store) OpenTasks() ([]Task, error) {
tasks, err := s.Load()
if err != nil {
return nil, err
}
var open []Task
for _, t := range tasks {
if !t.Done {
open = append(open, t)
}
}
return open, nil
}
// loadLocked liest ohne eigenes Lock (muss unter s.mu aufgerufen werden).
func (s *Store) loadLocked() ([]Task, error) {
data, err := os.ReadFile(s.path)
if os.IsNotExist(err) {
return []Task{}, nil
}
if err != nil {
return nil, fmt.Errorf("tasks lesen: %w", err)
}
var tasks []Task
if err := json.Unmarshal(data, &tasks); err != nil {
return nil, fmt.Errorf("tasks parsen: %w", err)
}
return tasks, nil
}