llm mail integration

This commit is contained in:
Christoph K.
2026-03-19 21:46:12 +01:00
parent fdc7a8588d
commit 0e7aa3e7f2
19 changed files with 1707 additions and 306 deletions

View File

@@ -0,0 +1,172 @@
// 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"`
}
// 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 string) (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(),
}
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
}