package dyzurbot import ( "context" "fmt" "os" "time" "github.com/joho/godotenv" ) // Config holds the cache/sync settings, sourced from the environment/.env. type Config struct { SpreadsheetID string SheetRange string CachePath string SyncInterval time.Duration } // LoadConfig reads configuration from the environment (and .env if present), // applying defaults for everything except the required spreadsheet ID. func LoadConfig() (Config, error) { _ = godotenv.Load() cfg := Config{ SpreadsheetID: os.Getenv("SPREADSHEET_ID"), SheetRange: getenvDefault("SHEET_RANGE", "Dyżury!A1:F100"), CachePath: getenvDefault("ROTA_CACHE_PATH", "rota.json"), } if cfg.SpreadsheetID == "" { return Config{}, fmt.Errorf("missing SPREADSHEET_ID in environment/.env") } raw := getenvDefault("SYNC_INTERVAL", "30m") interval, err := time.ParseDuration(raw) if err != nil { return Config{}, fmt.Errorf("invalid SYNC_INTERVAL %q: %w", raw, err) } if interval <= 0 { return Config{}, fmt.Errorf("SYNC_INTERVAL must be positive, got %v", interval) } cfg.SyncInterval = interval return cfg, nil } func getenvDefault(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback } // Service ties the Store and Syncer together for the bot to consume. type Service struct { Store *Store Syncer *Syncer interval time.Duration } // NewService builds the cache + syncer from config. func NewService(cfg Config) *Service { store := NewStore(cfg.CachePath) syncer := NewSyncer(store, SheetFetcher(cfg.SpreadsheetID, cfg.SheetRange)) return &Service{Store: store, Syncer: syncer, interval: cfg.SyncInterval} } // Start loads the cached rota, runs one best-effort sync in the background // (a failure leaves the cached/empty data in place — offline-tolerant), then // polls in the background until ctx is canceled. func (s *Service) Start(ctx context.Context) error { if err := s.Store.Load(); err != nil { return err } go func() { _ = s.Syncer.Sync(ctx) }() go s.Syncer.Run(ctx, s.interval) return nil }