this repo has no description

create a db wrapper that allows for easy locking (#13)

authored by hailey and committed by GitHub 6cf863b3 997dc83e

+9 -9
blockstore/blockstore.go
··· 5 5 "fmt" 6 6 7 7 "github.com/bluesky-social/indigo/atproto/syntax" 8 + "github.com/haileyok/cocoon/internal/db" 8 9 "github.com/haileyok/cocoon/models" 9 10 blocks "github.com/ipfs/go-block-format" 10 11 "github.com/ipfs/go-cid" 11 - "gorm.io/gorm" 12 12 "gorm.io/gorm/clause" 13 13 ) 14 14 15 15 type SqliteBlockstore struct { 16 - db *gorm.DB 16 + db *db.DB 17 17 did string 18 18 readonly bool 19 19 inserts map[cid.Cid]blocks.Block 20 20 } 21 21 22 - func New(did string, db *gorm.DB) *SqliteBlockstore { 22 + func New(did string, db *db.DB) *SqliteBlockstore { 23 23 return &SqliteBlockstore{ 24 24 did: did, 25 25 db: db, ··· 28 28 } 29 29 } 30 30 31 - func NewReadOnly(did string, db *gorm.DB) *SqliteBlockstore { 31 + func NewReadOnly(did string, db *db.DB) *SqliteBlockstore { 32 32 return &SqliteBlockstore{ 33 33 did: did, 34 34 db: db, ··· 45 45 return maybeBlock, nil 46 46 } 47 47 48 - if err := bs.db.Raw("SELECT * FROM blocks WHERE did = ? AND cid = ?", bs.did, cid.Bytes()).Scan(&block).Error; err != nil { 48 + if err := bs.db.Raw("SELECT * FROM blocks WHERE did = ? AND cid = ?", nil, bs.did, cid.Bytes()).Scan(&block).Error; err != nil { 49 49 return nil, err 50 50 } 51 51 ··· 71 71 Value: block.RawData(), 72 72 } 73 73 74 - if err := bs.db.Clauses(clause.OnConflict{ 74 + if err := bs.db.Create(&b, []clause.Expression{clause.OnConflict{ 75 75 Columns: []clause.Column{{Name: "did"}, {Name: "cid"}}, 76 76 UpdateAll: true, 77 - }).Create(&b).Error; err != nil { 77 + }}).Error; err != nil { 78 78 return err 79 79 } 80 80 ··· 94 94 } 95 95 96 96 func (bs *SqliteBlockstore) PutMany(ctx context.Context, blocks []blocks.Block) error { 97 - tx := bs.db.Begin() 97 + tx := bs.db.BeginDangerously() 98 98 99 99 for _, block := range blocks { 100 100 bs.inserts[block.Cid()] = block ··· 137 137 } 138 138 139 139 func (bs *SqliteBlockstore) UpdateRepo(ctx context.Context, root cid.Cid, rev string) error { 140 - if err := bs.db.Exec("UPDATE repos SET root = ?, rev = ? WHERE did = ?", root.Bytes(), rev, bs.did).Error; err != nil { 140 + if err := bs.db.Exec("UPDATE repos SET root = ?, rev = ? WHERE did = ?", nil, root.Bytes(), rev, bs.did).Error; err != nil { 141 141 return err 142 142 } 143 143
+32
cmd/cocoon/main.go
··· 91 91 Required: false, 92 92 EnvVars: []string{"COCOON_SMTP_NAME"}, 93 93 }, 94 + &cli.BoolFlag{ 95 + Name: "s3-backups-enabled", 96 + EnvVars: []string{"COCOON_S3_BACKUPS_ENABLED"}, 97 + }, 98 + &cli.StringFlag{ 99 + Name: "s3-region", 100 + EnvVars: []string{"COCOON_S3_REGION"}, 101 + }, 102 + &cli.StringFlag{ 103 + Name: "s3-bucket", 104 + EnvVars: []string{"COCOON_S3_BUCKET"}, 105 + }, 106 + &cli.StringFlag{ 107 + Name: "s3-endpoint", 108 + EnvVars: []string{"COCOON_S3_ENDPOINT"}, 109 + }, 110 + &cli.StringFlag{ 111 + Name: "s3-access-key", 112 + EnvVars: []string{"COCOON_S3_ACCESS_KEY"}, 113 + }, 114 + &cli.StringFlag{ 115 + Name: "s3-secret-key", 116 + EnvVars: []string{"COCOON_S3_SECRET_KEY"}, 117 + }, 94 118 }, 95 119 Commands: []*cli.Command{ 96 120 run, ··· 126 150 SmtpPort: cmd.String("smtp-port"), 127 151 SmtpEmail: cmd.String("smtp-email"), 128 152 SmtpName: cmd.String("smtp-name"), 153 + S3Config: &server.S3Config{ 154 + BackupsEnabled: cmd.Bool("s3-backups-enabled"), 155 + Region: cmd.String("s3-region"), 156 + Bucket: cmd.String("s3-bucket"), 157 + Endpoint: cmd.String("s3-endpoint"), 158 + AccessKey: cmd.String("s3-access-key"), 159 + SecretKey: cmd.String("s3-secret-key"), 160 + }, 129 161 }) 130 162 if err != nil { 131 163 fmt.Printf("error creating cocoon: %v", err)
+8 -15
go.mod
··· 4 4 5 5 require ( 6 6 github.com/Azure/go-autorest/autorest/to v0.4.1 7 + github.com/aws/aws-sdk-go v1.55.7 7 8 github.com/bluesky-social/indigo v0.0.0-20250414202759-826fcdeaa36b 8 9 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 10 + github.com/domodwyer/mailyak/v3 v3.6.2 9 11 github.com/go-playground/validator v9.31.0+incompatible 10 12 github.com/golang-jwt/jwt/v4 v4.5.2 11 13 github.com/google/uuid v1.4.0 14 + github.com/gorilla/websocket v1.5.1 15 + github.com/hashicorp/golang-lru/v2 v2.0.7 12 16 github.com/ipfs/go-block-format v0.2.0 13 17 github.com/ipfs/go-cid v0.4.1 14 18 github.com/ipfs/go-ipld-cbor v0.1.0 ··· 16 20 github.com/joho/godotenv v1.5.1 17 21 github.com/labstack/echo/v4 v4.13.3 18 22 github.com/lestrrat-go/jwx/v2 v2.0.12 23 + github.com/multiformats/go-multihash v0.2.3 19 24 github.com/samber/slog-echo v1.16.1 20 25 github.com/urfave/cli/v2 v2.27.6 26 + github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e 27 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b 21 28 golang.org/x/crypto v0.36.0 22 29 gorm.io/driver/sqlite v1.5.7 23 30 gorm.io/gorm v1.25.12 ··· 27 34 github.com/Azure/go-autorest v14.2.0+incompatible // indirect 28 35 github.com/RussellLuo/slidingwindow v0.0.0-20200528002341-535bb99d338b // indirect 29 36 github.com/beorn7/perks v1.0.1 // indirect 30 - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect 31 37 github.com/carlmjohnson/versioninfo v0.22.5 // indirect 32 38 github.com/cespare/xxhash/v2 v2.2.0 // indirect 33 39 github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect 34 40 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 35 - github.com/domodwyer/mailyak/v3 v3.6.2 // indirect 36 41 github.com/felixge/httpsnoop v1.0.4 // indirect 37 42 github.com/go-logr/logr v1.4.2 // indirect 38 43 github.com/go-logr/stdr v1.2.2 // indirect ··· 41 46 github.com/goccy/go-json v0.10.2 // indirect 42 47 github.com/gocql/gocql v1.7.0 // indirect 43 48 github.com/gogo/protobuf v1.3.2 // indirect 44 - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect 45 49 github.com/golang/snappy v0.0.4 // indirect 46 - github.com/gorilla/websocket v1.5.1 // indirect 47 50 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect 48 51 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 49 52 github.com/hashicorp/go-retryablehttp v0.7.5 // indirect 50 53 github.com/hashicorp/golang-lru v1.0.2 // indirect 51 - github.com/hashicorp/golang-lru/arc/v2 v2.0.6 // indirect 52 - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 53 54 github.com/ipfs/bbloom v0.0.4 // indirect 54 55 github.com/ipfs/go-blockservice v0.5.2 // indirect 55 56 github.com/ipfs/go-datastore v0.6.0 // indirect ··· 65 66 github.com/ipfs/go-merkledag v0.11.0 // indirect 66 67 github.com/ipfs/go-metrics-interface v0.0.1 // indirect 67 68 github.com/ipfs/go-verifcid v0.0.3 // indirect 68 - github.com/ipld/go-car/v2 v2.13.1 // indirect 69 69 github.com/ipld/go-codec-dagpb v1.6.0 // indirect 70 70 github.com/ipld/go-ipld-prime v0.21.0 // indirect 71 71 github.com/jackc/pgpassfile v1.0.0 // indirect ··· 75 75 github.com/jbenet/goprocess v0.1.4 // indirect 76 76 github.com/jinzhu/inflection v1.0.0 // indirect 77 77 github.com/jinzhu/now v1.1.5 // indirect 78 + github.com/jmespath/go-jmespath v0.4.0 // indirect 78 79 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 79 80 github.com/labstack/gommon v0.4.2 // indirect 80 81 github.com/leodido/go-urn v1.4.0 // indirect ··· 92 93 github.com/multiformats/go-base32 v0.1.0 // indirect 93 94 github.com/multiformats/go-base36 v0.2.0 // indirect 94 95 github.com/multiformats/go-multibase v0.2.0 // indirect 95 - github.com/multiformats/go-multicodec v0.9.0 // indirect 96 - github.com/multiformats/go-multihash v0.2.3 // indirect 97 96 github.com/multiformats/go-varint v0.0.7 // indirect 98 97 github.com/opentracing/opentracing-go v1.2.0 // indirect 99 - github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect 100 98 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 101 99 github.com/prometheus/client_golang v1.17.0 // indirect 102 100 github.com/prometheus/client_model v0.5.0 // indirect ··· 108 106 github.com/spaolacci/murmur3 v1.1.0 // indirect 109 107 github.com/valyala/bytebufferpool v1.0.0 // indirect 110 108 github.com/valyala/fasttemplate v1.2.2 // indirect 111 - github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect 112 - github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e // indirect 113 - github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 // indirect 114 109 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect 115 - gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 116 110 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 117 111 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect 118 112 go.opentelemetry.io/otel v1.29.0 // indirect ··· 121 115 go.uber.org/atomic v1.11.0 // indirect 122 116 go.uber.org/multierr v1.11.0 // indirect 123 117 go.uber.org/zap v1.26.0 // indirect 124 - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect 125 118 golang.org/x/net v0.33.0 // indirect 126 119 golang.org/x/sync v0.12.0 // indirect 127 120 golang.org/x/sys v0.31.0 // indirect
+6 -22
go.sum
··· 7 7 github.com/RussellLuo/slidingwindow v0.0.0-20200528002341-535bb99d338b/go.mod h1:4+EPqMRApwwE/6yo6CxiHoSnBzjRr3jsqer7frxP8y4= 8 8 github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 h1:iW0a5ljuFxkLGPNem5Ui+KBjFJzKg4Fv2fnxe4dvzpM= 9 9 github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5/go.mod h1:Y2QMoi1vgtOIfc+6DhrMOGkLoGzqSV2rKp4Sm+opsyA= 10 + github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= 11 + github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= 10 12 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 11 13 github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= 12 14 github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= ··· 14 16 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 15 17 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= 16 18 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= 17 - github.com/bluesky-social/indigo v0.0.0-20250322011324-8e3fa7af986a h1:clnSZRgkiifbvfqu9++OHfIh2DWuIoZ8CucxLueQxO0= 18 - github.com/bluesky-social/indigo v0.0.0-20250322011324-8e3fa7af986a/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA= 19 19 github.com/bluesky-social/indigo v0.0.0-20250414202759-826fcdeaa36b h1:elwfbe+W7GkUmPKFX1h7HaeHvC/kC0XJWfiEHC62xPg= 20 20 github.com/bluesky-social/indigo v0.0.0-20250414202759-826fcdeaa36b/go.mod h1:yjdhLA1LkK8VDS/WPUoYPo25/Hq/8rX38Ftr67EsqKY= 21 21 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 22 22 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 23 - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= 24 - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= 25 23 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= 26 24 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 27 25 github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= ··· 65 63 github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4= 66 64 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 67 65 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 68 - github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 69 - github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 70 66 github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= 71 67 github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 72 68 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= ··· 93 89 github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 94 90 github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 95 91 github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 96 - github.com/hashicorp/golang-lru/arc/v2 v2.0.6 h1:4NU7uP5vSoK6TbaMj3NtY478TTAWLso/vL1gpNrInHg= 97 - github.com/hashicorp/golang-lru/arc/v2 v2.0.6/go.mod h1:cfdDIX05DWvYV6/shsxDfa/OVcRieOt+q4FnM8x+Xno= 98 92 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 99 93 github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 100 94 github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= 101 95 github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= 102 96 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= 103 97 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= 104 - github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= 105 - github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= 106 98 github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= 107 99 github.com/ipfs/go-bitswap v0.11.0/go.mod h1:05aE8H3XOU+LXpTedeAS0OZpcO1WFsj5niYQH9a1Tmk= 108 100 github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= ··· 123 115 github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= 124 116 github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= 125 117 github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= 126 - github.com/ipfs/go-ipfs-chunker v0.0.5 h1:ojCf7HV/m+uS2vhUGWcogIIxiO5ubl5O57Q7NapWLY8= 127 - github.com/ipfs/go-ipfs-chunker v0.0.5/go.mod h1:jhgdF8vxRHycr00k13FM8Y0E+6BoalYeobXmUyTreP8= 128 118 github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= 129 119 github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= 130 120 github.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw= ··· 158 148 github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= 159 149 github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg= 160 150 github.com/ipfs/go-peertaskqueue v0.8.1/go.mod h1:Oxxd3eaK279FxeydSPPVGHzbwVeHjatZ2GA8XD+KbPU= 161 - github.com/ipfs/go-unixfsnode v1.8.0 h1:yCkakzuE365glu+YkgzZt6p38CSVEBPgngL9ZkfnyQU= 162 - github.com/ipfs/go-unixfsnode v1.8.0/go.mod h1:HxRu9HYHOjK6HUqFBAi++7DVoWAHn0o4v/nZ/VA+0g8= 163 151 github.com/ipfs/go-verifcid v0.0.3 h1:gmRKccqhWDocCRkC+a59g5QW7uJw5bpX9HWBevXa0zs= 164 152 github.com/ipfs/go-verifcid v0.0.3/go.mod h1:gcCtGniVzelKrbk9ooUSX/pM3xlH73fZZJDzQJRvOUw= 165 153 github.com/ipld/go-car v0.6.1-0.20230509095817-92d28eb23ba4 h1:oFo19cBmcP0Cmg3XXbrr0V/c+xU9U1huEZp8+OgBzdI= ··· 170 158 github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s= 171 159 github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E= 172 160 github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ= 173 - github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo= 174 - github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd/go.mod h1:wZ8hH8UxeryOs4kJEJaiui/s00hDSbE37OKsL47g+Sw= 175 161 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 176 162 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 177 163 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= ··· 189 175 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 190 176 github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 191 177 github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 178 + github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 179 + github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 180 + github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 181 + github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 192 182 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 193 183 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 194 184 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= ··· 281 271 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 282 272 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 283 273 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 284 - github.com/orandin/slog-gorm v1.3.2 h1:C0lKDQPAx/pF+8K2HL7bdShPwOEJpPM0Bn80zTzxU1g= 285 - github.com/orandin/slog-gorm v1.3.2/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA= 286 274 github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= 287 275 github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw= 288 276 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= ··· 345 333 github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ= 346 334 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4= 347 335 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 348 - github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= 349 - github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= 350 - github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 h1:yJ9/LwIGIk/c0CdoavpC9RNSGSruIspSZtxG3Nnldic= 351 - github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6/go.mod h1:39U9RRVr4CKbXpXYopWn+FSH5s+vWu6+RmguSPWAq5s= 352 336 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= 353 337 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= 354 338 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+65
internal/db/db.go
··· 1 + package db 2 + 3 + import ( 4 + "sync" 5 + 6 + "gorm.io/gorm" 7 + "gorm.io/gorm/clause" 8 + ) 9 + 10 + type DB struct { 11 + cli *gorm.DB 12 + mu sync.Mutex 13 + } 14 + 15 + func NewDB(cli *gorm.DB) *DB { 16 + return &DB{ 17 + cli: cli, 18 + mu: sync.Mutex{}, 19 + } 20 + } 21 + 22 + func (db *DB) Create(value any, clauses []clause.Expression) *gorm.DB { 23 + db.mu.Lock() 24 + defer db.mu.Unlock() 25 + return db.cli.Clauses(clauses...).Create(value) 26 + } 27 + 28 + func (db *DB) Exec(sql string, clauses []clause.Expression, values ...any) *gorm.DB { 29 + db.mu.Lock() 30 + defer db.mu.Unlock() 31 + return db.cli.Clauses(clauses...).Exec(sql, values...) 32 + } 33 + 34 + func (db *DB) Raw(sql string, clauses []clause.Expression, values ...any) *gorm.DB { 35 + return db.cli.Clauses(clauses...).Raw(sql, values...) 36 + } 37 + 38 + func (db *DB) AutoMigrate(models ...any) error { 39 + return db.cli.AutoMigrate(models...) 40 + } 41 + 42 + func (db *DB) Delete(value any, clauses []clause.Expression) *gorm.DB { 43 + db.mu.Lock() 44 + defer db.mu.Unlock() 45 + return db.cli.Clauses(clauses...).Delete(value) 46 + } 47 + 48 + func (db *DB) First(dest any, conds ...any) *gorm.DB { 49 + return db.cli.First(dest, conds...) 50 + } 51 + 52 + // TODO: this isn't actually good. we can commit even if the db is locked here. this is probably okay for the time being, but need to figure 53 + // out a better solution. right now we only do this whenever we're importing a repo though so i'm mostly not worried, but it's still bad. 54 + // e.g. when we do apply writes we should also be using a transcation but we don't right now 55 + func (db *DB) BeginDangerously() *gorm.DB { 56 + return db.cli.Begin() 57 + } 58 + 59 + func (db *DB) Lock() { 60 + db.mu.Lock() 61 + } 62 + 63 + func (db *DB) Unlock() { 64 + db.mu.Unlock() 65 + }
+2 -2
server/common.go
··· 22 22 23 23 func (s *Server) getRepoActorByEmail(email string) (*models.RepoActor, error) { 24 24 var repo models.RepoActor 25 - if err := s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.email= ?", email).Scan(&repo).Error; err != nil { 25 + if err := s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.email= ?", nil, email).Scan(&repo).Error; err != nil { 26 26 return nil, err 27 27 } 28 28 return &repo, nil ··· 30 30 31 31 func (s *Server) getRepoActorByDid(did string) (*models.RepoActor, error) { 32 32 var repo models.RepoActor 33 - if err := s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", did).Scan(&repo).Error; err != nil { 33 + if err := s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", nil, did).Scan(&repo).Error; err != nil { 34 34 return nil, err 35 35 } 36 36 return &repo, nil
+1 -1
server/handle_actor_put_preferences.go
··· 22 22 return err 23 23 } 24 24 25 - if err := s.db.Exec("UPDATE repos SET preferences = ? WHERE did = ?", b, repo.Repo.Did).Error; err != nil { 25 + if err := s.db.Exec("UPDATE repos SET preferences = ? WHERE did = ?", nil, b, repo.Repo.Did).Error; err != nil { 26 26 return err 27 27 } 28 28
+1 -1
server/handle_identity_update_handle.go
··· 103 103 }, 104 104 }) 105 105 106 - if err := s.db.Exec("UPDATE actors SET handle = ? WHERE did = ?", req.Handle, repo.Repo.Did).Error; err != nil { 106 + if err := s.db.Exec("UPDATE actors SET handle = ? WHERE did = ?", nil, req.Handle, repo.Repo.Did).Error; err != nil { 107 107 s.logger.Error("error updating handle in db", "error", err) 108 108 return helpers.ServerError(e, nil) 109 109 }
+1 -1
server/handle_import_repo.go
··· 64 64 return helpers.ServerError(e, nil) 65 65 } 66 66 67 - tx := s.db.Begin() 67 + tx := s.db.BeginDangerously() 68 68 69 69 clock := syntax.NewTIDClock(0) 70 70
+1 -1
server/handle_repo_describe_repo.go
··· 64 64 } 65 65 66 66 var records []models.Record 67 - if err := s.db.Raw("SELECT DISTINCT(nsid) FROM records WHERE did = ?", repo.Repo.Did).Scan(&records).Error; err != nil { 67 + if err := s.db.Raw("SELECT DISTINCT(nsid) FROM records WHERE did = ?", nil, repo.Repo.Did).Scan(&records).Error; err != nil { 68 68 s.logger.Error("error getting collections", "error", err) 69 69 return helpers.ServerError(e, nil) 70 70 }
+1 -1
server/handle_repo_get_record.go
··· 32 32 } 33 33 34 34 var record models.Record 35 - if err := s.db.Raw("SELECT * FROM records WHERE did = ? AND nsid = ? AND rkey = ?"+cidquery, params...).Scan(&record).Error; err != nil { 35 + if err := s.db.Raw("SELECT * FROM records WHERE did = ? AND nsid = ? AND rkey = ?"+cidquery, nil, params...).Scan(&record).Error; err != nil { 36 36 // TODO: handle error nicely 37 37 return err 38 38 }
+1 -1
server/handle_repo_list_records.go
··· 64 64 params = append(params, limit) 65 65 66 66 var records []models.Record 67 - if err := s.db.Raw("SELECT * FROM records WHERE did = ? AND nsid = ? "+cursorquery+" ORDER BY created_at "+sort+" limit ?", params...).Scan(&records).Error; err != nil { 67 + if err := s.db.Raw("SELECT * FROM records WHERE did = ? AND nsid = ? "+cursorquery+" ORDER BY created_at "+sort+" limit ?", nil, params...).Scan(&records).Error; err != nil { 68 68 s.logger.Error("error getting records", "error", err) 69 69 return helpers.ServerError(e, nil) 70 70 }
+1 -1
server/handle_repo_list_repos.go
··· 22 22 // TODO: paginate this bitch 23 23 func (s *Server) handleListRepos(e echo.Context) error { 24 24 var repos []models.Repo 25 - if err := s.db.Raw("SELECT * FROM repos ORDER BY created_at DESC LIMIT 500").Scan(&repos).Error; err != nil { 25 + if err := s.db.Raw("SELECT * FROM repos ORDER BY created_at DESC LIMIT 500", nil).Scan(&repos).Error; err != nil { 26 26 return err 27 27 } 28 28
+3 -3
server/handle_repo_upload_blob.go
··· 40 40 CreatedAt: s.repoman.clock.Next().String(), 41 41 } 42 42 43 - if err := s.db.Create(&blob).Error; err != nil { 43 + if err := s.db.Create(&blob, nil).Error; err != nil { 44 44 s.logger.Error("error creating new blob in db", "error", err) 45 45 return helpers.ServerError(e, nil) 46 46 } ··· 72 72 Data: data, 73 73 } 74 74 75 - if err := s.db.Create(&blobPart).Error; err != nil { 75 + if err := s.db.Create(&blobPart, nil).Error; err != nil { 76 76 s.logger.Error("error adding blob part to db", "error", err) 77 77 return helpers.ServerError(e, nil) 78 78 } ··· 89 89 return helpers.ServerError(e, nil) 90 90 } 91 91 92 - if err := s.db.Exec("UPDATE blobs SET cid = ? WHERE id = ?", c.Bytes(), blob.ID).Error; err != nil { 92 + if err := s.db.Exec("UPDATE blobs SET cid = ? WHERE id = ?", nil, c.Bytes(), blob.ID).Error; err != nil { 93 93 // there should probably be somme handling here if this fails... 94 94 s.logger.Error("error updating blob", "error", err) 95 95 return helpers.ServerError(e, nil)
+1 -1
server/handle_server_confirm_email.go
··· 41 41 42 42 now := time.Now().UTC() 43 43 44 - if err := s.db.Exec("UPDATE repos SET email_verification_code = NULL, email_verification_code_expires_at = NULL, email_confirmed_at = ? WHERE did = ?", now, urepo.Repo.Did).Error; err != nil { 44 + if err := s.db.Exec("UPDATE repos SET email_verification_code = NULL, email_verification_code_expires_at = NULL, email_confirmed_at = ? WHERE did = ?", nil, now, urepo.Repo.Did).Error; err != nil { 45 45 s.logger.Error("error updating user", "error", err) 46 46 return helpers.ServerError(e, nil) 47 47 }
+4 -4
server/handle_server_create_account.go
··· 102 102 } 103 103 104 104 var ic models.InviteCode 105 - if err := s.db.Raw("SELECT * FROM invite_codes WHERE code = ?", request.InviteCode).Scan(&ic).Error; err != nil { 105 + if err := s.db.Raw("SELECT * FROM invite_codes WHERE code = ?", nil, request.InviteCode).Scan(&ic).Error; err != nil { 106 106 if err == gorm.ErrRecordNotFound { 107 107 return helpers.InputError(e, to.StringPtr("InvalidInviteCode")) 108 108 } ··· 166 166 Handle: request.Handle, 167 167 } 168 168 169 - if err := s.db.Create(&urepo).Error; err != nil { 169 + if err := s.db.Create(&urepo, nil).Error; err != nil { 170 170 s.logger.Error("error inserting new repo", "error", err) 171 171 return helpers.ServerError(e, nil) 172 172 } 173 173 174 - if err := s.db.Create(&actor).Error; err != nil { 174 + if err := s.db.Create(&actor, nil).Error; err != nil { 175 175 s.logger.Error("error inserting new actor", "error", err) 176 176 return helpers.ServerError(e, nil) 177 177 } ··· 210 210 }) 211 211 } 212 212 213 - if err := s.db.Raw("UPDATE invite_codes SET remaining_use_count = remaining_use_count - 1 WHERE code = ?", request.InviteCode).Scan(&ic).Error; err != nil { 213 + if err := s.db.Raw("UPDATE invite_codes SET remaining_use_count = remaining_use_count - 1 WHERE code = ?", nil, request.InviteCode).Scan(&ic).Error; err != nil { 214 214 s.logger.Error("error decrementing use count", "error", err) 215 215 return helpers.ServerError(e, nil) 216 216 }
+1 -1
server/handle_server_create_invite_code.go
··· 41 41 Code: ic, 42 42 Did: acc, 43 43 RemainingUseCount: req.UseCount, 44 - }).Error; err != nil { 44 + }, nil).Error; err != nil { 45 45 s.logger.Error("error creating invite code", "error", err) 46 46 return helpers.ServerError(e, nil) 47 47 }
+1 -1
server/handle_server_create_invite_codes.go
··· 54 54 Code: ic, 55 55 Did: did, 56 56 RemainingUseCount: req.UseCount, 57 - }).Error; err != nil { 57 + }, nil).Error; err != nil { 58 58 s.logger.Error("error creating invite code", "error", err) 59 59 return helpers.ServerError(e, nil) 60 60 }
+3 -3
server/handle_server_create_session.go
··· 65 65 var err error 66 66 switch idtype { 67 67 case "did": 68 - err = s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", req.Identifier).Scan(&repo).Error 68 + err = s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", nil, req.Identifier).Scan(&repo).Error 69 69 case "handle": 70 - err = s.db.Raw("SELECT r.*, a.* FROM actors a LEFT JOIN repos r ON a.did = r.did WHERE a.handle = ?", req.Identifier).Scan(&repo).Error 70 + err = s.db.Raw("SELECT r.*, a.* FROM actors a LEFT JOIN repos r ON a.did = r.did WHERE a.handle = ?", nil, req.Identifier).Scan(&repo).Error 71 71 case "email": 72 - err = s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.email = ?", req.Identifier).Scan(&repo).Error 72 + err = s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.email = ?", nil, req.Identifier).Scan(&repo).Error 73 73 } 74 74 75 75 if err != nil {
+2 -2
server/handle_server_delete_session.go
··· 10 10 token := e.Get("token").(string) 11 11 12 12 var acctok models.Token 13 - if err := s.db.Raw("DELETE FROM tokens WHERE token = ? RETURNING *", token).Scan(&acctok).Error; err != nil { 13 + if err := s.db.Raw("DELETE FROM tokens WHERE token = ? RETURNING *", nil, token).Scan(&acctok).Error; err != nil { 14 14 s.logger.Error("error deleting access token from db", "error", err) 15 15 return helpers.ServerError(e, nil) 16 16 } 17 17 18 - if err := s.db.Exec("DELETE FROM refresh_tokens WHERE token = ?", acctok.RefreshToken).Error; err != nil { 18 + if err := s.db.Exec("DELETE FROM refresh_tokens WHERE token = ?", nil, acctok.RefreshToken).Error; err != nil { 19 19 s.logger.Error("error deleting refresh token from db", "error", err) 20 20 return helpers.ServerError(e, nil) 21 21 }
+2 -2
server/handle_server_refresh_session.go
··· 19 19 token := e.Get("token").(string) 20 20 repo := e.Get("repo").(*models.RepoActor) 21 21 22 - if err := s.db.Exec("DELETE FROM refresh_tokens WHERE token = ?", token).Error; err != nil { 22 + if err := s.db.Exec("DELETE FROM refresh_tokens WHERE token = ?", nil, token).Error; err != nil { 23 23 s.logger.Error("error getting refresh token from db", "error", err) 24 24 return helpers.ServerError(e, nil) 25 25 } 26 26 27 - if err := s.db.Exec("DELETE FROM tokens WHERE refresh_token = ?", token).Error; err != nil { 27 + if err := s.db.Exec("DELETE FROM tokens WHERE refresh_token = ?", nil, token).Error; err != nil { 28 28 s.logger.Error("error deleting access token from db", "error", err) 29 29 return helpers.ServerError(e, nil) 30 30 }
+1 -1
server/handle_server_request_email_confirmation.go
··· 20 20 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 21 21 eat := time.Now().Add(10 * time.Minute).UTC() 22 22 23 - if err := s.db.Exec("UPDATE repos SET email_verification_code = ?, email_verification_code_expires_at = ? WHERE did = ?", code, eat, urepo.Repo.Did).Error; err != nil { 23 + if err := s.db.Exec("UPDATE repos SET email_verification_code = ?, email_verification_code_expires_at = ? WHERE did = ?", nil, code, eat, urepo.Repo.Did).Error; err != nil { 24 24 s.logger.Error("error updating user", "error", err) 25 25 return helpers.ServerError(e, nil) 26 26 }
+1 -1
server/handle_server_request_email_update.go
··· 20 20 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 21 21 eat := time.Now().Add(10 * time.Minute).UTC() 22 22 23 - if err := s.db.Exec("UPDATE repos SET email_update_code = ?, email_update_code_expires_at = ? WHERE did = ?", code, eat, urepo.Repo.Did).Error; err != nil { 23 + if err := s.db.Exec("UPDATE repos SET email_update_code = ?, email_update_code_expires_at = ? WHERE did = ?", nil, code, eat, urepo.Repo.Did).Error; err != nil { 24 24 s.logger.Error("error updating repo", "error", err) 25 25 return helpers.ServerError(e, nil) 26 26 }
+1 -1
server/handle_server_request_password_reset.go
··· 36 36 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 37 37 eat := time.Now().Add(10 * time.Minute).UTC() 38 38 39 - if err := s.db.Exec("UPDATE repos SET password_reset_code = ?, password_reset_code_expires_at = ? WHERE did = ?", code, eat, urepo.Repo.Did).Error; err != nil { 39 + if err := s.db.Exec("UPDATE repos SET password_reset_code = ?, password_reset_code_expires_at = ? WHERE did = ?", nil, code, eat, urepo.Repo.Did).Error; err != nil { 40 40 s.logger.Error("error updating repo", "error", err) 41 41 return helpers.ServerError(e, nil) 42 42 }
+1 -1
server/handle_server_reset_password.go
··· 46 46 return helpers.ServerError(e, nil) 47 47 } 48 48 49 - if err := s.db.Exec("UPDATE repos SET password_reset_code = NULL, password_reset_code_expires_at = NULL, password = ? WHERE did = ?", hash, urepo.Repo.Did).Error; err != nil { 49 + if err := s.db.Exec("UPDATE repos SET password_reset_code = NULL, password_reset_code_expires_at = NULL, password = ? WHERE did = ?", nil, hash, urepo.Repo.Did).Error; err != nil { 50 50 s.logger.Error("error updating repo", "error", err) 51 51 return helpers.ServerError(e, nil) 52 52 }
+1 -1
server/handle_server_update_email.go
··· 40 40 return helpers.InputError(e, to.StringPtr("ExpiredToken")) 41 41 } 42 42 43 - if err := s.db.Exec("UPDATE repos SET email_update_code = NULL, email_update_code_expires_at = NULL, email_confirmed_at = NULL, email = ? WHERE did = ?", req.Email, urepo.Repo.Did).Error; err != nil { 43 + if err := s.db.Exec("UPDATE repos SET email_update_code = NULL, email_update_code_expires_at = NULL, email_confirmed_at = NULL, email = ? WHERE did = ?", nil, req.Email, urepo.Repo.Did).Error; err != nil { 44 44 s.logger.Error("error updating repo", "error", err) 45 45 return helpers.ServerError(e, nil) 46 46 }
+3 -3
server/handle_sync_get_blob.go
··· 26 26 } 27 27 28 28 var blob models.Blob 29 - if err := s.db.Raw("SELECT * FROM blobs WHERE did = ? AND cid = ?", did, c.Bytes()).Scan(&blob).Error; err != nil { 29 + if err := s.db.Raw("SELECT * FROM blobs WHERE did = ? AND cid = ?", nil, did, c.Bytes()).Scan(&blob).Error; err != nil { 30 30 s.logger.Error("error looking up blob", "error", err) 31 31 return helpers.ServerError(e, nil) 32 32 } ··· 34 34 buf := new(bytes.Buffer) 35 35 36 36 var parts []models.BlobPart 37 - if err := s.db.Raw("SELECT * FROM blob_parts WHERE blob_id = ? ORDER BY idx", blob.ID).Scan(&parts).Error; err != nil { 37 + if err := s.db.Raw("SELECT * FROM blob_parts WHERE blob_id = ? ORDER BY idx", nil, blob.ID).Scan(&parts).Error; err != nil { 38 38 s.logger.Error("error getting blob parts", "error", err) 39 39 return helpers.ServerError(e, nil) 40 40 } ··· 44 44 buf.Write(p.Data) 45 45 } 46 46 47 - e.Response().Header().Set(echo.HeaderContentDisposition, "attachment; filename=" + c.String()) 47 + e.Response().Header().Set(echo.HeaderContentDisposition, "attachment; filename="+c.String()) 48 48 49 49 return e.Stream(200, "application/octet-stream", buf) 50 50 }
+1 -1
server/handle_sync_get_record.go
··· 18 18 rkey := e.QueryParam("rkey") 19 19 20 20 var urepo models.Repo 21 - if err := s.db.Raw("SELECT * FROM repos WHERE did = ?", did).Scan(&urepo).Error; err != nil { 21 + if err := s.db.Raw("SELECT * FROM repos WHERE did = ?", nil, did).Scan(&urepo).Error; err != nil { 22 22 s.logger.Error("error getting repo", "error", err) 23 23 return helpers.ServerError(e, nil) 24 24 }
+1 -1
server/handle_sync_get_repo.go
··· 41 41 } 42 42 43 43 var blocks []models.Block 44 - if err := s.db.Raw("SELECT * FROM blocks WHERE did = ? ORDER BY rev ASC", urepo.Repo.Did).Scan(&blocks).Error; err != nil { 44 + if err := s.db.Raw("SELECT * FROM blocks WHERE did = ? ORDER BY rev ASC", nil, urepo.Repo.Did).Scan(&blocks).Error; err != nil { 45 45 return err 46 46 } 47 47
+1 -1
server/handle_sync_list_blobs.go
··· 35 35 params = append(params, limit) 36 36 37 37 var blobs []models.Blob 38 - if err := s.db.Raw("SELECT * FROM blobs WHERE did = ? "+cursorquery+" ORDER BY created_at DESC LIMIT ?", params...).Scan(&blobs).Error; err != nil { 38 + if err := s.db.Raw("SELECT * FROM blobs WHERE did = ? "+cursorquery+" ORDER BY created_at DESC LIMIT ?", nil, params...).Scan(&blobs).Error; err != nil { 39 39 s.logger.Error("error getting records", "error", err) 40 40 return helpers.ServerError(e, nil) 41 41 }
+10 -10
server/repo.go
··· 18 18 "github.com/bluesky-social/indigo/repo" 19 19 "github.com/bluesky-social/indigo/util" 20 20 "github.com/haileyok/cocoon/blockstore" 21 + "github.com/haileyok/cocoon/internal/db" 21 22 "github.com/haileyok/cocoon/models" 22 23 blocks "github.com/ipfs/go-block-format" 23 24 "github.com/ipfs/go-cid" 24 25 cbor "github.com/ipfs/go-ipld-cbor" 25 26 "github.com/ipld/go-car" 26 - "gorm.io/gorm" 27 27 "gorm.io/gorm/clause" 28 28 ) 29 29 30 30 type RepoMan struct { 31 - db *gorm.DB 31 + db *db.DB 32 32 s *Server 33 33 clock *syntax.TIDClock 34 34 } ··· 162 162 }) 163 163 case OpTypeDelete: 164 164 var old models.Record 165 - if err := rm.db.Raw("SELECT value FROM records WHERE did = ? AND nsid = ? AND rkey = ?", urepo.Did, op.Collection, op.Rkey).Scan(&old).Error; err != nil { 165 + if err := rm.db.Raw("SELECT value FROM records WHERE did = ? AND nsid = ? AND rkey = ?", nil, urepo.Did, op.Collection, op.Rkey).Scan(&old).Error; err != nil { 166 166 return nil, err 167 167 } 168 168 entries = append(entries, models.Record{ ··· 284 284 for _, entry := range entries { 285 285 var cids []cid.Cid 286 286 if entry.Cid != "" { 287 - if err := rm.s.db.Clauses(clause.OnConflict{ 287 + if err := rm.s.db.Create(&entry, []clause.Expression{clause.OnConflict{ 288 288 Columns: []clause.Column{{Name: "did"}, {Name: "nsid"}, {Name: "rkey"}}, 289 289 UpdateAll: true, 290 - }).Create(&entry).Error; err != nil { 290 + }}).Error; err != nil { 291 291 return nil, err 292 292 } 293 293 ··· 296 296 return nil, err 297 297 } 298 298 } else { 299 - if err := rm.s.db.Delete(&entry).Error; err != nil { 299 + if err := rm.s.db.Delete(&entry, nil).Error; err != nil { 300 300 return nil, err 301 301 } 302 302 cids, err = rm.decrementBlobRefs(urepo, entry.Value) ··· 368 368 } 369 369 370 370 for _, c := range cids { 371 - if err := rm.db.Exec("UPDATE blobs SET ref_count = ref_count + 1 WHERE did = ? AND cid = ?", urepo.Did, c.Bytes()).Error; err != nil { 371 + if err := rm.db.Exec("UPDATE blobs SET ref_count = ref_count + 1 WHERE did = ? AND cid = ?", nil, urepo.Did, c.Bytes()).Error; err != nil { 372 372 return nil, err 373 373 } 374 374 } ··· 387 387 ID uint 388 388 Count int 389 389 } 390 - if err := rm.db.Raw("UPDATE blobs SET ref_count = ref_count - 1 WHERE did = ? AND cid = ? RETURNING id, ref_count", urepo.Did, c.Bytes()).Scan(&res).Error; err != nil { 390 + if err := rm.db.Raw("UPDATE blobs SET ref_count = ref_count - 1 WHERE did = ? AND cid = ? RETURNING id, ref_count", nil, urepo.Did, c.Bytes()).Scan(&res).Error; err != nil { 391 391 return nil, err 392 392 } 393 393 394 394 if res.Count == 0 { 395 - if err := rm.db.Exec("DELETE FROM blobs WHERE id = ?", res.ID).Error; err != nil { 395 + if err := rm.db.Exec("DELETE FROM blobs WHERE id = ?", nil, res.ID).Error; err != nil { 396 396 return nil, err 397 397 } 398 - if err := rm.db.Exec("DELETE FROM blob_parts WHERE blob_id = ?", res.ID).Error; err != nil { 398 + if err := rm.db.Exec("DELETE FROM blob_parts WHERE blob_id = ?", nil, res.ID).Error; err != nil { 399 399 return nil, err 400 400 } 401 401 }
+148 -4
server/server.go
··· 1 1 package server 2 2 3 3 import ( 4 + "bytes" 4 5 "context" 5 6 "crypto/ecdsa" 6 7 "errors" 7 8 "fmt" 9 + "io" 8 10 "log/slog" 9 11 "net/http" 10 12 "net/smtp" ··· 14 16 "time" 15 17 16 18 "github.com/Azure/go-autorest/autorest/to" 19 + "github.com/aws/aws-sdk-go/aws" 20 + "github.com/aws/aws-sdk-go/aws/credentials" 21 + "github.com/aws/aws-sdk-go/aws/session" 22 + "github.com/aws/aws-sdk-go/service/s3" 17 23 "github.com/bluesky-social/indigo/api/atproto" 18 24 "github.com/bluesky-social/indigo/atproto/syntax" 19 25 "github.com/bluesky-social/indigo/events" ··· 23 29 "github.com/go-playground/validator" 24 30 "github.com/golang-jwt/jwt/v4" 25 31 "github.com/haileyok/cocoon/identity" 32 + "github.com/haileyok/cocoon/internal/db" 26 33 "github.com/haileyok/cocoon/internal/helpers" 27 34 "github.com/haileyok/cocoon/models" 28 35 "github.com/haileyok/cocoon/plc" ··· 34 41 "gorm.io/gorm" 35 42 ) 36 43 44 + type S3Config struct { 45 + BackupsEnabled bool 46 + Endpoint string 47 + Region string 48 + Bucket string 49 + AccessKey string 50 + SecretKey string 51 + } 52 + 37 53 type Server struct { 38 54 http *http.Client 39 55 httpd *http.Server 40 56 mail *mailyak.MailYak 41 57 mailLk *sync.Mutex 42 58 echo *echo.Echo 43 - db *gorm.DB 59 + db *db.DB 44 60 plcClient *plc.Client 45 61 logger *slog.Logger 46 62 config *config ··· 48 64 repoman *RepoMan 49 65 evtman *events.EventManager 50 66 passport *identity.Passport 67 + 68 + dbName string 69 + s3Config *S3Config 51 70 } 52 71 53 72 type Args struct { ··· 69 88 SmtpPort string 70 89 SmtpEmail string 71 90 SmtpName string 91 + 92 + S3Config *S3Config 72 93 } 73 94 74 95 type config struct { ··· 176 197 Found bool 177 198 } 178 199 var result Result 179 - if err := s.db.Raw("SELECT EXISTS(SELECT 1 FROM "+table+" WHERE token = ?) AS found", tokenstr).Scan(&result).Error; err != nil { 200 + if err := s.db.Raw("SELECT EXISTS(SELECT 1 FROM "+table+" WHERE token = ?) AS found", nil, tokenstr).Scan(&result).Error; err != nil { 180 201 if err == gorm.ErrRecordNotFound { 181 202 return helpers.InputError(e, to.StringPtr("InvalidToken")) 182 203 } ··· 299 320 IdleTimeout: 5 * time.Minute, 300 321 } 301 322 302 - db, err := gorm.Open(sqlite.Open("cocoon.db"), &gorm.Config{}) 323 + gdb, err := gorm.Open(sqlite.Open("cocoon.db"), &gorm.Config{}) 303 324 if err != nil { 304 325 return nil, err 305 326 } 327 + dbw := db.NewDB(gdb) 306 328 307 329 rkbytes, err := os.ReadFile(args.RotationKeyPath) 308 330 if err != nil { ··· 341 363 httpd: httpd, 342 364 echo: e, 343 365 logger: args.Logger, 344 - db: db, 366 + db: dbw, 345 367 plcClient: plcClient, 346 368 privateKey: &pkey, 347 369 config: &config{ ··· 357 379 }, 358 380 evtman: events.NewEventManager(events.NewMemPersister()), 359 381 passport: identity.NewPassport(h, identity.NewMemCache(10_000)), 382 + 383 + dbName: args.DbName, 384 + s3Config: args.S3Config, 360 385 } 361 386 362 387 s.repoman = NewRepoMan(s) // TODO: this is way too lazy, stop it ··· 461 486 } 462 487 }() 463 488 489 + go s.backupRoutine() 490 + 464 491 for _, relay := range s.config.Relays { 465 492 cli := xrpc.Client{Host: relay} 466 493 atproto.SyncRequestCrawl(ctx, &cli, &atproto.SyncRequestCrawl_Input{ ··· 474 501 475 502 return nil 476 503 } 504 + 505 + func (s *Server) doBackup() { 506 + start := time.Now() 507 + 508 + s.logger.Info("beginning backup to s3...") 509 + 510 + var buf bytes.Buffer 511 + if err := func() error { 512 + s.logger.Info("reading database bytes...") 513 + s.db.Lock() 514 + defer s.db.Unlock() 515 + 516 + sf, err := os.Open(s.dbName) 517 + if err != nil { 518 + return fmt.Errorf("error opening database for backup: %w", err) 519 + } 520 + defer sf.Close() 521 + 522 + if _, err := io.Copy(&buf, sf); err != nil { 523 + return fmt.Errorf("error reading bytes of backup db: %w", err) 524 + } 525 + 526 + return nil 527 + }(); err != nil { 528 + s.logger.Error("error backing up database", "error", err) 529 + return 530 + } 531 + 532 + if err := func() error { 533 + s.logger.Info("sending to s3...") 534 + 535 + currTime := time.Now().Format("2006-01-02_15-04-05") 536 + key := "cocoon-backup-" + currTime + ".db" 537 + 538 + config := &aws.Config{ 539 + Region: aws.String(s.s3Config.Region), 540 + Credentials: credentials.NewStaticCredentials(s.s3Config.AccessKey, s.s3Config.SecretKey, ""), 541 + } 542 + 543 + if s.s3Config.Endpoint != "" { 544 + config.Endpoint = aws.String(s.s3Config.Endpoint) 545 + config.S3ForcePathStyle = aws.Bool(true) 546 + } 547 + 548 + sess, err := session.NewSession(config) 549 + if err != nil { 550 + return err 551 + } 552 + 553 + svc := s3.New(sess) 554 + 555 + if _, err := svc.PutObject(&s3.PutObjectInput{ 556 + Bucket: aws.String(s.s3Config.Bucket), 557 + Key: aws.String(key), 558 + Body: bytes.NewReader(buf.Bytes()), 559 + }); err != nil { 560 + return fmt.Errorf("error uploading file to s3: %w", err) 561 + } 562 + 563 + s.logger.Info("finished uploading backup to s3", "key", key, "duration", time.Now().Sub(start).Seconds()) 564 + 565 + return nil 566 + }(); err != nil { 567 + s.logger.Error("error uploading database backup", "error", err) 568 + return 569 + } 570 + 571 + os.WriteFile("last-backup.txt", []byte(time.Now().String()), 0644) 572 + } 573 + 574 + func (s *Server) backupRoutine() { 575 + if s.s3Config == nil || !s.s3Config.BackupsEnabled { 576 + return 577 + } 578 + 579 + if s.s3Config.Region == "" { 580 + s.logger.Warn("no s3 region configured but backups are enabled. backups will not run.") 581 + return 582 + } 583 + 584 + if s.s3Config.Bucket == "" { 585 + s.logger.Warn("no s3 bucket configured but backups are enabled. backups will not run.") 586 + return 587 + } 588 + 589 + if s.s3Config.AccessKey == "" { 590 + s.logger.Warn("no s3 access key configured but backups are enabled. backups will not run.") 591 + return 592 + } 593 + 594 + if s.s3Config.SecretKey == "" { 595 + s.logger.Warn("no s3 secret key configured but backups are enabled. backups will not run.") 596 + return 597 + } 598 + 599 + shouldBackupNow := false 600 + lastBackupStr, err := os.ReadFile("last-backup.txt") 601 + if err != nil { 602 + shouldBackupNow = true 603 + } else { 604 + lastBackup, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", string(lastBackupStr)) 605 + if err != nil { 606 + shouldBackupNow = true 607 + } else if time.Now().Sub(lastBackup).Seconds() > 3600 { 608 + shouldBackupNow = true 609 + } 610 + } 611 + 612 + if shouldBackupNow { 613 + go s.doBackup() 614 + } 615 + 616 + ticker := time.NewTicker(time.Hour) 617 + for range ticker.C { 618 + go s.doBackup() 619 + } 620 + }
+2 -2
server/session.go
··· 55 55 RefreshToken: refreshString, 56 56 CreatedAt: now, 57 57 ExpiresAt: accexp, 58 - }).Error; err != nil { 58 + }, nil).Error; err != nil { 59 59 return nil, err 60 60 } 61 61 ··· 64 64 Did: repo.Did, 65 65 CreatedAt: now, 66 66 ExpiresAt: refexp, 67 - }).Error; err != nil { 67 + }, nil).Error; err != nil { 68 68 return nil, err 69 69 } 70 70