Low-latency AT Protocol interaction indexer.
20

Configure Feed

Select the types of activity you want to include in your feed.

save records to sqlite

Aly Raffauf (Jul 2, 2026, 8:46 PM EDT) 21bd54ca d86274fa

+171 -18
+1
.gitignore
··· 1 1 .gocache/ 2 + *.db
+7
cmd/asterism/main.go
··· 10 10 "github.com/gorilla/websocket" 11 11 12 12 "github.com/alyraffauf/asterism/internal/firehose" 13 + "github.com/alyraffauf/asterism/internal/store" 13 14 ) 14 15 15 16 const relayURL = "wss://relay1.us-east.bsky.network/xrpc/com.atproto.sync.subscribeRepos" ··· 44 45 } 45 46 defer conn.Close() 46 47 48 + linkStore, err := store.Open("asterism.db") 49 + if err != nil { 50 + panic(err) 51 + } 52 + 47 53 consumer := &firehose.Consumer{ 48 54 WantedCollections: parseCollections(*collectionsFlag), 55 + Store: linkStore, 49 56 } 50 57 51 58 if err := consumer.Run(ctx, conn, logger); err != nil {
+9 -2
go.mod
··· 6 6 github.com/bluesky-social/indigo v0.0.0-20260629160527-dfe5578fd537 7 7 github.com/gorilla/websocket v1.5.3 8 8 github.com/ipfs/go-cid v0.4.1 9 + modernc.org/sqlite v1.53.0 9 10 ) 10 11 11 12 require ( 12 13 github.com/RussellLuo/slidingwindow v0.0.0-20200528002341-535bb99d338b // indirect 13 14 github.com/beorn7/perks v1.0.1 // indirect 14 15 github.com/cespare/xxhash/v2 v2.2.0 // indirect 16 + github.com/dustin/go-humanize v1.0.1 // indirect 15 17 github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect 16 18 github.com/felixge/httpsnoop v1.0.4 // indirect 17 19 github.com/go-logr/logr v1.4.1 // indirect 18 20 github.com/go-logr/stdr v1.2.2 // indirect 19 21 github.com/gogo/protobuf v1.3.2 // indirect 20 - github.com/google/uuid v1.4.0 // indirect 22 + github.com/google/uuid v1.6.0 // indirect 21 23 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 22 24 github.com/hashicorp/go-retryablehttp v0.7.5 // indirect 23 25 github.com/hashicorp/golang-lru v1.0.2 // indirect ··· 54 56 github.com/multiformats/go-multibase v0.2.0 // indirect 55 57 github.com/multiformats/go-multihash v0.2.3 // indirect 56 58 github.com/multiformats/go-varint v0.0.7 // indirect 59 + github.com/ncruces/go-strftime v1.0.0 // indirect 57 60 github.com/opentracing/opentracing-go v1.2.0 // indirect 58 61 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 59 62 github.com/prometheus/client_golang v1.17.0 // indirect 60 63 github.com/prometheus/client_model v0.5.0 // indirect 61 64 github.com/prometheus/common v0.45.0 // indirect 62 65 github.com/prometheus/procfs v0.12.0 // indirect 66 + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 63 67 github.com/spaolacci/murmur3 v1.1.0 // indirect 64 68 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e // indirect 65 69 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect ··· 72 76 go.uber.org/multierr v1.11.0 // indirect 73 77 go.uber.org/zap v1.26.0 // indirect 74 78 golang.org/x/crypto v0.21.0 // indirect 75 - golang.org/x/sys v0.22.0 // indirect 79 + golang.org/x/sys v0.44.0 // indirect 76 80 golang.org/x/time v0.3.0 // indirect 77 81 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 78 82 google.golang.org/protobuf v1.33.0 // indirect 79 83 gorm.io/gorm v1.25.9 // indirect 80 84 lukechampine.com/blake3 v1.2.1 // indirect 85 + modernc.org/libc v1.73.4 // indirect 86 + modernc.org/mathutil v1.7.1 // indirect 87 + modernc.org/memory v1.11.0 // indirect 81 88 )
+46 -10
go.sum
··· 18 18 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 19 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= 20 20 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 21 + github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 22 + github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 21 23 github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= 22 24 github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= 23 25 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= ··· 38 40 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 39 41 github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= 40 42 github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 43 + github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= 44 + github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 41 45 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 42 - github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 43 - github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 46 + github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 47 + github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 44 48 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 45 49 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 46 50 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= ··· 193 197 github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= 194 198 github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= 195 199 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 200 + github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= 201 + github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= 196 202 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 197 203 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 198 204 github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= ··· 210 216 github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= 211 217 github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= 212 218 github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 219 + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 220 + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 213 221 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 214 222 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 215 223 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= ··· 283 291 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 284 292 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 285 293 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 286 - golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= 287 - golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 294 + golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= 295 + golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= 288 296 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 289 297 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 290 298 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= ··· 297 305 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 298 306 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 299 307 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 300 - golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 301 - golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 308 + golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= 309 + golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 302 310 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 303 311 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 312 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ··· 308 316 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 309 317 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 310 318 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 311 - golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 312 - golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 319 + golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= 320 + golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 313 321 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 314 322 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 315 323 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= ··· 325 333 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 326 334 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 327 335 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 328 - golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= 329 - golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= 336 + golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= 337 + golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= 330 338 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 331 339 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 332 340 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ··· 353 361 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 354 362 lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= 355 363 lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= 364 + modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c= 365 + modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= 366 + modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws= 367 + modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE= 368 + modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= 369 + modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= 370 + modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= 371 + modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= 372 + modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc= 373 + modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= 374 + modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= 375 + modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= 376 + modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA= 377 + modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8= 378 + modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= 379 + modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= 380 + modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= 381 + modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= 382 + modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= 383 + modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= 384 + modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= 385 + modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= 386 + modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M= 387 + modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s= 388 + modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= 389 + modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= 390 + modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= 391 + modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+3 -6
internal/firehose/commit.go
··· 50 50 fmt.Println("op:", operation.Action, "collection:", collection, "rkey:", recordKey) 51 51 52 52 if operation.Action == "delete" { 53 - return nil 53 + return c.Store.DeleteLinks(ctx, event.Repo, collection, recordKey) 54 54 } 55 55 56 56 recordCid, recordBytes, err := repo.GetRecordBytes(ctx, operation.Path) ··· 80 80 Rev: event.Rev, 81 81 } 82 82 83 - for _, link := range backlink.Extract(record, base) { 84 - fmt.Printf("link: %+v\n", link) 85 - } 83 + links := backlink.Extract(record, base) 86 84 87 - return nil 88 - 85 + return c.Store.SaveLinks(ctx, event.Repo, collection, recordKey, links) 89 86 }
+3
internal/firehose/consumer.go
··· 1 1 package firehose 2 2 3 + import "github.com/alyraffauf/asterism/internal/store" 4 + 3 5 type Consumer struct { 4 6 WantedCollections map[string]struct{} 7 + Store *store.Store 5 8 } 6 9 7 10 func (c *Consumer) wants(collection string) bool {
+60
internal/store/links.go
··· 1 + package store 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "fmt" 7 + 8 + "github.com/alyraffauf/asterism/internal/backlink" 9 + ) 10 + 11 + func deleteLinks(ctx context.Context, tx *sql.Tx, actorDid, collection, recordKey string) error { 12 + _, err := tx.ExecContext(ctx, 13 + `DELETE FROM links WHERE actor_did = ? AND collection = ? AND record_key = ?`, 14 + actorDid, collection, recordKey, 15 + ) 16 + 17 + if err != nil { 18 + return fmt.Errorf("delete links: %w", err) 19 + } 20 + return nil 21 + } 22 + 23 + func (s *Store) DeleteLinks(ctx context.Context, actorDid, collection, recordKey string) error { 24 + tx, err := s.db.BeginTx(ctx, nil) 25 + if err != nil { 26 + return fmt.Errorf("begin transaction: %w", err) 27 + } 28 + defer tx.Rollback() 29 + 30 + if err := deleteLinks(ctx, tx, actorDid, collection, recordKey); err != nil { 31 + return err 32 + } 33 + 34 + return tx.Commit() 35 + } 36 + 37 + func (s *Store) SaveLinks(ctx context.Context, actorDid, collection, recordKey string, links []backlink.Link) error { 38 + tx, err := s.db.BeginTx(ctx, nil) 39 + if err != nil { 40 + return fmt.Errorf("begin transaction: %w", err) 41 + } 42 + defer tx.Rollback() 43 + 44 + if err := deleteLinks(ctx, tx, actorDid, collection, recordKey); err != nil { 45 + return err 46 + } 47 + 48 + for _, link := range links { 49 + _, err := tx.ExecContext(ctx, 50 + `INSERT INTO links (target, target_cid, actor_did, collection, record_key, record_cid, field_path, rev) 51 + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, 52 + link.Target, link.TargetCid, link.ActorDid, link.Collection, link.RecordKey, link.RecordCid, link.FieldPath, link.Rev, 53 + ) 54 + if err != nil { 55 + return fmt.Errorf("insert link: %w", err) 56 + } 57 + } 58 + 59 + return tx.Commit() 60 + }
+42
internal/store/store.go
··· 1 + package store 2 + 3 + import ( 4 + "database/sql" 5 + "fmt" 6 + 7 + _ "modernc.org/sqlite" 8 + ) 9 + 10 + type Store struct { 11 + db *sql.DB 12 + } 13 + 14 + const schema = ` 15 + CREATE TABLE IF NOT EXISTS links ( 16 + id INTEGER PRIMARY KEY AUTOINCREMENT, 17 + target TEXT NOT NULL, 18 + target_cid TEXT NOT NULL, 19 + actor_did TEXT NOT NULL, 20 + collection TEXT NOT NULL, 21 + record_key TEXT NOT NULL, 22 + record_cid TEXT NOT NULL, 23 + field_path TEXT NOT NULL, 24 + rev TEXT NOT NULL 25 + ); 26 + CREATE INDEX IF NOT EXISTS idx_links_target ON links(target); 27 + CREATE INDEX IF NOT EXISTS idx_links_source ON links(actor_did, collection, record_key); 28 + ` 29 + 30 + func Open(path string) (*Store, error) { 31 + db, err := sql.Open("sqlite", path) 32 + if err != nil { 33 + return nil, fmt.Errorf("open database: %w", err) 34 + } 35 + 36 + if _, err := db.Exec(schema); err != nil { 37 + return nil, fmt.Errorf("create schema: %w", err) 38 + } 39 + 40 + return &Store{db: db}, nil 41 + 42 + }