Yōten: A social tracker for your language learning journey built on the atproto.

refactor: move computed data code to function to simplify visuals #21

merged opened by brookjeynes.dev targeting master from push-lokxzmtxxkxo
Labels

None yet.

Participants 1
AT URI
at://did:plc:4mj54vc4ha3lh32ksxwunnbh/sh.tangled.repo.pull/3m3c7vpu5pg22
+523 -290
Diff #0
+14
go.mod
··· 9 9 github.com/bluesky-social/indigo v0.0.0-20251003000214-3259b215110e 10 10 github.com/bluesky-social/jetstream v0.0.0-20250414024304-d17bd81a945e 11 11 github.com/carlmjohnson/versioninfo v0.22.5 12 + github.com/charmbracelet/log v0.4.2 12 13 github.com/go-chi/chi/v5 v5.2.1 13 14 github.com/gorilla/sessions v1.4.0 14 15 github.com/ipfs/go-cid v0.4.1 ··· 28 29 require ( 29 30 github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect 30 31 github.com/andybalholm/brotli v1.1.0 // indirect 32 + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 31 33 github.com/beorn7/perks v1.0.1 // indirect 32 34 github.com/cenkalti/backoff/v4 v4.3.0 // indirect 33 35 github.com/cespare/xxhash/v2 v2.3.0 // indirect 36 + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect 37 + github.com/charmbracelet/lipgloss v1.1.0 // indirect 38 + github.com/charmbracelet/x/ansi v0.8.0 // indirect 39 + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect 40 + github.com/charmbracelet/x/term v0.2.1 // indirect 34 41 github.com/cli/browser v1.3.0 // indirect 35 42 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 36 43 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 37 44 github.com/fatih/color v1.16.0 // indirect 38 45 github.com/felixge/httpsnoop v1.0.4 // indirect 39 46 github.com/fsnotify/fsnotify v1.7.0 // indirect 47 + github.com/go-logfmt/logfmt v0.6.0 // indirect 40 48 github.com/go-logr/logr v1.4.2 // indirect 41 49 github.com/go-logr/stdr v1.2.2 // indirect 42 50 github.com/goccy/go-json v0.10.2 // indirect ··· 69 77 github.com/lestrrat-go/httprc v1.0.4 // indirect 70 78 github.com/lestrrat-go/iter v1.0.2 // indirect 71 79 github.com/lestrrat-go/option v1.0.1 // indirect 80 + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 72 81 github.com/mattn/go-colorable v0.1.13 // indirect 73 82 github.com/mattn/go-isatty v0.0.20 // indirect 83 + github.com/mattn/go-runewidth v0.0.16 // indirect 74 84 github.com/minio/sha256-simd v1.0.1 // indirect 75 85 github.com/mr-tron/base58 v1.2.0 // indirect 86 + github.com/muesli/termenv v0.16.0 // indirect 76 87 github.com/multiformats/go-base32 v0.1.0 // indirect 77 88 github.com/multiformats/go-base36 v0.2.0 // indirect 78 89 github.com/multiformats/go-multibase v0.2.0 // indirect ··· 85 96 github.com/prometheus/client_model v0.6.1 // indirect 86 97 github.com/prometheus/common v0.54.0 // indirect 87 98 github.com/prometheus/procfs v0.15.1 // indirect 99 + github.com/rivo/uniseg v0.4.7 // indirect 88 100 github.com/segmentio/asm v1.2.0 // indirect 89 101 github.com/spaolacci/murmur3 v1.1.0 // indirect 102 + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 90 103 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 91 104 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 92 105 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect ··· 97 110 go.uber.org/multierr v1.11.0 // indirect 98 111 go.uber.org/zap v1.26.0 // indirect 99 112 golang.org/x/crypto v0.40.0 // indirect 113 + golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect 100 114 golang.org/x/mod v0.26.0 // indirect 101 115 golang.org/x/sys v0.34.0 // indirect 102 116 golang.org/x/time v0.8.0 // indirect
+27
go.sum
··· 5 5 github.com/a-h/templ v0.3.898/go.mod h1:oLBbZVQ6//Q6zpvSMPTuBK0F3qOtBdFBcGRspcT+VNQ= 6 6 github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= 7 7 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 8 + github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 9 + github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 8 10 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 9 11 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 10 12 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= ··· 22 24 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 23 25 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 24 26 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 27 + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= 28 + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= 29 + github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 30 + github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 31 + github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= 32 + github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= 33 + github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= 34 + github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= 35 + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= 36 + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 37 + github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= 38 + github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 25 39 github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= 26 40 github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= 27 41 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= ··· 42 56 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 43 57 github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= 44 58 github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 59 + github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= 60 + github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 45 61 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 46 62 github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 47 63 github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= ··· 148 164 github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 149 165 github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= 150 166 github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 167 + github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 168 + github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 151 169 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 152 170 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 153 171 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 154 172 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 155 173 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 156 174 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 175 + github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 176 + github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 157 177 github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 158 178 github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 159 179 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 160 180 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 161 181 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 162 182 github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 183 + github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 184 + github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 163 185 github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 164 186 github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= 165 187 github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= ··· 196 218 github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 197 219 github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE= 198 220 github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= 221 + github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 222 + github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 223 + github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 199 224 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 200 225 github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 201 226 github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= ··· 231 256 github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ= 232 257 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4= 233 258 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 259 + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 260 + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 234 261 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 235 262 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 236 263 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+25 -7
internal/server/log/log.go
··· 4 4 "context" 5 5 "log/slog" 6 6 "os" 7 + 8 + "github.com/charmbracelet/log" 7 9 ) 8 10 9 - // NewHandler sets up a new slog.Handler with the service name as an attribute 10 11 func NewHandler(name string) slog.Handler { 11 - handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{}) 12 - 13 - var attrs []slog.Attr 14 - attrs = append(attrs, slog.Attr{Key: "service", Value: slog.StringValue(name)}) 15 - handler.WithAttrs(attrs) 16 - return handler 12 + return log.NewWithOptions(os.Stderr, log.Options{ 13 + ReportTimestamp: true, 14 + Prefix: name, 15 + Level: log.DebugLevel, 16 + }) 17 17 } 18 18 19 19 func New(name string) *slog.Logger { ··· 45 45 46 46 return slog.Default() 47 47 } 48 + 49 + // Sublogger derives a new logger from an existing one by appending a suffix to 50 + // its prefix. 51 + func SubLogger(base *slog.Logger, suffix string) *slog.Logger { 52 + // Try to get the underlying charmbracelet logger. 53 + if cl, ok := base.Handler().(*log.Logger); ok { 54 + prefix := cl.GetPrefix() 55 + if prefix != "" { 56 + prefix = prefix + "/" + suffix 57 + } else { 58 + prefix = suffix 59 + } 60 + return slog.New(NewHandler(prefix)) 61 + } 62 + 63 + // Fallback to no known handler type. 64 + return slog.New(NewHandler(suffix)) 65 + }
+96 -13
internal/server/oauth/handler.go
··· 1 + package oauth 1 2 2 - 3 3 import ( 4 + "context" 4 5 "encoding/json" 5 - "log" 6 + "fmt" 6 7 "net/http" 8 + "time" 7 9 10 + comatproto "github.com/bluesky-social/indigo/api/atproto" 11 + lexutil "github.com/bluesky-social/indigo/lex/util" 8 12 "github.com/go-chi/chi/v5" 13 + "github.com/lestrrat-go/jwx/v2/jwk" 14 + "github.com/posthog/posthog-go" 9 15 16 + "yoten.app/api/yoten" 17 + ph "yoten.app/internal/clients/posthog" 18 + "yoten.app/internal/db" 19 + "yoten.app/internal/server/htmx" 20 + ) 10 21 22 + func (o *OAuth) Router() http.Handler { 11 23 12 24 13 25 ··· 41 53 42 54 43 55 44 - 45 - 46 - 47 - 48 - 49 56 jwks := o.Config.OAuth.Jwks 50 57 pubKey, err := pubKeyFromJwk(jwks) 51 58 if err != nil { 52 - log.Printf("failed to parse public key: %v", err) 59 + o.Logger.Error("failed to parse public key", "err", err) 53 60 http.Error(w, err.Error(), http.StatusInternalServerError) 54 61 return 55 62 } ··· 61 68 62 69 63 70 71 + } 64 72 73 + func (o *OAuth) callback(w http.ResponseWriter, r *http.Request) { 74 + l := o.Logger.With("handler", "callback") 75 + ctx := r.Context() 65 76 66 - 67 - 68 - 69 77 sessData, err := o.ClientApp.ProcessCallback(ctx, r.URL.Query()) 70 78 if err != nil { 79 + o.Logger.Error("failed to process callback", "err", err) 71 80 http.Error(w, err.Error(), http.StatusInternalServerError) 72 81 return 73 82 } 74 83 75 84 if err := o.SaveSession(w, r, sessData); err != nil { 85 + l.Error("failed to save session", "err", err) 76 86 http.Error(w, err.Error(), http.StatusInternalServerError) 77 87 return 78 88 } 79 89 90 + did := sessData.AccountDID.String() 91 + resolved, err := o.IdResolver.ResolveIdent(context.Background(), did) 92 + if err != nil { 93 + l.Error("failed to resolve handle", "handle", resolved.Handle.String(), "err", err) 94 + htmx.HxError(w, http.StatusBadRequest, fmt.Sprintf("'%s' is an invalid handle", resolved.Handle.String())) 95 + return 96 + } 80 97 98 + client, err := o.AuthorizedClient(r) 99 + if err != nil { 100 + l.Error("failed to get authorized client", "err", err) 101 + http.Error(w, err.Error(), http.StatusInternalServerError) 102 + return 103 + } 81 104 105 + ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", yoten.ActorProfileNSID, did, "self") 106 + var cid *string 107 + if ex != nil { 108 + cid = ex.Cid 109 + } 82 110 83 - Event: ph.UserSignInSuccessEvent, 111 + // This should only occur once per account 112 + if ex == nil { 113 + createdAt := time.Now().Format(time.RFC3339) 114 + atresp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 115 + Collection: yoten.ActorProfileNSID, 116 + Repo: did, 117 + Rkey: "self", 118 + Record: &lexutil.LexiconTypeDecoder{ 119 + Val: &yoten.ActorProfile{ 120 + DisplayName: resolved.Handle.String(), 121 + Description: db.ToPtr(""), 122 + Languages: make([]string, 0), 123 + Location: db.ToPtr(""), 124 + CreatedAt: createdAt, 125 + }}, 126 + 127 + SwapRecord: cid, 84 128 }) 85 129 if err != nil { 86 - log.Println("failed to enqueue posthog event:", err) 130 + l.Error("failed to create profile record", "err", err) 131 + htmx.HxError(w, http.StatusInternalServerError, "Failed to announce profile creation, try again later") 132 + return 133 + } 134 + 135 + l.Debug("created profile record", "uri", atresp.Uri) 136 + 137 + if !o.Config.Core.Dev { 138 + err = o.Posthog.Enqueue(posthog.Capture{ 139 + DistinctId: sessData.AccountDID.String(), 140 + Event: ph.UserSignInSuccessEvent, 141 + }) 142 + if err != nil { 143 + l.Error("failed to enqueue posthog event", "err", err) 144 + } 145 + 146 + properties := posthog.NewProperties(). 147 + Set("display_name", resolved.Handle.String()). 148 + Set("language_count", 0). 149 + Set("$set_once", posthog.NewProperties(). 150 + Set("initial_did", did). 151 + Set("initial_handle", resolved.Handle.String()). 152 + Set("created_at", createdAt), 153 + ) 154 + 155 + err = o.Posthog.Enqueue(posthog.Identify{ 156 + DistinctId: did, 157 + Properties: properties, 158 + }) 159 + if err != nil { 160 + l.Error("failed to enqueue posthog identify event", "err", err) 161 + } 162 + 163 + err = o.Posthog.Enqueue(posthog.Capture{ 164 + DistinctId: did, 165 + Event: ph.ProfileRecordCreatedEvent, 166 + }) 167 + if err != nil { 168 + l.Error("failed to enqueue posthog event", "err", err) 169 + } 87 170 } 88 171 } 89 172
+12 -6
internal/server/oauth/oauth.go
··· 3 3 import ( 4 4 "errors" 5 5 "fmt" 6 + "log/slog" 6 7 "net/http" 7 8 "time" 8 9 ··· 11 12 12 13 13 14 15 + "github.com/gorilla/sessions" 16 + "github.com/posthog/posthog-go" 14 17 18 + "yoten.app/internal/atproto" 19 + "yoten.app/internal/server/config" 20 + "yoten.app/internal/types" 21 + ) 15 22 16 23 17 24 18 25 19 - 20 - 21 - 22 - 23 - 24 26 Config *config.Config 25 27 JwksUri string 26 28 Posthog posthog.Client 29 + Logger *slog.Logger 30 + IdResolver *atproto.Resolver 27 31 } 28 32 29 - func New(config *config.Config, ph posthog.Client) (*OAuth, error) { 33 + func New(config *config.Config, ph posthog.Client, idResolver *atproto.Resolver, logger *slog.Logger) (*OAuth, error) { 30 34 var oauthConfig oauth.ClientConfig 31 35 var clientUri string 32 36 ··· 56 60 SessionStore: sessStore, 57 61 JwksUri: jwksUri, 58 62 Posthog: ph, 63 + IdResolver: idResolver, 64 + Logger: logger, 59 65 }, nil 60 66 61 67 }
+7 -4
internal/db/db.go
··· 4 4 "context" 5 5 "database/sql" 6 6 "fmt" 7 + "log/slog" 7 8 "strings" 8 9 9 10 _ "github.com/mattn/go-sqlite3" ··· 11 12 12 13 type DB struct { 13 14 *sql.DB 15 + logger *slog.Logger 14 16 } 15 17 16 18 type Execer interface { ··· 24 26 PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) 25 27 } 26 28 27 - func Make(dbPath string) (*DB, error) { 29 + func Make(ctx context.Context, dbPath string, logger *slog.Logger) (*DB, error) { 28 30 opts := []string{ 29 31 "_foreign_keys=1", 30 32 "_journal_mode=WAL", ··· 37 39 return nil, fmt.Errorf("failed to open db: %w", err) 38 40 } 39 41 40 - ctx := context.Background() 41 - 42 42 conn, err := db.Conn(ctx) 43 43 if err != nil { 44 44 return nil, err ··· 231 231 return nil, fmt.Errorf("failed to execute db create statement: %w", err) 232 232 } 233 233 234 - return &DB{db}, nil 234 + return &DB{ 235 + db, 236 + logger, 237 + }, nil 235 238 }
+58 -47
internal/server/handlers/study-session.go
··· 3 3 import ( 4 4 "errors" 5 5 "fmt" 6 - "log" 7 6 "net/http" 8 7 "strconv" 9 8 "time" ··· 117 116 } 118 117 119 118 func (h *Handler) HandleStudySessionFeed(w http.ResponseWriter, r *http.Request) { 119 + l := h.Logger.With("handler", "HandleStudySessionFeed") 120 + 120 121 isFriends, err := strconv.ParseBool(r.URL.Query().Get("friends")) 121 122 if err != nil { 122 - log.Println("failed to parse friends value:", err) 123 + l.Error("failed to parse friends value", "err", err) 123 124 isFriends = false 124 125 } 125 126 ··· 129 130 } 130 131 page, err := strconv.ParseInt(pageStr, 10, 64) 131 132 if err != nil { 132 - log.Println("failed to parse page value:", err) 133 + l.Error("failed to parse page value", "err", err) 133 134 page = 1 134 135 } 135 136 if page == 0 { ··· 148 149 if !isFriends { 149 150 feed, err = db.GetStudySessionFeed(h.Db, pageSize+1, int(offset)) 150 151 if err != nil { 151 - log.Println("failed to get global feed:", err) 152 + l.Error("failed to get global feed", "err", err) 152 153 htmx.HxError(w, http.StatusInternalServerError, "Failed to get global study session feed, try again later.") 153 154 return 154 155 } 155 156 err = h.GetBskyProfileHydratedSessionFeed(feed) 156 157 if err != nil { 157 - log.Println("failed to hydrate bsky profiles:", err) 158 + l.Error("failed to hydrate bsky profiles", "err", err) 158 159 htmx.HxError(w, http.StatusInternalServerError, "Failed to get global study session feed, try again later.") 159 160 return 160 161 } 161 162 } else { 162 163 if fetchUserError != nil { 163 - log.Println("failed to get logged-in user:", err) 164 + l.Error("failed to get logged-in user", "err", err) 164 165 htmx.HxRedirect(w, "/login") 165 166 return 166 167 } 167 168 168 169 feed, err = db.GetFriendsStudySessionFeed(h.Db, user.Did, pageSize+1, int(offset)) 169 170 if err != nil { 170 - log.Println("failed to get global feed:", err) 171 + l.Error("failed to get global feed", "err", err) 171 172 htmx.HxError(w, http.StatusInternalServerError, "Failed to get global study session feed, try again later.") 172 173 return 173 174 } 174 175 err = h.GetBskyProfileHydratedSessionFeed(feed) 175 176 if err != nil { 176 - log.Println("failed to hydrate bsky profiles:", err) 177 + l.Error("failed to hydrate bsky profiles", "err", err) 177 178 htmx.HxError(w, http.StatusInternalServerError, "Failed to get global study session feed, try again later.") 178 179 return 179 180 } ··· 181 182 182 183 feed, err = ApplyPendingChanges(h, w, r, feed, PendingStudySessionCreation, PendingStudySessionUpdates, PendingStudySessionDeletion) 183 184 if err != nil { 184 - log.Printf("failed to save yoten-session after processing pending changes: %v", err) 185 + l.Error("failed to save yoten-session after processing pending changes", "err", err) 185 186 } 186 187 187 188 nextPage := 0 ··· 200 201 } 201 202 202 203 func (h *Handler) HandleEditStudySessionPage(w http.ResponseWriter, r *http.Request) { 204 + l := h.Logger.With("handler", "HandleEditStudySessionPage") 205 + 203 206 client, err := h.Oauth.AuthorizedClient(r) 204 207 if err != nil { 205 - log.Println("failed to get authorized client:", err) 208 + l.Error("failed to get authorized client", "err", err) 206 209 htmx.HxRedirect(w, "/login") 207 210 return 208 211 } 209 212 210 213 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 211 214 if err != nil { 212 - log.Println("failed to get logged-in user:", err) 215 + l.Error("failed to get logged-in user", "err", err) 213 216 htmx.HxRedirect(w, "/login") 214 217 return 215 218 } ··· 217 220 rkey := chi.URLParam(r, "rkey") 218 221 studySession, err := db.GetStudySessionByRkey(h.Db, user.Did, rkey) 219 222 if err != nil { 220 - log.Println("failed to get study session from db:", err) 223 + l.Error("failed to get study session from db", "err", err) 221 224 htmx.HxError(w, http.StatusInternalServerError, "Failed to update study session, try again later.") 222 225 return 223 226 } 224 227 225 228 if user.Did != studySession.Did { 226 - log.Printf("user '%s' does not own record '%s'", user.Did, studySession.Rkey) 229 + l.Error("user does not own record", "did", user.Did, "sessionDid", studySession.Did) 227 230 htmx.HxError(w, http.StatusUnauthorized, "You do not have permissions to edit this study session.") 228 231 return 229 232 } ··· 232 235 case http.MethodGet: 233 236 userDefinedActivities, err := db.GetActivitiesByDid(h.Db, user.Did) 234 237 if err != nil { 235 - log.Println("failed to get user-defined activities:", err) 238 + l.Error("failed to get user-defined activities", "err", err) 236 239 } 237 240 238 241 resources, err := db.GetResourcesByDid(h.Db, user.Did) 239 242 if err != nil { 240 - log.Println("failed to get user-defined resources:", err) 243 + l.Error("failed to get user-defined resources", "err", err) 241 244 } 242 245 243 246 preDefinedActivities, err := db.GetPredefinedActivities(h.Db) 244 247 if err != nil { 245 - log.Println("failed to get pre-defined activities:", err) 248 + l.Error("failed to get pre-defined activities", "err", err) 246 249 } 247 250 248 251 currentResource := studySession.Resource ··· 267 270 268 271 languages, err := db.GetProfileLanguages(h.Db, user.Did) 269 272 if err != nil { 270 - log.Println("failed to fetch profile languages:", err) 273 + l.Error("failed to fetch profile languages", "err", err) 271 274 } 272 275 273 276 views.EditStudySessionPage(views.EditStudySessionPageParams{ ··· 280 283 case http.MethodPost: 281 284 updatedStudySession, err := h.parseStudySessionForm(r) 282 285 if err != nil { 283 - log.Println("invalid study session form:", err) 286 + l.Error("invalid study session form", "err", err) 284 287 htmx.HxError(w, http.StatusBadRequest, "Failed to update study session, ensure all data is valid.") 285 288 return 286 289 } ··· 289 292 updatedStudySession.CreatedAt = studySession.CreatedAt 290 293 291 294 if err := db.ValidateStudySession(updatedStudySession); err != nil { 292 - log.Println("invalid study session:", err) 295 + l.Error("invalid study session", "err", err) 293 296 switch { 294 297 case errors.Is(err, db.ErrSessionDescriptionTooLong): 295 298 htmx.HxError(w, http.StatusBadRequest, "Study session description cannot be more than 256 characters.") ··· 358 361 SwapRecord: cid, 359 362 }) 360 363 if err != nil { 361 - log.Println("failed to update study session record:", err) 364 + l.Error("failed to update study session record", "err", err) 362 365 htmx.HxError(w, http.StatusInternalServerError, "Failed to update study session, try again later.") 363 366 return 364 367 } 365 368 366 369 err = SavePendingUpdate(h, w, r, PendingStudySessionUpdates, updatedStudySession) 367 370 if err != nil { 368 - log.Printf("failed to save yoten-session to add pending study session updates: %v", err) 371 + l.Error("failed to save yoten-session to add pending study session updates", "err", err) 369 372 } 370 373 371 374 if !h.Config.Core.Dev { ··· 377 380 Set("rkey", rkey), 378 381 }) 379 382 if err != nil { 380 - log.Println("failed to enqueue posthog event:", err) 383 + l.Error("failed to enqueue posthog event", "err", err) 381 384 } 382 385 } 383 386 ··· 386 389 } 387 390 388 391 func (h *Handler) HandleNewStudySessionPage(w http.ResponseWriter, r *http.Request) { 392 + l := h.Logger.With("handler", "HandleNewStudySessionPage") 393 + 389 394 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 390 395 if err != nil { 391 - log.Println("failed to get logged-in user:", err) 396 + l.Error("failed to get logged-in user", "err", err) 392 397 htmx.HxRedirect(w, "/login") 393 398 return 394 399 } 395 400 396 401 client, err := h.Oauth.AuthorizedClient(r) 397 402 if err != nil { 398 - log.Println("failed to get authorized client:", err) 403 + l.Error("failed to get authorized client", "err", err) 399 404 htmx.HxRedirect(w, "/login") 400 405 return 401 406 } ··· 404 409 case http.MethodGet: 405 410 profile, err := db.GetProfile(h.Db, user.Did) 406 411 if err != nil { 407 - log.Printf("failed to find %s in db: %s", user.Did, err) 412 + l.Error("failed to find user in db", "did", user.Did, "err", err) 408 413 htmx.HxError(w, http.StatusNotFound, "Failed to find user.") 409 414 return 410 415 } 411 416 412 417 userDefinedActivities, err := db.GetActivitiesByDid(h.Db, user.Did) 413 418 if err != nil { 414 - log.Println("failed to get user-defined activities:", err) 419 + l.Error("failed to get user-defined activities", "err", err) 415 420 } 416 421 preDefinedActivities, err := db.GetPredefinedActivities(h.Db) 417 422 if err != nil { 418 - log.Println("failed to get pre-defined activities:", err) 423 + l.Error("failed to get pre-defined activities", "err", err) 419 424 } 420 425 activeActivities := utils.Filter(userDefinedActivities, func(activity db.Activity) bool { 421 426 return activity.Status != db.Deleted ··· 424 429 425 430 resources, err := db.GetResourcesByDid(h.Db, user.Did) 426 431 if err != nil { 427 - log.Println("failed to get user-defined resources:", err) 432 + l.Error("failed to get user-defined resources", "err", err) 428 433 } 429 434 activeResources := utils.Filter(resources, func(resource db.Resource) bool { 430 435 return resource.Status != db.Deleted ··· 440 445 case http.MethodPost: 441 446 newStudySession, err := h.parseStudySessionForm(r) 442 447 if err != nil { 443 - log.Println("invalid study session form:", err) 448 + l.Error("invalid study session form", "err", err) 444 449 htmx.HxError(w, http.StatusBadRequest, "Failed to update study session, ensure all data is valid.") 445 450 return 446 451 } ··· 459 464 } 460 465 461 466 if err := db.ValidateStudySession(newStudySession); err != nil { 462 - log.Println("invalid study session:", err) 467 + l.Error("invalid study session", "err", err) 463 468 switch { 464 469 case errors.Is(err, db.ErrSessionDescriptionTooLong): 465 470 htmx.HxError(w, http.StatusBadRequest, "Study session description cannot be more than 256 characters.") ··· 521 526 }, 522 527 }) 523 528 if err != nil { 524 - log.Println("failed to create study session record:", err) 529 + l.Error("failed to create study session record", "err", err) 525 530 htmx.HxError(w, http.StatusInternalServerError, "Failed to create study session, try again later.") 526 531 return 527 532 } 528 533 529 534 err = SavePendingCreate(h, w, r, PendingStudySessionCreation, newStudySession) 530 535 if err != nil { 531 - log.Printf("failed to save yoten-session to add pending study session creation: %v", err) 536 + l.Error("failed to save yoten-session to add pending study session creation", "err", err) 532 537 } 533 538 534 539 if !h.Config.Core.Dev { ··· 545 550 Set("date_is_today", newStudySession.Date.Truncate(24*time.Hour).Equal(time.Now().UTC().In(loc).Truncate(24*time.Hour))), 546 551 }) 547 552 if err != nil { 548 - log.Println("failed to enqueue posthog event:", err) 553 + l.Error("failed to enqueue posthog event", "err", err) 549 554 } 550 555 } 551 556 ··· 554 559 } 555 560 556 561 func (h *Handler) HandleDeleteStudySession(w http.ResponseWriter, r *http.Request) { 562 + l := h.Logger.With("handler", "HandleDeleteStudySession") 563 + 557 564 user := h.Oauth.GetUser(r) 558 565 if user == nil { 559 - log.Println("failed to get logged-in user") 566 + l.Error("failed to get logged-in user") 560 567 htmx.HxRedirect(w, "/login") 561 568 return 562 569 } 563 570 564 571 client, err := h.Oauth.AuthorizedClient(r) 565 572 if err != nil { 566 - log.Println("failed to get authorized client:", err) 573 + l.Error("failed to get authorized client", "err", err) 567 574 htmx.HxError(w, http.StatusUnauthorized, "Failed to delete study session, try again later.") 568 575 return 569 576 } ··· 572 579 case http.MethodDelete: 573 580 err := r.ParseForm() 574 581 if err != nil { 575 - log.Println("failed to parse study session delete form:", err) 582 + l.Error("failed to parse study session delete form", "err", err) 576 583 htmx.HxError(w, http.StatusBadRequest, "Failed to delete study session, try again later.") 577 584 return 578 585 } ··· 580 587 rkey := chi.URLParam(r, "rkey") 581 588 studySession, err := db.GetStudySessionByRkey(h.Db, user.Did, rkey) 582 589 if err != nil { 583 - log.Println("failed to get study session from db:", err) 590 + l.Error("failed to get study session from db", "err", err) 584 591 htmx.HxError(w, http.StatusInternalServerError, "Failed to delete study session, try again later.") 585 592 return 586 593 } 587 594 588 595 if user.Did != studySession.Did { 589 - log.Println("failed to delete study session: user does not own record") 596 + l.Error("user does not own record", "did", user.Did, "sessionDid", studySession.Did) 590 597 htmx.HxError(w, http.StatusUnauthorized, "Failed to delete study session, try again later.") 591 598 return 592 599 } ··· 597 604 Rkey: rkey, 598 605 }) 599 606 if err != nil { 600 - log.Println("failed to delete study session from PDS:", err) 607 + l.Error("failed to delete study session from PDS", "err", err) 601 608 htmx.HxError(w, http.StatusInternalServerError, "Failed to delete study session, try again later.") 602 609 return 603 610 } 604 611 605 612 err = SavePendingDelete(h, w, r, PendingStudySessionDeletion, studySession) 606 613 if err != nil { 607 - log.Printf("failed to save yoten-session to add pending study session deletion: %v", err) 614 + l.Error("failed to save yoten-session to add pending study session deletion", "err", err) 608 615 } 609 616 610 617 if !h.Config.Core.Dev { ··· 617 624 Set("session_age_seconds", time.Since(studySession.CreatedAt).Seconds()), 618 625 }) 619 626 if err != nil { 620 - log.Println("failed to enqueue posthog event:", err) 627 + l.Error("failed to enqueue posthog event", "err", err) 621 628 } 622 629 } 623 630 ··· 644 651 } 645 652 646 653 func (h *Handler) HandleStudySessionPage(w http.ResponseWriter, r *http.Request) { 654 + l := h.Logger.With("handler", "HandleStudySessionPage") 655 + 647 656 user, _ := bsky.GetUserWithBskyProfile(h.Oauth, r) 648 657 didOrHandle := chi.URLParam(r, "user") 649 658 if didOrHandle == "" { ··· 661 670 662 671 studySession, err := db.GetStudySessionByRkey(h.Db, ident.DID.String(), rkey) 663 672 if err != nil { 664 - log.Println("failed to retrieve study session:", err) 673 + l.Error("failed to retrieve study session", "err", err) 665 674 htmx.HxError(w, http.StatusInternalServerError, "Failed to retrieve study session, try again later.") 666 675 return 667 676 } 668 677 669 678 bskyProfile, err := bsky.GetBskyProfile(ident.DID.String()) 670 679 if err != nil { 671 - log.Println("failed to retrieve bsky profile for study session:", err) 680 + l.Error("failed to retrieve bsky profile for study session", "err", err) 672 681 htmx.HxError(w, http.StatusInternalServerError, "Failed to retrieve bsky profile, try again later.") 673 682 return 674 683 } 675 684 676 685 profile, err := db.GetProfile(h.Db, ident.DID.String()) 677 686 if err != nil { 678 - log.Println("failed to retrieve profile for study session:", err) 687 + l.Error("failed to retrieve profile for study session", "err", err) 679 688 htmx.HxError(w, http.StatusInternalServerError, "Failed to retrieve profile, try again later.") 680 689 return 681 690 } ··· 698 707 } 699 708 700 709 func (h *Handler) HandleStudySessionPageCommentFeed(w http.ResponseWriter, r *http.Request) { 710 + l := h.Logger.With("handler", "HandleStudySessionPageCommentFeed") 711 + 701 712 user, _ := bsky.GetUserWithBskyProfile(h.Oauth, r) 702 713 703 714 didOrHandle := chi.URLParam(r, "user") ··· 721 732 } 722 733 page, err := strconv.ParseInt(pageStr, 10, 64) 723 734 if err != nil { 724 - log.Println("failed to parse page value:", err) 735 + l.Error("failed to parse page value", "err", err) 725 736 page = 1 726 737 } 727 738 if page == 0 { ··· 733 744 734 745 commentFeed, err := db.GetCommentsForSession(h.Db, studySessionUri.String(), pageSize+1, int(offset)) 735 746 if err != nil { 736 - log.Println("failed to get comment feed:", err) 747 + l.Error("failed to get comment feed", "err", err) 737 748 htmx.HxError(w, http.StatusInternalServerError, "Failed to get comment feed, try again later.") 738 749 return 739 750 } ··· 769 780 770 781 populatedCommentFeed, err := h.BuildCommentFeed(finalFeed) 771 782 if err != nil { 772 - log.Println("failed to populate comment feed:", err) 783 + l.Error("failed to populate comment feed", "err", err) 773 784 htmx.HxError(w, http.StatusInternalServerError, "Failed to get comment feed, try again later.") 774 785 return 775 786 }
+31 -26
internal/server/handlers/resource.go
··· 3 3 import ( 4 4 "errors" 5 5 "fmt" 6 - "log" 7 6 "net/http" 8 7 "time" 9 8 ··· 63 62 } 64 63 65 64 func (h *Handler) HandleNewResourcePage(w http.ResponseWriter, r *http.Request) { 65 + l := h.Logger.With("handler", "HandleNewResourcePage") 66 + 66 67 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 67 68 if err != nil { 68 - log.Println("failed to get logged-in user:", err) 69 + l.Error("failed to get logged-in user", "err", err) 69 70 htmx.HxRedirect(w, "/login") 70 71 return 71 72 } ··· 79 80 case http.MethodPost: 80 81 client, err := h.Oauth.AuthorizedClient(r) 81 82 if err != nil { 82 - log.Println("failed to get authorized client:", err) 83 + l.Error("failed to get authorized client", "err", err) 83 84 htmx.HxRedirect(w, "/login") 84 85 return 85 86 } 86 87 87 88 newResource, err := parseResourceForm(r) 88 89 if err != nil { 89 - log.Println("invalid resource form:", err) 90 + l.Error("invalid resource form", "err", err) 90 91 htmx.HxError(w, http.StatusBadRequest, "Failed to create resource, ensure all fields contain valid data.") 91 92 return 92 93 } ··· 95 96 newResource.CreatedAt = time.Now().UTC() 96 97 97 98 if err := db.ValidateResource(newResource); err != nil { 98 - log.Println("invalid resource definition:", err) 99 + l.Error("invalid resource definition", "err", err) 99 100 switch { 100 101 case errors.Is(err, db.ErrResourceTitleEmpty): 101 102 htmx.HxError(w, http.StatusBadRequest, "Resource must have a title.") ··· 118 119 if newResource.Link != nil { 119 120 linkCheckResult := google.CheckResourceLinkSafety(*newResource.Link) 120 121 if linkCheckResult.Err != nil { 121 - log.Println("invalid resource definition:", linkCheckResult.Err) 122 + l.Error("invalid resource definition", "err", linkCheckResult.Err) 122 123 switch { 123 124 case errors.Is(linkCheckResult.Err, google.ErrResourceLinkSketchy): 124 125 if !h.Config.Core.Dev { ··· 186 187 }, 187 188 }) 188 189 if err != nil { 189 - log.Println("failed to create resource record:", err) 190 + l.Error("failed to create resource record", "err", err) 190 191 htmx.HxError(w, http.StatusInternalServerError, "Failed to create resource, try again later.") 191 192 return 192 193 } 193 194 194 195 err = SavePendingCreate(h, w, r, PendingResourceCreation, newResource) 195 196 if err != nil { 196 - log.Printf("failed to save yoten-session to add pending resource creation: %v", err) 197 + l.Error("failed to save yoten-session to add pending resource creation", "err", err) 197 198 } 198 199 199 200 if !h.Config.Core.Dev { ··· 209 210 Set("link_provided", newResource.Link != nil), 210 211 }) 211 212 if err != nil { 212 - log.Println("failed to enqueue posthog event:", err) 213 + l.Error("failed to enqueue posthog event", "err", err) 213 214 } 214 215 } 215 216 ··· 218 219 } 219 220 220 221 func (h *Handler) HandleDeleteResource(w http.ResponseWriter, r *http.Request) { 222 + l := h.Logger.With("handler", "HandleDeleteResource") 223 + 221 224 user := h.Oauth.GetUser(r) 222 225 if user == nil { 223 - log.Println("failed to get logged-in user") 226 + l.Error("failed to get logged-in user") 224 227 htmx.HxRedirect(w, "/login") 225 228 return 226 229 } 227 230 client, err := h.Oauth.AuthorizedClient(r) 228 231 if err != nil { 229 - log.Println("failed to get authorized client:", err) 232 + l.Error("failed to get authorized client", "err", err) 230 233 htmx.HxError(w, http.StatusUnauthorized, "Failed to delete resource, try again later.") 231 234 return 232 235 } ··· 236 239 rkey := chi.URLParam(r, "rkey") 237 240 resource, err := db.GetResourceByRkey(h.Db, user.Did, rkey) 238 241 if err != nil { 239 - log.Println("failed to get resource from db:", err) 242 + l.Error("failed to get resource from db", "err", err) 240 243 htmx.HxError(w, http.StatusInternalServerError, "Failed to delete resource, try again later.") 241 244 return 242 245 } 243 246 244 247 if user.Did != resource.Did { 245 - log.Printf("user '%s' does not own record '%s'", user.Did, rkey) 248 + l.Error("user does not own record", "did", user.Did, "resourceDid", resource.Did) 246 249 htmx.HxError(w, http.StatusUnauthorized, "You do not have permissions to delete this resource.") 247 250 return 248 251 } ··· 253 256 Rkey: resource.Rkey, 254 257 }) 255 258 if err != nil { 256 - log.Println("failed to delete resource from PDS:", err) 259 + l.Error("failed to delete resource from PDS", "err", err) 257 260 htmx.HxError(w, http.StatusInternalServerError, "Failed to delete resource, try again later.") 258 261 return 259 262 } 260 263 261 264 err = SavePendingDelete(h, w, r, PendingResourceDeletion, resource) 262 265 if err != nil { 263 - log.Printf("failed to save yoten-session to add pending resource deletion: %v", err) 266 + l.Error("failed to save yoten-session to add pending resource deletion", "err", err) 264 267 } 265 268 266 269 if !h.Config.Core.Dev { ··· 272 275 Set("resource_type", resource.Type), 273 276 }) 274 277 if err != nil { 275 - log.Println("failed to enqueue posthog event:", err) 278 + l.Error("failed to enqueue posthog event", "err", err) 276 279 } 277 280 } 278 281 ··· 281 284 } 282 285 283 286 func (h *Handler) HandleEditResourcePage(w http.ResponseWriter, r *http.Request) { 287 + l := h.Logger.With("handler", "HandleEditResourcePage") 288 + 284 289 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 285 290 if err != nil { 286 - log.Println("failed to get logged-in user:", err) 291 + l.Error("failed to get logged-in user", "err", err) 287 292 htmx.HxRedirect(w, "/login") 288 293 return 289 294 } ··· 291 296 rkey := chi.URLParam(r, "rkey") 292 297 resource, err := db.GetResourceByRkey(h.Db, user.Did, rkey) 293 298 if err != nil { 294 - log.Println("failed to get resource from db:", err) 299 + l.Error("failed to get resource from db", "err", err) 295 300 htmx.HxError(w, http.StatusInternalServerError, "Failed to update resource, try again later.") 296 301 return 297 302 } 298 303 299 304 if user.Did != resource.Did { 300 - log.Printf("user '%s' does not own record '%s'", user.Did, rkey) 305 + l.Error("user does not own record", "did", user.Did, "resourceDid", resource.Did) 301 306 htmx.HxError(w, http.StatusUnauthorized, "You do not have permissions to edit this resource.") 302 307 return 303 308 } ··· 312 317 case http.MethodPost: 313 318 client, err := h.Oauth.AuthorizedClient(r) 314 319 if err != nil { 315 - log.Println("failed to get authorized client:", err) 320 + l.Error("failed to get authorized client", "err", err) 316 321 htmx.HxRedirect(w, "/login") 317 322 return 318 323 } 319 324 320 325 updatedResource, err := parseResourceForm(r) 321 326 if err != nil { 322 - log.Println("invalid resource form:", err) 327 + l.Error("invalid resource form", "err", err) 323 328 htmx.HxError(w, http.StatusBadRequest, "Failed to create resource, ensure all fields contain valid data.") 324 329 return 325 330 } ··· 328 333 updatedResource.CreatedAt = resource.CreatedAt 329 334 330 335 if err := db.ValidateResource(updatedResource); err != nil { 331 - log.Println("invalid resource definition:", err) 336 + l.Error("invalid resource definition", "err", err) 332 337 switch { 333 338 case errors.Is(err, db.ErrResourceTitleEmpty): 334 339 htmx.HxError(w, http.StatusBadRequest, "Resource must have a title.") ··· 352 357 if updatedResource.Link != nil && (resource.Link == nil || *updatedResource.Link != *resource.Link) { 353 358 linkCheckResult := google.CheckResourceLinkSafety(*updatedResource.Link) 354 359 if linkCheckResult.Err != nil { 355 - log.Println("invalid resource definition:", linkCheckResult.Err) 360 + l.Error("invalid resource link", "link", resource.Link, "threatType", linkCheckResult.ThreatType, "err", linkCheckResult.Err) 356 361 switch { 357 362 case errors.Is(linkCheckResult.Err, google.ErrResourceLinkSketchy): 358 363 if !h.Config.Core.Dev { ··· 427 432 SwapRecord: cid, 428 433 }) 429 434 if err != nil { 430 - log.Println("failed to update resource record:", err) 435 + l.Error("failed to update resource record", "err", err) 431 436 htmx.HxError(w, http.StatusInternalServerError, "Failed to update resource, try again later.") 432 437 return 433 438 } 434 439 435 440 err = SavePendingUpdate(h, w, r, PendingResourceUpdates, updatedResource) 436 441 if err != nil { 437 - log.Printf("failed to save yoten-session to add pending resource updates: %v", err) 442 + l.Error("failed to save yoten-session to add pending resource updates", "err", err) 438 443 } 439 444 440 445 if !h.Config.Core.Dev { ··· 450 455 Set("link_provided", updatedResource.Link != nil), 451 456 }) 452 457 if err != nil { 453 - log.Println("failed to enqueue posthog event:", err) 458 + l.Error("failed to enqueue posthog event", "err", err) 454 459 } 455 460 } 456 461
+36 -27
internal/server/handlers/comment.go
··· 1 1 package handlers 2 2 3 3 import ( 4 - "log" 5 4 "net/http" 6 5 "strings" 7 6 "time" ··· 23 22 ) 24 23 25 24 func (h *Handler) HandleNewComment(w http.ResponseWriter, r *http.Request) { 25 + l := h.Logger.With("handler", "HandleNewComment") 26 + 26 27 client, err := h.Oauth.AuthorizedClient(r) 27 28 if err != nil { 28 - log.Println("failed to get authorized client:", err) 29 + l.Error("failed to get authorized client", "err", err) 29 30 htmx.HxRedirect(w, "/login") 30 31 return 31 32 } 32 33 33 34 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 34 35 if err != nil { 35 - log.Println("failed to get logged-in user:", err) 36 + l.Error("failed to get logged-in user", "err", err) 36 37 htmx.HxRedirect(w, "/login") 37 38 return 38 39 } 39 40 40 41 profile, err := db.GetProfile(h.Db, user.Did) 41 42 if err != nil { 42 - log.Println("failed to get logged-in user:", err) 43 + l.Error("failed to get logged-in user", "err", err) 43 44 htmx.HxRedirect(w, "/login") 44 45 return 45 46 } 46 47 47 48 err = r.ParseForm() 48 49 if err != nil { 49 - log.Println("invalid comment form:", err) 50 + l.Error("invalid comment form", "err", err) 50 51 htmx.HxError(w, http.StatusBadRequest, "Unable to process comment, please try again later.") 51 52 return 52 53 } 53 54 54 55 commentBody := r.FormValue("comment") 55 56 if len(strings.TrimSpace(commentBody)) == 0 { 56 - log.Println("invalid comment form: missing comment body") 57 + l.Error("invalid comment form: missing comment body") 57 58 htmx.HxError(w, http.StatusBadRequest, "Comment cannot be empty.") 58 59 return 59 60 } 60 61 61 62 studySessionUri := r.FormValue("study_session_uri") 62 63 if len(studySessionUri) == 0 { 63 - log.Println("invalid comment form: missing study session Uri") 64 + l.Error("invalid comment form: missing study session Uri") 64 65 htmx.HxError(w, http.StatusBadRequest, "Unable to create comment, please try again later.") 65 66 return 66 67 } ··· 100 101 }, 101 102 }) 102 103 if err != nil { 103 - log.Println("failed to create comment record:", err) 104 + l.Error("failed to create comment record", "err", err) 104 105 htmx.HxError(w, http.StatusInternalServerError, "Failed to create comment, try again later.") 105 106 return 106 107 } ··· 121 122 122 123 err = h.Posthog.Enqueue(event) 123 124 if err != nil { 124 - log.Println("failed to enqueue posthog event:", err) 125 + l.Error("failed to enqueue posthog event", "err", err) 125 126 } 126 127 } 127 128 ··· 153 154 } 154 155 155 156 func (h *Handler) HandleDeleteComment(w http.ResponseWriter, r *http.Request) { 157 + l := h.Logger.With("handler", "HandleDeleteComment") 158 + 156 159 user := h.Oauth.GetUser(r) 157 160 if user == nil { 158 - log.Println("failed to get logged-in user") 161 + l.Error("failed to get logged-in user") 159 162 htmx.HxRedirect(w, "/login") 160 163 return 161 164 } 162 165 client, err := h.Oauth.AuthorizedClient(r) 163 166 if err != nil { 164 - log.Println("failed to get authorized client:", err) 167 + l.Error("failed to get authorized client", "err", err) 165 168 htmx.HxRedirect(w, "/login") 166 169 return 167 170 } ··· 171 174 rkey := chi.URLParam(r, "rkey") 172 175 comment, err := db.GetCommentByRkey(h.Db, user.Did, rkey) 173 176 if err != nil { 174 - log.Println("failed to get comment from db:", err) 177 + l.Error("failed to get comment from db", "err", err) 175 178 htmx.HxError(w, http.StatusInternalServerError, "Failed to delete comment, try again later.") 176 179 return 177 180 } 178 181 179 182 if user.Did != comment.Did { 180 - log.Printf("user '%s' does not own record '%s'", user.Did, rkey) 183 + l.Error("user does not own record", "did", user.Did, "commentDid", comment.Did) 181 184 htmx.HxError(w, http.StatusUnauthorized, "You do not have permissions to delete this comment.") 182 185 return 183 186 } ··· 188 191 Rkey: comment.Rkey, 189 192 }) 190 193 if err != nil { 191 - log.Println("failed to delete comment from PDS:", err) 194 + l.Error("failed to delete comment from PDS", "err", err) 192 195 htmx.HxError(w, http.StatusInternalServerError, "Failed to delete comment, try again later.") 193 196 return 194 197 } ··· 209 212 210 213 err = h.Posthog.Enqueue(event) 211 214 if err != nil { 212 - log.Println("failed to enqueue posthog event:", err) 215 + l.Error("failed to enqueue posthog event", "err", err) 213 216 } 214 217 } 215 218 ··· 218 221 } 219 222 220 223 func (h *Handler) HandleEditCommentPage(w http.ResponseWriter, r *http.Request) { 224 + l := h.Logger.With("handler", "HandleEditCommentPage") 225 + 221 226 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 222 227 if err != nil { 223 - log.Println("failed to get logged-in user:", err) 228 + l.Error("failed to get logged-in user", "err", err) 224 229 htmx.HxRedirect(w, "/login") 225 230 return 226 231 } ··· 228 233 rkey := chi.URLParam(r, "rkey") 229 234 comment, err := db.GetCommentByRkey(h.Db, user.Did, rkey) 230 235 if err != nil { 231 - log.Println("failed to get comment from db:", err) 236 + l.Error("failed to get comment from db", "err", err) 232 237 htmx.HxError(w, http.StatusInternalServerError, "Failed to update comment, try again later.") 233 238 return 234 239 } 235 240 236 241 if user.Did != comment.Did { 237 - log.Printf("user '%s' does not own record '%s'", user.Did, rkey) 242 + l.Error("user does not own record", "did", user.Did, "commentDid", comment.Did) 238 243 htmx.HxError(w, http.StatusUnauthorized, "You do not have permissions to edit this comment.") 239 244 return 240 245 } ··· 245 250 case http.MethodPost: 246 251 client, err := h.Oauth.AuthorizedClient(r) 247 252 if err != nil { 248 - log.Println("failed to get authorized client:", err) 253 + l.Error("failed to get authorized client", "err", err) 249 254 htmx.HxRedirect(w, "/login") 250 255 return 251 256 } 252 257 253 258 err = r.ParseForm() 254 259 if err != nil { 255 - log.Println("invalid comment form:", err) 260 + l.Error("invalid comment form", "err", err) 256 261 htmx.HxError(w, http.StatusBadRequest, "Unable to process comment, please try again later.") 257 262 return 258 263 } 259 264 260 265 commentBody := r.FormValue("comment") 261 266 if len(strings.TrimSpace(commentBody)) == 0 { 262 - log.Println("invalid comment form: missing comment body") 267 + l.Error("invalid comment form: missing comment body") 263 268 htmx.HxError(w, http.StatusBadRequest, "Comment cannot be empty.") 264 269 return 265 270 } ··· 303 308 SwapRecord: cid, 304 309 }) 305 310 if err != nil { 306 - log.Println("failed to update study session record:", err) 311 + l.Error("failed to update study session record", "err", err) 307 312 htmx.HxError(w, http.StatusInternalServerError, "Failed to update comment, try again later.") 308 313 return 309 314 } ··· 324 329 325 330 err = h.Posthog.Enqueue(event) 326 331 if err != nil { 327 - log.Println("failed to enqueue posthog event:", err) 332 + l.Error("failed to enqueue posthog event", "err", err) 328 333 } 329 334 } 330 335 ··· 387 392 } 388 393 389 394 func (h *Handler) HandleReply(w http.ResponseWriter, r *http.Request) { 395 + l := h.Logger.With("handler", "HandleReply") 396 + 390 397 user := h.Oauth.GetUser(r) 391 398 if user == nil { 392 - log.Println("failed to get logged-in user") 399 + l.Error("failed to get logged-in user") 393 400 htmx.HxRedirect(w, "/login") 394 401 return 395 402 } ··· 397 404 studySessionUri := r.URL.Query().Get("root") 398 405 parentCommentUri := r.URL.Query().Get("parent") 399 406 if len(studySessionUri) == 0 || len(parentCommentUri) == 0 { 400 - log.Println("invalid reply form: study session uri or parent comment uri is empty") 407 + l.Error("invalid reply form: study session uri or parent comment uri is empty") 401 408 htmx.HxError(w, http.StatusBadRequest, "Unable to process comment, please try again later.") 402 409 return 403 410 } ··· 409 416 } 410 417 411 418 func (h *Handler) HandleCancelCommentEdit(w http.ResponseWriter, r *http.Request) { 419 + l := h.Logger.With("handler", "HandleCancelCommentEdit") 420 + 412 421 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 413 422 if err != nil { 414 - log.Println("failed to get logged-in user:", err) 423 + l.Error("failed to get logged-in user", "err", err) 415 424 htmx.HxRedirect(w, "/login") 416 425 return 417 426 } ··· 419 428 rkey := chi.URLParam(r, "rkey") 420 429 comment, err := db.GetCommentByRkey(h.Db, user.Did, rkey) 421 430 if err != nil { 422 - log.Println("failed to get comment from db:", err) 431 + l.Error("failed to get comment from db", "err", err) 423 432 htmx.HxError(w, http.StatusInternalServerError, "Failed to update comment, try again later.") 424 433 return 425 434 }
+11 -10
internal/server/handlers/follow.go
··· 1 1 package handlers 2 2 3 3 import ( 4 - "log" 5 4 "net/http" 6 5 "time" 7 6 ··· 20 19 ) 21 20 22 21 func (h *Handler) HandleFollow(w http.ResponseWriter, r *http.Request) { 22 + l := h.Logger.With("handler", "HandleFollow") 23 + 23 24 client, err := h.Oauth.AuthorizedClient(r) 24 25 if err != nil { 25 - log.Println("failed to get authorized client:", err) 26 + l.Error("failed to get authorized client", "err", err) 26 27 htmx.HxRedirect(w, "/login") 27 28 return 28 29 } 29 30 30 31 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 31 32 if err != nil { 32 - log.Println("failed to get logged-in user:", err) 33 + l.Error("failed to get logged-in user", "err", err) 33 34 htmx.HxRedirect(w, "/login") 34 35 return 35 36 } ··· 42 43 43 44 subjectIdent, err := h.IdResolver.ResolveIdent(r.Context(), subject) 44 45 if err != nil { 45 - log.Println("failed to follow, invalid did:", err) 46 + l.Error("failed to follow, invalid did", "err", err) 46 47 htmx.HxError(w, http.StatusBadRequest, "Failed to follow profile, try again later.") 47 48 return 48 49 } 49 50 50 51 if user.Did == subjectIdent.DID.String() { 51 - log.Println("failed to follow, cannot follow yourself") 52 + l.Error("failed to follow, cannot follow yourself") 52 53 htmx.HxError(w, http.StatusBadRequest, "You cannot follow yourself.") 53 54 return 54 55 } ··· 68 69 }}, 69 70 }) 70 71 if err != nil { 71 - log.Println("failed to create follow record:", err) 72 + l.Error("failed to create follow record", "err", err) 72 73 htmx.HxError(w, http.StatusInternalServerError, "Failed to follow profile, try again later.") 73 74 return 74 75 } ··· 84 85 Set("is_mutual_follow", followStatus == db.IsMutual), 85 86 }) 86 87 if err != nil { 87 - log.Println("failed to enqueue posthog event:", err) 88 + l.Error("failed to enqueue posthog event", "err", err) 88 89 } 89 90 } 90 91 ··· 95 96 case http.MethodDelete: 96 97 follow, err := db.GetFollow(h.Db, user.Did, subjectIdent.DID.String()) 97 98 if err != nil { 98 - log.Println("failed to get follow relationship:", err) 99 + l.Error("failed to get follow relationship", "err", err) 99 100 htmx.HxError(w, http.StatusInternalServerError, "Failed to unfollow profile, try again later.") 100 101 return 101 102 } ··· 106 107 Rkey: follow.Rkey, 107 108 }) 108 109 if err != nil { 109 - log.Println("failed to delete follow record:", err) 110 + l.Error("failed to delete follow record", "err", err) 110 111 htmx.HxError(w, http.StatusInternalServerError, "Failed to unfollow profile, try again later.") 111 112 return 112 113 } ··· 119 120 Set("subject_did", subjectIdent.DID.String()), 120 121 }) 121 122 if err != nil { 122 - log.Println("failed to enqueue posthog event:", err) 123 + l.Error("failed to enqueue posthog event", "err", err) 123 124 } 124 125 } 125 126
+29 -24
internal/server/handlers/activity.go
··· 3 3 import ( 4 4 "errors" 5 5 "fmt" 6 - "log" 7 6 "net/http" 8 7 "time" 9 8 ··· 55 54 } 56 55 57 56 func (h *Handler) HandleNewActivityPage(w http.ResponseWriter, r *http.Request) { 57 + l := h.Logger.With("handler", "HandleNewActivityPage") 58 + 58 59 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 59 60 if err != nil { 60 - log.Println("failed to get logged-in user:", err) 61 + l.Error("failed to get logged-in user", "err", err) 61 62 htmx.HxRedirect(w, "/login") 62 63 return 63 64 } ··· 71 72 case http.MethodPost: 72 73 client, err := h.Oauth.AuthorizedClient(r) 73 74 if err != nil { 74 - log.Println("failed to get authorized client:", err) 75 + l.Error("failed to get authorized client", "err", err) 75 76 htmx.HxRedirect(w, "/login") 76 77 return 77 78 } 78 79 79 80 newActivity, err := parseActivityForm(r) 80 81 if err != nil { 81 - log.Println("invalid activity form:", err) 82 + l.Error("invalid activity form", "err", err) 82 83 htmx.HxError(w, http.StatusBadRequest, "Failed to create activity, ensure all fields contain valid data.") 83 84 return 84 85 } ··· 87 88 newActivity.CreatedAt = time.Now().UTC() 88 89 89 90 if err := db.ValidateActivity(newActivity); err != nil { 90 - log.Println("invalid activity def:", err) 91 + l.Error("invalid activity def", "err", err) 91 92 switch { 92 93 case errors.Is(err, db.ErrActivityNameEmpty): 93 94 htmx.HxError(w, http.StatusBadRequest, "Activity must have a name.") ··· 123 124 }, 124 125 }) 125 126 if err != nil { 126 - log.Println("failed to create activity record:", err) 127 + l.Error("failed to create activity record", "err", err) 127 128 htmx.HxError(w, http.StatusInternalServerError, "Failed to create activity, try again later.") 128 129 return 129 130 } 130 131 131 132 err = SavePendingCreate(h, w, r, PendingActivityCreation, newActivity) 132 133 if err != nil { 133 - log.Printf("failed to save yoten-session to add pending activity creation: %v", err) 134 + l.Error("failed to save yoten-session to add pending activity creation", "err", err) 134 135 } 135 136 136 137 if !h.Config.Core.Dev { ··· 145 146 Set("category_count", len(categoriesString)), 146 147 }) 147 148 if err != nil { 148 - log.Println("failed to enqueue posthog event:", err) 149 + l.Error("failed to enqueue posthog event", "err", err) 149 150 } 150 151 } 151 152 ··· 154 155 } 155 156 156 157 func (h *Handler) HandleDeleteActivity(w http.ResponseWriter, r *http.Request) { 158 + l := h.Logger.With("handler", "HandleDeleteActivity") 159 + 157 160 user := h.Oauth.GetUser(r) 158 161 if user == nil { 159 - log.Println("failed to get logged-in user") 162 + l.Error("failed to get logged-in user") 160 163 htmx.HxRedirect(w, "/login") 161 164 return 162 165 } 163 166 client, err := h.Oauth.AuthorizedClient(r) 164 167 if err != nil { 165 - log.Println("failed to get authorized client:", err) 168 + l.Error("failed to get authorized client", "err", err) 166 169 htmx.HxError(w, http.StatusUnauthorized, "Failed to delete activity, try again later.") 167 170 return 168 171 } ··· 172 175 rkey := chi.URLParam(r, "rkey") 173 176 activity, err := db.GetActivityByRkey(h.Db, user.Did, rkey) 174 177 if err != nil { 175 - log.Println("failed to get activity from db:", err) 178 + l.Error("failed to get activity from db", "err", err) 176 179 htmx.HxError(w, http.StatusInternalServerError, "Failed to delete activity, try again later.") 177 180 return 178 181 } 179 182 180 183 if user.Did != activity.Did { 181 - log.Printf("user '%s' does not own record '%s'", user.Did, rkey) 184 + l.Error("user does not own record", "did", user.Did, "activityDid", activity.Did) 182 185 htmx.HxError(w, http.StatusUnauthorized, "You do not have permissions to edit this activity.") 183 186 return 184 187 } ··· 189 192 Rkey: activity.Rkey, 190 193 }) 191 194 if err != nil { 192 - log.Println("failed to delete activity from PDS:", err) 195 + l.Error("failed to delete activity from PDS", "err", err) 193 196 htmx.HxError(w, http.StatusInternalServerError, "Failed to delete activity, try again later.") 194 197 return 195 198 } 196 199 197 200 err = SavePendingDelete(h, w, r, PendingActivityDeletion, activity) 198 201 if err != nil { 199 - log.Printf("failed to save yoten-session to add pending activity deletion: %v", err) 202 + l.Error("failed to save yoten-session to add pending activity deletion", "err", err) 200 203 } 201 204 202 205 if !h.Config.Core.Dev { ··· 207 210 Set("activity_id", activity.ID), 208 211 }) 209 212 if err != nil { 210 - log.Println("failed to enqueue posthog event:", err) 213 + l.Error("failed to enqueue posthog event", "err", err) 211 214 } 212 215 } 213 216 ··· 216 219 } 217 220 218 221 func (h *Handler) HandleEditActivityPage(w http.ResponseWriter, r *http.Request) { 222 + l := h.Logger.With("handler", "HandleEditActivityPage") 223 + 219 224 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 220 225 if err != nil { 221 - log.Println("failed to get logged-in user:", err) 226 + l.Error("failed to get logged-in user", "err", err) 222 227 htmx.HxRedirect(w, "/login") 223 228 return 224 229 } ··· 226 231 rkey := chi.URLParam(r, "rkey") 227 232 activity, err := db.GetActivityByRkey(h.Db, user.Did, rkey) 228 233 if err != nil { 229 - log.Println("failed to get activity from db:", err) 234 + l.Error("failed to get activity from db", "err", err) 230 235 htmx.HxError(w, http.StatusInternalServerError, "Failed to update activity, try again later.") 231 236 return 232 237 } 233 238 234 239 if user.Did != activity.Did { 235 - log.Printf("user '%s' does not own record '%s'", user.Did, rkey) 240 + l.Error("user does not own record", "did", user.Did, "activityDid", activity.Did) 236 241 htmx.HxError(w, http.StatusUnauthorized, "You do not have permissions to edit this activity.") 237 242 return 238 243 } ··· 247 252 case http.MethodPost: 248 253 client, err := h.Oauth.AuthorizedClient(r) 249 254 if err != nil { 250 - log.Println("failed to get authorized client:", err) 255 + l.Error("failed to get authorized client", "err", err) 251 256 htmx.HxRedirect(w, "/login") 252 257 return 253 258 } 254 259 255 260 updatedActivity, err := parseActivityForm(r) 256 261 if err != nil { 257 - log.Println("invalid activity form:", err) 262 + l.Error("invalid activity form", "err", err) 258 263 htmx.HxError(w, http.StatusBadRequest, "Failed to create activity, ensure all fields contain valid data.") 259 264 return 260 265 } ··· 263 268 updatedActivity.CreatedAt = activity.CreatedAt 264 269 265 270 if err := db.ValidateActivity(updatedActivity); err != nil { 266 - log.Println("invalid activity def:", err) 271 + l.Error("invalid activity def", "err", err) 267 272 switch { 268 273 case errors.Is(err, db.ErrActivityNameEmpty): 269 274 htmx.HxError(w, http.StatusBadRequest, "Activity must have a name.") ··· 306 311 SwapRecord: cid, 307 312 }) 308 313 if err != nil { 309 - log.Println("failed to update study session record:", err) 314 + l.Error("failed to update study session record", "err", err) 310 315 htmx.HxError(w, http.StatusInternalServerError, "Failed to update activity, try again later.") 311 316 return 312 317 } 313 318 314 319 err = SavePendingUpdate(h, w, r, PendingActivityUpdates, updatedActivity) 315 320 if err != nil { 316 - log.Printf("failed to save yoten-session to add pending activity updates: %v", err) 321 + l.Error("failed to save yoten-session to add pending activity updates", "err", err) 317 322 } 318 323 319 324 if !h.Config.Core.Dev { ··· 328 333 Set("category_count", len(categoriesString)), 329 334 }) 330 335 if err != nil { 331 - log.Println("failed to enqueue posthog event:", err) 336 + l.Error("failed to enqueue posthog event", "err", err) 332 337 } 333 338 } 334 339
+26 -18
internal/server/handlers/login.go
··· 3 3 import ( 4 4 "context" 5 5 "fmt" 6 - "log" 7 6 "net/http" 8 7 "strings" 9 8 ··· 17 16 ) 18 17 19 18 func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { 19 + l := h.Logger.With("handler", "Login") 20 + 20 21 switch r.Method { 21 22 case http.MethodGet: 22 23 var user *types.User ··· 24 25 if oauth != nil { 25 26 bskyProfile, err := bsky.GetBskyProfile(oauth.Did) 26 27 if err != nil { 27 - log.Println("failed to get bsky profile:", err) 28 + l.Error("failed to get bsky profile", "err", err) 28 29 } 29 30 user = &types.User{ 30 31 OauthUser: *oauth, ··· 54 55 55 56 // Basic handle validation 56 57 if !strings.Contains(handle, ".") { 57 - log.Println("invalid handle format:", handle) 58 - htmx.HxError(w, http.StatusBadGateway, fmt.Sprintf("'%s' is an invalid handle. Did you mean %s.bsky.social?", handle, handle)) 58 + l.Error("invalid handle format", "handle", handle) 59 + htmx.HxError(w, http.StatusBadRequest, fmt.Sprintf("'%s' is an invalid handle. Did you mean %s.bsky.social?", handle, handle)) 59 60 return 60 61 } 61 62 62 - 63 - 64 - 65 - 66 - 63 + resolved, err := h.IdResolver.ResolveIdent(context.Background(), handle) 64 + if err != nil { 65 + l.Error("failed to resolve handle", "handle", handle, "err", err) 66 + htmx.HxError(w, http.StatusBadRequest, fmt.Sprintf("'%s' is an invalid handle", handle)) 67 + return 68 + } else { 69 + if !h.Config.Core.Dev && resolved.DID.String() != "" { 70 + err := h.Posthog.Enqueue(posthog.Capture{ 71 + DistinctId: string(resolved.DID), 67 72 Event: ph.UserSignInInitiatedEvent, 68 73 }) 69 74 if err != nil { 70 - log.Println("failed to enqueue posthog event:", err) 75 + l.Error("failed to enqueue posthog event", "err", err) 71 76 } 72 77 } 73 78 } 74 79 80 + redirectURL, err := h.Oauth.ClientApp.StartAuthFlow(r.Context(), handle) 81 + if err != nil { 82 + l.Error("failed to resolve auth flow", "handle", handle, "err", err) 83 + http.Error(w, err.Error(), http.StatusInternalServerError) 84 + return 85 + } 75 86 76 87 77 88 78 - 79 - 80 - 81 - 82 - 83 89 } 84 90 85 91 func (h *Handler) Logout(w http.ResponseWriter, r *http.Request) { 92 + l := h.Logger.With("handler", "Logout") 93 + 86 94 did := h.Oauth.GetDid(r) 87 95 88 96 err := h.Oauth.DeleteSession(w, r) 89 97 if err != nil { 90 - log.Println("failed to logout", "err", err) 98 + l.Error("failed to logout", "err", err) 91 99 } else { 92 - log.Println("logged out successfully") 100 + l.Error("logged out successfully") 93 101 } 94 102 95 103 if !h.Config.Core.Dev && did != "" { ··· 98 106 Event: ph.UserLoggedOutEvent, 99 107 }) 100 108 if err != nil { 101 - log.Println("failed to enqueue posthog event:", err) 109 + l.Error("failed to enqueue posthog event", "err", err) 102 110 } 103 111 } 104 112
+10 -7
internal/server/handlers/notification.go
··· 1 1 package handlers 2 2 3 3 import ( 4 - "log" 5 4 "net/http" 6 5 "strconv" 7 6 ··· 12 11 ) 13 12 14 13 func (h *Handler) HandleNotificationFeed(w http.ResponseWriter, r *http.Request) { 14 + l := h.Logger.With("handler", "HandleNotificationFeed") 15 + 15 16 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 16 17 if err != nil { 17 - log.Println("failed to get logged-in user:", err) 18 + l.Error("failed to get logged-in user", "err", err) 18 19 htmx.HxRedirect(w, "/login") 19 20 return 20 21 } ··· 25 26 } 26 27 page, err := strconv.ParseInt(pageStr, 10, 64) 27 28 if err != nil { 28 - log.Println("failed to parse page value:", err) 29 + l.Error("failed to parse page value", "err", err) 29 30 page = 1 30 31 } 31 32 if page == 0 { ··· 39 40 case http.MethodGet: 40 41 notifications, err := db.GetNotificationsByDid(h.Db, user.Did, pageSize+1, int(offset)) 41 42 if err != nil { 42 - log.Println("failed to retrieve notifications:", err) 43 + l.Error("failed to retrieve notifications", "err", err) 43 44 htmx.HxError(w, http.StatusInternalServerError, "Failed to get notifications, try again later.") 44 45 return 45 46 } 46 47 47 48 hydratedNotifications, err := h.getBskyProfileHydratedNotificationFeed(notifications) 48 49 if err != nil { 49 - log.Println("failed to hydrate notifications with bsky profile:", err) 50 + l.Error("failed to hydrate notifications with bsky profile", "err", err) 50 51 htmx.HxError(w, http.StatusInternalServerError, "Failed to get notifications, try again later.") 51 52 return 52 53 } ··· 66 67 } 67 68 68 69 func (h *Handler) HandleNotificationMarkAllRead(w http.ResponseWriter, r *http.Request) { 70 + l := h.Logger.With("handler", "HandleNotificationMarkAllRead") 71 + 69 72 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 70 73 if err != nil { 71 - log.Println("failed to get logged-in user:", err) 74 + l.Error("failed to get logged-in user", "err", err) 72 75 htmx.HxRedirect(w, "/login") 73 76 return 74 77 } 75 78 76 79 err = db.MarkAllNotificationsAsRead(h.Db, user.Did) 77 80 if err != nil { 78 - log.Println("failed to mark all notifications:", err) 81 + l.Error("failed to mark all notifications", "err", err) 79 82 htmx.HxError(w, http.StatusInternalServerError, "Failed to mark all notifications as read, try again later.") 80 83 return 81 84 }
+56 -40
internal/server/handlers/profile.go
··· 3 3 import ( 4 4 "errors" 5 5 "fmt" 6 - "log" 6 + "log/slog" 7 7 "net/http" 8 8 "strconv" 9 9 "time" ··· 30 30 PendingProfileUpdate string = "pending_profile_update" 31 31 ) 32 32 33 - func parseProfileForm(r *http.Request) (db.Profile, error) { 33 + func parseProfileForm(r *http.Request, logger *slog.Logger) (db.Profile, error) { 34 34 err := r.ParseForm() 35 35 if err != nil { 36 36 return db.Profile{}, fmt.Errorf("invalid profile form: %w", err) ··· 45 45 for _, code := range languageCodes { 46 46 language, ok := db.Languages[db.LanguageCode(code)] 47 47 if !ok { 48 - log.Printf("invalid language code submitted: %s", code) 48 + logger.Warn("invalid language code submitted", "languageCode", code) 49 49 continue 50 50 } 51 51 languages = append(languages, language) ··· 62 62 } 63 63 64 64 func (h *Handler) HandleProfilePage(w http.ResponseWriter, r *http.Request) { 65 + l := h.Logger.With("handler", "HandleProfilePage") 66 + 65 67 didOrHandle := chi.URLParam(r, "user") 66 68 if didOrHandle == "" { 67 69 http.Error(w, "Bad request", http.StatusBadRequest) ··· 102 104 103 105 totalStudyTime, err = db.GetTotalStudyTime(h.Db, profileDid) 104 106 if err != nil { 105 - log.Println("failed to get total study time:", err) 107 + l.Error("failed to get total study time", "err", err) 106 108 } 107 109 108 110 totalStudySessions, _ = db.GetTotalStudySessions(h.Db, profileDid) 109 111 if err != nil { 110 - log.Println("failed to get total study study sessions:", err) 112 + l.Error("failed to get total study study sessions", "err", err) 111 113 } 112 114 113 115 followers, following, _ = db.GetFollowerFollowingCount(h.Db, profileDid) 114 116 if err != nil { 115 - log.Println("failed to get follow stats:", err) 117 + l.Error("failed to get follow stats", "err", err) 116 118 } 117 119 118 120 streak, _ = db.GetCurrentStreak(h.Db, profileDid) 119 121 if err != nil { 120 - log.Println("failed to get streak:", err) 122 + l.Error("failed to get streak", "err", err) 121 123 } 122 124 123 125 if user != nil { ··· 128 130 }) 129 131 130 132 if err := g.Wait(); err != nil { 131 - log.Printf("failed to fetch critical profile data for %s: %v", profileDid, err) 133 + l.Error("failed to fetch critical profile data", "did", profileDid, "err", err) 132 134 htmx.HxError(w, http.StatusInternalServerError, "Failed to fetch profile data, try again later.") 133 135 return 134 136 } ··· 160 162 161 163 err := h.Posthog.Enqueue(capture) 162 164 if err != nil { 163 - log.Println("failed to enqueue posthog event:", err) 165 + l.Error("failed to enqueue posthog event", "err", err) 164 166 } 165 167 } 166 168 ··· 178 180 } 179 181 180 182 func (h *Handler) HandleEditProfilePage(w http.ResponseWriter, r *http.Request) { 183 + l := h.Logger.With("handler", "HandleEditProfilePage") 184 + 181 185 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 182 186 if err != nil { 183 - log.Println("failed to get logged-in user:", err) 187 + l.Error("failed to get logged-in user", "err", err) 184 188 htmx.HxRedirect(w, "/login") 185 189 return 186 190 } 187 191 188 192 profile, err := h.GetUserProfileWithAvatar(user.Did) 189 193 if err != nil { 190 - log.Printf("failed to find %s in db: %s", user.Did, err) 194 + l.Error("failed to find user in db", "did", user.Did, "err", err) 191 195 w.WriteHeader(http.StatusNotFound) 192 196 views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w) 193 197 return 194 198 } 195 199 196 200 if user.Did != profile.Did { 197 - log.Printf("user '%s' does not own record '%s'", user.Did, profile.Did) 201 + l.Error("user does not own record", "did", user.Did, "profileDid", profile.Did) 198 202 htmx.HxError(w, http.StatusUnauthorized, "You do not have permissions to edit this profile.") 199 203 return 200 204 } ··· 214 218 case http.MethodPost: 215 219 client, err := h.Oauth.AuthorizedClient(r) 216 220 if err != nil { 217 - log.Println("failed to get authorized client:", err) 221 + l.Error("failed to get authorized client", "err", err) 218 222 htmx.HxRedirect(w, "/login") 219 223 return 220 224 } 221 225 222 - updatedProfile, err := parseProfileForm(r) 226 + updatedProfile, err := parseProfileForm(r, l) 223 227 if err != nil { 224 - log.Println("invalid profile form:", err) 228 + l.Error("invalid profile form", "err", err) 225 229 htmx.HxError(w, http.StatusBadRequest, "Failed to update profile, ensure all fields contain valid data.") 226 230 return 227 231 } ··· 236 240 } 237 241 238 242 if err := db.ValidateProfile(updatedProfile); err != nil { 239 - log.Println("invalid profile:", err) 243 + l.Error("invalid profile", "err", err) 240 244 switch { 241 245 case errors.Is(err, db.ErrProfileNameTooLong): 242 246 htmx.HxError(w, http.StatusBadRequest, "Profile name cannot be more than 64 characters.") ··· 280 284 SwapRecord: cid, 281 285 }) 282 286 if err != nil { 283 - log.Println("failed to put profile record:", err) 287 + l.Error("failed to put profile record", "err", err) 284 288 htmx.HxError(w, http.StatusInternalServerError, "Failed to update PDS, try again later.") 285 289 return 286 290 } 287 291 288 292 err = SavePendingUpdate(h, w, r, PendingProfileUpdate, profile) 289 293 if err != nil { 290 - log.Printf("failed to save yoten-session to add pending profile update: %v", err) 294 + l.Error("failed to save yoten-session to add pending profile update", "err", err) 291 295 } 292 296 293 297 if !h.Config.Core.Dev { ··· 308 312 Properties: properties, 309 313 }) 310 314 if err != nil { 311 - log.Println("failed to enqueue posthog identify event:", err) 315 + l.Error("failed to enqueue posthog identify event", "err", err) 312 316 } 313 317 314 318 err = h.Posthog.Enqueue(posthog.Capture{ ··· 316 320 Event: ph.ProfileRecordEditedEvent, 317 321 }) 318 322 if err != nil { 319 - log.Println("failed to enqueue posthog event:", err) 323 + l.Error("failed to enqueue posthog event", "err", err) 320 324 } 321 325 } 322 326 ··· 325 329 } 326 330 327 331 func (h *Handler) HandleResourcesPage(w http.ResponseWriter, r *http.Request) { 332 + l := h.Logger.With("handler", "HandleResourcesPage") 333 + 328 334 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 329 335 if err != nil { 330 - log.Println("failed to get logged-in user:", err) 336 + l.Error("failed to get logged-in user", "err", err) 331 337 htmx.HxRedirect(w, "/login") 332 338 return 333 339 } 334 340 335 341 resources, err := db.GetResourcesByDid(h.Db, user.Did) 336 342 if err != nil { 337 - log.Println("failed to get resources:", err) 343 + l.Error("failed to get resources", "err", err) 338 344 htmx.HxError(w, http.StatusInternalServerError, "Failed to retrieve profile resources, try again later.") 339 345 return 340 346 } 341 347 342 348 resources, err = ApplyPendingChanges(h, w, r, resources, PendingResourceCreation, PendingResourceUpdates, PendingResourceDeletion) 343 349 if err != nil { 344 - log.Printf("failed to save yoten-session after processing pending changes: %v", err) 350 + l.Error("failed to save yoten-session after processing pending changes", "err", err) 345 351 } 346 352 347 353 activeResources := utils.Filter(resources, func(resource db.Resource) bool { ··· 355 361 } 356 362 357 363 func (h *Handler) HandleActivitiesPage(w http.ResponseWriter, r *http.Request) { 364 + l := h.Logger.With("handler", "HandleActivitiesPage") 365 + 358 366 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 359 367 if err != nil { 360 - log.Println("failed to get logged-in user:", err) 368 + l.Error("failed to get logged-in user", "err", err) 361 369 htmx.HxRedirect(w, "/login") 362 370 return 363 371 } 364 372 365 373 activities, err := db.GetActivitiesByDid(h.Db, user.Did) 366 374 if err != nil { 367 - log.Println("failed to get activities:", err) 375 + l.Error("failed to get activities", "err", err) 368 376 htmx.HxError(w, http.StatusInternalServerError, "Failed to retrieve profile activities, try again later.") 369 377 return 370 378 } 371 379 372 380 activities, err = ApplyPendingChanges(h, w, r, activities, PendingActivityCreation, PendingActivityUpdates, PendingActivityDeletion) 373 381 if err != nil { 374 - log.Printf("failed to save yoten-session after processing pending changes: %v", err) 382 + l.Error("failed to save yoten-session after processing pending changes", "err", err) 375 383 } 376 384 377 385 activeActivities := utils.Filter(activities, func(activity db.Activity) bool { ··· 400 408 } 401 409 402 410 func (h *Handler) HandleProfileFeed(w http.ResponseWriter, r *http.Request) { 411 + l := h.Logger.With("handler", "HandleProfileFeed") 412 + 403 413 didOrHandle := chi.URLParam(r, "user") 404 414 if didOrHandle == "" { 405 415 http.Error(w, "Bad request", http.StatusBadRequest) ··· 415 425 416 426 profile, err := h.GetUserProfileWithAvatar(ident.DID.String()) 417 427 if err != nil { 418 - log.Printf("failed to find %s in db: %s", ident.DID.String(), err) 428 + l.Error("failed to find user in db", "did", ident.DID.String(), "err", err) 419 429 w.WriteHeader(http.StatusNotFound) 420 430 views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w) 421 431 return ··· 423 433 424 434 bskyProfile, err := bsky.GetBskyProfile(ident.DID.String()) 425 435 if err != nil { 426 - log.Println("failed to get bsky profile:", err) 436 + l.Error("failed to get bsky profile", "err", err) 427 437 w.WriteHeader(http.StatusNotFound) 428 438 views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w) 429 439 return ··· 435 445 } 436 446 page, err := strconv.ParseInt(pageStr, 10, 64) 437 447 if err != nil { 438 - log.Println("failed to parse page value:", err) 448 + l.Error("failed to parse page value", "err", err) 439 449 page = 1 440 450 } 441 451 if page == 0 { ··· 447 457 448 458 sessions, err := db.GetStudySessionLogs(h.Db, ident.DID.String(), pageSize+1, int(offset)) 449 459 if err != nil { 450 - log.Println("failed to get study sessions:", err) 460 + l.Error("failed to get study sessions", "err", err) 451 461 w.WriteHeader(http.StatusNotFound) 452 462 views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w) 453 463 return ··· 455 465 456 466 sessions, err = ApplyPendingChanges(h, w, r, sessions, PendingStudySessionCreation, PendingStudySessionUpdates, PendingStudySessionDeletion) 457 467 if err != nil { 458 - log.Printf("failed to save yoten-session after processing pending changes: %v", err) 468 + l.Error("failed to save yoten-session after processing pending changes", "err", err) 459 469 } 460 470 461 471 feed := []*db.StudySessionFeedItem{} ··· 493 503 } 494 504 495 505 func (h *Handler) HandleFriendsPage(w http.ResponseWriter, r *http.Request) { 506 + l := h.Logger.With("handler", "HandleFriendsPage") 507 + 496 508 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 497 509 if err != nil { 498 - log.Println("failed to get logged-in user:", err) 510 + l.Error("failed to get logged-in user", "err", err) 499 511 htmx.HxRedirect(w, "/login") 500 512 return 501 513 } 502 514 503 515 followers, following, err := db.GetFollowerFollowingCount(h.Db, user.Did) 504 516 if err != nil { 505 - log.Printf("getting follow stats repos for %s: %s", user.Did, err) 517 + l.Error("failed to get follow stats", "err", err) 506 518 } 507 519 508 520 views.FriendsPage(views.FriendsPageParams{ ··· 513 525 } 514 526 515 527 func (h *Handler) HandleFriendsFeed(w http.ResponseWriter, r *http.Request) { 528 + l := h.Logger.With("handler", "HandleFriendsFeed") 529 + 516 530 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 517 531 if err != nil { 518 - log.Println("failed to get logged-in user") 532 + l.Error("failed to get logged-in user") 519 533 htmx.HxRedirect(w, "/login") 520 534 return 521 535 } ··· 529 543 } 530 544 page, err := strconv.ParseInt(pageStr, 10, 64) 531 545 if err != nil { 532 - log.Println("failed to parse page value:", err) 546 + l.Error("failed to parse page value", "err", err) 533 547 page = 1 534 548 } 535 549 if page == 0 { ··· 545 559 if mode == partials.Following { 546 560 feed, err := db.GetFollowing(h.Db, user.Did, pageSize+1, int(offset)) 547 561 if err != nil { 548 - log.Println("failed to get following list:", err) 562 + l.Error("failed to get following list", "err", err) 549 563 htmx.HxError(w, http.StatusInternalServerError, "Failed to get following list, try again later.") 550 564 return 551 565 } 552 566 bskyHydratedFeed, err = h.GetHydratedFollowerProfiles(feed) 553 567 if err != nil { 554 - log.Println("failed to hydrate bsky profiles:", err) 568 + l.Error("failed to hydrate bsky profiles", "err", err) 555 569 htmx.HxError(w, http.StatusInternalServerError, "Failed to get following list, try again later.") 556 570 return 557 571 } 558 572 } else { 559 573 feed, err := db.GetFollowers(h.Db, user.Did, pageSize+1, int(offset)) 560 574 if err != nil { 561 - log.Println("failed to get followers list:", err) 575 + l.Error("failed to get followers list", "err", err) 562 576 htmx.HxError(w, http.StatusInternalServerError, "Failed to get followers list, try again later.") 563 577 return 564 578 } 565 579 bskyHydratedFeed, err = h.GetHydratedFollowerProfiles(feed) 566 580 if err != nil { 567 - log.Println("failed to hydrate bsky profiles:", err) 581 + l.Error("failed to hydrate bsky profiles", "err", err) 568 582 htmx.HxError(w, http.StatusInternalServerError, "Failed to get following list, try again later.") 569 583 return 570 584 } ··· 586 600 } 587 601 588 602 func (h *Handler) HandleNotificationsPage(w http.ResponseWriter, r *http.Request) { 603 + l := h.Logger.With("handler", "HandleNotificationsPage") 604 + 589 605 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 590 606 if err != nil { 591 - log.Println("failed to get logged-in user:", err) 607 + l.Error("failed to get logged-in user", "err", err) 592 608 htmx.HxRedirect(w, "/login") 593 609 return 594 610 }
+17 -17
internal/server/handlers/reaction.go
··· 1 1 package handlers 2 2 3 3 import ( 4 - "log" 5 4 "net/http" 6 5 "slices" 7 6 "strconv" ··· 21 20 ) 22 21 23 22 func (h *Handler) HandleReaction(w http.ResponseWriter, r *http.Request) { 23 + l := h.Logger.With("handler", "HandleReaction") 24 + 24 25 client, err := h.Oauth.AuthorizedClient(r) 25 26 if err != nil { 26 - log.Println("failed to get authorized client:", err) 27 + l.Error("failed to get authorized client", "err", err) 27 28 htmx.HxRedirect(w, "/login") 28 29 return 29 30 } 30 31 31 32 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 32 33 if err != nil { 33 - log.Println("failed to get logged-in user:", err) 34 + l.Error("failed to get logged-in user", "err", err) 34 35 htmx.HxRedirect(w, "/login") 35 36 return 36 37 } ··· 53 54 } 54 55 55 56 if user.Did == session.Did { 56 - log.Println("failed to react to study session, cannot react to your own study session") 57 + l.Error("failed to react to study session, cannot react to your own study session") 57 58 htmx.HxError(w, http.StatusBadRequest, "You cannot react to your own study sessions.") 58 59 return 59 60 } ··· 73 74 74 75 reaction, err := db.ReactionFromString(db.ReactionType(reactionId).String()) 75 76 if err != nil { 76 - log.Printf("failed to get reaction types: %v", err) 77 + l.Error("failed to get reaction types", "err", err) 77 78 htmx.HxError(w, http.StatusInternalServerError, "Failed to get global study session feed, try again later.") 78 79 return 79 80 } 80 81 81 82 reactionEvents, err := db.GetReactionsForSession(h.Db, subjectDid, subjectRkey) 82 83 if err != nil { 83 - log.Println("failed to get reactions for study session from db:", err) 84 + l.Error("failed to get reactions for study session from db", "err", err) 84 85 htmx.HxError(w, http.StatusInternalServerError, "Failed to get global study session feed, try again later.") 85 86 return 86 87 } ··· 89 90 case http.MethodPost: 90 91 reactionEvent, err := db.GetReactionEvent(h.Db, user.Did, session, reaction.ID) 91 92 if err != nil { 92 - log.Println("failed to get reaction event from db:", err) 93 + l.Error("failed to get reaction event from db", "err", err) 93 94 htmx.HxError(w, http.StatusInternalServerError, "Failed to add reaction, try again later.") 94 95 return 95 96 } 96 97 if reactionEvent != nil { 97 - log.Println("failed to add reaction, user already reacted") 98 + l.Error("failed to add reaction, user already reacted") 98 99 htmx.HxError(w, http.StatusBadRequest, "You cannot react multiple times with the same reaction.") 99 100 return 100 101 } ··· 113 114 }}, 114 115 }) 115 116 if err != nil { 116 - log.Println("failed to create reaction record:", err) 117 + l.Error("failed to create reaction record", "err", err) 117 118 htmx.HxError(w, http.StatusInternalServerError, "Failed to add reaction, try again later.") 118 119 return 119 120 } ··· 128 129 Set("session_rkey", subjectRkey), 129 130 }) 130 131 if err != nil { 131 - log.Println("failed to enqueue posthog event:", err) 132 + l.Error("failed to enqueue posthog event", "err", err) 132 133 } 133 134 } 134 135 ··· 153 154 case http.MethodDelete: 154 155 reactionEvent, err := db.GetReactionEvent(h.Db, user.Did, session, reaction.ID) 155 156 if err != nil { 156 - log.Println("failed to get reaction event from db:", err) 157 + l.Error("failed to get reaction event from db", "err", err) 157 158 htmx.HxError(w, http.StatusInternalServerError, "Failed to remove reaction, try again later.") 158 159 return 159 160 } ··· 164 165 Rkey: reactionEvent.Rkey, 165 166 }) 166 167 if err != nil { 167 - log.Println("failed to delete reaction record:", err) 168 + l.Error("failed to delete reaction record", "err", err) 168 169 htmx.HxError(w, http.StatusInternalServerError, "Failed to remove reaction, try again later.") 169 170 return 170 171 } ··· 179 180 Set("session_rkey", subjectRkey), 180 181 }) 181 182 if err != nil { 182 - log.Println("failed to enqueue posthog event:", err) 183 + l.Error("failed to enqueue posthog event", "err", err) 183 184 } 184 185 } 185 186 ··· 188 189 }) 189 190 190 191 partials.NewReactions(partials.NewReactionsProps{ 191 - User: user, 192 - SessionRkey: subjectRkey, 193 - SessionDid: subjectDid, 194 - // Reactions: reactions, 192 + User: user, 193 + SessionRkey: subjectRkey, 194 + SessionDid: subjectDid, 195 195 ReactionEvents: reactionEvents, 196 196 }).Render(r.Context(), w) 197 197 }
+14 -11
internal/server/handlers/stats.go
··· 1 1 package handlers 2 2 3 3 import ( 4 - "log" 5 4 "net/http" 6 5 7 6 "yoten.app/internal/clients/bsky" ··· 12 11 ) 13 12 14 13 func (h *Handler) HandleTimePerGraphs(w http.ResponseWriter, r *http.Request) { 14 + l := h.Logger.With("handler", "HandleTimePerGraphs") 15 + 15 16 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 16 17 if err != nil { 17 - log.Println("failed to get logged-in user:", err) 18 + l.Error("failed to get logged-in user", "err", err) 18 19 htmx.HxRedirect(w, "/login") 19 20 return 20 21 } ··· 24 25 25 26 chartData, err := db.GetTimePerData(h.Db, user.Did, period) 26 27 if err != nil { 27 - log.Println("failed to get time per chart data:", err) 28 + l.Error("failed to get time per chart data", "err", err) 28 29 chartData = db.ChartsData{ 29 30 ActivityData: []db.ChartData{}, 30 31 CategoryData: []db.ChartData{}, ··· 38 39 } 39 40 40 41 func (h *Handler) HandleStatsPage(w http.ResponseWriter, r *http.Request) { 42 + l := h.Logger.With("handler", "HandleStatsPage") 43 + 41 44 user, err := bsky.GetUserWithBskyProfile(h.Oauth, r) 42 45 if err != nil { 43 - log.Println("failed to get logged-in user:", err) 46 + l.Error("failed to get logged-in user", "err", err) 44 47 htmx.HxRedirect(w, "/login") 45 48 return 46 49 } 47 50 48 51 totalStudyTime, err := db.GetTotalStudyTime(h.Db, user.Did) 49 52 if err != nil { 50 - log.Println("failed to get total study time:", err) 53 + l.Error("failed to get total study time", "err", err) 51 54 } 52 55 53 56 totalStudySessions, err := db.GetTotalStudySessions(h.Db, user.Did) 54 57 if err != nil { 55 - log.Println("failed to get total study study sessions:", err) 58 + l.Error("failed to get total study study sessions", "err", err) 56 59 } 57 60 58 61 totalActiveDays, err := db.GetTotalActiveDays(h.Db, user.Did) 59 62 if err != nil { 60 - log.Println("failed to get total active days:", err) 63 + l.Error("failed to get total active days", "err", err) 61 64 } 62 65 63 66 streak, err := db.GetCurrentStreak(h.Db, user.Did) 64 67 if err != nil { 65 - log.Println("failed to get streak:", err) 68 + l.Error("failed to get streak", "err", err) 66 69 } 67 70 68 71 heatmap, err := db.GetHeatmapData(h.Db, user.Did) 69 72 if err != nil { 70 - log.Println("failed to get heatmap data:", err) 73 + l.Error("failed to get heatmap data", "err", err) 71 74 } 72 75 73 76 inputOutputPercentage, err := db.GetInputOutputPercentage(h.Db, user.Did) 74 77 if err != nil { 75 - log.Println("failed to get input vs output data:", err) 78 + l.Error("failed to get input vs output data", "err", err) 76 79 } 77 80 78 81 languageSummary, err := db.GetLanguageSummary(h.Db, user.Did) 79 82 if err != nil { 80 - log.Println("failed to get language time summary:", err) 83 + l.Error("failed to get language time summary", "err", err) 81 84 } 82 85 languageChartSegments := db.ConvertToDonutChartSegments(languageSummary) 83 86
+30 -20
internal/consumer/ingester.go
··· 4 4 "context" 5 5 "encoding/json" 6 6 "fmt" 7 - "log" 7 + "log/slog" 8 8 "strings" 9 9 "time" 10 10 ··· 20 20 type Ingester struct { 21 21 Db db.DbWrapper 22 22 Config *config.Config 23 + Logger *slog.Logger 23 24 } 24 25 25 26 type processFunc func(ctx context.Context, e *models.Event) error ··· 35 36 } 36 37 }() 37 38 39 + l := i.Logger.With("kind", e.Kind) 38 40 switch e.Kind { 39 41 case models.EventKindCommit: 40 42 switch e.Commit.Collection { 41 43 case yoten.ActorProfileNSID: 44 + l = l.With("handler", "ingestProfile") 42 45 err = i.ingestProfile(e) 43 46 case yoten.FeedSessionNSID: 47 + l = l.With("handler", "ingestStudySession") 44 48 err = i.ingestStudySession(e) 45 49 case yoten.ActivityDefNSID: 50 + l = l.With("handler", "ingestActivityDef") 46 51 err = i.ingestActivityDef(e) 47 52 case yoten.FeedResourceNSID: 53 + l = l.With("handler", "ingestResource") 48 54 err = i.ingestResource(e) 49 55 case yoten.GraphFollowNSID: 56 + l = l.With("handler", "ingestFollow") 50 57 err = i.ingestFollow(e) 51 58 case yoten.FeedReactionNSID: 59 + l = l.With("handler", "ingestReaction") 52 60 err = i.ingestReaction(e) 53 61 case yoten.FeedCommentNSID: 62 + l = l.With("handler", "ingestComment") 54 63 err = i.ingestComment(e) 55 64 } 65 + l = i.Logger.With("nsid", e.Commit.Collection) 56 66 } 57 67 if err != nil { 58 - log.Printf("failed to ingest event for collection %s: %v", e.Commit.Collection, err) 68 + l.Error("failed to ingest event", "err", err) 59 69 } 60 70 61 71 return nil ··· 129 139 return fmt.Errorf("failed to start transaction: %w", err) 130 140 } 131 141 132 - log.Printf("upserting profile '%s' from pds request", profile.Did) 142 + i.Logger.Debug("upserting profile from pds request") 133 143 err = db.UpsertProfile(tx, &profile) 134 144 if err != nil { 135 145 tx.Rollback() ··· 160 170 161 171 date, err := time.Parse(time.RFC3339, record.Date) 162 172 if err != nil { 163 - log.Printf("invalid record: %s", err) 173 + i.Logger.Error("invalid record", "err", err) 164 174 return err 165 175 } 166 176 ··· 227 237 return fmt.Errorf("failed to start transaction: %w", err) 228 238 } 229 239 230 - log.Println("upserting study session from pds request") 240 + i.Logger.Debug("upserting study session from pds request") 231 241 err = db.UpsertStudySession(tx, &studySession, e.Commit.RKey) 232 242 if err != nil { 233 243 tx.Rollback() ··· 252 262 return fmt.Errorf("failed to start transaction: %w", err) 253 263 } 254 264 255 - log.Println("deleting study session from pds request") 265 + i.Logger.Debug("deleting study session from pds request") 256 266 err = db.DeleteStudySessionByRkey(tx, did, e.Commit.RKey) 257 267 if err != nil { 258 268 tx.Rollback() ··· 344 354 return fmt.Errorf("failed to start transaction: %w", err) 345 355 } 346 356 347 - log.Println("upserting activity def from pds request") 357 + i.Logger.Debug("upserting activity def from pds request") 348 358 err = db.UpsertActivityDef(tx, &activityDef, e.Commit.RKey) 349 359 if err != nil { 350 360 tx.Rollback() ··· 352 362 } 353 363 return tx.Commit() 354 364 case models.CommitOperationDelete: 355 - log.Println("deleting activity def from pds request") 365 + i.Logger.Debug("deleting activity def from pds request") 356 366 err = db.DeleteActivityDefByRkey(i.Db, did, e.Commit.RKey) 357 367 } 358 368 if err != nil { ··· 387 397 388 398 subjectDid := record.Subject 389 399 390 - log.Println("upserting follow from pds request") 400 + i.Logger.Debug("upserting follow from pds request") 391 401 err = db.AddFollow(tx, did, subjectDid, e.Commit.RKey) 392 402 if err != nil { 393 403 tx.Rollback() ··· 397 407 subjectUri := fmt.Sprintf("at://%s/%s/%s", did, yoten.GraphFollowNSID, e.Commit.RKey) 398 408 err = db.CreateNotification(tx, subjectDid, did, subjectUri, db.NotificationTypeFollow) 399 409 if err != nil { 400 - log.Println("failed to create notification record:", err) 410 + i.Logger.Error("failed to create notification record", "err", err) 401 411 } 402 412 403 413 return tx.Commit() 404 414 case models.CommitOperationDelete: 405 - log.Println("deleting follow from pds request") 415 + i.Logger.Debug("deleting follow from pds request") 406 416 err = db.DeleteFollowByRkey(i.Db, did, e.Commit.RKey) 407 417 } 408 418 if err != nil { ··· 465 475 CreatedAt: createdAt, 466 476 } 467 477 468 - log.Println("upserting reaction from pds request") 478 + i.Logger.Debug("upserting reaction from pds request") 469 479 err = db.UpsertReaction(i.Db, reactionEvent) 470 480 if err != nil { 471 481 tx.Rollback() ··· 474 484 475 485 err = db.CreateNotification(tx, subjectDid.String(), did, subject.String(), db.NotificationTypeReaction) 476 486 if err != nil { 477 - log.Println("failed to create notification record:", err) 487 + i.Logger.Error("failed to create notification record", "err", err) 478 488 } 479 489 480 490 return tx.Commit() 481 491 case models.CommitOperationDelete: 482 - log.Println("deleting reaction from pds request") 492 + i.Logger.Debug("deleting reaction from pds request") 483 493 err = db.DeleteReactionByRkey(i.Db, did, e.Commit.RKey) 484 494 } 485 495 if err != nil { ··· 546 556 return fmt.Errorf("invalid resource: %w", err) 547 557 } 548 558 549 - log.Println("upserting resource from pds request") 559 + i.Logger.Debug("upserting resource from pds request") 550 560 err = db.UpsertResource(i.Db, resource, resource.Rkey) 551 561 if err != nil { 552 562 tx.Rollback() ··· 554 564 } 555 565 return tx.Commit() 556 566 case models.CommitOperationDelete: 557 - log.Println("deleting resource from pds request") 567 + i.Logger.Debug("deleting resource from pds request") 558 568 err = db.DeleteResourceByRkey(i.Db, did, e.Commit.RKey) 559 569 } 560 570 if err != nil { ··· 626 636 CreatedAt: createdAt, 627 637 } 628 638 629 - log.Println("upserting comment from pds request") 639 + i.Logger.Debug("upserting comment from pds request") 630 640 err = db.UpsertComment(i.Db, comment) 631 641 if err != nil { 632 642 tx.Rollback() ··· 637 647 if subjectDid.String() != did { 638 648 err = db.CreateNotification(tx, subjectDid.String(), did, subjectUri.String(), db.NotificationTypeComment) 639 649 if err != nil { 640 - log.Println("failed to create notification record:", err) 650 + i.Logger.Error("failed to create notification record", "err", err) 641 651 } 642 652 } 643 653 ··· 645 655 if comment.ParentCommentUri != nil && comment.ParentCommentUri.Authority().String() != did { 646 656 err = db.CreateNotification(tx, comment.ParentCommentUri.Authority().String(), did, parentCommentUri.String(), db.NotificationTypeReply) 647 657 if err != nil { 648 - log.Println("failed to create notification record:", err) 658 + i.Logger.Error("failed to create notification record", "err", err) 649 659 } 650 660 } 651 661 652 662 return tx.Commit() 653 663 case models.CommitOperationDelete: 654 - log.Println("deleting comment from pds request") 664 + i.Logger.Debug("deleting comment from pds request") 655 665 err = db.DeleteCommentByRkey(i.Db, did, e.Commit.RKey) 656 666 } 657 667 if err != nil {
+9 -7
internal/server/app.go
··· 61 61 62 62 63 63 64 + idResolver := atproto.DefaultResolver() 64 65 66 + oauth, err := oauth.New(config, posthog, idResolver, log.SubLogger(logger, "oauth")) 67 + if err != nil { 68 + return nil, fmt.Errorf("failed to start oauth handler: %w", err) 69 + } 65 70 66 71 67 72 ··· 77 82 78 83 79 84 80 - 81 - 82 - 83 - 84 - 85 85 yoten.GraphFollowNSID, 86 86 }, 87 87 nil, 88 - slog.Default(), 88 + log.SubLogger(logger, "jetstream"), 89 89 wrapper, 90 90 false, 91 91 ) ··· 95 95 96 96 ingester := consumer.Ingester{ 97 97 Db: wrapper, 98 - Config: config} 98 + Config: config, 99 + Logger: log.SubLogger(logger, "ingester"), 100 + } 99 101 err = jc.StartJetstream(ctx, ingester.Ingest()) 100 102 if err != nil { 101 103 return nil, fmt.Errorf("failed to start jetstream watcher: %w", err)
+2
internal/server/handlers/router.go
··· 7 7 "github.com/go-chi/chi/v5" 8 8 9 9 "yoten.app/internal/server" 10 + "yoten.app/internal/server/log" 10 11 "yoten.app/internal/server/middleware" 11 12 "yoten.app/internal/server/views" 12 13 ) ··· 25 26 h.Oauth, 26 27 h.Db, 27 28 h.IdResolver, 29 + log.SubLogger(h.Logger, "middleware"), 28 30 ) 29 31 30 32 router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
+13 -6
internal/server/middleware/middleware.go
··· 3 3 import ( 4 4 "context" 5 5 "fmt" 6 - "log" 6 + "log/slog" 7 7 "net/http" 8 8 "net/url" 9 9 "slices" ··· 25 25 oauth *oauth.OAuth 26 26 db *db.DB 27 27 idResolver *atproto.Resolver 28 + logger *slog.Logger 28 29 } 29 30 30 - func New(oauth *oauth.OAuth, db *db.DB, idResolver *atproto.Resolver) Middleware { 31 + func New(oauth *oauth.OAuth, db *db.DB, idResolver *atproto.Resolver, logger *slog.Logger) Middleware { 31 32 return Middleware{ 32 33 oauth: oauth, 33 34 db: db, 34 35 idResolver: idResolver, 36 + logger: logger, 35 37 } 36 38 } 37 39 38 40 type middlewareFunc func(http.Handler) http.Handler 39 41 40 42 func AuthMiddleware(o *oauth.OAuth) middlewareFunc { 43 + l := o.Logger.With("middleware", "AuthMiddleware") 44 + 41 45 return func(next http.Handler) http.Handler { 42 46 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 43 47 returnURL := "/" ··· 59 63 60 64 sess, err := o.ResumeSession(r) 61 65 if err != nil { 62 - log.Println("failed to resume session, redirecting...", "err", err, "url", r.URL.String()) 66 + l.Error("failed to resume session, redirecting...", "err", err, "url", r.URL.String()) 63 67 redirectFunc(w, r) 64 68 return 65 69 } 66 70 67 71 if sess == nil { 68 - log.Printf("session is nil, redirecting...") 72 + l.Warn("session is nil, redirecting...") 69 73 redirectFunc(w, r) 70 74 return 71 75 } ··· 76 80 } 77 81 78 82 func (mw Middleware) ResolveIdent() middlewareFunc { 83 + l := mw.logger.With("middleware", "ResolveIdent") 79 84 excluded := []string{"favicon.ico"} 80 85 81 86 return func(next http.Handler) http.Handler { ··· 90 95 91 96 id, err := mw.idResolver.ResolveIdent(r.Context(), didOrHandle) 92 97 if err != nil { 93 - log.Println("failed to resolve did/handle:", err) 98 + l.Error("failed to resolve did/handle", "err", err) 94 99 w.WriteHeader(http.StatusNotFound) 95 100 views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w) 96 101 return ··· 104 109 } 105 110 106 111 func (mw Middleware) LoadUnreadNotificationCount() middlewareFunc { 112 + l := mw.logger.With("middleware", "LoadUnreadNotificationCount") 113 + 107 114 return func(next http.Handler) http.Handler { 108 115 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 109 116 user := mw.oauth.GetUser(r) ··· 114 121 115 122 count, err := db.GetUnreadNotificationCount(mw.db, user.Did) 116 123 if err != nil { 117 - log.Println("failed to get notification count:", err) 124 + l.Error("failed to get notification count", "err", err) 118 125 } 119 126 120 127 ctx := context.WithValue(r.Context(), UnreadNotificationCountCtxKey, count)

History

2 rounds 0 comments
sign up or login to add to the discussion
1 commit
expand
fix: accounts not created on sign-in
expand 0 comments
pull request successfully merged
brookjeynes.dev submitted #0
18 commits
expand
refactor: move computed data code to function to simplify visuals
feat: use charmbracelet logger
feat(oauth): use slogger
feat(db): use slogger
feat(handlers/study-session): use slogger
feat(handlers/resource): use slogger
feat(handlers/comment): use slogger
feat(handlers/follow): use slogger
feat(handlers/activity): use slogger
feat(handlers/login): use slogger
feat(handlers/notification): use slogger
feat(handlers/profile): use slogger
feat(handlers/reaction): use slogger
feat(handlers/stats): use slogger
feat(consumer/ingester): use slogger
feat(middleware): use slogger
fix(auth): handle bad identity lookups
fix: accounts not created on sign-in
expand 0 comments