package internal import ( "context" "fmt" "io" "os" "sort" "royalroadupdates/internal/formatter" "royalroadupdates/internal/pocketbase" "royalroadupdates/internal/royalroad" ) // Config holds the compile-time settings for the application. type Config struct { PBURL string } // App orchestrates the full pipeline. type App struct { pocketbase *pocketbase.Client scraper *royalroad.Scraper outW io.Writer errW io.Writer } // New creates a new App with the given configuration. func New(cfg Config) *App { return &App{ pocketbase: pocketbase.New(cfg.PBURL), scraper: royalroad.NewScraper(), outW: os.Stdout, errW: os.Stderr, } } // Run executes the full pipeline and outputs results. func (a *App) Run(ctx context.Context, jsonOutput bool) error { urls, err := a.pocketbase.GetBookmarkURLs(ctx) if err != nil { return fmt.Errorf("fetching bookmarks: %w", err) } var results []royalroad.Story for _, u := range urls { story, err := a.scraper.FetchStory(ctx, u) if err != nil { continue // silently skip } results = append(results, story) } sort.Slice(results, func(i, j int) bool { return results[i].DateModified.After(results[j].DateModified) }) if jsonOutput { if err := formatter.PrintJSON(a.outW, results); err != nil { return fmt.Errorf("encoding JSON: %w", err) } } else { formatter.PrintTable(a.outW, results) } return nil }