[mirror] yet another tui rss reader
github.com/olexsmir/smutok
1package main
2
3import (
4 "context"
5 "errors"
6 "log/slog"
7 "os"
8
9 "olexsmir.xyz/smutok/internal/config"
10 "olexsmir.xyz/smutok/internal/freshrss"
11 "olexsmir.xyz/smutok/internal/store"
12)
13
14type app struct {
15 cfg *config.Config
16 store *store.Sqlite
17 freshrss *freshrss.Client
18 freshrssSyncer *freshrss.Syncer
19 freshrssWorker *freshrss.Worker
20}
21
22func bootstrap(ctx context.Context, outputToFile bool) (*app, error) {
23 cfg, err := config.New()
24 if err != nil {
25 return nil, err
26 }
27
28 if lerr := setupLogger(cfg, outputToFile); lerr != nil {
29 return nil, lerr
30 }
31
32 store, err := store.NewSQLite(cfg.DBPath)
33 if err != nil {
34 return nil, err
35 }
36
37 if merr := store.Migrate(ctx); merr != nil {
38 return nil, merr
39 }
40
41 fr := freshrss.NewClient(cfg.FreshRSS.Host)
42 token, err := getAuthToken(ctx, fr, store, cfg)
43 if err != nil {
44 return nil, err
45 }
46 fr.SetAuthToken(token)
47
48 fs := freshrss.NewSyncer(fr, store)
49
50 writeToken, err := getWriteToken(ctx, fr, store)
51 if err != nil {
52 return nil, err
53 }
54
55 fw := freshrss.NewWorker(fr, store, writeToken, cfg.FreshRSS.Host)
56
57 return &app{
58 cfg: cfg,
59 store: store,
60 freshrss: fr,
61 freshrssSyncer: fs,
62 freshrssWorker: fw,
63 }, nil
64}
65
66func getAuthToken(
67 ctx context.Context,
68 fr *freshrss.Client,
69 db *store.Sqlite,
70 cfg *config.Config,
71) (string, error) {
72 token, err := db.GetToken(ctx)
73 if err == nil {
74 return token, nil
75 }
76
77 if !errors.Is(err, store.ErrNotFound) {
78 return "", err
79 }
80
81 slog.Info("requesting auth key")
82
83 token, err = fr.Login(ctx, cfg.FreshRSS.Username, cfg.FreshRSS.Password)
84 if err != nil {
85 return "", err
86 }
87
88 if serr := db.SetToken(ctx, token); serr != nil {
89 return "", serr
90 }
91
92 return token, nil
93}
94
95func getWriteToken(ctx context.Context, fr *freshrss.Client, db *store.Sqlite) (string, error) {
96 token, err := db.GetWriteToken(ctx)
97 if err == nil {
98 return token, nil
99 }
100
101 if !errors.Is(err, store.ErrNotFound) {
102 return "", err
103 }
104
105 slog.Info("requesting write token")
106
107 token, err = fr.GetWriteToken(ctx)
108 if err != nil {
109 return "", err
110 }
111
112 if serr := db.SetWriteToken(ctx, token); serr != nil {
113 return "", serr
114 }
115
116 return token, nil
117}
118
119var (
120 ErrUnknownLevel = errors.New("unknown log level")
121 loggerLevels = map[string]slog.Level{
122 "info": slog.LevelInfo,
123 "debug": slog.LevelDebug,
124 "error": slog.LevelError,
125 "warn": slog.LevelWarn,
126 }
127)
128
129func setupLogger(cfg *config.Config, outputToFile bool) error {
130 out := os.Stdout
131 if outputToFile {
132 file, err := os.OpenFile(cfg.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
133 if err != nil {
134 return err
135 }
136 out = file
137 }
138
139 logLevel, ok := loggerLevels[cfg.LogLevel]
140 if !ok {
141 return ErrUnknownLevel
142 }
143
144 logger := slog.New(slog.NewTextHandler(out, &slog.HandlerOptions{
145 Level: logLevel,
146 }))
147 slog.SetDefault(logger)
148 return nil
149}