diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 2893509..a559fae 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -68,7 +68,7 @@ func main() { if err := database.Migrate(); err != nil { logger.Fatal("Failed to run migrations: %v", err) } - database.MigrateUnifiedNotes() + database.MigrateUnifiedNotes(context.Background()) go func() { ticker := time.NewTicker(1 * time.Hour) @@ -76,10 +76,10 @@ func main() { runCleanup := func() { _, err := database.TryAdvisoryLock(context.Background(), db.LockSessionCleanup, func() error { - if err := database.DeleteExpiredOAuthSessions(); err != nil { + if err := database.DeleteExpiredOAuthSessions(context.Background()); err != nil { return err } - return database.DeleteExpiredPendingAuthsOAuth() + return database.DeleteExpiredPendingAuthsOAuth(context.Background()) }) if err != nil { logger.Error("Failed to run cleanup of expired sessions: %v", err) diff --git a/backend/go.mod b/backend/go.mod index 1cb3fe8..b7687d4 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -9,11 +9,12 @@ require ( github.com/go-jose/go-jose/v4 v4.0.4 github.com/gorilla/websocket v1.5.3 github.com/ipfs/go-cid v0.6.0 + github.com/jackc/pgx/v5 v5.10.0 github.com/joho/godotenv v1.5.1 - github.com/lib/pq v1.10.9 github.com/multiformats/go-multihash v0.2.3 github.com/posthog/posthog-go v1.11.2 github.com/pressly/goose/v3 v3.27.0 + golang.org/x/net v0.53.0 ) require ( @@ -21,6 +22,9 @@ require ( github.com/goccy/go-json v0.10.5 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/mfridman/interpolate v0.0.2 // indirect github.com/minio/sha256-simd v1.0.0 // indirect @@ -35,7 +39,6 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.50.0 // indirect - golang.org/x/net v0.53.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index e8c7bef..01d7db7 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,3 +1,4 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -22,13 +23,19 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/ipfs/go-cid v0.6.0 h1:DlOReBV1xhHBhhfy/gBNNTSyfOM6rLiIx9J7A4DGf30= github.com/ipfs/go-cid v0.6.0/go.mod h1:NC4kS1LZjzfhK40UGmpXv5/qD2kcMzACYJNntCUiDhQ= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= @@ -49,6 +56,7 @@ github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOo github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posthog/posthog-go v1.11.2 h1:ApKTtOhIeWhUBc4ByO+mlbg2o0iZaEGJnJHX2QDnn5Q= @@ -61,30 +69,29 @@ github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= diff --git a/backend/internal/api/apikey.go b/backend/internal/api/apikey.go index 85dd6b9..56467a5 100644 --- a/backend/internal/api/apikey.go +++ b/backend/internal/api/apikey.go @@ -4,10 +4,8 @@ import ( "context" "crypto/rand" "crypto/sha256" - "crypto/x509" "encoding/hex" "encoding/json" - "encoding/pem" "fmt" "net/http" "strings" @@ -92,7 +90,7 @@ func (h *APIKeyHandler) CreateKey(w http.ResponseWriter, r *http.Request) { IndexedAt: time.Now(), } - if err := h.db.CreateAPIKey(apiKey); err != nil { + if err := h.db.CreateAPIKey(r.Context(), apiKey); err != nil { logger.Error("[ERROR] Failed to insert API key into DB: %v", err) WriteInternalError(w, "Failed to create key") return @@ -113,7 +111,7 @@ func (h *APIKeyHandler) ListKeys(w http.ResponseWriter, r *http.Request) { return } - keys, err := h.db.GetAPIKeysByOwner(session.DID) + keys, err := h.db.GetAPIKeysByOwner(r.Context(), session.DID) if err != nil { WriteInternalError(w, "Failed to get keys") return @@ -139,7 +137,7 @@ func (h *APIKeyHandler) DeleteKey(w http.ResponseWriter, r *http.Request) { return } - uri, err := h.db.DeleteAPIKey(keyID, session.DID) + uri, err := h.db.DeleteAPIKey(r.Context(), keyID, session.DID) if err != nil { WriteInternalError(w, "Failed to delete key") return @@ -218,12 +216,12 @@ func (h *APIKeyHandler) QuickBookmark(w http.ResponseWriter, r *http.Request) { logger.Info("[QuickBookmark] created record URI=%s for DID %s", result.URI, apiKey.OwnerDID) - h.db.UpdateAPIKeyLastUsed(apiKey.ID) + h.db.UpdateAPIKeyLastUsed(r.Context(), apiKey.ID) capturedTags := append([]string(nil), req.Tags...) capturedNoteURI := result.URI go func(did, url, noteURI string) { - prefs, dbErr := h.db.GetPreferences(did) + prefs, dbErr := h.db.GetPreferences(context.Background(), did) communityEnabled := dbErr == nil && prefs != nil && (prefs.EnableCommunityBookmarks == nil || *prefs.EnableCommunityBookmarks) if !communityEnabled { return @@ -245,7 +243,7 @@ func (h *APIKeyHandler) QuickBookmark(w http.ResponseWriter, r *http.Request) { defer cancel() communityResult, communityErr := client.CreateRecord(ctx, did, xrpc.CollectionCommunityBookmark, communityRecord) if communityErr == nil && communityResult != nil { - _ = h.db.SaveCommunityBookmarkRef(noteURI, communityResult.URI) + _ = h.db.SaveCommunityBookmarkRef(context.Background(), noteURI, communityResult.URI) } }(apiKey.OwnerDID, req.URL, capturedNoteURI) @@ -276,7 +274,7 @@ func (h *APIKeyHandler) QuickBookmark(w http.ResponseWriter, r *http.Request) { IndexedAt: time.Now(), CID: &cid, } - h.db.CreateNote(note) + h.db.CreateNote(r.Context(), note) WriteSuccess(w, map[string]string{ "uri": result.URI, @@ -359,7 +357,7 @@ func (h *APIKeyHandler) QuickSave(w http.ResponseWriter, r *http.Request) { return createErr }) if err == nil { - h.db.UpdateAPIKeyLastUsed(apiKey.ID) + h.db.UpdateAPIKeyLastUsed(r.Context(), apiKey.ID) selectorJSON, _ := json.Marshal(req.Selector) selectorStr := string(selectorJSON) colorPtr := &color @@ -384,7 +382,7 @@ func (h *APIKeyHandler) QuickSave(w http.ResponseWriter, r *http.Request) { CID: &result.CID, } go func() { - if err := h.db.CreateNote(note); err != nil { + if err := h.db.CreateNote(context.Background(), note); err != nil { logger.Error("Warning: failed to index highlight note in local DB: %v", err) } }() @@ -406,7 +404,7 @@ func (h *APIKeyHandler) QuickSave(w http.ResponseWriter, r *http.Request) { return createErr }) if err == nil { - h.db.UpdateAPIKeyLastUsed(apiKey.ID) + h.db.UpdateAPIKeyLastUsed(r.Context(), apiKey.ID) var selectorStrPtr *string if req.Selector != nil { @@ -438,7 +436,7 @@ func (h *APIKeyHandler) QuickSave(w http.ResponseWriter, r *http.Request) { CID: &result.CID, } go func() { - h.db.CreateNote(note) + h.db.CreateNote(context.Background(), note) }() } } @@ -518,7 +516,7 @@ func (h *APIKeyHandler) QuickHighlight(w http.ResponseWriter, r *http.Request) { return } - h.db.UpdateAPIKeyLastUsed(apiKey.ID) + h.db.UpdateAPIKeyLastUsed(r.Context(), apiKey.ID) selectorJSON, _ := json.Marshal(req.Selector) selectorStr := string(selectorJSON) @@ -548,7 +546,7 @@ func (h *APIKeyHandler) QuickHighlight(w http.ResponseWriter, r *http.Request) { IndexedAt: time.Now(), CID: &result.CID, } - if err := h.db.CreateNote(note); err != nil { + if err := h.db.CreateNote(r.Context(), note); err != nil { logger.Error("Warning: failed to index highlight note in local DB: %v", err) } @@ -579,7 +577,7 @@ func (h *APIKeyHandler) GetMe(w http.ResponseWriter, r *http.Request) { WriteUnauthorized(w, "Unauthorized") return } - sess, err := h.db.GetOAuthSessionByID(token) + sess, err := h.db.GetOAuthSessionByID(r.Context(), token) if err != nil { WriteUnauthorized(w, "Invalid session") return @@ -603,7 +601,7 @@ func (h *APIKeyHandler) authenticateAPIKey(r *http.Request) (*db.APIKey, error) rawKey := strings.TrimPrefix(auth, "Bearer ") keyHash := hashAPIKey(rawKey) - apiKey, err := h.db.GetAPIKeyByHash(keyHash) + apiKey, err := h.db.GetAPIKeyByHash(r.Context(), keyHash) if err != nil { return nil, fmt.Errorf("invalid API key") } @@ -612,53 +610,28 @@ func (h *APIKeyHandler) authenticateAPIKey(r *http.Request) (*db.APIKey, error) } func (h *APIKeyHandler) getSessionByDID(did string) (*SessionData, error) { - rows, err := h.db.Query(` - SELECT id, did, handle, access_token, refresh_token, COALESCE(dpop_key, '') - FROM sessions - WHERE did = $1 AND expires_at > $2 - ORDER BY created_at DESC - LIMIT 1 - `, did, time.Now()) + row, err := h.db.GetLatestOAuthSessionByDID(context.Background(), did) if err != nil { - return nil, err - } - defer rows.Close() - - if !rows.Next() { return nil, fmt.Errorf("no active session") } - var sessionID, sessDID, handle, accessToken, refreshToken, dpopKeyStr string - if err := rows.Scan(&sessionID, &sessDID, &handle, &accessToken, &refreshToken, &dpopKeyStr); err != nil { - return nil, err - } - - block, _ := pem.Decode([]byte(dpopKeyStr)) - if block == nil { - return nil, fmt.Errorf("invalid session DPoP key") - } - dpopKey, err := x509.ParseECPrivateKey(block.Bytes) + session, err := sessionFromRow(row) if err != nil { - return nil, fmt.Errorf("invalid session DPoP key: %w", err) + return nil, err } - pds, err := xrpc.ResolveDIDToPDS(sessDID) - if err != nil { - return nil, fmt.Errorf("failed to resolve PDS: %w", err) + if session.PDS == "" { + pds, perr := xrpc.ResolveDIDToPDS(session.DID) + if perr != nil { + return nil, fmt.Errorf("failed to resolve PDS: %w", perr) + } + session.PDS = pds } - if pds == "" { - return nil, fmt.Errorf("PDS not found for DID: %s", sessDID) + if session.PDS == "" { + return nil, fmt.Errorf("PDS not found for DID: %s", session.DID) } - return &SessionData{ - ID: sessionID, - DID: sessDID, - Handle: handle, - AccessToken: accessToken, - RefreshToken: refreshToken, - DPoPKey: dpopKey, - PDS: pds, - }, nil + return session, nil } func generateAPIKey() string { diff --git a/backend/internal/api/collections.go b/backend/internal/api/collections.go index 70f1086..65078c6 100644 --- a/backend/internal/api/collections.go +++ b/backend/internal/api/collections.go @@ -89,7 +89,7 @@ func (s *CollectionService) CreateCollection(w http.ResponseWriter, r *http.Requ CreatedAt: time.Now(), IndexedAt: time.Now(), } - s.db.CreateCollection(collection) + s.db.CreateCollection(r.Context(), collection) WriteSuccess(w, result) } @@ -148,7 +148,7 @@ func (s *CollectionService) AddCollectionItem(w http.ResponseWriter, r *http.Req CreatedAt: time.Now(), IndexedAt: time.Now(), } - if err := s.db.AddToCollection(item); err != nil { + if err := s.db.AddToCollection(r.Context(), item); err != nil { logger.Error("Failed to add to collection in DB: %v", err) } @@ -175,7 +175,7 @@ func (s *CollectionService) RemoveCollectionItem(w http.ResponseWriter, r *http. logger.Error("Warning: PDS delete failed for %s: %v", itemURI, err) } - s.db.RemoveFromCollection(itemURI) + s.db.RemoveFromCollection(r.Context(), itemURI) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -189,7 +189,7 @@ func (s *CollectionService) GetAnnotationCollections(w http.ResponseWriter, r *h return } - uris, err := s.db.GetCollectionURIsForAnnotation(annotationURI) + uris, err := s.db.GetCollectionURIsForAnnotation(r.Context(), annotationURI) if err != nil { WriteInternalError(w, "Internal server error") return @@ -216,7 +216,7 @@ func (s *CollectionService) GetCollections(w http.ResponseWriter, r *http.Reques return } - collections, err := s.db.GetCollectionsByAuthor(authorDID) + collections, err := s.db.GetCollectionsByAuthor(r.Context(), authorDID) if err != nil { WriteInternalError(w, "Internal server error") return @@ -229,7 +229,7 @@ func (s *CollectionService) GetCollections(w http.ResponseWriter, r *http.Reques for i, c := range collections { collectionURIs[i] = c.URI } - itemCounts, _ := s.db.GetCollectionItemCounts(collectionURIs) + itemCounts, _ := s.db.GetCollectionItemCounts(r.Context(), collectionURIs) apiCollections := make([]APICollection, len(collections)) for i, c := range collections { @@ -285,7 +285,7 @@ func (s *CollectionService) GetCollectionItems(w http.ResponseWriter, r *http.Re return } - items, err := s.db.GetCollectionItems(collectionURI) + items, err := s.db.GetCollectionItems(r.Context(), collectionURI) if err != nil { WriteInternalError(w, "Internal server error") return @@ -398,7 +398,7 @@ func (s *CollectionService) UpdateCollection(w http.ResponseWriter, r *http.Requ CreatedAt: time.Now(), IndexedAt: time.Now(), } - s.db.CreateCollection(collection) + s.db.CreateCollection(r.Context(), collection) WriteSuccess(w, result) } @@ -421,7 +421,7 @@ func (s *CollectionService) DeleteCollection(w http.ResponseWriter, r *http.Requ return } - items, _ := s.db.GetCollectionItems(uri) + items, _ := s.db.GetCollectionItems(r.Context(), uri) err = s.refresher.ExecuteWithAutoRefresh(r, session, func(client *xrpc.Client, did string) error { for _, item := range items { @@ -437,7 +437,7 @@ func (s *CollectionService) DeleteCollection(w http.ResponseWriter, r *http.Requ return } - s.db.DeleteCollection(uri) + s.db.DeleteCollection(r.Context(), uri) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"status": "deleted"}) @@ -450,7 +450,7 @@ func (s *CollectionService) GetCollection(w http.ResponseWriter, r *http.Request return } - collection, err := s.db.GetCollectionByURI(uri) + collection, err := s.db.GetCollectionByURI(r.Context(), uri) if err != nil { if strings.Contains(uri, "at.margin.collection") && strings.HasPrefix(uri, "at://") { uriWithoutScheme := strings.TrimPrefix(uri, "at://") @@ -460,7 +460,7 @@ func (s *CollectionService) GetCollection(w http.ResponseWriter, r *http.Request rkey := parts[len(parts)-1] sembleURI := fmt.Sprintf("at://%s/network.cosmik.collection/%s", did, rkey) - collection, err = s.db.GetCollectionByURI(sembleURI) + collection, err = s.db.GetCollectionByURI(r.Context(), sembleURI) } } } @@ -473,7 +473,7 @@ func (s *CollectionService) GetCollection(w http.ResponseWriter, r *http.Request profiles := fetchProfilesForDIDs(s.db, []string{collection.AuthorDID}) creator := profiles[collection.AuthorDID] - itemCounts, _ := s.db.GetCollectionItemCounts([]string{collection.URI}) + itemCounts, _ := s.db.GetCollectionItemCounts(r.Context(), []string{collection.URI}) icon := "" if collection.Icon != nil { diff --git a/backend/internal/api/handler.go b/backend/internal/api/handler.go index 89db0c7..5b65786 100644 --- a/backend/internal/api/handler.go +++ b/backend/internal/api/handler.go @@ -87,7 +87,6 @@ type Handler struct { noteRepo domain.NoteRepository engagementRepo domain.EngagementRepository notificationRepo domain.NotificationRepository - sessionRepo domain.SessionRepository noteWriter *NoteWriteService refresher *TokenRefresher apiKeys *APIKeyHandler @@ -102,13 +101,12 @@ type Handler struct { } func NewHandler(database *db.DB, noteWriter *NoteWriteService, refresher *TokenRefresher, syncService *internal_sync.Service, recService *recommendations.Service, ac *analytics.Client) *Handler { - noteRepo := postgres.NewNoteRepository(database.DB) - engagementRepo := postgres.NewEngagementRepository(database.DB) - notificationRepo := postgres.NewNotificationRepository(database.DB) - sessionRepo := postgres.NewSessionRepository(database.DB) + noteRepo := postgres.NewNoteRepository(database.Pool()) + engagementRepo := postgres.NewEngagementRepository(database.Pool()) + notificationRepo := postgres.NewNotificationRepository(database.Pool()) profileRepo := &fullProfileRepository{db: database} // rich resolution: cache → DB → bsky.social profileSvc := service.NewProfileService(profileRepo) // service-lifetime TTL cache on top - collectionRepo := postgres.NewCollectionRepository(database.DB) + collectionRepo := postgres.NewCollectionRepository(database.Pool()) hydration := service.NewHydrationService(engagementRepo, profileSvc, collectionRepo) feedSvc := service.NewFeedService(noteRepo, hydration, database) @@ -117,7 +115,6 @@ func NewHandler(database *db.DB, noteWriter *NoteWriteService, refresher *TokenR noteRepo: noteRepo, engagementRepo: engagementRepo, notificationRepo: notificationRepo, - sessionRepo: sessionRepo, noteWriter: noteWriter, refresher: refresher, apiKeys: NewAPIKeyHandler(database, refresher), @@ -429,12 +426,12 @@ func (h *Handler) GetFeed(w http.ResponseWriter, r *http.Request) { if feedType == db.FeedTypeRecent && motivation == "" && tag == "" { var collItems []db.CollectionItem if creator != "" { - collItems, _ = h.db.GetCollectionItemsByAuthor(creator) + collItems, _ = h.db.GetCollectionItemsByAuthor(r.Context(), creator) if len(collItems) > fetchLimit { collItems = collItems[:fetchLimit] } } else { - collItems, _ = h.db.GetRecentCollectionItems(fetchLimit, 0) + collItems, _ = h.db.GetRecentCollectionItems(r.Context(), fetchLimit, 0) } if len(collItems) > 0 { @@ -686,14 +683,14 @@ func (h *Handler) DiscoverForURL(w http.ResponseWriter, r *http.Request) { } } - localAnnotations, _ := h.db.GetAnnotationsByURIs(annotationURIs) - localHighlights, _ := h.db.GetHighlightsByURIs(highlightURIs) - localBookmarks, _ := h.db.GetBookmarksByURIs(bookmarkURIs) + localAnnotations, _ := h.db.GetAnnotationsByURIs(r.Context(), annotationURIs) + localHighlights, _ := h.db.GetHighlightsByURIs(r.Context(), highlightURIs) + localBookmarks, _ := h.db.GetBookmarksByURIs(r.Context(), bookmarkURIs) urlHash := db.HashURL(source) - dbAnnotations, _ := h.db.GetAnnotationsByTargetHash(urlHash, 100, 0) - dbHighlights, _ := h.db.GetHighlightsByTargetHash(urlHash, 100, 0) - dbBookmarks, _ := h.db.GetBookmarksByTargetHash(urlHash, 100, 0) + dbAnnotations, _ := h.db.GetAnnotationsByTargetHash(r.Context(), urlHash, 100, 0) + dbHighlights, _ := h.db.GetHighlightsByTargetHash(r.Context(), urlHash, 100, 0) + dbBookmarks, _ := h.db.GetBookmarksByTargetHash(r.Context(), urlHash, 100, 0) annoMap := make(map[string]db.Annotation) for _, a := range localAnnotations { @@ -1002,7 +999,7 @@ func (h *Handler) GetReplies(w http.ResponseWriter, r *http.Request) { return } - replies, err := h.db.GetRepliesByRoot(uri) + replies, err := h.db.GetRepliesByRoot(r.Context(), uri) if err != nil { WriteInternalError(w, "Internal server error") return @@ -1057,7 +1054,7 @@ func (h *Handler) GetEditHistory(w http.ResponseWriter, r *http.Request) { return } - history, err := h.db.GetEditHistory(uri) + history, err := h.db.GetEditHistory(r.Context(), uri) if err != nil { WriteInternalError(w, "Failed to fetch edit history") return @@ -1366,7 +1363,7 @@ func (h *Handler) getViewerDID(r *http.Request) string { if err != nil { return "" } - sess, err := h.db.GetOAuthSessionByID(cookie.Value) + sess, err := h.db.GetOAuthSessionByID(r.Context(), cookie.Value) if err != nil { return "" } @@ -1391,12 +1388,12 @@ func (r *fullProfileRepository) GetProfiles(_ context.Context, dids []string) (m return result, nil } -func (r *fullProfileRepository) GetProfile(_ context.Context, did string) (*domain.Profile, error) { - return r.db.GetProfile(did) +func (r *fullProfileRepository) GetProfile(ctx context.Context, did string) (*domain.Profile, error) { + return r.db.GetProfile(ctx, did) } -func (r *fullProfileRepository) UpsertProfile(_ context.Context, p *domain.Profile) error { - return r.db.UpsertProfile(p) +func (r *fullProfileRepository) UpsertProfile(ctx context.Context, p *domain.Profile) error { + return r.db.UpsertProfile(ctx, p) } func (h *Handler) Search(w http.ResponseWriter, r *http.Request) { @@ -1480,9 +1477,9 @@ func (h *Handler) GetDocuments(w http.ResponseWriter, r *http.Request) { switch sort { case "popular": - docs, err = h.db.GetPopularDocuments(limit, offset) + docs, err = h.db.GetPopularDocuments(r.Context(), limit, offset) default: - docs, err = h.db.GetRecentDocuments(limit, offset) + docs, err = h.db.GetRecentDocuments(r.Context(), limit, offset) } if err != nil { @@ -1526,7 +1523,7 @@ func (h *Handler) GetDocuments(w http.ResponseWriter, r *http.Request) { } } - total, _ := h.db.GetDocumentCount() + total, _ := h.db.GetDocumentCount(r.Context()) WriteSuccess(w, map[string]interface{}{ "items": items, @@ -1584,7 +1581,7 @@ func (h *Handler) AdminBackfill(w http.ResponseWriter, r *http.Request) { } res.ProfilesRebuilt = profileCount - docCount, _ := h.db.GetDocumentCount() + docCount, _ := h.db.GetDocumentCount(r.Context()) res.Documents = docCount WriteSuccess(w, res) diff --git a/backend/internal/api/hydration.go b/backend/internal/api/hydration.go index b9d8a2c..8d39ef5 100644 --- a/backend/internal/api/hydration.go +++ b/backend/internal/api/hydration.go @@ -192,7 +192,7 @@ func fetchCounts(ctx context.Context, database *db.DB, uris []string, viewerDID wg.Add(2) go func() { defer wg.Done() - if lc, err := database.GetLikeCounts(uris); err == nil { + if lc, err := database.GetLikeCounts(ctx, uris); err == nil { mu.Lock() likeCounts = lc mu.Unlock() @@ -200,7 +200,7 @@ func fetchCounts(ctx context.Context, database *db.DB, uris []string, viewerDID }() go func() { defer wg.Done() - if rc, err := database.GetReplyCounts(uris); err == nil { + if rc, err := database.GetReplyCounts(ctx, uris); err == nil { mu.Lock() replyCounts = rc mu.Unlock() @@ -210,7 +210,7 @@ func fetchCounts(ctx context.Context, database *db.DB, uris []string, viewerDID wg.Add(1) go func() { defer wg.Done() - if vl, err := database.GetViewerLikes(viewerDID, uris); err == nil { + if vl, err := database.GetViewerLikes(ctx, viewerDID, uris); err == nil { mu.Lock() viewerLikes = vl mu.Unlock() @@ -279,7 +279,7 @@ func fetchEngagementData(database *db.DB, uris []string, authorDIDs []string, vi wg.Add(1) go func() { defer wg.Done() - if ul, err := database.GetContentLabelsForURIs(uris, labelerDIDs); err == nil { + if ul, err := database.GetContentLabelsForURIs(context.Background(), uris, labelerDIDs); err == nil { mu.Lock() uriLabels = ul mu.Unlock() @@ -289,7 +289,7 @@ func fetchEngagementData(database *db.DB, uris []string, authorDIDs []string, vi wg.Add(1) go func() { defer wg.Done() - if dl, err := database.GetContentLabelsForDIDs(authorDIDs, labelerDIDs); err == nil { + if dl, err := database.GetContentLabelsForDIDs(context.Background(), authorDIDs, labelerDIDs); err == nil { mu.Lock() didLabels = dl mu.Unlock() @@ -299,7 +299,7 @@ func fetchEngagementData(database *db.DB, uris []string, authorDIDs []string, vi wg.Add(1) go func() { defer wg.Done() - if et, err := database.GetLatestEditTimes(uris); err == nil { + if et, err := database.GetLatestEditTimes(context.Background(), uris); err == nil { mu.Lock() editTimes = et mu.Unlock() @@ -630,7 +630,7 @@ func fetchProfilesForDIDs(database *db.DB, dids []string) map[string]Author { } if database != nil { - marginProfiles, err := database.GetProfilesByDIDs(missingDIDs) + marginProfiles, err := database.GetProfilesByDIDs(context.Background(), missingDIDs) if err == nil { for did, mp := range marginProfiles { author := Author{DID: did} @@ -719,7 +719,7 @@ func fetchProfilesForDIDs(database *db.DB, dids []string) map[string]Author { } if database != nil && len(dids) > 0 { - marginProfiles, err := database.GetProfilesByDIDs(dids) + marginProfiles, err := database.GetProfilesByDIDs(context.Background(), dids) if err == nil { for did, mp := range marginProfiles { author, exists := profiles[did] @@ -833,7 +833,7 @@ func hydrateCollectionItemsWithData(database *db.DB, items []db.CollectionItem, collectionsMap := make(map[string]APICollection) if len(collectionURIs) > 0 { - colls, err := database.GetCollectionsByURIs(collectionURIs) + colls, err := database.GetCollectionsByURIs(context.Background(), collectionURIs) if err == nil { for _, coll := range colls { icon := "" @@ -875,7 +875,7 @@ func hydrateCollectionItemsWithData(database *db.DB, items []db.CollectionItem, wg.Add(1) go func() { defer wg.Done() - result, err := database.GetNotesByURIs(noteURIs) + result, err := database.GetNotesByURIs(context.Background(), noteURIs) if err == nil { mu.Lock() rawNotes = result @@ -887,7 +887,7 @@ func hydrateCollectionItemsWithData(database *db.DB, items []db.CollectionItem, wg.Add(1) go func() { defer wg.Done() - result, err := database.GetAnnotationsByURIs(annotationURIs) + result, err := database.GetAnnotationsByURIs(context.Background(), annotationURIs) if err == nil { mu.Lock() rawAnnos = result @@ -899,7 +899,7 @@ func hydrateCollectionItemsWithData(database *db.DB, items []db.CollectionItem, wg.Add(1) go func() { defer wg.Done() - result, err := database.GetHighlightsByURIs(highlightURIs) + result, err := database.GetHighlightsByURIs(context.Background(), highlightURIs) if err == nil { mu.Lock() rawHighlights = result @@ -911,7 +911,7 @@ func hydrateCollectionItemsWithData(database *db.DB, items []db.CollectionItem, wg.Add(1) go func() { defer wg.Done() - result, err := database.GetBookmarksByURIs(bookmarkURIs) + result, err := database.GetBookmarksByURIs(context.Background(), bookmarkURIs) if err == nil { mu.Lock() rawBookmarks = result @@ -1202,7 +1202,7 @@ func hydrateNotifications(database *db.DB, notifications []db.Notification) ([]A replyMap := make(map[string]APIReply) if len(replyURIs) > 0 { - replies, err := database.GetRepliesByURIs(replyURIs) + replies, err := database.GetRepliesByURIs(context.Background(), replyURIs) if err == nil { hydratedReplies, _ := hydrateReplies(database, replies) for _, r := range hydratedReplies { @@ -1213,19 +1213,19 @@ func hydrateNotifications(database *db.DB, notifications []db.Notification) ([]A contentMap := make(map[string]interface{}) if len(contentURIs) > 0 { - if annotations, err := database.GetAnnotationsByURIs(contentURIs); err == nil && len(annotations) > 0 { + if annotations, err := database.GetAnnotationsByURIs(context.Background(), contentURIs); err == nil && len(annotations) > 0 { hydratedAnnotations, _ := hydrateAnnotations(database, annotations, "") for _, a := range hydratedAnnotations { contentMap[a.ID] = a } } - if highlights, err := database.GetHighlightsByURIs(contentURIs); err == nil && len(highlights) > 0 { + if highlights, err := database.GetHighlightsByURIs(context.Background(), contentURIs); err == nil && len(highlights) > 0 { hydratedHighlights, _ := hydrateHighlights(database, highlights, "") for _, h := range hydratedHighlights { contentMap[h.ID] = h } } - if bookmarks, err := database.GetBookmarksByURIs(contentURIs); err == nil && len(bookmarks) > 0 { + if bookmarks, err := database.GetBookmarksByURIs(context.Background(), contentURIs); err == nil && len(bookmarks) > 0 { hydratedBookmarks, _ := hydrateBookmarks(database, bookmarks, "") for _, b := range hydratedBookmarks { contentMap[b.ID] = b @@ -1310,7 +1310,7 @@ func getSubscribedLabelers(database *db.DB, viewerDID string) []string { return nil } - prefs, err := database.GetPreferences(viewerDID) + prefs, err := database.GetPreferences(context.Background(), viewerDID) if err != nil || prefs == nil || prefs.SubscribedLabelers == nil { if serviceDID != "" { return []string{serviceDID} diff --git a/backend/internal/api/moderation.go b/backend/internal/api/moderation.go index 5980d89..e912336 100644 --- a/backend/internal/api/moderation.go +++ b/backend/internal/api/moderation.go @@ -1,6 +1,7 @@ package api import ( + "context" "encoding/json" "net/http" "strconv" @@ -39,7 +40,7 @@ func (m *ModerationHandler) BlockUser(w http.ResponseWriter, r *http.Request) { return } - if err := m.db.CreateBlock(session.DID, req.DID); err != nil { + if err := m.db.CreateBlock(r.Context(), session.DID, req.DID); err != nil { logger.Error("Failed to create block: %v", err) WriteInternalError(w, "Failed to block user") return @@ -61,7 +62,7 @@ func (m *ModerationHandler) UnblockUser(w http.ResponseWriter, r *http.Request) return } - if err := m.db.DeleteBlock(session.DID, did); err != nil { + if err := m.db.DeleteBlock(r.Context(), session.DID, did); err != nil { logger.Error("Failed to delete block: %v", err) WriteInternalError(w, "Failed to unblock user") return @@ -77,7 +78,7 @@ func (m *ModerationHandler) GetBlocks(w http.ResponseWriter, r *http.Request) { return } - blocks, err := m.db.GetBlocks(session.DID) + blocks, err := m.db.GetBlocks(r.Context(), session.DID) if err != nil { WriteInternalError(w, "Failed to fetch blocks") return @@ -127,7 +128,7 @@ func (m *ModerationHandler) MuteUser(w http.ResponseWriter, r *http.Request) { return } - if err := m.db.CreateMute(session.DID, req.DID); err != nil { + if err := m.db.CreateMute(r.Context(), session.DID, req.DID); err != nil { logger.Error("Failed to create mute: %v", err) WriteInternalError(w, "Failed to mute user") return @@ -149,7 +150,7 @@ func (m *ModerationHandler) UnmuteUser(w http.ResponseWriter, r *http.Request) { return } - if err := m.db.DeleteMute(session.DID, did); err != nil { + if err := m.db.DeleteMute(r.Context(), session.DID, did); err != nil { logger.Error("Failed to delete mute: %v", err) WriteInternalError(w, "Failed to unmute user") return @@ -165,7 +166,7 @@ func (m *ModerationHandler) GetMutes(w http.ResponseWriter, r *http.Request) { return } - mutes, err := m.db.GetMutes(session.DID) + mutes, err := m.db.GetMutes(r.Context(), session.DID) if err != nil { WriteInternalError(w, "Failed to fetch mutes") return @@ -204,7 +205,7 @@ func (m *ModerationHandler) GetRelationship(w http.ResponseWriter, r *http.Reque return } - blocked, muted, blockedBy, err := m.db.GetViewerRelationship(viewerDID, subjectDID) + blocked, muted, blockedBy, err := m.db.GetViewerRelationship(r.Context(), viewerDID, subjectDID) if err != nil { WriteInternalError(w, "Failed to get relationship") return @@ -254,7 +255,7 @@ func (m *ModerationHandler) CreateReport(w http.ResponseWriter, r *http.Request) return } - id, err := m.db.CreateReport(session.DID, req.SubjectDID, req.SubjectURI, req.ReasonType, req.ReasonText) + id, err := m.db.CreateReport(r.Context(), session.DID, req.SubjectDID, req.SubjectURI, req.ReasonType, req.ReasonText) if err != nil { logger.Error("Failed to create report: %v", err) WriteInternalError(w, "Failed to submit report") @@ -280,7 +281,7 @@ func (m *ModerationHandler) AdminGetReports(w http.ResponseWriter, r *http.Reque limit := parseIntParam(r, "limit", 50) offset := parseIntParam(r, "offset", 0) - reports, err := m.db.GetReports(status, limit, offset) + reports, err := m.db.GetReports(r.Context(), status, limit, offset) if err != nil { WriteInternalError(w, "Failed to fetch reports") return @@ -329,8 +330,8 @@ func (m *ModerationHandler) AdminGetReports(w http.ResponseWriter, r *http.Reque items[i].ResolvedBy = rpt.ResolvedBy } - pendingCount, _ := m.db.GetReportCount("pending") - totalCount, _ := m.db.GetReportCount("") + pendingCount, _ := m.db.GetReportCount(r.Context(), "pending") + totalCount, _ := m.db.GetReportCount(r.Context(), "") WriteSuccess(w, map[string]interface{}{ "items": items, @@ -373,13 +374,13 @@ func (m *ModerationHandler) AdminTakeAction(w http.ResponseWriter, r *http.Reque return } - report, err := m.db.GetReport(req.ReportID) + report, err := m.db.GetReport(r.Context(), req.ReportID) if err != nil { WriteNotFound(w, "Report not found") return } - if err := m.db.CreateModerationAction(req.ReportID, session.DID, req.Action, req.Comment); err != nil { + if err := m.db.CreateModerationAction(r.Context(), req.ReportID, session.DID, req.Action, req.Comment); err != nil { logger.Error("Failed to create moderation action: %v", err) WriteInternalError(w, "Failed to take action") return @@ -394,13 +395,13 @@ func (m *ModerationHandler) AdminTakeAction(w http.ResponseWriter, r *http.Reque case "takedown": resolveStatus = "resolved" if report.SubjectURI != nil && *report.SubjectURI != "" { - m.deleteContent(*report.SubjectURI) + m.deleteContent(r.Context(), *report.SubjectURI) } case "acknowledge": resolveStatus = "acknowledged" } - if err := m.db.ResolveReport(req.ReportID, session.DID, resolveStatus); err != nil { + if err := m.db.ResolveReport(r.Context(), req.ReportID, session.DID, resolveStatus); err != nil { logger.Error("Failed to resolve report: %v", err) } @@ -426,13 +427,13 @@ func (m *ModerationHandler) AdminGetReport(w http.ResponseWriter, r *http.Reques return } - report, err := m.db.GetReport(id) + report, err := m.db.GetReport(r.Context(), id) if err != nil { WriteNotFound(w, "Report not found") return } - actions, _ := m.db.GetReportActions(id) + actions, _ := m.db.GetReportActions(r.Context(), id) profiles := fetchProfilesForDIDs(m.db, []string{report.ReporterDID, report.SubjectDID}) @@ -455,13 +456,13 @@ func (m *ModerationHandler) AdminCheckAccess(w http.ResponseWriter, r *http.Requ WriteSuccess(w, map[string]bool{"isAdmin": config.Get().IsAdmin(session.DID)}) } -func (m *ModerationHandler) deleteContent(uri string) { - m.db.MarkTakenDown(uri) - m.db.Exec("DELETE FROM notes WHERE uri = $1", uri) - m.db.Exec("DELETE FROM annotations WHERE uri = $1", uri) - m.db.Exec("DELETE FROM highlights WHERE uri = $1", uri) - m.db.Exec("DELETE FROM bookmarks WHERE uri = $1", uri) - m.db.Exec("DELETE FROM replies WHERE uri = $1", uri) +func (m *ModerationHandler) deleteContent(ctx context.Context, uri string) { + m.db.MarkTakenDown(ctx, uri) + m.db.Pool().Exec(ctx, "DELETE FROM notes WHERE uri = $1", uri) + m.db.Pool().Exec(ctx, "DELETE FROM annotations WHERE uri = $1", uri) + m.db.Pool().Exec(ctx, "DELETE FROM highlights WHERE uri = $1", uri) + m.db.Pool().Exec(ctx, "DELETE FROM bookmarks WHERE uri = $1", uri) + m.db.Pool().Exec(ctx, "DELETE FROM replies WHERE uri = $1", uri) } func (m *ModerationHandler) AdminCreateLabel(w http.ResponseWriter, r *http.Request) { @@ -520,7 +521,7 @@ func (m *ModerationHandler) AdminCreateLabel(w http.ResponseWriter, r *http.Requ return } - if err := m.db.CreateContentLabel(labelerDID, targetURI, req.Val, session.DID); err != nil { + if err := m.db.CreateContentLabel(r.Context(), labelerDID, targetURI, req.Val, session.DID); err != nil { logger.Error("Failed to create content label: %v", err) WriteInternalError(w, "Failed to create label") return @@ -548,7 +549,7 @@ func (m *ModerationHandler) AdminDeleteLabel(w http.ResponseWriter, r *http.Requ return } - if err := m.db.DeleteContentLabel(id); err != nil { + if err := m.db.DeleteContentLabel(r.Context(), id); err != nil { WriteInternalError(w, "Failed to delete label") return } @@ -571,7 +572,7 @@ func (m *ModerationHandler) AdminGetLabels(w http.ResponseWriter, r *http.Reques limit := parseIntParam(r, "limit", 50) offset := parseIntParam(r, "offset", 0) - labels, err := m.db.GetAllContentLabels(limit, offset) + labels, err := m.db.GetAllContentLabels(r.Context(), limit, offset) if err != nil { WriteInternalError(w, "Failed to fetch labels") return @@ -624,7 +625,7 @@ func (m *ModerationHandler) getViewerDID(r *http.Request) string { if err != nil { return "" } - sess, err := m.db.GetOAuthSessionByID(cookie.Value) + sess, err := m.db.GetOAuthSessionByID(r.Context(), cookie.Value) if err != nil { return "" } @@ -677,13 +678,13 @@ func (m *ModerationHandler) AdminBanAccount(w http.ResponseWriter, r *http.Reque return } - if err := m.db.BanAccount(req.DID, session.DID, req.Reason); err != nil { + if err := m.db.BanAccount(r.Context(), req.DID, session.DID, req.Reason); err != nil { logger.Error("Failed to ban account: %v", err) WriteInternalError(w, "Failed to ban account") return } - m.db.DeleteOAuthSessionsByDID(req.DID) + m.db.DeleteOAuthSessionsByDID(r.Context(), req.DID) WriteSuccess(w, map[string]string{"status": "ok"}) } @@ -705,7 +706,7 @@ func (m *ModerationHandler) AdminUnbanAccount(w http.ResponseWriter, r *http.Req return } - if err := m.db.UnbanAccount(did); err != nil { + if err := m.db.UnbanAccount(r.Context(), did); err != nil { logger.Error("Failed to unban account: %v", err) WriteInternalError(w, "Failed to unban account") return @@ -725,7 +726,7 @@ func (m *ModerationHandler) AdminGetBannedAccounts(w http.ResponseWriter, r *htt return } - accounts, err := m.db.GetBannedAccounts() + accounts, err := m.db.GetBannedAccounts(r.Context()) if err != nil { WriteInternalError(w, "Failed to get banned accounts") return diff --git a/backend/internal/api/notes.go b/backend/internal/api/notes.go index f43c924..d100aea 100644 --- a/backend/internal/api/notes.go +++ b/backend/internal/api/notes.go @@ -58,89 +58,107 @@ type NoteIndexDB interface { type dbAdapter struct{ d *db.DB } -func (a *dbAdapter) CreateNote(n *domain.Note) error { return a.d.CreateNote(n) } -func (a *dbAdapter) GetNoteByURI(uri string) (*domain.Note, error) { return a.d.GetNoteByURI(uri) } -func (a *dbAdapter) DeleteNote(uri string) error { return a.d.DeleteNote(uri) } +func (a *dbAdapter) CreateNote(n *domain.Note) error { return a.d.CreateNote(context.Background(), n) } +func (a *dbAdapter) GetNoteByURI(uri string) (*domain.Note, error) { + return a.d.GetNoteByURI(context.Background(), uri) +} +func (a *dbAdapter) DeleteNote(uri string) error { return a.d.DeleteNote(context.Background(), uri) } func (a *dbAdapter) UpdateNoteAnnotation(uri, body, tags, cid string) error { - return a.d.UpdateNoteAnnotation(uri, body, tags, cid) + return a.d.UpdateNoteAnnotation(context.Background(), uri, body, tags, cid) } func (a *dbAdapter) UpdateNoteHighlight(uri, color, tags, cid string) error { - return a.d.UpdateNoteHighlight(uri, color, tags, cid) + return a.d.UpdateNoteHighlight(context.Background(), uri, color, tags, cid) } func (a *dbAdapter) UpdateNoteBookmark(uri, title, desc, tags, cid string) error { - return a.d.UpdateNoteBookmark(uri, title, desc, tags, cid) + return a.d.UpdateNoteBookmark(context.Background(), uri, title, desc, tags, cid) +} +func (a *dbAdapter) CreateAnnotation(ann *domain.Annotation) error { + return a.d.CreateAnnotation(context.Background(), ann) } -func (a *dbAdapter) CreateAnnotation(ann *domain.Annotation) error { return a.d.CreateAnnotation(ann) } func (a *dbAdapter) GetAnnotationByURI(uri string) (*domain.Annotation, error) { - return a.d.GetAnnotationByURI(uri) + return a.d.GetAnnotationByURI(context.Background(), uri) } func (a *dbAdapter) GetAnnotationsByAuthor(did string, limit, offset int) ([]domain.Annotation, error) { - return a.d.GetAnnotationsByAuthor(did, limit, offset) + return a.d.GetAnnotationsByAuthor(context.Background(), did, limit, offset) } func (a *dbAdapter) UpdateAnnotation(uri, body, tags, cid string) error { - return a.d.UpdateAnnotation(uri, body, tags, cid) + return a.d.UpdateAnnotation(context.Background(), uri, body, tags, cid) +} +func (a *dbAdapter) DeleteAnnotation(uri string) error { + return a.d.DeleteAnnotation(context.Background(), uri) +} +func (a *dbAdapter) CreateHighlight(h *domain.Highlight) error { + return a.d.CreateHighlight(context.Background(), h) } -func (a *dbAdapter) DeleteAnnotation(uri string) error { return a.d.DeleteAnnotation(uri) } -func (a *dbAdapter) CreateHighlight(h *domain.Highlight) error { return a.d.CreateHighlight(h) } func (a *dbAdapter) GetHighlightByURI(uri string) (*domain.Highlight, error) { - return a.d.GetHighlightByURI(uri) + return a.d.GetHighlightByURI(context.Background(), uri) } func (a *dbAdapter) GetHighlightsByAuthor(did string, limit, offset int) ([]domain.Highlight, error) { - return a.d.GetHighlightsByAuthor(did, limit, offset) + return a.d.GetHighlightsByAuthor(context.Background(), did, limit, offset) } func (a *dbAdapter) UpdateHighlight(uri, color, tags, cid string) error { - return a.d.UpdateHighlight(uri, color, tags, cid) + return a.d.UpdateHighlight(context.Background(), uri, color, tags, cid) +} +func (a *dbAdapter) DeleteHighlight(uri string) error { + return a.d.DeleteHighlight(context.Background(), uri) +} +func (a *dbAdapter) CreateBookmark(b *domain.Bookmark) error { + return a.d.CreateBookmark(context.Background(), b) } -func (a *dbAdapter) DeleteHighlight(uri string) error { return a.d.DeleteHighlight(uri) } -func (a *dbAdapter) CreateBookmark(b *domain.Bookmark) error { return a.d.CreateBookmark(b) } func (a *dbAdapter) GetBookmarkByURI(uri string) (*domain.Bookmark, error) { - return a.d.GetBookmarkByURI(uri) + return a.d.GetBookmarkByURI(context.Background(), uri) } func (a *dbAdapter) GetBookmarksByTargetHash(hash string, limit, offset int) ([]domain.Bookmark, error) { - return a.d.GetBookmarksByTargetHash(hash, limit, offset) + return a.d.GetBookmarksByTargetHash(context.Background(), hash, limit, offset) } func (a *dbAdapter) UpdateBookmark(uri, title, desc, tags, cid string) error { - return a.d.UpdateBookmark(uri, title, desc, tags, cid) + return a.d.UpdateBookmark(context.Background(), uri, title, desc, tags, cid) +} +func (a *dbAdapter) DeleteBookmark(uri string) error { + return a.d.DeleteBookmark(context.Background(), uri) } -func (a *dbAdapter) DeleteBookmark(uri string) error { return a.d.DeleteBookmark(uri) } -func (a *dbAdapter) CreateLike(l *domain.Like) error { return a.d.CreateLike(l) } +func (a *dbAdapter) CreateLike(l *domain.Like) error { return a.d.CreateLike(context.Background(), l) } func (a *dbAdapter) GetLikeByUserAndSubject(did, sub string) (*domain.Like, error) { - return a.d.GetLikeByUserAndSubject(did, sub) + return a.d.GetLikeByUserAndSubject(context.Background(), did, sub) +} +func (a *dbAdapter) DeleteLike(uri string) error { return a.d.DeleteLike(context.Background(), uri) } +func (a *dbAdapter) CreateReply(rep *domain.Reply) error { + return a.d.CreateReply(context.Background(), rep) } -func (a *dbAdapter) DeleteLike(uri string) error { return a.d.DeleteLike(uri) } -func (a *dbAdapter) CreateReply(rep *domain.Reply) error { return a.d.CreateReply(rep) } func (a *dbAdapter) GetReplyByURI(uri string) (*domain.Reply, error) { - return a.d.GetReplyByURI(uri) + return a.d.GetReplyByURI(context.Background(), uri) } -func (a *dbAdapter) DeleteReply(uri string) error { return a.d.DeleteReply(uri) } +func (a *dbAdapter) DeleteReply(uri string) error { return a.d.DeleteReply(context.Background(), uri) } func (a *dbAdapter) CreateNotification(n *domain.Notification) error { - return a.d.CreateNotification(n) + return a.d.CreateNotification(context.Background(), n) +} +func (a *dbAdapter) GetAuthorByURI(uri string) (string, error) { + return a.d.GetAuthorByURI(context.Background(), uri) } -func (a *dbAdapter) GetAuthorByURI(uri string) (string, error) { return a.d.GetAuthorByURI(uri) } func (a *dbAdapter) GetPreferences(did string) (*domain.Preferences, error) { - return a.d.GetPreferences(did) + return a.d.GetPreferences(context.Background(), did) } func (a *dbAdapter) SyncSelfLabels(author, uri string, labels []string) error { - return a.d.SyncSelfLabels(author, uri, labels) + return a.d.SyncSelfLabels(context.Background(), author, uri, labels) } func (a *dbAdapter) CreateContentLabel(src, uri, val, by string) error { - return a.d.CreateContentLabel(src, uri, val, by) + return a.d.CreateContentLabel(context.Background(), src, uri, val, by) } func (a *dbAdapter) SaveEditHistory(uri, rt, prev string, cid *string) error { - return a.d.SaveEditHistory(uri, rt, prev, cid) + return a.d.SaveEditHistory(context.Background(), uri, rt, prev, cid) } func (a *dbAdapter) HashURL(rawURL string) string { return db.HashURL(rawURL) } func (a *dbAdapter) CommunityBookmarkExists(did, hash, tags string) (bool, error) { - return a.d.CommunityBookmarkExists(did, hash, tags) + return a.d.CommunityBookmarkExists(context.Background(), did, hash, tags) } func (a *dbAdapter) SaveCommunityBookmarkRef(noteURI, communityURI string) error { - return a.d.SaveCommunityBookmarkRef(noteURI, communityURI) + return a.d.SaveCommunityBookmarkRef(context.Background(), noteURI, communityURI) } func (a *dbAdapter) GetCommunityBookmarkURI(noteURI string) (string, error) { - return a.d.GetCommunityBookmarkURI(noteURI) + return a.d.GetCommunityBookmarkURI(context.Background(), noteURI) } func (a *dbAdapter) DeleteCommunityBookmarkRef(noteURI string) error { - return a.d.DeleteCommunityBookmarkRef(noteURI) + return a.d.DeleteCommunityBookmarkRef(context.Background(), noteURI) } type NoteWriteService struct { diff --git a/backend/internal/api/pds.go b/backend/internal/api/pds.go index 0601bb5..c39a413 100644 --- a/backend/internal/api/pds.go +++ b/backend/internal/api/pds.go @@ -55,13 +55,13 @@ func (h *Handler) FetchLatestUserRecords(r *http.Request, did string, collection if err == nil && parsed != nil { switch v := parsed.(type) { case *db.Annotation: - h.db.CreateAnnotation(v) + h.db.CreateAnnotation(r.Context(), v) case *db.Highlight: - h.db.CreateHighlight(v) + h.db.CreateHighlight(r.Context(), v) case *db.Bookmark: - h.db.CreateBookmark(v) + h.db.CreateBookmark(r.Context(), v) case *db.APIKey: - h.db.CreateAPIKey(v) + h.db.CreateAPIKey(r.Context(), v) case *db.Preferences: } results = append(results, parsed) diff --git a/backend/internal/api/preferences.go b/backend/internal/api/preferences.go index 0f7c02f..48f94be 100644 --- a/backend/internal/api/preferences.go +++ b/backend/internal/api/preferences.go @@ -37,7 +37,7 @@ func (h *Handler) GetPreferences(w http.ResponseWriter, r *http.Request) { return } - prefs, err := h.db.GetPreferences(session.DID) + prefs, err := h.db.GetPreferences(r.Context(), session.DID) if err != nil { WriteInternalError(w, "Failed to fetch preferences") return @@ -152,7 +152,7 @@ func (h *Handler) UpdatePreferences(w http.ResponseWriter, r *http.Request) { uri := fmt.Sprintf("at://%s/%s/self", session.DID, xrpc.CollectionPreferences) - err = h.db.UpsertPreferences(&db.Preferences{ + err = h.db.UpsertPreferences(r.Context(), &db.Preferences{ URI: uri, AuthorDID: session.DID, ExternalLinkSkippedHostnames: &hostnamesStr, diff --git a/backend/internal/api/profile.go b/backend/internal/api/profile.go index 50115f3..1c8131a 100644 --- a/backend/internal/api/profile.go +++ b/backend/internal/api/profile.go @@ -83,7 +83,7 @@ func (h *Handler) UpdateProfile(w http.ResponseWriter, r *http.Request) { CreatedAt: time.Now(), IndexedAt: time.Now(), } - h.db.UpsertProfile(profile) + h.db.UpsertProfile(r.Context(), profile) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -110,7 +110,7 @@ func (h *Handler) GetProfile(w http.ResponseWriter, r *http.Request) { if !strings.HasPrefix(did, "did:") { var resolvedDID string - err := h.db.QueryRow("SELECT did FROM sessions WHERE handle = $1 LIMIT 1", did).Scan(&resolvedDID) + err := h.db.Pool().QueryRow(r.Context(), "SELECT did FROM oauth_sessions WHERE handle = $1 LIMIT 1", did).Scan(&resolvedDID) if err == nil { did = resolvedDID } else { @@ -121,7 +121,7 @@ func (h *Handler) GetProfile(w http.ResponseWriter, r *http.Request) { } } - profile, err := h.db.GetProfile(did) + profile, err := h.db.GetProfile(r.Context(), did) if err != nil { WriteInternalError(w, "Failed to fetch profile") return @@ -165,7 +165,7 @@ func (h *Handler) GetProfile(w http.ResponseWriter, r *http.Request) { } var handle string - if err := h.db.QueryRow("SELECT handle FROM sessions WHERE did = $1 LIMIT 1", profile.AuthorDID).Scan(&handle); err == nil { + if err := h.db.Pool().QueryRow(r.Context(), "SELECT handle FROM oauth_sessions WHERE did = $1 LIMIT 1", profile.AuthorDID).Scan(&handle); err == nil { resp.Handle = handle } @@ -190,7 +190,7 @@ func (h *Handler) GetProfile(w http.ResponseWriter, r *http.Request) { viewerDID := h.getViewerDID(r) if viewerDID != "" && viewerDID != profile.AuthorDID { - blocking, muting, blockedBy, err := h.db.GetViewerRelationship(viewerDID, profile.AuthorDID) + blocking, muting, blockedBy, err := h.db.GetViewerRelationship(r.Context(), viewerDID, profile.AuthorDID) if err == nil { resp.Viewer = &struct { Blocking bool `json:"blocking"` @@ -211,7 +211,7 @@ func (h *Handler) GetProfile(w http.ResponseWriter, r *http.Request) { subscribedLabelers = []string{serviceDID} } } - if didLabels, err := h.db.GetContentLabelsForDIDs([]string{profile.AuthorDID}, subscribedLabelers); err == nil { + if didLabels, err := h.db.GetContentLabelsForDIDs(r.Context(), []string{profile.AuthorDID}, subscribedLabelers); err == nil { if labels, ok := didLabels[profile.AuthorDID]; ok { for _, l := range labels { resp.Labels = append(resp.Labels, struct { diff --git a/backend/internal/api/semble_fetch.go b/backend/internal/api/semble_fetch.go index 6dfb528..42a758d 100644 --- a/backend/internal/api/semble_fetch.go +++ b/backend/internal/api/semble_fetch.go @@ -68,8 +68,8 @@ func ensureSembleCardsIndexed(ctx context.Context, database *db.DB, uris []strin return } - existingAnnos, _ := database.GetAnnotationsByURIs(deduped) - existingBooks, _ := database.GetBookmarksByURIs(deduped) + existingAnnos, _ := database.GetAnnotationsByURIs(ctx, deduped) + existingBooks, _ := database.GetBookmarksByURIs(ctx, deduped) foundSet := make(map[string]bool, len(existingAnnos)+len(existingBooks)) for _, a := range existingAnnos { @@ -235,7 +235,7 @@ func indexSembleCard(database *db.DB, uri, did string, card *xrpc.SembleCard) er CreatedAt: createdAt, IndexedAt: time.Now(), } - return database.CreateAnnotation(annotation) + return database.CreateAnnotation(context.Background(), annotation) case "URL": urlContent, ok := content.(*xrpc.SembleURLContent) @@ -264,7 +264,7 @@ func indexSembleCard(database *db.DB, uri, did string, card *xrpc.SembleCard) er CreatedAt: createdAt, IndexedAt: time.Now(), } - return database.CreateBookmark(bookmark) + return database.CreateBookmark(context.Background(), bookmark) } return nil diff --git a/backend/internal/api/tags.go b/backend/internal/api/tags.go index c13c907..41691db 100644 --- a/backend/internal/api/tags.go +++ b/backend/internal/api/tags.go @@ -15,7 +15,7 @@ func (h *Handler) HandleGetTrendingTags(w http.ResponseWriter, r *http.Request) } } - tags, err := h.db.GetTrendingTags(limit) + tags, err := h.db.GetTrendingTags(r.Context(), limit) if err != nil { WriteInternalError(w, "Failed to fetch trending tags") return @@ -39,7 +39,7 @@ func (h *Handler) HandleGetUserTags(w http.ResponseWriter, r *http.Request) { } } - tags, err := h.db.GetUserTags(did, limit) + tags, err := h.db.GetUserTags(r.Context(), did, limit) if err != nil { WriteInternalError(w, "Failed to fetch user tags") return diff --git a/backend/internal/api/token_refresh.go b/backend/internal/api/token_refresh.go index 2659f3f..58ace8a 100644 --- a/backend/internal/api/token_refresh.go +++ b/backend/internal/api/token_refresh.go @@ -33,19 +33,8 @@ func NewTokenRefresher(database *db.DB, signingKey *ecdsa.PrivateKey) *TokenRefr } } -func (tr *TokenRefresher) oauthClient(r *http.Request) *oauth.Client { +func (tr *TokenRefresher) oauthClient() *oauth.Client { base := tr.baseURL - if base == "" { - scheme := "http" - if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { - scheme = "https" - } - host := r.Header.Get("X-Forwarded-Host") - if host == "" { - host = r.Host - } - base = strings.TrimRight(fmt.Sprintf("%s://%s", scheme, host), "/") - } return oauth.NewClient(base+"/oauth-client-metadata.json", base+"/auth/callback", tr.signingKey) } @@ -86,7 +75,7 @@ func (tr *TokenRefresher) GetSessionWithAutoRefresh(r *http.Request) (*SessionDa return nil, fmt.Errorf("not authenticated") } - sess, err := tr.db.GetOAuthSessionByID(sessionID) + sess, err := tr.db.GetOAuthSessionByID(r.Context(), sessionID) if err != nil { return nil, fmt.Errorf("%w: session expired", ErrSessionInvalid) } @@ -95,7 +84,7 @@ func (tr *TokenRefresher) GetSessionWithAutoRefresh(r *http.Request) (*SessionDa refreshed, rerr := tr.refreshSession(r, sessionID) if rerr != nil { logger.Error("Proactive token refresh failed for %s: %v", sess.DID, rerr) - tr.db.DeleteOAuthSession(sessionID) + tr.db.DeleteOAuthSession(r.Context(), sessionID) return nil, fmt.Errorf("%w: %v", ErrSessionInvalid, rerr) } return refreshed, nil @@ -108,7 +97,7 @@ func (tr *TokenRefresher) refreshSession(r *http.Request, sessionID string) (*Se var result *SessionData lockErr := tr.db.WithAdvisoryLock(context.Background(), "oauth_refresh:"+sessionID, func(ctx context.Context) error { - fresh, err := tr.db.GetOAuthSessionByID(sessionID) + fresh, err := tr.db.GetOAuthSessionByID(ctx, sessionID) if err != nil { return err } @@ -122,7 +111,7 @@ func (tr *TokenRefresher) refreshSession(r *http.Request, sessionID string) (*Se return err } meta := &oauth.AuthServerMetadata{Issuer: fresh.Issuer, TokenEndpoint: fresh.TokenEndpoint} - tok, err := tr.oauthClient(r).RefreshToken(ctx, meta, fresh.RefreshToken, dpopKey) + tok, err := tr.oauthClient().RefreshToken(ctx, meta, fresh.RefreshToken, dpopKey) if err != nil { return err } @@ -135,7 +124,7 @@ func (tr *TokenRefresher) refreshSession(r *http.Request, sessionID string) (*Se if tok.ExpiresIn > 0 { atExpiry = time.Now().Add(time.Duration(tok.ExpiresIn) * time.Second) } - if err := tr.db.UpdateOAuthSessionTokens(sessionID, tok.AccessToken, newRefresh, atExpiry); err != nil { + if err := tr.db.UpdateOAuthSessionTokens(ctx, sessionID, tok.AccessToken, newRefresh, atExpiry); err != nil { logger.Error("persist refreshed tokens for %s: %v", fresh.DID, err) } @@ -189,7 +178,7 @@ func (tr *TokenRefresher) ExecuteWithAutoRefresh( newSession, refreshErr := tr.refreshSession(r, session.ID) if refreshErr != nil { logger.Error("Token refresh failed for user %s, invalidating session: %v", session.Handle, refreshErr) - tr.db.DeleteOAuthSession(session.ID) + tr.db.DeleteOAuthSession(r.Context(), session.ID) return fmt.Errorf("%w: %v", ErrSessionInvalid, refreshErr) } diff --git a/backend/internal/db/crypto.go b/backend/internal/db/crypto.go index 31d0959..e62c047 100644 --- a/backend/internal/db/crypto.go +++ b/backend/internal/db/crypto.go @@ -1,6 +1,7 @@ package db import ( + "context" "crypto/aes" "crypto/cipher" "crypto/rand" @@ -77,12 +78,12 @@ func (db *DB) SetEncryptionKey(key []byte) error { return nil } -func (db *DB) GetOrCreateEncryptedKV(key, value string) (string, error) { +func (db *DB) GetOrCreateEncryptedKV(ctx context.Context, key, value string) (string, error) { enc, err := db.crypter.encrypt(value) if err != nil { return "", err } - stored, err := db.GetOrCreateKV(key, enc) + stored, err := db.GetOrCreateKV(ctx, key, enc) if err != nil { return "", err } diff --git a/backend/internal/db/db.go b/backend/internal/db/db.go index 424b23e..7cea230 100644 --- a/backend/internal/db/db.go +++ b/backend/internal/db/db.go @@ -8,15 +8,21 @@ import ( "strings" "time" - _ "github.com/lib/pq" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5/stdlib" + "margin.at/internal/db/sqlcdb" "margin.at/internal/domain" ) type DB struct { - *sql.DB - crypter fieldCrypter + pool *pgxpool.Pool + migrationDB *sql.DB + q *sqlcdb.Queries + crypter fieldCrypter } +func (db *DB) Pool() *pgxpool.Pool { return db.pool } + type ( Note = domain.Note Annotation = domain.Annotation @@ -43,55 +49,68 @@ func New(dsn string) (*DB, error) { return nil, fmt.Errorf("only PostgreSQL is supported; DSN must start with postgres:// or postgresql://") } - sqlDB, err := sql.Open("postgres", dsn) + cfg, err := pgxpool.ParseConfig(dsn) if err != nil { - return nil, fmt.Errorf("open database: %w", err) + return nil, fmt.Errorf("parse config: %w", err) } + cfg.MaxConns = 25 + cfg.MinConns = 2 + cfg.MaxConnLifetime = time.Hour + cfg.MaxConnIdleTime = 15 * time.Minute + cfg.HealthCheckPeriod = 30 * time.Second - sqlDB.SetMaxOpenConns(25) - sqlDB.SetMaxIdleConns(10) - sqlDB.SetConnMaxLifetime(5 * time.Minute) - sqlDB.SetConnMaxIdleTime(2 * time.Minute) - - if err := sqlDB.Ping(); err != nil { + pool, err := pgxpool.NewWithConfig(context.Background(), cfg) + if err != nil { + return nil, fmt.Errorf("open database: %w", err) + } + if err := pool.Ping(context.Background()); err != nil { + pool.Close() return nil, fmt.Errorf("ping database: %w", err) } - return &DB{DB: sqlDB}, nil + return &DB{pool: pool, migrationDB: stdlib.OpenDBFromPool(pool), q: sqlcdb.New(pool)}, nil } -func (db *DB) Close() error { return db.DB.Close() } +func (db *DB) Close() error { + if db.migrationDB != nil { + db.migrationDB.Close() + } + if db.pool != nil { + db.pool.Close() + } + return nil +} func (db *DB) AdvisoryLock(ctx context.Context, key int64, fn func() error) (bool, error) { - conn, err := db.Conn(ctx) + conn, err := db.pool.Acquire(ctx) if err != nil { return false, fmt.Errorf("advisory lock: acquire conn: %w", err) } - defer conn.Close() + defer conn.Release() - if _, err := conn.ExecContext(ctx, "SELECT pg_advisory_lock($1)", key); err != nil { + if _, err := conn.Exec(ctx, "SELECT pg_advisory_lock($1)", key); err != nil { return false, fmt.Errorf("advisory lock: acquire: %w", err) } - defer conn.ExecContext(ctx, "SELECT pg_advisory_unlock($1)", key) //nolint:errcheck + defer conn.Exec(context.Background(), "SELECT pg_advisory_unlock($1)", key) //nolint:errcheck return true, fn() } func (db *DB) TryAdvisoryLock(ctx context.Context, key int64, fn func() error) (bool, error) { - conn, err := db.Conn(ctx) + conn, err := db.pool.Acquire(ctx) if err != nil { return false, fmt.Errorf("try advisory lock: acquire conn: %w", err) } - defer conn.Close() + defer conn.Release() var acquired bool - if err := conn.QueryRowContext(ctx, "SELECT pg_try_advisory_lock($1)", key).Scan(&acquired); err != nil { + if err := conn.QueryRow(ctx, "SELECT pg_try_advisory_lock($1)", key).Scan(&acquired); err != nil { return false, fmt.Errorf("try advisory lock: %w", err) } if !acquired { return false, nil } - defer conn.ExecContext(ctx, "SELECT pg_advisory_unlock($1)", key) //nolint:errcheck + defer conn.Exec(context.Background(), "SELECT pg_advisory_unlock($1)", key) //nolint:errcheck return true, fn() } diff --git a/backend/internal/db/filter.go b/backend/internal/db/filter.go index 4ffa78a..5bdb0f2 100644 --- a/backend/internal/db/filter.go +++ b/backend/internal/db/filter.go @@ -1,11 +1,12 @@ package db import ( - "database/sql" + "context" "fmt" "strings" "time" + "github.com/jackc/pgx/v5" "margin.at/internal/domain" ) @@ -20,7 +21,7 @@ const ( FeedTypeSemble = domain.FeedTypeSemble ) -func (db *DB) ListNotes(f NoteFilter) ([]Note, error) { +func (db *DB) ListNotes(ctx context.Context, f NoteFilter) ([]Note, error) { var where []string var args []interface{} n := 1 @@ -123,7 +124,7 @@ func (db *DB) ListNotes(f NoteFilter) ([]Note, error) { args = append(args, limit, f.Offset) - rows, err := db.Query(query, args...) + rows, err := db.pool.Query(ctx, query, args...) if err != nil { return nil, err } @@ -131,7 +132,7 @@ func (db *DB) ListNotes(f NoteFilter) ([]Note, error) { return scanNotes(rows) } -func (db *DB) GetNoteByURIFromUnified(uri string) (*Note, error) { +func (db *DB) GetNoteByURIFromUnified(ctx context.Context, uri string) (*Note, error) { query := ` SELECT uri, author_did, motivation, color, description, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid @@ -139,13 +140,13 @@ func (db *DB) GetNoteByURIFromUnified(uri string) (*Note, error) { WHERE uri = $1 ` var note Note - err := db.QueryRow(query, uri).Scan( + err := db.pool.QueryRow(ctx, query, uri).Scan( ¬e.URI, ¬e.AuthorDID, ¬e.Motivation, ¬e.Color, ¬e.Description, ¬e.BodyValue, ¬e.BodyFormat, ¬e.BodyURI, ¬e.TargetSource, ¬e.TargetHash, ¬e.TargetTitle, ¬e.SelectorJSON, ¬e.TagsJSON, ¬e.CreatedAt, ¬e.IndexedAt, ¬e.CID, ) - if err == sql.ErrNoRows { + if err == pgx.ErrNoRows { return nil, nil } if err != nil { @@ -174,8 +175,8 @@ func scanNotes(rows interface { return notes, nil } -func (db *DB) MigrateUnifiedNotes() { - db.Exec(` +func (db *DB) MigrateUnifiedNotes(ctx context.Context) { + db.pool.Exec(ctx, ` CREATE OR REPLACE VIEW unified_notes AS -- New notes table (primary path) SELECT diff --git a/backend/internal/db/migrate.go b/backend/internal/db/migrate.go index 9b9c90c..2111ef9 100644 --- a/backend/internal/db/migrate.go +++ b/backend/internal/db/migrate.go @@ -2,10 +2,12 @@ package db import ( "context" - "database/sql" - "embed" + "errors" "time" + "embed" + + "github.com/jackc/pgx/v5" "github.com/pressly/goose/v3" ) @@ -27,7 +29,7 @@ func (db *DB) Migrate() error { if err := goose.SetDialect("postgres"); err != nil { return err } - return goose.Up(db.DB, "migrations") + return goose.Up(db.migrationDB, "migrations") }) if err != nil { return err @@ -41,11 +43,11 @@ func (db *DB) Migrate() error { func (db *DB) GetCursor(id string) (int64, error) { var cursor int64 - err := db.QueryRow("SELECT last_cursor FROM cursors WHERE id = $1", id).Scan(&cursor) - switch err { - case nil: + err := db.pool.QueryRow(context.Background(), "SELECT last_cursor FROM cursors WHERE id = $1", id).Scan(&cursor) + switch { + case err == nil: return cursor, nil - case sql.ErrNoRows: + case errors.Is(err, pgx.ErrNoRows): return 0, nil default: return 0, err @@ -53,7 +55,7 @@ func (db *DB) GetCursor(id string) (int64, error) { } func (db *DB) SetCursor(id string, cursor int64) error { - _, err := db.Exec(` + _, err := db.pool.Exec(context.Background(), ` INSERT INTO cursors (id, last_cursor, updated_at) VALUES ($1, $2, $3) ON CONFLICT(id) DO UPDATE SET diff --git a/backend/internal/db/queries.go b/backend/internal/db/queries.go index 22732f1..021e1e8 100644 --- a/backend/internal/db/queries.go +++ b/backend/internal/db/queries.go @@ -1,6 +1,7 @@ package db import ( + "context" "crypto/sha256" "encoding/hex" "encoding/json" @@ -63,23 +64,16 @@ func ToJSON(v interface{}) string { return string(b) } -func (db *DB) GetAuthorByURI(uri string) (string, error) { - var authorDID string - err := db.QueryRow(`SELECT author_did FROM annotations WHERE uri = $1`, uri).Scan(&authorDID) - if err == nil { +func (db *DB) GetAuthorByURI(ctx context.Context, uri string) (string, error) { + if authorDID, err := db.q.GetAnnotationAuthorByURI(ctx, uri); err == nil { return authorDID, nil } - - err = db.QueryRow(`SELECT author_did FROM highlights WHERE uri = $1`, uri).Scan(&authorDID) - if err == nil { + if authorDID, err := db.q.GetHighlightAuthorByURI(ctx, uri); err == nil { return authorDID, nil } - - err = db.QueryRow(`SELECT author_did FROM bookmarks WHERE uri = $1`, uri).Scan(&authorDID) - if err == nil { + if authorDID, err := db.q.GetBookmarkAuthorByURI(ctx, uri); err == nil { return authorDID, nil } - return "", fmt.Errorf("uri not found or no author") } diff --git a/backend/internal/db/queries_annotations.go b/backend/internal/db/queries_annotations.go index 9c0da84..9cbaa25 100644 --- a/backend/internal/db/queries_annotations.go +++ b/backend/internal/db/queries_annotations.go @@ -1,383 +1,315 @@ package db import ( + "context" "time" + + "margin.at/internal/db/sqlcdb" ) -func (db *DB) CreateAnnotation(a *Annotation) error { - if taken, _ := db.IsTakenDown(a.URI); taken { +func mapAnnotation(r sqlcdb.AllAnnotation) Annotation { + a := Annotation{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + BodyValue: r.BodyValue, + BodyFormat: r.BodyFormat, + BodyURI: r.BodyUri, + TargetTitle: r.TargetTitle, + SelectorJSON: r.SelectorJson, + TagsJSON: r.TagsJson, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + CID: r.Cid, + } + if r.Motivation != nil { + a.Motivation = *r.Motivation + } + if r.TargetSource != nil { + a.TargetSource = *r.TargetSource + } + if r.TargetHash != nil { + a.TargetHash = *r.TargetHash + } + return a +} + +func mapAnnotations(rows []sqlcdb.AllAnnotation) []Annotation { + var annotations []Annotation + for _, r := range rows { + annotations = append(annotations, mapAnnotation(r)) + } + return annotations +} + +func (db *DB) CreateAnnotation(ctx context.Context, a *Annotation) error { + if taken, _ := db.IsTakenDown(ctx, a.URI); taken { return nil } - _, err := db.Exec(` - INSERT INTO annotations (uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) - ON CONFLICT(uri) DO UPDATE SET - motivation = EXCLUDED.motivation, - body_value = EXCLUDED.body_value, - body_format = EXCLUDED.body_format, - body_uri = EXCLUDED.body_uri, - target_source = EXCLUDED.target_source, - target_hash = EXCLUDED.target_hash, - target_title = EXCLUDED.target_title, - selector_json = EXCLUDED.selector_json, - tags_json = EXCLUDED.tags_json, - indexed_at = EXCLUDED.indexed_at, - cid = EXCLUDED.cid - `, a.URI, a.AuthorDID, a.Motivation, a.BodyValue, a.BodyFormat, a.BodyURI, a.TargetSource, a.TargetHash, a.TargetTitle, a.SelectorJSON, a.TagsJSON, a.CreatedAt, a.IndexedAt, a.CID) - return err + motivation := a.Motivation + targetSource := a.TargetSource + targetHash := a.TargetHash + return db.q.CreateAnnotation(ctx, sqlcdb.CreateAnnotationParams{ + Uri: a.URI, + AuthorDid: a.AuthorDID, + Motivation: &motivation, + BodyValue: a.BodyValue, + BodyFormat: a.BodyFormat, + BodyUri: a.BodyURI, + TargetSource: &targetSource, + TargetHash: &targetHash, + TargetTitle: a.TargetTitle, + SelectorJson: a.SelectorJSON, + TagsJson: a.TagsJSON, + CreatedAt: a.CreatedAt, + IndexedAt: a.IndexedAt, + Cid: a.CID, + }) } -func (db *DB) GetAnnotationByURI(uri string) (*Annotation, error) { - var a Annotation - err := db.QueryRow(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE uri = $1 - `, uri).Scan(&a.URI, &a.AuthorDID, &a.Motivation, &a.BodyValue, &a.BodyFormat, &a.BodyURI, &a.TargetSource, &a.TargetHash, &a.TargetTitle, &a.SelectorJSON, &a.TagsJSON, &a.CreatedAt, &a.IndexedAt, &a.CID) +func (db *DB) GetAnnotationByURI(ctx context.Context, uri string) (*Annotation, error) { + r, err := db.q.GetAnnotationByURI(ctx, uri) if err != nil { return nil, err } + a := mapAnnotation(r) return &a, nil } -func (db *DB) GetAnnotationsByTargetHash(targetHash string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE target_hash = $1 - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, targetHash, limit, offset) +func (db *DB) GetAnnotationsByTargetHash(ctx context.Context, targetHash string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetAnnotationsByTargetHash(ctx, sqlcdb.GetAnnotationsByTargetHashParams{ + TargetHash: &targetHash, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetAnnotationsByAuthor(authorDID string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE author_did = $1 - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, authorDID, limit, offset) +func (db *DB) GetAnnotationsByAuthor(ctx context.Context, authorDID string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetAnnotationsByAuthor(ctx, sqlcdb.GetAnnotationsByAuthorParams{ + AuthorDid: authorDID, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetMarginAnnotationsByAuthor(authorDID string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, authorDID, limit, offset) +func (db *DB) GetMarginAnnotationsByAuthor(ctx context.Context, authorDID string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetMarginAnnotationsByAuthor(ctx, sqlcdb.GetMarginAnnotationsByAuthorParams{ + AuthorDid: authorDID, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetSembleAnnotationsByAuthor(authorDID string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE author_did = $1 AND uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, authorDID, limit, offset) +func (db *DB) GetSembleAnnotationsByAuthor(ctx context.Context, authorDID string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetSembleAnnotationsByAuthor(ctx, sqlcdb.GetSembleAnnotationsByAuthorParams{ + AuthorDid: authorDID, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetAnnotationsByMotivation(motivation string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE motivation = $1 - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, motivation, limit, offset) +func (db *DB) GetAnnotationsByMotivation(ctx context.Context, motivation string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetAnnotationsByMotivation(ctx, sqlcdb.GetAnnotationsByMotivationParams{ + Motivation: &motivation, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetRecentAnnotations(limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - ORDER BY created_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) +func (db *DB) GetRecentAnnotations(ctx context.Context, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetRecentAnnotations(ctx, sqlcdb.GetRecentAnnotationsParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetPopularAnnotations(limit, offset int) ([]Annotation, error) { +func (db *DB) GetPopularAnnotations(ctx context.Context, limit, offset int) ([]Annotation, error) { since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(` - SELECT - a.uri, a.author_did, a.motivation, a.body_value, a.body_format, - a.body_uri, a.target_source, a.target_hash, a.target_title, - a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid - FROM all_annotations a - LEFT JOIN LATERAL ( - SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = a.uri - ) l ON true - LEFT JOIN LATERAL ( - SELECT COUNT(*) as cnt FROM replies WHERE root_uri = a.uri - ) r ON true - WHERE a.created_at > $1 AND (l.cnt + r.cnt) > 0 - ORDER BY (l.cnt + r.cnt) DESC, a.created_at DESC - LIMIT $2 OFFSET $3 - `, since, limit, offset) + rows, err := db.q.GetPopularAnnotations(ctx, sqlcdb.GetPopularAnnotationsParams{ + CreatedAt: since, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetShelvedAnnotations(limit, offset int) ([]Annotation, error) { +func (db *DB) GetShelvedAnnotations(ctx context.Context, limit, offset int) ([]Annotation, error) { olderThan := time.Now().AddDate(0, 0, -1) since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(` - SELECT - a.uri, a.author_did, a.motivation, a.body_value, a.body_format, - a.body_uri, a.target_source, a.target_hash, a.target_title, - a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid - FROM all_annotations a - WHERE a.created_at < $1 AND a.created_at > $2 - AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = a.uri) - AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = a.uri) - ORDER BY RANDOM() - LIMIT $3 OFFSET $4 - `, olderThan, since, limit, offset) + rows, err := db.q.GetShelvedAnnotations(ctx, sqlcdb.GetShelvedAnnotationsParams{ + CreatedAt: olderThan, + CreatedAt_2: since, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetMarginAnnotations(limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) +func (db *DB) GetMarginAnnotations(ctx context.Context, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetMarginAnnotations(ctx, sqlcdb.GetMarginAnnotationsParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetSembleAnnotations(limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) +func (db *DB) GetSembleAnnotations(ctx context.Context, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetSembleAnnotations(ctx, sqlcdb.GetSembleAnnotationsParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetAnnotationsByTag(tag string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $1) - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, tag, limit, offset) +func (db *DB) GetAnnotationsByTag(ctx context.Context, tag string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetAnnotationsByTag(ctx, sqlcdb.GetAnnotationsByTagParams{ + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetMarginAnnotationsByTag(tag string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $1) AND uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, tag, limit, offset) +func (db *DB) GetMarginAnnotationsByTag(ctx context.Context, tag string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetMarginAnnotationsByTag(ctx, sqlcdb.GetMarginAnnotationsByTagParams{ + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetSembleAnnotationsByTag(tag string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $1) AND uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, tag, limit, offset) +func (db *DB) GetSembleAnnotationsByTag(ctx context.Context, tag string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetSembleAnnotationsByTag(ctx, sqlcdb.GetSembleAnnotationsByTagParams{ + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) DeleteAnnotation(uri string) error { - _, err := db.Exec(`DELETE FROM annotations WHERE uri = $1`, uri) - return err +func (db *DB) DeleteAnnotation(ctx context.Context, uri string) error { + return db.q.DeleteAnnotation(ctx, uri) } -func (db *DB) UpdateAnnotation(uri, bodyValue, tagsJSON, cid string) error { - _, err := db.Exec(` - UPDATE annotations - SET body_value = $1, tags_json = $2, cid = $3, indexed_at = $4 - WHERE uri = $5 - `, bodyValue, tagsJSON, cid, time.Now(), uri) - return err +func (db *DB) UpdateAnnotation(ctx context.Context, uri, bodyValue, tagsJSON, cid string) error { + return db.q.UpdateAnnotation(ctx, sqlcdb.UpdateAnnotationParams{ + BodyValue: &bodyValue, + TagsJson: &tagsJSON, + Cid: &cid, + IndexedAt: time.Now(), + Uri: uri, + }) } -func (db *DB) GetAnnotationsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE author_did = $1 AND EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $2) - ORDER BY created_at DESC - LIMIT $3 OFFSET $4 - `, authorDID, tag, limit, offset) +func (db *DB) GetAnnotationsByTagAndAuthor(ctx context.Context, tag, authorDID string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetAnnotationsByTagAndAuthor(ctx, sqlcdb.GetAnnotationsByTagAndAuthorParams{ + AuthorDid: authorDID, + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetMarginAnnotationsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE author_did = $1 AND EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $2) AND uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $3 OFFSET $4 - `, authorDID, tag, limit, offset) +func (db *DB) GetMarginAnnotationsByTagAndAuthor(ctx context.Context, tag, authorDID string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetMarginAnnotationsByTagAndAuthor(ctx, sqlcdb.GetMarginAnnotationsByTagAndAuthorParams{ + AuthorDid: authorDID, + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetSembleAnnotationsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE author_did = $1 AND EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $2) AND uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $3 OFFSET $4 - `, authorDID, tag, limit, offset) +func (db *DB) GetSembleAnnotationsByTagAndAuthor(ctx context.Context, tag, authorDID string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetSembleAnnotationsByTagAndAuthor(ctx, sqlcdb.GetSembleAnnotationsByTagAndAuthorParams{ + AuthorDid: authorDID, + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetAnnotationsByAuthorAndTargetHash(authorDID, targetHash string, limit, offset int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE author_did = $1 AND target_hash = $2 - ORDER BY created_at DESC - LIMIT $3 OFFSET $4 - `, authorDID, targetHash, limit, offset) +func (db *DB) GetAnnotationsByAuthorAndTargetHash(ctx context.Context, authorDID, targetHash string, limit, offset int) ([]Annotation, error) { + rows, err := db.q.GetAnnotationsByAuthorAndTargetHash(ctx, sqlcdb.GetAnnotationsByAuthorAndTargetHashParams{ + AuthorDid: authorDID, + TargetHash: &targetHash, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetAnnotationsByURIs(uris []string) ([]Annotation, error) { +func (db *DB) GetAnnotationsByURIs(ctx context.Context, uris []string) ([]Annotation, error) { if len(uris) == 0 { return []Annotation{}, nil } - query := ` - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM all_annotations - WHERE uri = ANY($1) - ` - - rows, err := db.Query(query, pqStringArray(uris)) + rows, err := db.q.GetAnnotationsByURIs(ctx, uris) if err != nil { return nil, err } - defer rows.Close() - - return scanAnnotations(rows) + return mapAnnotations(rows), nil } -func (db *DB) GetAnnotationURIs(authorDID string) ([]string, error) { - rows, err := db.Query(` - SELECT uri FROM all_annotations WHERE author_did = $1 - `, authorDID) - if err != nil { - return nil, err - } - defer rows.Close() - - var uris []string - for rows.Next() { - var uri string - if err := rows.Scan(&uri); err != nil { - return nil, err - } - uris = append(uris, uri) - } - return uris, nil +func (db *DB) GetAnnotationURIs(ctx context.Context, authorDID string) ([]string, error) { + return db.q.GetAnnotationURIs(ctx, authorDID) } diff --git a/backend/internal/db/queries_bookmarks.go b/backend/internal/db/queries_bookmarks.go index 3c3bcf3..86feadb 100644 --- a/backend/internal/db/queries_bookmarks.go +++ b/backend/internal/db/queries_bookmarks.go @@ -1,355 +1,270 @@ package db import ( + "context" "time" + + "margin.at/internal/db/sqlcdb" ) -func (db *DB) CreateBookmark(b *Bookmark) error { - _, err := db.Exec(` - INSERT INTO bookmarks (uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) - ON CONFLICT(uri) DO UPDATE SET - source = EXCLUDED.source, - source_hash = EXCLUDED.source_hash, - title = EXCLUDED.title, - description = EXCLUDED.description, - tags_json = EXCLUDED.tags_json, - indexed_at = EXCLUDED.indexed_at, - cid = EXCLUDED.cid - `, b.URI, b.AuthorDID, b.Source, b.SourceHash, b.Title, b.Description, b.TagsJSON, b.CreatedAt, b.IndexedAt, b.CID) - return err +func (db *DB) CreateBookmark(ctx context.Context, b *Bookmark) error { + return db.q.CreateBookmark(ctx, sqlcdb.CreateBookmarkParams{ + Uri: b.URI, + AuthorDid: b.AuthorDID, + Source: b.Source, + SourceHash: b.SourceHash, + Title: b.Title, + Description: b.Description, + TagsJson: b.TagsJSON, + CreatedAt: b.CreatedAt, + IndexedAt: b.IndexedAt, + Cid: b.CID, + }) } -func (db *DB) GetBookmarkByURI(uri string) (*Bookmark, error) { - var b Bookmark - err := db.QueryRow(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE uri = $1 - `, uri).Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID) +func (db *DB) GetBookmarkByURI(ctx context.Context, uri string) (*Bookmark, error) { + r, err := db.q.GetBookmarkByURI(ctx, uri) if err != nil { return nil, err } + b := mapBookmark(r) return &b, nil } -func (db *DB) GetRecentBookmarks(limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - ORDER BY created_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) +func (db *DB) GetRecentBookmarks(ctx context.Context, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetRecentBookmarks(ctx, sqlcdb.GetRecentBookmarksParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetPopularBookmarks(limit, offset int) ([]Bookmark, error) { +func (db *DB) GetPopularBookmarks(ctx context.Context, limit, offset int) ([]Bookmark, error) { since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(` - SELECT - b.uri, b.author_did, b.source, b.source_hash, b.title, - b.description, b.tags_json, b.created_at, b.indexed_at, b.cid - FROM all_bookmarks b - LEFT JOIN LATERAL ( - SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = b.uri - ) l ON true - LEFT JOIN LATERAL ( - SELECT COUNT(*) as cnt FROM replies WHERE root_uri = b.uri - ) r ON true - WHERE b.created_at > $1 AND (l.cnt + r.cnt) > 0 - ORDER BY (l.cnt + r.cnt) DESC, b.created_at DESC - LIMIT $2 OFFSET $3 - `, since, limit, offset) + rows, err := db.q.GetPopularBookmarks(ctx, sqlcdb.GetPopularBookmarksParams{ + CreatedAt: since, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetShelvedBookmarks(limit, offset int) ([]Bookmark, error) { +func (db *DB) GetShelvedBookmarks(ctx context.Context, limit, offset int) ([]Bookmark, error) { olderThan := time.Now().AddDate(0, 0, -1) since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(` - SELECT - b.uri, b.author_did, b.source, b.source_hash, b.title, - b.description, b.tags_json, b.created_at, b.indexed_at, b.cid - FROM all_bookmarks b - WHERE b.created_at < $1 AND b.created_at > $2 - AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = b.uri) - AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = b.uri) - ORDER BY RANDOM() - LIMIT $3 OFFSET $4 - `, olderThan, since, limit, offset) + rows, err := db.q.GetShelvedBookmarks(ctx, sqlcdb.GetShelvedBookmarksParams{ + CreatedAt: olderThan, + CreatedAt_2: since, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetMarginBookmarks(limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) +func (db *DB) GetMarginBookmarks(ctx context.Context, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetMarginBookmarks(ctx, sqlcdb.GetMarginBookmarksParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetSembleBookmarks(limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) +func (db *DB) GetSembleBookmarks(ctx context.Context, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetSembleBookmarks(ctx, sqlcdb.GetSembleBookmarksParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetBookmarksByTag(tag string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $1) - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, tag, limit, offset) +func (db *DB) GetBookmarksByTag(ctx context.Context, tag string, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetBookmarksByTag(ctx, sqlcdb.GetBookmarksByTagParams{ + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetMarginBookmarksByTag(tag string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $1) AND uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, tag, limit, offset) +func (db *DB) GetMarginBookmarksByTag(ctx context.Context, tag string, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetMarginBookmarksByTag(ctx, sqlcdb.GetMarginBookmarksByTagParams{ + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetSembleBookmarksByTag(tag string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $1) AND uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, tag, limit, offset) +func (db *DB) GetSembleBookmarksByTag(ctx context.Context, tag string, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetSembleBookmarksByTag(ctx, sqlcdb.GetSembleBookmarksByTagParams{ + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetBookmarksByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE author_did = $1 AND EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $2) - ORDER BY created_at DESC - LIMIT $3 OFFSET $4 - `, authorDID, tag, limit, offset) +func (db *DB) GetBookmarksByTagAndAuthor(ctx context.Context, tag, authorDID string, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetBookmarksByTagAndAuthor(ctx, sqlcdb.GetBookmarksByTagAndAuthorParams{ + AuthorDid: authorDID, + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetMarginBookmarksByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE author_did = $1 AND EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $2) AND uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $3 OFFSET $4 - `, authorDID, tag, limit, offset) +func (db *DB) GetMarginBookmarksByTagAndAuthor(ctx context.Context, tag, authorDID string, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetMarginBookmarksByTagAndAuthor(ctx, sqlcdb.GetMarginBookmarksByTagAndAuthorParams{ + AuthorDid: authorDID, + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetSembleBookmarksByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE author_did = $1 AND EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $2) AND uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $3 OFFSET $4 - `, authorDID, tag, limit, offset) +func (db *DB) GetSembleBookmarksByTagAndAuthor(ctx context.Context, tag, authorDID string, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetSembleBookmarksByTagAndAuthor(ctx, sqlcdb.GetSembleBookmarksByTagAndAuthorParams{ + AuthorDid: authorDID, + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetBookmarksByAuthor(authorDID string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE author_did = $1 - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, authorDID, limit, offset) +func (db *DB) GetBookmarksByAuthor(ctx context.Context, authorDID string, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetBookmarksByAuthor(ctx, sqlcdb.GetBookmarksByAuthorParams{ + AuthorDid: authorDID, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetMarginBookmarksByAuthor(authorDID string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, authorDID, limit, offset) +func (db *DB) GetMarginBookmarksByAuthor(ctx context.Context, authorDID string, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetMarginBookmarksByAuthor(ctx, sqlcdb.GetMarginBookmarksByAuthorParams{ + AuthorDid: authorDID, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) GetSembleBookmarksByAuthor(authorDID string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE author_did = $1 AND uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, authorDID, limit, offset) +func (db *DB) GetSembleBookmarksByAuthor(ctx context.Context, authorDID string, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetSembleBookmarksByAuthor(ctx, sqlcdb.GetSembleBookmarksByAuthorParams{ + AuthorDid: authorDID, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanBookmarks(rows) + return mapBookmarks(rows), nil } -func (db *DB) DeleteBookmark(uri string) error { - _, err := db.Exec(`DELETE FROM bookmarks WHERE uri = $1`, uri) - return err +func (db *DB) DeleteBookmark(ctx context.Context, uri string) error { + return db.q.DeleteBookmark(ctx, uri) } -func (db *DB) UpdateBookmark(uri, title, description, tagsJSON, cid string) error { - _, err := db.Exec(` - UPDATE bookmarks - SET title = $1, description = $2, tags_json = $3, cid = $4, indexed_at = $5 - WHERE uri = $6 - `, title, description, tagsJSON, cid, time.Now(), uri) - return err +func (db *DB) UpdateBookmark(ctx context.Context, uri, title, description, tagsJSON, cid string) error { + return db.q.UpdateBookmark(ctx, sqlcdb.UpdateBookmarkParams{ + Title: &title, + Description: &description, + TagsJson: &tagsJSON, + Cid: &cid, + IndexedAt: time.Now(), + Uri: uri, + }) } -func (db *DB) GetBookmarksByURIs(uris []string) ([]Bookmark, error) { +func (db *DB) GetBookmarksByURIs(ctx context.Context, uris []string) ([]Bookmark, error) { if len(uris) == 0 { return []Bookmark{}, nil } - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE uri = ANY($1) - `, pqStringArray(uris)) + rows, err := db.q.GetBookmarksByURIs(ctx, uris) if err != nil { return nil, err } - defer rows.Close() + return mapBookmarks(rows), nil +} - return scanBookmarks(rows) +func (db *DB) GetBookmarkURIs(ctx context.Context, authorDID string) ([]string, error) { + return db.q.GetBookmarkURIs(ctx, authorDID) } -func (db *DB) GetBookmarkURIs(authorDID string) ([]string, error) { - rows, err := db.Query(` - SELECT uri FROM all_bookmarks WHERE author_did = $1 - `, authorDID) +func (db *DB) GetBookmarksByTargetHash(ctx context.Context, targetHash string, limit, offset int) ([]Bookmark, error) { + rows, err := db.q.GetBookmarksByTargetHash(ctx, sqlcdb.GetBookmarksByTargetHashParams{ + SourceHash: targetHash, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - var uris []string - for rows.Next() { - var uri string - if err := rows.Scan(&uri); err != nil { - return nil, err - } - uris = append(uris, uri) - } - return uris, nil + return mapBookmarks(rows), nil } -func (db *DB) GetBookmarksByTargetHash(targetHash string, limit, offset int) ([]Bookmark, error) { - rows, err := db.Query(` - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid - FROM all_bookmarks - WHERE source_hash = $1 - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, targetHash, limit, offset) - if err != nil { - return nil, err +func mapBookmark(r sqlcdb.AllBookmark) Bookmark { + return Bookmark{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + Source: r.Source, + SourceHash: r.SourceHash, + Title: r.Title, + Description: r.Description, + TagsJSON: r.TagsJson, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + CID: r.Cid, } - defer rows.Close() - - return scanBookmarks(rows) } -func scanBookmarks(rows interface { - Next() bool - Scan(...interface{}) error -}) ([]Bookmark, error) { +func mapBookmarks(rows []sqlcdb.AllBookmark) []Bookmark { var bookmarks []Bookmark - for rows.Next() { - var b Bookmark - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { - return nil, err - } - bookmarks = append(bookmarks, b) + for _, r := range rows { + bookmarks = append(bookmarks, mapBookmark(r)) } - return bookmarks, nil + return bookmarks } diff --git a/backend/internal/db/queries_collections.go b/backend/internal/db/queries_collections.go index 958f4b9..260d5d9 100644 --- a/backend/internal/db/queries_collections.go +++ b/backend/internal/db/queries_collections.go @@ -1,293 +1,215 @@ package db -import "time" - -func (db *DB) CreateCollection(c *Collection) error { - _, err := db.Exec(` - INSERT INTO collections (uri, author_did, name, description, icon, created_at, indexed_at) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT(uri) DO UPDATE SET - name = EXCLUDED.name, - description = EXCLUDED.description, - icon = EXCLUDED.icon, - indexed_at = EXCLUDED.indexed_at - `, c.URI, c.AuthorDID, c.Name, c.Description, c.Icon, c.CreatedAt, c.IndexedAt) - return err -} - -func (db *DB) GetCollectionsByAuthor(authorDID string) ([]Collection, error) { - rows, err := db.Query(` - SELECT uri, author_did, name, description, icon, created_at, indexed_at - FROM collections - WHERE author_did = $1 - ORDER BY created_at DESC - `, authorDID) +import ( + "context" + "time" + + "github.com/jackc/pgx/v5/pgtype" + "margin.at/internal/db/sqlcdb" +) + +func (db *DB) CreateCollection(ctx context.Context, c *Collection) error { + return db.q.CreateCollection(ctx, sqlcdb.CreateCollectionParams{ + Uri: c.URI, + AuthorDid: c.AuthorDID, + Name: c.Name, + Description: c.Description, + Icon: c.Icon, + CreatedAt: c.CreatedAt, + IndexedAt: c.IndexedAt, + }) +} + +func (db *DB) GetCollectionsByAuthor(ctx context.Context, authorDID string) ([]Collection, error) { + rows, err := db.q.GetCollectionsByAuthor(ctx, authorDID) if err != nil { return nil, err } - defer rows.Close() - var collections []Collection - for rows.Next() { - var c Collection - if err := rows.Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt); err != nil { - return nil, err - } - collections = append(collections, c) + for _, r := range rows { + collections = append(collections, mapCollection(r)) } return collections, nil } -func (db *DB) GetCollectionByURI(uri string) (*Collection, error) { - var c Collection - err := db.QueryRow(` - SELECT uri, author_did, name, description, icon, created_at, indexed_at - FROM collections - WHERE uri = $1 - `, uri).Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt) +func (db *DB) GetCollectionByURI(ctx context.Context, uri string) (*Collection, error) { + r, err := db.q.GetCollectionByURI(ctx, uri) if err != nil { return nil, err } + c := mapCollection(r) return &c, nil } -func (db *DB) DeleteCollection(uri string) error { - db.Exec(`DELETE FROM collection_items WHERE collection_uri = $1`, uri) - _, err := db.Exec(`DELETE FROM collections WHERE uri = $1`, uri) - return err +func (db *DB) DeleteCollection(ctx context.Context, uri string) error { + db.q.DeleteCollectionItemsByCollection(ctx, uri) + return db.q.DeleteCollection(ctx, uri) } -func (db *DB) AddToCollection(item *CollectionItem) error { - _, err := db.Exec(` - INSERT INTO collection_items (uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT(uri) DO UPDATE SET - position = EXCLUDED.position, - indexed_at = EXCLUDED.indexed_at - `, item.URI, item.AuthorDID, item.CollectionURI, item.AnnotationURI, item.Position, item.CreatedAt, item.IndexedAt) - return err +func (db *DB) AddToCollection(ctx context.Context, item *CollectionItem) error { + return db.q.AddToCollection(ctx, sqlcdb.AddToCollectionParams{ + Uri: item.URI, + AuthorDid: item.AuthorDID, + CollectionUri: item.CollectionURI, + AnnotationUri: item.AnnotationURI, + Position: pgtype.Int4{Int32: int32(item.Position), Valid: true}, + CreatedAt: item.CreatedAt, + IndexedAt: item.IndexedAt, + }) } -func (db *DB) GetCollectionItems(collectionURI string) ([]CollectionItem, error) { - rows, err := db.Query(` - SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at - FROM collection_items - WHERE collection_uri = $1 - ORDER BY position ASC, created_at DESC - `, collectionURI) +func (db *DB) GetCollectionItems(ctx context.Context, collectionURI string) ([]CollectionItem, error) { + rows, err := db.q.GetCollectionItems(ctx, collectionURI) if err != nil { return nil, err } - defer rows.Close() - var items []CollectionItem - for rows.Next() { - var item CollectionItem - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { - return nil, err - } - items = append(items, item) + for _, r := range rows { + items = append(items, mapCollectionItem(r)) } return items, nil } -func (db *DB) RemoveFromCollection(uri string) error { - _, err := db.Exec(`DELETE FROM collection_items WHERE uri = $1`, uri) - return err +func (db *DB) RemoveFromCollection(ctx context.Context, uri string) error { + return db.q.RemoveFromCollection(ctx, uri) } -func (db *DB) GetRecentCollectionItems(limit, offset int) ([]CollectionItem, error) { - rows, err := db.Query(` - SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at - FROM collection_items - ORDER BY created_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) +func (db *DB) GetRecentCollectionItems(ctx context.Context, limit, offset int) ([]CollectionItem, error) { + rows, err := db.q.GetRecentCollectionItems(ctx, sqlcdb.GetRecentCollectionItemsParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanCollectionItems(rows) + return mapCollectionItems(rows), nil } -func (db *DB) GetPopularCollectionItems(limit, offset int) ([]CollectionItem, error) { +func (db *DB) GetPopularCollectionItems(ctx context.Context, limit, offset int) ([]CollectionItem, error) { since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(` - SELECT - c.uri, c.author_did, c.collection_uri, c.annotation_uri, - c.position, c.created_at, c.indexed_at - FROM collection_items c - LEFT JOIN LATERAL ( - SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = c.annotation_uri - ) l ON true - LEFT JOIN LATERAL ( - SELECT COUNT(*) as cnt FROM replies WHERE root_uri = c.annotation_uri - ) r ON true - WHERE c.created_at > $1 AND (l.cnt + r.cnt) > 0 - ORDER BY (l.cnt + r.cnt) DESC, c.created_at DESC - LIMIT $2 OFFSET $3 - `, since, limit, offset) + rows, err := db.q.GetPopularCollectionItems(ctx, sqlcdb.GetPopularCollectionItemsParams{ + CreatedAt: since, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanCollectionItems(rows) + return mapCollectionItems(rows), nil } -func (db *DB) GetShelvedCollectionItems(limit, offset int) ([]CollectionItem, error) { +func (db *DB) GetShelvedCollectionItems(ctx context.Context, limit, offset int) ([]CollectionItem, error) { olderThan := time.Now().AddDate(0, 0, -1) since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(` - SELECT - c.uri, c.author_did, c.collection_uri, c.annotation_uri, - c.position, c.created_at, c.indexed_at - FROM collection_items c - WHERE c.created_at < $1 AND c.created_at > $2 - AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = c.annotation_uri) - AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = c.annotation_uri) - ORDER BY RANDOM() - LIMIT $3 OFFSET $4 - `, olderThan, since, limit, offset) + rows, err := db.q.GetShelvedCollectionItems(ctx, sqlcdb.GetShelvedCollectionItemsParams{ + CreatedAt: olderThan, + CreatedAt_2: since, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanCollectionItems(rows) + return mapCollectionItems(rows), nil } -func (db *DB) GetCollectionItemsByAuthor(authorDID string) ([]CollectionItem, error) { - rows, err := db.Query(` - SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at - FROM collection_items - WHERE author_did = $1 - ORDER BY created_at DESC - `, authorDID) +func (db *DB) GetCollectionItemsByAuthor(ctx context.Context, authorDID string) ([]CollectionItem, error) { + rows, err := db.q.GetCollectionItemsByAuthor(ctx, authorDID) if err != nil { return nil, err } - defer rows.Close() - - return scanCollectionItems(rows) + return mapCollectionItems(rows), nil } -func (db *DB) GetCollectionURIsForAnnotation(annotationURI string) ([]string, error) { - rows, err := db.Query(` - SELECT collection_uri FROM collection_items WHERE annotation_uri = $1 - `, annotationURI) - if err != nil { - return nil, err - } - defer rows.Close() - - var uris []string - for rows.Next() { - var uri string - if err := rows.Scan(&uri); err != nil { - return nil, err - } - uris = append(uris, uri) - } - return uris, nil +func (db *DB) GetCollectionURIsForAnnotation(ctx context.Context, annotationURI string) ([]string, error) { + return db.q.GetCollectionURIsForAnnotation(ctx, annotationURI) } -func (db *DB) GetCollectionItemCounts(uris []string) (map[string]int, error) { +func (db *DB) GetCollectionItemCounts(ctx context.Context, uris []string) (map[string]int, error) { if len(uris) == 0 { return map[string]int{}, nil } - rows, err := db.Query(` - SELECT collection_uri, COUNT(*) - FROM collection_items - WHERE collection_uri = ANY($1) - GROUP BY collection_uri - `, pqStringArray(uris)) + rows, err := db.q.GetCollectionItemCounts(ctx, uris) if err != nil { return nil, err } - defer rows.Close() counts := make(map[string]int) - for rows.Next() { - var uri string - var count int - if err := rows.Scan(&uri, &count); err != nil { - return nil, err - } - counts[uri] = count + for _, r := range rows { + counts[r.CollectionUri] = int(r.Count) } return counts, nil } -func (db *DB) GetCollectionsForNoteURIs(noteURIs []string) (map[string]Collection, error) { +func (db *DB) GetCollectionsForNoteURIs(ctx context.Context, noteURIs []string) (map[string]Collection, error) { if len(noteURIs) == 0 { return map[string]Collection{}, nil } - rows, err := db.Query(` - SELECT DISTINCT ON (ci.annotation_uri) - ci.annotation_uri, - c.uri, c.author_did, c.name, c.description, c.icon, c.created_at, c.indexed_at - FROM collection_items ci - JOIN collections c ON c.uri = ci.collection_uri - WHERE ci.annotation_uri = ANY($1) - ORDER BY ci.annotation_uri, ci.created_at ASC - `, pqStringArray(noteURIs)) + rows, err := db.q.GetCollectionsForNoteURIs(ctx, noteURIs) if err != nil { return nil, err } - defer rows.Close() result := make(map[string]Collection) - for rows.Next() { - var noteURI string - var c Collection - if err := rows.Scan(¬eURI, &c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt); err != nil { - return nil, err + for _, r := range rows { + result[r.AnnotationUri] = Collection{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + Name: r.Name, + Description: r.Description, + Icon: r.Icon, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, } - result[noteURI] = c } return result, nil } -func (db *DB) GetCollectionsByURIs(uris []string) ([]Collection, error) { +func (db *DB) GetCollectionsByURIs(ctx context.Context, uris []string) ([]Collection, error) { if len(uris) == 0 { return []Collection{}, nil } - rows, err := db.Query(` - SELECT uri, author_did, name, description, icon, created_at, indexed_at - FROM collections - WHERE uri = ANY($1) - `, pqStringArray(uris)) + rows, err := db.q.GetCollectionsByURIs(ctx, uris) if err != nil { return nil, err } - defer rows.Close() - var collections []Collection - for rows.Next() { - var c Collection - if err := rows.Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt); err != nil { - return nil, err - } - collections = append(collections, c) + for _, r := range rows { + collections = append(collections, mapCollection(r)) } return collections, nil } -func scanCollectionItems(rows interface { - Next() bool - Scan(...interface{}) error -}) ([]CollectionItem, error) { +func mapCollection(r sqlcdb.Collection) Collection { + return Collection{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + Name: r.Name, + Description: r.Description, + Icon: r.Icon, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + } +} + +func mapCollectionItem(r sqlcdb.CollectionItem) CollectionItem { + return CollectionItem{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + CollectionURI: r.CollectionUri, + AnnotationURI: r.AnnotationUri, + Position: int(r.Position.Int32), + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + } +} + +func mapCollectionItems(rows []sqlcdb.CollectionItem) []CollectionItem { var items []CollectionItem - for rows.Next() { - var item CollectionItem - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { - return nil, err - } - items = append(items, item) + for _, r := range rows { + items = append(items, mapCollectionItem(r)) } - return items, nil + return items } diff --git a/backend/internal/db/queries_highlights.go b/backend/internal/db/queries_highlights.go index 3ca8b52..886876c 100644 --- a/backend/internal/db/queries_highlights.go +++ b/backend/internal/db/queries_highlights.go @@ -1,362 +1,289 @@ package db import ( + "context" "time" + + "margin.at/internal/db/sqlcdb" ) -func (db *DB) CreateHighlight(h *Highlight) error { - if taken, _ := db.IsTakenDown(h.URI); taken { +func (db *DB) CreateHighlight(ctx context.Context, h *Highlight) error { + if taken, _ := db.IsTakenDown(ctx, h.URI); taken { return nil } - _, err := db.Exec(` - INSERT INTO highlights (uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) - ON CONFLICT(uri) DO UPDATE SET - target_source = EXCLUDED.target_source, - target_hash = EXCLUDED.target_hash, - target_title = EXCLUDED.target_title, - selector_json = EXCLUDED.selector_json, - color = EXCLUDED.color, - tags_json = EXCLUDED.tags_json, - indexed_at = EXCLUDED.indexed_at, - cid = EXCLUDED.cid - `, h.URI, h.AuthorDID, h.TargetSource, h.TargetHash, h.TargetTitle, h.SelectorJSON, h.Color, h.TagsJSON, h.CreatedAt, h.IndexedAt, h.CID) - return err + return db.q.CreateHighlight(ctx, sqlcdb.CreateHighlightParams{ + Uri: h.URI, + AuthorDid: h.AuthorDID, + TargetSource: h.TargetSource, + TargetHash: h.TargetHash, + TargetTitle: h.TargetTitle, + SelectorJson: h.SelectorJSON, + Color: h.Color, + TagsJson: h.TagsJSON, + CreatedAt: h.CreatedAt, + IndexedAt: h.IndexedAt, + Cid: h.CID, + }) } -func (db *DB) GetHighlightByURI(uri string) (*Highlight, error) { - var h Highlight - err := db.QueryRow(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE uri = $1 - `, uri).Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID) +func (db *DB) GetHighlightByURI(ctx context.Context, uri string) (*Highlight, error) { + r, err := db.q.GetHighlightByURI(ctx, uri) if err != nil { return nil, err } + h := mapHighlight(r) return &h, nil } -func (db *DB) GetRecentHighlights(limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - ORDER BY created_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) +func (db *DB) GetRecentHighlights(ctx context.Context, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetRecentHighlights(ctx, sqlcdb.GetRecentHighlightsParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetPopularHighlights(limit, offset int) ([]Highlight, error) { +func (db *DB) GetPopularHighlights(ctx context.Context, limit, offset int) ([]Highlight, error) { since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(` - SELECT - h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, - h.selector_json, h.color, h.tags_json, h.created_at, h.indexed_at, h.cid - FROM all_highlights h - LEFT JOIN LATERAL ( - SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = h.uri - ) l ON true - LEFT JOIN LATERAL ( - SELECT COUNT(*) as cnt FROM replies WHERE root_uri = h.uri - ) r ON true - WHERE h.created_at > $1 AND (l.cnt + r.cnt) > 0 - ORDER BY (l.cnt + r.cnt) DESC, h.created_at DESC - LIMIT $2 OFFSET $3 - `, since, limit, offset) + rows, err := db.q.GetPopularHighlights(ctx, sqlcdb.GetPopularHighlightsParams{ + CreatedAt: since, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetShelvedHighlights(limit, offset int) ([]Highlight, error) { +func (db *DB) GetShelvedHighlights(ctx context.Context, limit, offset int) ([]Highlight, error) { olderThan := time.Now().AddDate(0, 0, -1) since := time.Now().AddDate(0, 0, -14) - rows, err := db.Query(` - SELECT - h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, - h.selector_json, h.color, h.tags_json, h.created_at, h.indexed_at, h.cid - FROM all_highlights h - WHERE h.created_at < $1 AND h.created_at > $2 - AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = h.uri) - AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = h.uri) - ORDER BY RANDOM() - LIMIT $3 OFFSET $4 - `, olderThan, since, limit, offset) + rows, err := db.q.GetShelvedHighlights(ctx, sqlcdb.GetShelvedHighlightsParams{ + CreatedAt: olderThan, + CreatedAt_2: since, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetMarginHighlights(limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) +func (db *DB) GetMarginHighlights(ctx context.Context, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetMarginHighlights(ctx, sqlcdb.GetMarginHighlightsParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetSembleHighlights(limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) +func (db *DB) GetSembleHighlights(ctx context.Context, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetSembleHighlights(ctx, sqlcdb.GetSembleHighlightsParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetHighlightsByTag(tag string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $1) - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, tag, limit, offset) +func (db *DB) GetHighlightsByTag(ctx context.Context, tag string, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetHighlightsByTag(ctx, sqlcdb.GetHighlightsByTagParams{ + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetMarginHighlightsByTag(tag string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $1) AND uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, tag, limit, offset) +func (db *DB) GetMarginHighlightsByTag(ctx context.Context, tag string, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetMarginHighlightsByTag(ctx, sqlcdb.GetMarginHighlightsByTagParams{ + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetSembleHighlightsByTag(tag string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $1) AND uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, tag, limit, offset) +func (db *DB) GetSembleHighlightsByTag(ctx context.Context, tag string, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetSembleHighlightsByTag(ctx, sqlcdb.GetSembleHighlightsByTagParams{ + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetHighlightsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE author_did = $1 AND EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $2) - ORDER BY created_at DESC - LIMIT $3 OFFSET $4 - `, authorDID, tag, limit, offset) +func (db *DB) GetHighlightsByTagAndAuthor(ctx context.Context, tag, authorDID string, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetHighlightsByTagAndAuthor(ctx, sqlcdb.GetHighlightsByTagAndAuthorParams{ + AuthorDid: authorDID, + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetMarginHighlightsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE author_did = $1 AND EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $2) AND uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $3 OFFSET $4 - `, authorDID, tag, limit, offset) +func (db *DB) GetMarginHighlightsByTagAndAuthor(ctx context.Context, tag, authorDID string, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetMarginHighlightsByTagAndAuthor(ctx, sqlcdb.GetMarginHighlightsByTagAndAuthorParams{ + AuthorDid: authorDID, + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetSembleHighlightsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE author_did = $1 AND EXISTS(SELECT 1 FROM jsonb_array_elements_text(tags_json::jsonb) elem WHERE lower(elem) = $2) AND uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $3 OFFSET $4 - `, authorDID, tag, limit, offset) +func (db *DB) GetSembleHighlightsByTagAndAuthor(ctx context.Context, tag, authorDID string, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetSembleHighlightsByTagAndAuthor(ctx, sqlcdb.GetSembleHighlightsByTagAndAuthorParams{ + AuthorDid: authorDID, + TagsJson: &tag, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetHighlightsByTargetHash(targetHash string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE target_hash = $1 - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, targetHash, limit, offset) +func (db *DB) GetHighlightsByTargetHash(ctx context.Context, targetHash string, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetHighlightsByTargetHash(ctx, sqlcdb.GetHighlightsByTargetHashParams{ + TargetHash: targetHash, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetHighlightsByAuthor(authorDID string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE author_did = $1 - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, authorDID, limit, offset) +func (db *DB) GetHighlightsByAuthor(ctx context.Context, authorDID string, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetHighlightsByAuthor(ctx, sqlcdb.GetHighlightsByAuthorParams{ + AuthorDid: authorDID, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetMarginHighlightsByAuthor(authorDID string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, authorDID, limit, offset) +func (db *DB) GetMarginHighlightsByAuthor(ctx context.Context, authorDID string, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetMarginHighlightsByAuthor(ctx, sqlcdb.GetMarginHighlightsByAuthorParams{ + AuthorDid: authorDID, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetSembleHighlightsByAuthor(authorDID string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE author_did = $1 AND uri LIKE '%network.cosmik%' - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, authorDID, limit, offset) +func (db *DB) GetSembleHighlightsByAuthor(ctx context.Context, authorDID string, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetSembleHighlightsByAuthor(ctx, sqlcdb.GetSembleHighlightsByAuthorParams{ + AuthorDid: authorDID, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) GetHighlightsByAuthorAndTargetHash(authorDID, targetHash string, limit, offset int) ([]Highlight, error) { - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE author_did = $1 AND target_hash = $2 - ORDER BY created_at DESC - LIMIT $3 OFFSET $4 - `, authorDID, targetHash, limit, offset) +func (db *DB) GetHighlightsByAuthorAndTargetHash(ctx context.Context, authorDID, targetHash string, limit, offset int) ([]Highlight, error) { + rows, err := db.q.GetHighlightsByAuthorAndTargetHash(ctx, sqlcdb.GetHighlightsByAuthorAndTargetHashParams{ + AuthorDid: authorDID, + TargetHash: targetHash, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - - return scanHighlights(rows) + return mapHighlights(rows), nil } -func (db *DB) DeleteHighlight(uri string) error { - _, err := db.Exec(`DELETE FROM highlights WHERE uri = $1`, uri) - return err +func (db *DB) DeleteHighlight(ctx context.Context, uri string) error { + return db.q.DeleteHighlight(ctx, uri) } -func (db *DB) UpdateHighlight(uri, color, tagsJSON, cid string) error { - _, err := db.Exec(` - UPDATE highlights - SET color = $1, tags_json = $2, cid = $3, indexed_at = $4 - WHERE uri = $5 - `, color, tagsJSON, cid, time.Now(), uri) - return err +func (db *DB) UpdateHighlight(ctx context.Context, uri, color, tagsJSON, cid string) error { + return db.q.UpdateHighlight(ctx, sqlcdb.UpdateHighlightParams{ + Color: &color, + TagsJson: &tagsJSON, + Cid: &cid, + IndexedAt: time.Now(), + Uri: uri, + }) } -func (db *DB) GetHighlightsByURIs(uris []string) ([]Highlight, error) { +func (db *DB) GetHighlightsByURIs(ctx context.Context, uris []string) ([]Highlight, error) { if len(uris) == 0 { return []Highlight{}, nil } - rows, err := db.Query(` - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid - FROM all_highlights - WHERE uri = ANY($1) - `, pqStringArray(uris)) + rows, err := db.q.GetHighlightsByURIs(ctx, uris) if err != nil { return nil, err } - defer rows.Close() + return mapHighlights(rows), nil +} - return scanHighlights(rows) +func (db *DB) GetHighlightURIs(ctx context.Context, authorDID string) ([]string, error) { + return db.q.GetHighlightURIs(ctx, authorDID) } -func (db *DB) GetHighlightURIs(authorDID string) ([]string, error) { - rows, err := db.Query(` - SELECT uri FROM all_highlights WHERE author_did = $1 - `, authorDID) - if err != nil { - return nil, err +func mapHighlight(r sqlcdb.AllHighlight) Highlight { + return Highlight{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + TargetSource: r.TargetSource, + TargetHash: r.TargetHash, + TargetTitle: r.TargetTitle, + SelectorJSON: r.SelectorJson, + Color: r.Color, + TagsJSON: r.TagsJson, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + CID: r.Cid, } - defer rows.Close() +} - var uris []string - for rows.Next() { - var uri string - if err := rows.Scan(&uri); err != nil { - return nil, err - } - uris = append(uris, uri) +func mapHighlights(rows []sqlcdb.AllHighlight) []Highlight { + var highlights []Highlight + for _, r := range rows { + highlights = append(highlights, mapHighlight(r)) } - return uris, nil + return highlights } func scanHighlights(rows interface { diff --git a/backend/internal/db/queries_history.go b/backend/internal/db/queries_history.go index 55eba4e..9a5901c 100644 --- a/backend/internal/db/queries_history.go +++ b/backend/internal/db/queries_history.go @@ -1,75 +1,58 @@ package db import ( - "fmt" - "strings" + "context" "time" + + "margin.at/internal/db/sqlcdb" ) -func (db *DB) SaveEditHistory(uri, recordType, previousContent string, previousCID *string) error { - _, err := db.Exec(` - INSERT INTO edit_history (uri, record_type, previous_content, previous_cid, edited_at) - VALUES ($1, $2, $3, $4, $5) - `, uri, recordType, previousContent, previousCID, time.Now()) - return err +func (db *DB) SaveEditHistory(ctx context.Context, uri, recordType, previousContent string, previousCID *string) error { + return db.q.SaveEditHistory(ctx, sqlcdb.SaveEditHistoryParams{ + Uri: uri, + RecordType: recordType, + PreviousContent: previousContent, + PreviousCid: previousCID, + EditedAt: time.Now(), + }) } -func (db *DB) GetEditHistory(uri string) ([]EditHistory, error) { - rows, err := db.Query(` - SELECT id, uri, record_type, previous_content, previous_cid, edited_at - FROM edit_history - WHERE uri = $1 - ORDER BY edited_at DESC - `, uri) +func (db *DB) GetEditHistory(ctx context.Context, uri string) ([]EditHistory, error) { + rows, err := db.q.GetEditHistory(ctx, uri) if err != nil { return nil, err } - defer rows.Close() - var history []EditHistory - for rows.Next() { - var h EditHistory - if err := rows.Scan(&h.ID, &h.URI, &h.RecordType, &h.PreviousContent, &h.PreviousCID, &h.EditedAt); err != nil { - return nil, err - } - history = append(history, h) + for _, r := range rows { + history = append(history, EditHistory{ + ID: int(r.ID), + URI: r.Uri, + RecordType: r.RecordType, + PreviousContent: r.PreviousContent, + PreviousCID: r.PreviousCid, + EditedAt: r.EditedAt, + }) } return history, nil } -func (db *DB) GetLatestEditTimes(uris []string) (map[string]time.Time, error) { +func (db *DB) GetLatestEditTimes(ctx context.Context, uris []string) (map[string]time.Time, error) { if len(uris) == 0 { return nil, nil } - placeholders := make([]string, len(uris)) - args := make([]interface{}, len(uris)) - for i, uri := range uris { - placeholders[i] = fmt.Sprintf("$%d", i+1) - args[i] = uri - } - - query := ` - SELECT uri, MAX(edited_at) as edited_at - FROM edit_history - WHERE uri IN (` + strings.Join(placeholders, ",") + `) - GROUP BY uri - ` - - rows, err := db.Query(query, args...) + rows, err := db.q.GetLatestEditTimes(ctx, uris) if err != nil { return nil, err } - defer rows.Close() result := make(map[string]time.Time) - for rows.Next() { - var uri string - var editedAt time.Time - if err := rows.Scan(&uri, &editedAt); err != nil { + for _, r := range rows { + editedAt, ok := r.EditedAt.(time.Time) + if !ok { continue } - result[uri] = editedAt + result[r.Uri] = editedAt } return result, nil diff --git a/backend/internal/db/queries_keys.go b/backend/internal/db/queries_keys.go index 325ce1a..ceaa490 100644 --- a/backend/internal/db/queries_keys.go +++ b/backend/internal/db/queries_keys.go @@ -1,59 +1,67 @@ package db import ( + "context" "time" + + "margin.at/internal/db/sqlcdb" ) -func (db *DB) CreateAPIKey(key *APIKey) error { - _, err := db.Exec(` - INSERT INTO api_keys (id, owner_did, name, key_hash, created_at, uri, cid) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT (id) DO UPDATE SET - name = EXCLUDED.name, - key_hash = EXCLUDED.key_hash, - uri = EXCLUDED.uri, - cid = EXCLUDED.cid - `, key.ID, key.OwnerDID, key.Name, key.KeyHash, key.CreatedAt, key.URI, key.CID) - return err +func (db *DB) CreateAPIKey(ctx context.Context, key *APIKey) error { + var uri *string + if key.URI != "" { + u := key.URI + uri = &u + } + return db.q.CreateAPIKey(ctx, sqlcdb.CreateAPIKeyParams{ + ID: key.ID, + OwnerDid: key.OwnerDID, + Name: key.Name, + KeyHash: key.KeyHash, + CreatedAt: key.CreatedAt, + Uri: uri, + Cid: key.CID, + }) } -func (db *DB) GetAPIKeysByOwner(ownerDID string) ([]APIKey, error) { - rows, err := db.Query(` - SELECT id, owner_did, name, key_hash, created_at, last_used_at - FROM api_keys - WHERE owner_did = $1 - ORDER BY created_at DESC - `, ownerDID) +func (db *DB) GetAPIKeysByOwner(ctx context.Context, ownerDID string) ([]APIKey, error) { + rows, err := db.q.GetAPIKeysByOwner(ctx, ownerDID) if err != nil { return nil, err } - defer rows.Close() - - var keys []APIKey - for rows.Next() { - var k APIKey - if err := rows.Scan(&k.ID, &k.OwnerDID, &k.Name, &k.KeyHash, &k.CreatedAt, &k.LastUsedAt); err != nil { - return nil, err - } - keys = append(keys, k) + keys := make([]APIKey, 0, len(rows)) + for _, r := range rows { + keys = append(keys, APIKey{ + ID: r.ID, + OwnerDID: r.OwnerDid, + Name: r.Name, + KeyHash: r.KeyHash, + CreatedAt: r.CreatedAt, + LastUsedAt: r.LastUsedAt, + }) } return keys, nil } -func (db *DB) GetAPIKeyByHash(keyHash string) (*APIKey, error) { - var k APIKey - err := db.QueryRow(` - SELECT id, owner_did, name, key_hash, created_at, last_used_at - FROM api_keys - WHERE key_hash = $1 - `, keyHash).Scan(&k.ID, &k.OwnerDID, &k.Name, &k.KeyHash, &k.CreatedAt, &k.LastUsedAt) +func (db *DB) GetAPIKeyByHash(ctx context.Context, keyHash string) (*APIKey, error) { + r, err := db.q.GetAPIKeyByHash(ctx, keyHash) if err != nil { return nil, err } - return &k, nil + return &APIKey{ + ID: r.ID, + OwnerDID: r.OwnerDid, + Name: r.Name, + KeyHash: r.KeyHash, + CreatedAt: r.CreatedAt, + LastUsedAt: r.LastUsedAt, + }, nil } -func (db *DB) UpdateAPIKeyLastUsed(id string) error { - _, err := db.Exec(`UPDATE api_keys SET last_used_at = $1 WHERE id = $2`, time.Now(), id) - return err +func (db *DB) UpdateAPIKeyLastUsed(ctx context.Context, id string) error { + now := time.Now() + return db.q.UpdateAPIKeyLastUsed(ctx, sqlcdb.UpdateAPIKeyLastUsedParams{ + LastUsedAt: &now, + ID: id, + }) } diff --git a/backend/internal/db/queries_kv.go b/backend/internal/db/queries_kv.go index ba727b0..f808a26 100644 --- a/backend/internal/db/queries_kv.go +++ b/backend/internal/db/queries_kv.go @@ -1,15 +1,21 @@ package db -func (db *DB) GetOrCreateKV(key, value string) (string, error) { - _, err := db.Exec( - `INSERT INTO kv_store (key, value, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (key) DO NOTHING`, - key, value) - if err != nil { +import ( + "context" + + "margin.at/internal/db/sqlcdb" +) + +func (db *DB) GetOrCreateKV(ctx context.Context, key, value string) (string, error) { + if err := db.q.InsertKVIgnore(ctx, sqlcdb.InsertKVIgnoreParams{ + Key: key, + Value: value, + }); err != nil { return "", err } - var v string - if err := db.QueryRow(`SELECT value FROM kv_store WHERE key = $1`, key).Scan(&v); err != nil { + v, err := db.q.GetKVValue(ctx, key) + if err != nil { return "", err } return v, nil diff --git a/backend/internal/db/queries_likes.go b/backend/internal/db/queries_likes.go index 33b8b6a..d53a590 100644 --- a/backend/internal/db/queries_likes.go +++ b/backend/internal/db/queries_likes.go @@ -1,128 +1,98 @@ package db -import "fmt" - -func (db *DB) CreateLike(l *Like) error { - _, err := db.Exec(` - INSERT INTO likes (uri, author_did, subject_uri, created_at, indexed_at) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT(uri) DO NOTHING - `, l.URI, l.AuthorDID, l.SubjectURI, l.CreatedAt, l.IndexedAt) - return err +import ( + "context" + + "margin.at/internal/db/sqlcdb" +) + +func (db *DB) CreateLike(ctx context.Context, l *Like) error { + return db.q.CreateLike(ctx, sqlcdb.CreateLikeParams{ + Uri: l.URI, + AuthorDid: l.AuthorDID, + SubjectUri: l.SubjectURI, + CreatedAt: l.CreatedAt, + IndexedAt: l.IndexedAt, + }) } -func (db *DB) DeleteLike(uri string) error { - _, err := db.Exec(`DELETE FROM likes WHERE uri = $1`, uri) - return err +func (db *DB) DeleteLike(ctx context.Context, uri string) error { + return db.q.DeleteLike(ctx, uri) } -func (db *DB) GetLikesByAuthor(authorDID string) ([]Like, error) { - rows, err := db.Query(` - SELECT uri, author_did, subject_uri, created_at, indexed_at - FROM likes - WHERE author_did = $1 - ORDER BY created_at DESC - `, authorDID) +func (db *DB) GetLikesByAuthor(ctx context.Context, authorDID string) ([]Like, error) { + rows, err := db.q.GetLikesByAuthor(ctx, authorDID) if err != nil { return nil, err } - defer rows.Close() - var likes []Like - for rows.Next() { - var l Like - if err := rows.Scan(&l.URI, &l.AuthorDID, &l.SubjectURI, &l.CreatedAt, &l.IndexedAt); err != nil { - return nil, err - } - likes = append(likes, l) + for _, r := range rows { + likes = append(likes, Like{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + SubjectURI: r.SubjectUri, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + }) } return likes, nil } -func (db *DB) GetLikeCount(subjectURI string) (int, error) { - var count int - err := db.QueryRow(`SELECT COUNT(*) FROM likes WHERE subject_uri = $1`, subjectURI).Scan(&count) - return count, err +func (db *DB) GetLikeCount(ctx context.Context, subjectURI string) (int, error) { + count, err := db.q.GetLikeCount(ctx, subjectURI) + return int(count), err } -func (db *DB) GetLikeByUserAndSubject(userDID, subjectURI string) (*Like, error) { - var like Like - err := db.QueryRow(` - SELECT uri, author_did, subject_uri, created_at, indexed_at - FROM likes - WHERE author_did = $1 AND subject_uri = $2 - `, userDID, subjectURI).Scan(&like.URI, &like.AuthorDID, &like.SubjectURI, &like.CreatedAt, &like.IndexedAt) +func (db *DB) GetLikeByUserAndSubject(ctx context.Context, userDID, subjectURI string) (*Like, error) { + r, err := db.q.GetLikeByUserAndSubject(ctx, sqlcdb.GetLikeByUserAndSubjectParams{ + AuthorDid: userDID, + SubjectUri: subjectURI, + }) if err != nil { return nil, err } - return &like, nil + return &Like{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + SubjectURI: r.SubjectUri, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + }, nil } -func (db *DB) GetLikeCounts(subjectURIs []string) (map[string]int, error) { +func (db *DB) GetLikeCounts(ctx context.Context, subjectURIs []string) (map[string]int, error) { if len(subjectURIs) == 0 { return map[string]int{}, nil } - query := ` - SELECT subject_uri, COUNT(*) - FROM likes - WHERE subject_uri IN (` + buildPlaceholders(len(subjectURIs), 1) + `) - GROUP BY subject_uri - ` - - args := make([]interface{}, len(subjectURIs)) - for i, uri := range subjectURIs { - args[i] = uri - } - - rows, err := db.Query(query, args...) + rows, err := db.q.GetLikeCounts(ctx, subjectURIs) if err != nil { return nil, err } - defer rows.Close() counts := make(map[string]int) - for rows.Next() { - var uri string - var count int - if err := rows.Scan(&uri, &count); err != nil { - return nil, err - } - counts[uri] = count + for _, r := range rows { + counts[r.SubjectUri] = int(r.Count) } return counts, nil } -func (db *DB) GetViewerLikes(viewerDID string, subjectURIs []string) (map[string]bool, error) { +func (db *DB) GetViewerLikes(ctx context.Context, viewerDID string, subjectURIs []string) (map[string]bool, error) { if len(subjectURIs) == 0 { return map[string]bool{}, nil } - query := fmt.Sprintf(` - SELECT subject_uri - FROM likes - WHERE author_did = $1 AND subject_uri IN (%s) - `, buildPlaceholders(len(subjectURIs), 2)) - - args := make([]interface{}, len(subjectURIs)+1) - args[0] = viewerDID - for i, uri := range subjectURIs { - args[i+1] = uri - } - - rows, err := db.Query(query, args...) + rows, err := db.q.GetViewerLikes(ctx, sqlcdb.GetViewerLikesParams{ + AuthorDid: viewerDID, + Column2: subjectURIs, + }) if err != nil { return nil, err } - defer rows.Close() likes := make(map[string]bool) - for rows.Next() { - var uri string - if err := rows.Scan(&uri); err != nil { - return nil, err - } + for _, uri := range rows { likes[uri] = true } diff --git a/backend/internal/db/queries_moderation.go b/backend/internal/db/queries_moderation.go index facda00..a911b91 100644 --- a/backend/internal/db/queries_moderation.go +++ b/backend/internal/db/queries_moderation.go @@ -1,152 +1,116 @@ package db import ( - "fmt" - "strings" + "context" "time" + + "margin.at/internal/db/sqlcdb" ) -func (db *DB) CreateBlock(actorDID, subjectDID string) error { - _, err := db.Exec(` - INSERT INTO blocks (actor_did, subject_did, created_at) VALUES ($1, $2, $3) - ON CONFLICT(actor_did, subject_did) DO NOTHING - `, actorDID, subjectDID, time.Now()) - return err +func (db *DB) CreateBlock(ctx context.Context, actorDID, subjectDID string) error { + return db.q.CreateBlock(ctx, sqlcdb.CreateBlockParams{ + ActorDid: actorDID, + SubjectDid: subjectDID, + CreatedAt: time.Now(), + }) } -func (db *DB) DeleteBlock(actorDID, subjectDID string) error { - _, err := db.Exec(`DELETE FROM blocks WHERE actor_did = $1 AND subject_did = $2`, actorDID, subjectDID) - return err +func (db *DB) DeleteBlock(ctx context.Context, actorDID, subjectDID string) error { + return db.q.DeleteBlock(ctx, sqlcdb.DeleteBlockParams{ + ActorDid: actorDID, + SubjectDid: subjectDID, + }) } -func (db *DB) GetBlocks(actorDID string) ([]Block, error) { - rows, err := db.Query(`SELECT id, actor_did, subject_did, created_at FROM blocks WHERE actor_did = $1 ORDER BY created_at DESC`, actorDID) +func (db *DB) GetBlocks(ctx context.Context, actorDID string) ([]Block, error) { + rows, err := db.q.GetBlocks(ctx, actorDID) if err != nil { return nil, err } - defer rows.Close() - var blocks []Block - for rows.Next() { - var b Block - if err := rows.Scan(&b.ID, &b.ActorDID, &b.SubjectDID, &b.CreatedAt); err != nil { - continue - } - blocks = append(blocks, b) + for _, r := range rows { + blocks = append(blocks, Block{ + ID: int(r.ID), + ActorDID: r.ActorDid, + SubjectDID: r.SubjectDid, + CreatedAt: r.CreatedAt, + }) } return blocks, nil } -func (db *DB) IsBlocked(actorDID, subjectDID string) (bool, error) { - var exists bool - err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM blocks WHERE actor_did = $1 AND subject_did = $2)`, actorDID, subjectDID).Scan(&exists) - return exists, err +func (db *DB) IsBlocked(ctx context.Context, actorDID, subjectDID string) (bool, error) { + return db.q.IsBlocked(ctx, sqlcdb.IsBlockedParams{ + ActorDid: actorDID, + SubjectDid: subjectDID, + }) } -func (db *DB) IsBlockedEither(did1, did2 string) (bool, error) { - var exists bool - err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM blocks WHERE (actor_did = $1 AND subject_did = $2) OR (actor_did = $2 AND subject_did = $1))`, did1, did2).Scan(&exists) - return exists, err +func (db *DB) IsBlockedEither(ctx context.Context, did1, did2 string) (bool, error) { + return db.q.IsBlockedEither(ctx, sqlcdb.IsBlockedEitherParams{ + ActorDid: did1, + SubjectDid: did2, + }) } -func (db *DB) GetBlockedDIDs(actorDID string) ([]string, error) { - rows, err := db.Query(`SELECT subject_did FROM blocks WHERE actor_did = $1`, actorDID) - if err != nil { - return nil, err - } - defer rows.Close() - - var dids []string - for rows.Next() { - var did string - if err := rows.Scan(&did); err != nil { - continue - } - dids = append(dids, did) - } - return dids, nil +func (db *DB) GetBlockedDIDs(ctx context.Context, actorDID string) ([]string, error) { + return db.q.GetBlockedDIDs(ctx, actorDID) } -func (db *DB) GetBlockedByDIDs(actorDID string) ([]string, error) { - rows, err := db.Query(`SELECT actor_did FROM blocks WHERE subject_did = $1`, actorDID) - if err != nil { - return nil, err - } - defer rows.Close() - - var dids []string - for rows.Next() { - var did string - if err := rows.Scan(&did); err != nil { - continue - } - dids = append(dids, did) - } - return dids, nil +func (db *DB) GetBlockedByDIDs(ctx context.Context, actorDID string) ([]string, error) { + return db.q.GetBlockedByDIDs(ctx, actorDID) } -func (db *DB) CreateMute(actorDID, subjectDID string) error { - _, err := db.Exec(` - INSERT INTO mutes (actor_did, subject_did, created_at) VALUES ($1, $2, $3) - ON CONFLICT(actor_did, subject_did) DO NOTHING - `, actorDID, subjectDID, time.Now()) - return err +func (db *DB) CreateMute(ctx context.Context, actorDID, subjectDID string) error { + return db.q.CreateMute(ctx, sqlcdb.CreateMuteParams{ + ActorDid: actorDID, + SubjectDid: subjectDID, + CreatedAt: time.Now(), + }) } -func (db *DB) DeleteMute(actorDID, subjectDID string) error { - _, err := db.Exec(`DELETE FROM mutes WHERE actor_did = $1 AND subject_did = $2`, actorDID, subjectDID) - return err +func (db *DB) DeleteMute(ctx context.Context, actorDID, subjectDID string) error { + return db.q.DeleteMute(ctx, sqlcdb.DeleteMuteParams{ + ActorDid: actorDID, + SubjectDid: subjectDID, + }) } -func (db *DB) GetMutes(actorDID string) ([]Mute, error) { - rows, err := db.Query(`SELECT id, actor_did, subject_did, created_at FROM mutes WHERE actor_did = $1 ORDER BY created_at DESC`, actorDID) +func (db *DB) GetMutes(ctx context.Context, actorDID string) ([]Mute, error) { + rows, err := db.q.GetMutes(ctx, actorDID) if err != nil { return nil, err } - defer rows.Close() - var mutes []Mute - for rows.Next() { - var m Mute - if err := rows.Scan(&m.ID, &m.ActorDID, &m.SubjectDID, &m.CreatedAt); err != nil { - continue - } - mutes = append(mutes, m) + for _, r := range rows { + mutes = append(mutes, Mute{ + ID: int(r.ID), + ActorDID: r.ActorDid, + SubjectDID: r.SubjectDid, + CreatedAt: r.CreatedAt, + }) } return mutes, nil } -func (db *DB) IsMuted(actorDID, subjectDID string) (bool, error) { - var exists bool - err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM mutes WHERE actor_did = $1 AND subject_did = $2)`, actorDID, subjectDID).Scan(&exists) - return exists, err +func (db *DB) IsMuted(ctx context.Context, actorDID, subjectDID string) (bool, error) { + return db.q.IsMuted(ctx, sqlcdb.IsMutedParams{ + ActorDid: actorDID, + SubjectDid: subjectDID, + }) } -func (db *DB) GetMutedDIDs(actorDID string) ([]string, error) { - rows, err := db.Query(`SELECT subject_did FROM mutes WHERE actor_did = $1`, actorDID) - if err != nil { - return nil, err - } - defer rows.Close() - - var dids []string - for rows.Next() { - var did string - if err := rows.Scan(&did); err != nil { - continue - } - dids = append(dids, did) - } - return dids, nil +func (db *DB) GetMutedDIDs(ctx context.Context, actorDID string) ([]string, error) { + return db.q.GetMutedDIDs(ctx, actorDID) } -func (db *DB) GetAllHiddenDIDs(actorDID string) (map[string]bool, error) { +func (db *DB) GetAllHiddenDIDs(ctx context.Context, actorDID string) (map[string]bool, error) { hidden := make(map[string]bool) if actorDID == "" { return hidden, nil } - blocked, err := db.GetBlockedDIDs(actorDID) + blocked, err := db.GetBlockedDIDs(ctx, actorDID) if err != nil { return hidden, err } @@ -154,7 +118,7 @@ func (db *DB) GetAllHiddenDIDs(actorDID string) (map[string]bool, error) { hidden[did] = true } - blockedBy, err := db.GetBlockedByDIDs(actorDID) + blockedBy, err := db.GetBlockedByDIDs(ctx, actorDID) if err != nil { return hidden, err } @@ -162,7 +126,7 @@ func (db *DB) GetAllHiddenDIDs(actorDID string) (map[string]bool, error) { hidden[did] = true } - muted, err := db.GetMutedDIDs(actorDID) + muted, err := db.GetMutedDIDs(ctx, actorDID) if err != nil { return hidden, err } @@ -173,246 +137,277 @@ func (db *DB) GetAllHiddenDIDs(actorDID string) (map[string]bool, error) { return hidden, nil } -func (db *DB) GetViewerRelationship(viewerDID, subjectDID string) (blocked bool, muted bool, blockedBy bool, err error) { +func (db *DB) GetViewerRelationship(ctx context.Context, viewerDID, subjectDID string) (blocked bool, muted bool, blockedBy bool, err error) { if viewerDID == "" || subjectDID == "" { return false, false, false, nil } - blocked, err = db.IsBlocked(viewerDID, subjectDID) + blocked, err = db.IsBlocked(ctx, viewerDID, subjectDID) if err != nil { return } - muted, err = db.IsMuted(viewerDID, subjectDID) + muted, err = db.IsMuted(ctx, viewerDID, subjectDID) if err != nil { return } - blockedBy, err = db.IsBlocked(subjectDID, viewerDID) + blockedBy, err = db.IsBlocked(ctx, subjectDID, viewerDID) return } -func (db *DB) CreateReport(reporterDID, subjectDID string, subjectURI *string, reasonType string, reasonText *string) (int, error) { - var id int - err := db.QueryRow(` - INSERT INTO moderation_reports (reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at) - VALUES ($1, $2, $3, $4, $5, 'pending', $6) - RETURNING id - `, reporterDID, subjectDID, subjectURI, reasonType, reasonText, time.Now()).Scan(&id) - return id, err +func (db *DB) CreateReport(ctx context.Context, reporterDID, subjectDID string, subjectURI *string, reasonType string, reasonText *string) (int, error) { + id, err := db.q.CreateReport(ctx, sqlcdb.CreateReportParams{ + ReporterDid: reporterDID, + SubjectDid: subjectDID, + SubjectUri: subjectURI, + ReasonType: reasonType, + ReasonText: reasonText, + CreatedAt: time.Now(), + }) + return int(id), err } -func (db *DB) GetReports(status string, limit, offset int) ([]ModerationReport, error) { - query := `SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by - FROM moderation_reports` - args := []interface{}{} - paramIdx := 1 - +func (db *DB) GetReports(ctx context.Context, status string, limit, offset int) ([]ModerationReport, error) { + var rows []sqlcdb.ModerationReport + var err error if status != "" { - query += ` WHERE status = $1` - args = append(args, status) - paramIdx = 2 + rows, err = db.q.GetReportsByStatus(ctx, sqlcdb.GetReportsByStatusParams{ + Status: status, + Limit: int32(limit), + Offset: int32(offset), + }) + } else { + rows, err = db.q.GetReports(ctx, sqlcdb.GetReportsParams{ + Limit: int32(limit), + Offset: int32(offset), + }) } - - query += ` ORDER BY created_at DESC LIMIT $` + itoa(paramIdx) + ` OFFSET $` + itoa(paramIdx+1) - args = append(args, limit, offset) - - rows, err := db.Query(query, args...) if err != nil { return nil, err } - defer rows.Close() var reports []ModerationReport - for rows.Next() { - var r ModerationReport - if err := rows.Scan(&r.ID, &r.ReporterDID, &r.SubjectDID, &r.SubjectURI, &r.ReasonType, &r.ReasonText, &r.Status, &r.CreatedAt, &r.ResolvedAt, &r.ResolvedBy); err != nil { - continue - } - reports = append(reports, r) + for _, r := range rows { + reports = append(reports, ModerationReport{ + ID: int(r.ID), + ReporterDID: r.ReporterDid, + SubjectDID: r.SubjectDid, + SubjectURI: r.SubjectUri, + ReasonType: r.ReasonType, + ReasonText: r.ReasonText, + Status: r.Status, + CreatedAt: r.CreatedAt, + ResolvedAt: r.ResolvedAt, + ResolvedBy: r.ResolvedBy, + }) } return reports, nil } -func (db *DB) GetReport(id int) (*ModerationReport, error) { - var r ModerationReport - err := db.QueryRow(`SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by FROM moderation_reports WHERE id = $1`, id).Scan( - &r.ID, &r.ReporterDID, &r.SubjectDID, &r.SubjectURI, &r.ReasonType, &r.ReasonText, &r.Status, &r.CreatedAt, &r.ResolvedAt, &r.ResolvedBy, - ) +func (db *DB) GetReport(ctx context.Context, id int) (*ModerationReport, error) { + r, err := db.q.GetReport(ctx, int32(id)) if err != nil { return nil, err } - return &r, nil -} - -func (db *DB) ResolveReport(id int, resolvedBy string, status string) error { - _, err := db.Exec(`UPDATE moderation_reports SET status = $1, resolved_at = $2, resolved_by = $3 WHERE id = $4`, status, time.Now(), resolvedBy, id) - return err -} - -func (db *DB) CreateModerationAction(reportID int, actorDID, action string, comment *string) error { - _, err := db.Exec(`INSERT INTO moderation_actions (report_id, actor_did, action, comment, created_at) VALUES ($1, $2, $3, $4, $5)`, reportID, actorDID, action, comment, time.Now()) - return err -} - -func (db *DB) GetReportActions(reportID int) ([]ModerationAction, error) { - rows, err := db.Query(`SELECT id, report_id, actor_did, action, comment, created_at FROM moderation_actions WHERE report_id = $1 ORDER BY created_at DESC`, reportID) + return &ModerationReport{ + ID: int(r.ID), + ReporterDID: r.ReporterDid, + SubjectDID: r.SubjectDid, + SubjectURI: r.SubjectUri, + ReasonType: r.ReasonType, + ReasonText: r.ReasonText, + Status: r.Status, + CreatedAt: r.CreatedAt, + ResolvedAt: r.ResolvedAt, + ResolvedBy: r.ResolvedBy, + }, nil +} + +func (db *DB) ResolveReport(ctx context.Context, id int, resolvedBy string, status string) error { + now := time.Now() + return db.q.ResolveReport(ctx, sqlcdb.ResolveReportParams{ + Status: status, + ResolvedAt: &now, + ResolvedBy: &resolvedBy, + ID: int32(id), + }) +} + +func (db *DB) CreateModerationAction(ctx context.Context, reportID int, actorDID, action string, comment *string) error { + return db.q.CreateModerationAction(ctx, sqlcdb.CreateModerationActionParams{ + ReportID: int32(reportID), + ActorDid: actorDID, + Action: action, + Comment: comment, + CreatedAt: time.Now(), + }) +} + +func (db *DB) GetReportActions(ctx context.Context, reportID int) ([]ModerationAction, error) { + rows, err := db.q.GetReportActions(ctx, int32(reportID)) if err != nil { return nil, err } - defer rows.Close() - var actions []ModerationAction - for rows.Next() { - var a ModerationAction - if err := rows.Scan(&a.ID, &a.ReportID, &a.ActorDID, &a.Action, &a.Comment, &a.CreatedAt); err != nil { - continue - } - actions = append(actions, a) + for _, r := range rows { + actions = append(actions, ModerationAction{ + ID: int(r.ID), + ReportID: int(r.ReportID), + ActorDID: r.ActorDid, + Action: r.Action, + Comment: r.Comment, + CreatedAt: r.CreatedAt, + }) } return actions, nil } -func (db *DB) GetReportCount(status string) (int, error) { - query := `SELECT COUNT(*) FROM moderation_reports` - args := []interface{}{} +func (db *DB) GetReportCount(ctx context.Context, status string) (int, error) { if status != "" { - query += ` WHERE status = $1` - args = append(args, status) + n, err := db.q.GetReportCountByStatus(ctx, status) + return int(n), err } - var count int - err := db.QueryRow(query, args...).Scan(&count) - return count, err + n, err := db.q.GetReportCount(ctx) + return int(n), err } -func (db *DB) CreateContentLabel(src, uri, val, createdBy string) error { - _, err := db.Exec(`INSERT INTO content_labels (src, uri, val, neg, created_by, created_at) VALUES ($1, $2, $3, 0, $4, $5)`, src, uri, val, createdBy, time.Now()) - return err +func (db *DB) CreateContentLabel(ctx context.Context, src, uri, val, createdBy string) error { + return db.q.CreateContentLabel(ctx, sqlcdb.CreateContentLabelParams{ + Src: src, + Uri: uri, + Val: val, + CreatedBy: createdBy, + CreatedAt: time.Now(), + }) } -func (db *DB) SyncSelfLabels(authorDID, uri string, labels []string) error { - _, err := db.Exec(`DELETE FROM content_labels WHERE src = $1 AND uri = $2 AND created_by = $3`, authorDID, uri, authorDID) - if err != nil { +func (db *DB) SyncSelfLabels(ctx context.Context, authorDID, uri string, labels []string) error { + if err := db.q.DeleteSelfLabels(ctx, sqlcdb.DeleteSelfLabelsParams{ + Src: authorDID, + Uri: uri, + CreatedBy: authorDID, + }); err != nil { return err } for _, val := range labels { - if err := db.CreateContentLabel(authorDID, uri, val, authorDID); err != nil { + if err := db.CreateContentLabel(ctx, authorDID, uri, val, authorDID); err != nil { return err } } return nil } -func (db *DB) NegateContentLabel(id int) error { - _, err := db.Exec(`UPDATE content_labels SET neg = 1 WHERE id = $1`, id) - return err +func (db *DB) NegateContentLabel(ctx context.Context, id int) error { + return db.q.NegateContentLabel(ctx, int32(id)) } -func (db *DB) DeleteContentLabel(id int) error { - _, err := db.Exec(`DELETE FROM content_labels WHERE id = $1`, id) - return err +func (db *DB) DeleteContentLabel(ctx context.Context, id int) error { + return db.q.DeleteContentLabel(ctx, int32(id)) } -func (db *DB) GetContentLabelsForURIs(uris []string, labelerDIDs []string) (map[string][]ContentLabel, error) { +func (db *DB) GetContentLabelsForURIs(ctx context.Context, uris []string, labelerDIDs []string) (map[string][]ContentLabel, error) { result := make(map[string][]ContentLabel) if len(uris) == 0 { return result, nil } - query := `SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels - WHERE uri = ANY($1) AND neg = 0` - args := []interface{}{pqStringArray(uris)} - + var rows []sqlcdb.ContentLabel + var err error if len(labelerDIDs) > 0 { - query += ` AND src = ANY($2)` - args = append(args, pqStringArray(labelerDIDs)) + rows, err = db.q.GetContentLabelsForURIsBySrc(ctx, sqlcdb.GetContentLabelsForURIsBySrcParams{ + Column1: uris, + Column2: labelerDIDs, + }) + } else { + rows, err = db.q.GetContentLabelsForURIs(ctx, uris) } - - query += ` ORDER BY created_at DESC` - - rows, err := db.Query(query, args...) if err != nil { return result, err } - defer rows.Close() - for rows.Next() { - var l ContentLabel - if err := rows.Scan(&l.ID, &l.Src, &l.URI, &l.Val, &l.Neg, &l.CreatedBy, &l.CreatedAt); err != nil { - continue + for _, r := range rows { + l := ContentLabel{ + ID: int(r.ID), + Src: r.Src, + URI: r.Uri, + Val: r.Val, + Neg: r.Neg != 0, + CreatedBy: r.CreatedBy, + CreatedAt: r.CreatedAt, } result[l.URI] = append(result[l.URI], l) } return result, nil } -func (db *DB) GetContentLabelsForDIDs(dids []string, labelerDIDs []string) (map[string][]ContentLabel, error) { +func (db *DB) GetContentLabelsForDIDs(ctx context.Context, dids []string, labelerDIDs []string) (map[string][]ContentLabel, error) { result := make(map[string][]ContentLabel) if len(dids) == 0 { return result, nil } - query := `SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels - WHERE uri = ANY($1) AND neg = 0` - args := []interface{}{pqStringArray(dids)} - + var rows []sqlcdb.ContentLabel + var err error if len(labelerDIDs) > 0 { - query += ` AND src = ANY($2)` - args = append(args, pqStringArray(labelerDIDs)) + rows, err = db.q.GetContentLabelsForURIsBySrc(ctx, sqlcdb.GetContentLabelsForURIsBySrcParams{ + Column1: dids, + Column2: labelerDIDs, + }) + } else { + rows, err = db.q.GetContentLabelsForURIs(ctx, dids) } - - query += ` ORDER BY created_at DESC` - - rows, err := db.Query(query, args...) if err != nil { return result, err } - defer rows.Close() - for rows.Next() { - var l ContentLabel - if err := rows.Scan(&l.ID, &l.Src, &l.URI, &l.Val, &l.Neg, &l.CreatedBy, &l.CreatedAt); err != nil { - continue + for _, r := range rows { + l := ContentLabel{ + ID: int(r.ID), + Src: r.Src, + URI: r.Uri, + Val: r.Val, + Neg: r.Neg != 0, + CreatedBy: r.CreatedBy, + CreatedAt: r.CreatedAt, } result[l.URI] = append(result[l.URI], l) } return result, nil } -func (db *DB) GetAllContentLabels(limit, offset int) ([]ContentLabel, error) { - rows, err := db.Query(`SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels ORDER BY created_at DESC LIMIT $1 OFFSET $2`, limit, offset) +func (db *DB) GetAllContentLabels(ctx context.Context, limit, offset int) ([]ContentLabel, error) { + rows, err := db.q.GetAllContentLabels(ctx, sqlcdb.GetAllContentLabelsParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - var labels []ContentLabel - for rows.Next() { - var l ContentLabel - if err := rows.Scan(&l.ID, &l.Src, &l.URI, &l.Val, &l.Neg, &l.CreatedBy, &l.CreatedAt); err != nil { - continue - } - labels = append(labels, l) + for _, r := range rows { + labels = append(labels, ContentLabel{ + ID: int(r.ID), + Src: r.Src, + URI: r.Uri, + Val: r.Val, + Neg: r.Neg != 0, + CreatedBy: r.CreatedBy, + CreatedAt: r.CreatedAt, + }) } return labels, nil } -func itoa(i int) string { - return strings.Repeat("", 0) + fmt.Sprintf("%d", i) +func (db *DB) MarkTakenDown(ctx context.Context, uri string) error { + return db.q.MarkTakenDown(ctx, sqlcdb.MarkTakenDownParams{ + Uri: uri, + TakenDownAt: time.Now(), + }) } -func (db *DB) MarkTakenDown(uri string) error { - _, err := db.Exec(` - INSERT INTO taken_down_uris (uri, taken_down_at) VALUES ($1, $2) - ON CONFLICT(uri) DO NOTHING - `, uri, time.Now()) - return err -} - -func (db *DB) IsTakenDown(uri string) (bool, error) { - var exists bool - err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM taken_down_uris WHERE uri = $1)`, uri).Scan(&exists) - return exists, err +func (db *DB) IsTakenDown(ctx context.Context, uri string) (bool, error) { + return db.q.IsTakenDown(ctx, uri) } type BannedAccount struct { @@ -422,58 +417,40 @@ type BannedAccount struct { BannedAt time.Time `json:"bannedAt"` } -func (db *DB) BanAccount(did, bannedBy string, reason *string) error { - _, err := db.Exec(` - INSERT INTO banned_accounts (did, reason, banned_by, banned_at) - VALUES ($1, $2, $3, $4) - ON CONFLICT(did) DO UPDATE SET reason = EXCLUDED.reason, banned_by = EXCLUDED.banned_by, banned_at = EXCLUDED.banned_at - `, did, reason, bannedBy, time.Now()) - return err +func (db *DB) BanAccount(ctx context.Context, did, bannedBy string, reason *string) error { + return db.q.BanAccount(ctx, sqlcdb.BanAccountParams{ + Did: did, + Reason: reason, + BannedBy: bannedBy, + BannedAt: time.Now(), + }) } -func (db *DB) UnbanAccount(did string) error { - _, err := db.Exec(`DELETE FROM banned_accounts WHERE did = $1`, did) - return err +func (db *DB) UnbanAccount(ctx context.Context, did string) error { + return db.q.UnbanAccount(ctx, did) } -func (db *DB) IsBanned(did string) (bool, error) { - var exists bool - err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM banned_accounts WHERE did = $1)`, did).Scan(&exists) - return exists, err +func (db *DB) IsBanned(ctx context.Context, did string) (bool, error) { + return db.q.IsBanned(ctx, did) } -func (db *DB) GetBannedAccounts() ([]BannedAccount, error) { - rows, err := db.Query(`SELECT did, reason, banned_by, banned_at FROM banned_accounts ORDER BY banned_at DESC`) +func (db *DB) GetBannedAccounts(ctx context.Context) ([]BannedAccount, error) { + rows, err := db.q.GetBannedAccounts(ctx) if err != nil { return nil, err } - defer rows.Close() - var accounts []BannedAccount - for rows.Next() { - var a BannedAccount - if err := rows.Scan(&a.DID, &a.Reason, &a.BannedBy, &a.BannedAt); err != nil { - continue - } - accounts = append(accounts, a) + for _, r := range rows { + accounts = append(accounts, BannedAccount{ + DID: r.Did, + Reason: r.Reason, + BannedBy: r.BannedBy, + BannedAt: r.BannedAt, + }) } return accounts, nil } -func (db *DB) GetBannedDIDs() ([]string, error) { - rows, err := db.Query(`SELECT did FROM banned_accounts`) - if err != nil { - return nil, err - } - defer rows.Close() - - var dids []string - for rows.Next() { - var did string - if err := rows.Scan(&did); err != nil { - continue - } - dids = append(dids, did) - } - return dids, nil +func (db *DB) GetBannedDIDs(ctx context.Context) ([]string, error) { + return db.q.GetBannedDIDs(ctx) } diff --git a/backend/internal/db/queries_notes.go b/backend/internal/db/queries_notes.go index 2d54acf..3a39c99 100644 --- a/backend/internal/db/queries_notes.go +++ b/backend/internal/db/queries_notes.go @@ -1,72 +1,52 @@ package db import ( - "database/sql" - "fmt" - "strings" + "context" "time" + + "github.com/jackc/pgx/v5" + "margin.at/internal/db/sqlcdb" ) -func (db *DB) CreateNote(n *Note) error { - query := ` - INSERT INTO notes ( - uri, author_did, motivation, color, description, body_value, body_format, body_uri, - target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16 - ) ON CONFLICT (uri) DO UPDATE SET - motivation = EXCLUDED.motivation, - color = EXCLUDED.color, - description = EXCLUDED.description, - body_value = EXCLUDED.body_value, - body_format = EXCLUDED.body_format, - body_uri = EXCLUDED.body_uri, - target_source = EXCLUDED.target_source, - target_hash = EXCLUDED.target_hash, - target_title = EXCLUDED.target_title, - selector_json = EXCLUDED.selector_json, - tags_json = EXCLUDED.tags_json, - indexed_at = EXCLUDED.indexed_at, - cid = EXCLUDED.cid - ` - _, err := db.Exec(query, - n.URI, n.AuthorDID, n.Motivation, n.Color, n.Description, n.BodyValue, n.BodyFormat, n.BodyURI, - n.TargetSource, n.TargetHash, n.TargetTitle, n.SelectorJSON, n.TagsJSON, n.CreatedAt, n.IndexedAt, n.CID, - ) - return err +func (db *DB) CreateNote(ctx context.Context, n *Note) error { + return db.q.CreateNote(ctx, sqlcdb.CreateNoteParams{ + Uri: n.URI, + AuthorDid: n.AuthorDID, + Motivation: &n.Motivation, + Color: n.Color, + Description: n.Description, + BodyValue: n.BodyValue, + BodyFormat: n.BodyFormat, + BodyUri: n.BodyURI, + TargetSource: n.TargetSource, + TargetHash: n.TargetHash, + TargetTitle: n.TargetTitle, + SelectorJson: n.SelectorJSON, + TagsJson: n.TagsJSON, + CreatedAt: n.CreatedAt, + IndexedAt: n.IndexedAt, + Cid: n.CID, + }) } -func (db *DB) GetNoteByURI(uri string) (*Note, error) { - query := ` - SELECT uri, author_did, motivation, color, description, body_value, body_format, body_uri, - target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM notes WHERE uri = $1 - ` - var n Note - err := db.QueryRow(query, uri).Scan( - &n.URI, &n.AuthorDID, &n.Motivation, &n.Color, &n.Description, &n.BodyValue, &n.BodyFormat, &n.BodyURI, - &n.TargetSource, &n.TargetHash, &n.TargetTitle, &n.SelectorJSON, &n.TagsJSON, &n.CreatedAt, &n.IndexedAt, &n.CID, - ) - if err == sql.ErrNoRows { +func (db *DB) GetNoteByURI(ctx context.Context, uri string) (*Note, error) { + r, err := db.q.GetNoteByURI(ctx, uri) + if err == pgx.ErrNoRows { return nil, nil } if err != nil { return nil, err } + n := mapNote(r) return &n, nil } -func (db *DB) MarginNoteBookmarkExists(authorDID, targetHash string) (bool, error) { - var dummy int - err := db.QueryRow(` - SELECT 1 FROM notes - WHERE author_did = $1 - AND target_hash = $2 - AND motivation = 'bookmarking' - AND uri LIKE 'at://%/at.margin.note/%' - LIMIT 1 - `, authorDID, targetHash).Scan(&dummy) - if err == sql.ErrNoRows { +func (db *DB) MarginNoteBookmarkExists(ctx context.Context, authorDID, targetHash string) (bool, error) { + _, err := db.q.MarginNoteBookmarkExists(ctx, sqlcdb.MarginNoteBookmarkExistsParams{ + AuthorDid: authorDID, + TargetHash: targetHash, + }) + if err == pgx.ErrNoRows { return false, nil } if err != nil { @@ -75,41 +55,31 @@ func (db *DB) MarginNoteBookmarkExists(authorDID, targetHash string) (bool, erro return true, nil } -func (db *DB) SaveCommunityBookmarkRef(noteURI, communityURI string) error { - _, err := db.Exec(` - INSERT INTO community_bookmark_refs (note_uri, community_uri) - VALUES ($1, $2) - ON CONFLICT (note_uri) DO UPDATE SET community_uri = EXCLUDED.community_uri - `, noteURI, communityURI) - return err +func (db *DB) SaveCommunityBookmarkRef(ctx context.Context, noteURI, communityURI string) error { + return db.q.SaveCommunityBookmarkRef(ctx, sqlcdb.SaveCommunityBookmarkRefParams{ + NoteUri: noteURI, + CommunityUri: communityURI, + }) } -func (db *DB) GetCommunityBookmarkURI(noteURI string) (string, error) { - var uri string - err := db.QueryRow(` - SELECT community_uri FROM community_bookmark_refs WHERE note_uri = $1 - `, noteURI).Scan(&uri) - if err == sql.ErrNoRows { +func (db *DB) GetCommunityBookmarkURI(ctx context.Context, noteURI string) (string, error) { + uri, err := db.q.GetCommunityBookmarkURI(ctx, noteURI) + if err == pgx.ErrNoRows { return "", nil } return uri, err } -func (db *DB) DeleteCommunityBookmarkRef(noteURI string) error { - _, err := db.Exec(`DELETE FROM community_bookmark_refs WHERE note_uri = $1`, noteURI) - return err +func (db *DB) DeleteCommunityBookmarkRef(ctx context.Context, noteURI string) error { + return db.q.DeleteCommunityBookmarkRef(ctx, noteURI) } -func (db *DB) CommunityBookmarkExists(authorDID, targetHash, tagsJSON string) (bool, error) { - var dummy int - err := db.QueryRow(` - SELECT 1 FROM community_bookmark_refs cbr - JOIN notes n ON n.uri = cbr.note_uri - WHERE n.author_did = $1 - AND n.target_hash = $2 - LIMIT 1 - `, authorDID, targetHash).Scan(&dummy) - if err == sql.ErrNoRows { +func (db *DB) CommunityBookmarkExists(ctx context.Context, authorDID, targetHash, tagsJSON string) (bool, error) { + _, err := db.q.CommunityBookmarkExists(ctx, sqlcdb.CommunityBookmarkExistsParams{ + AuthorDid: authorDID, + TargetHash: targetHash, + }) + if err == pgx.ErrNoRows { return false, nil } if err != nil { @@ -118,67 +88,76 @@ func (db *DB) CommunityBookmarkExists(authorDID, targetHash, tagsJSON string) (b return true, nil } -func (db *DB) GetNotesByURIs(uris []string) ([]Note, error) { +func (db *DB) GetNotesByURIs(ctx context.Context, uris []string) ([]Note, error) { if len(uris) == 0 { return nil, nil } - placeholders := make([]string, len(uris)) - args := make([]interface{}, len(uris)) - for i, u := range uris { - placeholders[i] = fmt.Sprintf("$%d", i+1) - args[i] = u - } - query := ` - SELECT uri, author_did, motivation, color, description, body_value, body_format, body_uri, - target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid - FROM notes WHERE uri IN (` + strings.Join(placeholders, ",") + `)` - rows, err := db.Query(query, args...) + rows, err := db.q.GetNotesByURIs(ctx, uris) if err != nil { return nil, err } - defer rows.Close() var notes []Note - for rows.Next() { - var n Note - if err := rows.Scan( - &n.URI, &n.AuthorDID, &n.Motivation, &n.Color, &n.Description, &n.BodyValue, &n.BodyFormat, &n.BodyURI, - &n.TargetSource, &n.TargetHash, &n.TargetTitle, &n.SelectorJSON, &n.TagsJSON, &n.CreatedAt, &n.IndexedAt, &n.CID, - ); err != nil { - return nil, err - } - notes = append(notes, n) + for _, r := range rows { + notes = append(notes, mapNote(r)) } return notes, nil } -func (db *DB) DeleteNote(uri string) error { - _, err := db.Exec("DELETE FROM notes WHERE uri = $1", uri) - return err +func (db *DB) DeleteNote(ctx context.Context, uri string) error { + return db.q.DeleteNote(ctx, uri) +} + +func (db *DB) UpdateNoteAnnotation(ctx context.Context, uri, bodyValue, tagsJSON, cid string) error { + return db.q.UpdateNoteAnnotation(ctx, sqlcdb.UpdateNoteAnnotationParams{ + BodyValue: &bodyValue, + Column2: tagsJSON, + Cid: &cid, + IndexedAt: time.Now(), + Uri: uri, + }) } -func (db *DB) UpdateNoteAnnotation(uri, bodyValue, tagsJSON, cid string) error { - _, err := db.Exec(` - UPDATE notes - SET body_value = $1, tags_json = NULLIF($2, ''), cid = $3, indexed_at = $4 - WHERE uri = $5 - `, bodyValue, tagsJSON, cid, time.Now(), uri) - return err +func (db *DB) UpdateNoteHighlight(ctx context.Context, uri, color, tagsJSON, cid string) error { + return db.q.UpdateNoteHighlight(ctx, sqlcdb.UpdateNoteHighlightParams{ + Column1: color, + Column2: tagsJSON, + Cid: &cid, + IndexedAt: time.Now(), + Uri: uri, + }) } -func (db *DB) UpdateNoteHighlight(uri, color, tagsJSON, cid string) error { - _, err := db.Exec(` - UPDATE notes - SET color = NULLIF($1, ''), tags_json = NULLIF($2, ''), cid = $3, indexed_at = $4 - WHERE uri = $5 - `, color, tagsJSON, cid, time.Now(), uri) - return err +func (db *DB) UpdateNoteBookmark(ctx context.Context, uri, title, description, tagsJSON, cid string) error { + return db.q.UpdateNoteBookmark(ctx, sqlcdb.UpdateNoteBookmarkParams{ + Column1: title, + Column2: description, + Column3: tagsJSON, + Cid: &cid, + IndexedAt: time.Now(), + Uri: uri, + }) } -func (db *DB) UpdateNoteBookmark(uri, title, description, tagsJSON, cid string) error { - _, err := db.Exec(` - UPDATE notes - SET target_title = NULLIF($1, ''), body_value = NULLIF($2, ''), tags_json = NULLIF($3, ''), cid = $4, indexed_at = $5 - WHERE uri = $6 - `, title, description, tagsJSON, cid, time.Now(), uri) - return err +func mapNote(r sqlcdb.Note) Note { + n := Note{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + Color: r.Color, + Description: r.Description, + BodyValue: r.BodyValue, + BodyFormat: r.BodyFormat, + BodyURI: r.BodyUri, + TargetSource: r.TargetSource, + TargetHash: r.TargetHash, + TargetTitle: r.TargetTitle, + SelectorJSON: r.SelectorJson, + TagsJSON: r.TagsJson, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + CID: r.Cid, + } + if r.Motivation != nil { + n.Motivation = *r.Motivation + } + return n } diff --git a/backend/internal/db/queries_notifications.go b/backend/internal/db/queries_notifications.go index c5cc4b9..0de0834 100644 --- a/backend/internal/db/queries_notifications.go +++ b/backend/internal/db/queries_notifications.go @@ -1,52 +1,56 @@ package db import ( + "context" "time" + + "margin.at/internal/db/sqlcdb" ) -func (db *DB) CreateNotification(n *Notification) error { - _, err := db.Exec(` - INSERT INTO notifications (recipient_did, actor_did, type, subject_uri, created_at) - VALUES ($1, $2, $3, $4, $5) - `, n.RecipientDID, n.ActorDID, n.Type, n.SubjectURI, n.CreatedAt) - return err +func (db *DB) CreateNotification(ctx context.Context, n *Notification) error { + return db.q.CreateNotification(ctx, sqlcdb.CreateNotificationParams{ + RecipientDid: n.RecipientDID, + ActorDid: n.ActorDID, + Type: n.Type, + SubjectUri: n.SubjectURI, + CreatedAt: n.CreatedAt, + }) } -func (db *DB) GetNotifications(recipientDID string, limit, offset int) ([]Notification, error) { - rows, err := db.Query(` - SELECT id, recipient_did, actor_did, type, subject_uri, created_at, read_at - FROM notifications - WHERE recipient_did = $1 - ORDER BY created_at DESC - LIMIT $2 OFFSET $3 - `, recipientDID, limit, offset) +func (db *DB) GetNotifications(ctx context.Context, recipientDID string, limit, offset int) ([]Notification, error) { + rows, err := db.q.GetNotifications(ctx, sqlcdb.GetNotificationsParams{ + RecipientDid: recipientDID, + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() var notifications []Notification - for rows.Next() { - var n Notification - if err := rows.Scan(&n.ID, &n.RecipientDID, &n.ActorDID, &n.Type, &n.SubjectURI, &n.CreatedAt, &n.ReadAt); err != nil { - continue - } - notifications = append(notifications, n) + for _, r := range rows { + notifications = append(notifications, Notification{ + ID: int(r.ID), + RecipientDID: r.RecipientDid, + ActorDID: r.ActorDid, + Type: r.Type, + SubjectURI: r.SubjectUri, + CreatedAt: r.CreatedAt, + ReadAt: r.ReadAt, + }) } return notifications, nil } -func (db *DB) GetUnreadNotificationCount(recipientDID string) (int, error) { - var count int - err := db.QueryRow(` - SELECT COUNT(*) FROM notifications WHERE recipient_did = $1 AND read_at IS NULL - `, recipientDID).Scan(&count) - return count, err +func (db *DB) GetUnreadNotificationCount(ctx context.Context, recipientDID string) (int, error) { + count, err := db.q.GetUnreadNotificationCount(ctx, recipientDID) + return int(count), err } -func (db *DB) MarkNotificationsRead(recipientDID string) error { - _, err := db.Exec(` - UPDATE notifications SET read_at = $1 WHERE recipient_did = $2 AND read_at IS NULL - `, time.Now(), recipientDID) - return err +func (db *DB) MarkNotificationsRead(ctx context.Context, recipientDID string) error { + now := time.Now() + return db.q.MarkNotificationsRead(ctx, sqlcdb.MarkNotificationsReadParams{ + ReadAt: &now, + RecipientDid: recipientDID, + }) } diff --git a/backend/internal/db/queries_oauth.go b/backend/internal/db/queries_oauth.go index b1adcdc..81a6e70 100644 --- a/backend/internal/db/queries_oauth.go +++ b/backend/internal/db/queries_oauth.go @@ -4,8 +4,15 @@ import ( "context" "fmt" "time" + + "github.com/jackc/pgx/v5/pgtype" + "margin.at/internal/db/sqlcdb" ) +func tstz(t time.Time) pgtype.Timestamptz { + return pgtype.Timestamptz{Time: t, Valid: true} +} + type DPoPKey struct { Crv string D string @@ -42,7 +49,7 @@ type OAuthSessionRow struct { CreatedAt time.Time } -func (db *DB) SavePendingAuthOAuth(p PendingAuth) error { +func (db *DB) SavePendingAuthOAuth(ctx context.Context, p PendingAuth) error { crv := p.DPoP.Crv if crv == "" { crv = "P-256" @@ -55,38 +62,43 @@ func (db *DB) SavePendingAuthOAuth(p PendingAuth) error { if err != nil { return fmt.Errorf("save pending auth: encrypt dpop: %w", err) } - _, err = db.Exec(` - INSERT INTO oauth_pending_auths - (state, did, handle, pds, issuer, token_endpoint, pkce_verifier, - dpop_crv, dpop_d, dpop_x, dpop_y, dpop_nonce, created_at) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) - ON CONFLICT (state) DO UPDATE SET - did = EXCLUDED.did, handle = EXCLUDED.handle, pds = EXCLUDED.pds, - issuer = EXCLUDED.issuer, token_endpoint = EXCLUDED.token_endpoint, - pkce_verifier = EXCLUDED.pkce_verifier, - dpop_crv = EXCLUDED.dpop_crv, dpop_d = EXCLUDED.dpop_d, - dpop_x = EXCLUDED.dpop_x, dpop_y = EXCLUDED.dpop_y, - dpop_nonce = EXCLUDED.dpop_nonce, created_at = EXCLUDED.created_at - `, p.State, p.DID, p.Handle, p.PDS, p.Issuer, p.TokenEndpoint, pkce, - crv, dpopD, p.DPoP.X, p.DPoP.Y, p.DPoPNonce, p.CreatedAt) - if err != nil { + if err := db.q.SavePendingAuthOAuth(ctx, sqlcdb.SavePendingAuthOAuthParams{ + State: p.State, + Did: p.DID, + Handle: p.Handle, + Pds: p.PDS, + Issuer: p.Issuer, + TokenEndpoint: p.TokenEndpoint, + PkceVerifier: pkce, + DpopCrv: crv, + DpopD: dpopD, + DpopX: p.DPoP.X, + DpopY: p.DPoP.Y, + DpopNonce: p.DPoPNonce, + CreatedAt: tstz(p.CreatedAt), + }); err != nil { return fmt.Errorf("save pending auth: %w", err) } return nil } -func (db *DB) GetPendingAuthOAuth(state string) (PendingAuth, error) { - var p PendingAuth - p.State = state - err := db.QueryRow(` - SELECT did, handle, pds, issuer, token_endpoint, pkce_verifier, - dpop_crv, dpop_d, dpop_x, dpop_y, dpop_nonce, created_at - FROM oauth_pending_auths WHERE state = $1 - `, state).Scan(&p.DID, &p.Handle, &p.PDS, &p.Issuer, &p.TokenEndpoint, &p.PKCEVerifier, - &p.DPoP.Crv, &p.DPoP.D, &p.DPoP.X, &p.DPoP.Y, &p.DPoPNonce, &p.CreatedAt) +func (db *DB) GetPendingAuthOAuth(ctx context.Context, state string) (PendingAuth, error) { + r, err := db.q.GetPendingAuthOAuth(ctx, state) if err != nil { return PendingAuth{}, fmt.Errorf("get pending auth: %w", err) } + p := PendingAuth{ + State: state, + DID: r.Did, + Handle: r.Handle, + PDS: r.Pds, + Issuer: r.Issuer, + TokenEndpoint: r.TokenEndpoint, + PKCEVerifier: r.PkceVerifier, + DPoP: DPoPKey{Crv: r.DpopCrv, D: r.DpopD, X: r.DpopX, Y: r.DpopY}, + DPoPNonce: r.DpopNonce, + CreatedAt: r.CreatedAt.Time, + } if p.PKCEVerifier, err = db.crypter.decrypt(p.PKCEVerifier); err != nil { return PendingAuth{}, fmt.Errorf("get pending auth: decrypt pkce: %w", err) } @@ -96,18 +108,15 @@ func (db *DB) GetPendingAuthOAuth(state string) (PendingAuth, error) { return p, nil } -func (db *DB) DeletePendingAuthOAuth(state string) error { - _, err := db.Exec(`DELETE FROM oauth_pending_auths WHERE state = $1`, state) - return err +func (db *DB) DeletePendingAuthOAuth(ctx context.Context, state string) error { + return db.q.DeletePendingAuthOAuth(ctx, state) } -func (db *DB) DeleteExpiredPendingAuthsOAuth() error { - _, err := db.Exec( - `DELETE FROM oauth_pending_auths WHERE created_at < $1`, time.Now().Add(-30*time.Minute)) - return err +func (db *DB) DeleteExpiredPendingAuthsOAuth(ctx context.Context) error { + return db.q.DeleteExpiredPendingAuthsOAuth(ctx, tstz(time.Now().Add(-30*time.Minute))) } -func (db *DB) CreateOAuthSession(s OAuthSessionRow) error { +func (db *DB) CreateOAuthSession(ctx context.Context, s OAuthSessionRow) error { crv := s.DPoP.Crv if crv == "" { crv = "P-256" @@ -120,40 +129,48 @@ func (db *DB) CreateOAuthSession(s OAuthSessionRow) error { if err != nil { return fmt.Errorf("create oauth session: encrypt dpop: %w", err) } - _, err = db.Exec(` - INSERT INTO oauth_sessions - (id, did, handle, pds, email, access_token, refresh_token, - token_endpoint, issuer, dpop_crv, dpop_d, dpop_x, dpop_y, - access_token_expires_at, expires_at, created_at) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15, NOW()) - ON CONFLICT (id) DO UPDATE SET - access_token = EXCLUDED.access_token, - refresh_token = EXCLUDED.refresh_token, - access_token_expires_at = EXCLUDED.access_token_expires_at, - expires_at = EXCLUDED.expires_at - `, s.ID, s.DID, s.Handle, s.PDS, s.Email, s.AccessToken, refresh, - s.TokenEndpoint, s.Issuer, crv, dpopD, s.DPoP.X, s.DPoP.Y, - s.AccessTokenExpiresAt, s.ExpiresAt) - if err != nil { + if err := db.q.CreateOAuthSession(ctx, sqlcdb.CreateOAuthSessionParams{ + ID: s.ID, + Did: s.DID, + Handle: s.Handle, + Pds: s.PDS, + Email: s.Email, + AccessToken: s.AccessToken, + RefreshToken: refresh, + TokenEndpoint: s.TokenEndpoint, + Issuer: s.Issuer, + DpopCrv: crv, + DpopD: dpopD, + DpopX: s.DPoP.X, + DpopY: s.DPoP.Y, + AccessTokenExpiresAt: tstz(s.AccessTokenExpiresAt), + ExpiresAt: tstz(s.ExpiresAt), + }); err != nil { return fmt.Errorf("create oauth session: %w", err) } return nil } -func (db *DB) GetOAuthSessionByID(id string) (OAuthSessionRow, error) { - var s OAuthSessionRow - s.ID = id - err := db.QueryRow(` - SELECT did, handle, pds, email, access_token, refresh_token, - token_endpoint, issuer, dpop_crv, dpop_d, dpop_x, dpop_y, - access_token_expires_at, expires_at, created_at - FROM oauth_sessions WHERE id = $1 AND expires_at > NOW() - `, id).Scan(&s.DID, &s.Handle, &s.PDS, &s.Email, &s.AccessToken, &s.RefreshToken, - &s.TokenEndpoint, &s.Issuer, &s.DPoP.Crv, &s.DPoP.D, &s.DPoP.X, &s.DPoP.Y, - &s.AccessTokenExpiresAt, &s.ExpiresAt, &s.CreatedAt) +func (db *DB) GetOAuthSessionByID(ctx context.Context, id string) (OAuthSessionRow, error) { + r, err := db.q.GetOAuthSessionByID(ctx, id) if err != nil { return OAuthSessionRow{}, fmt.Errorf("get oauth session: %w", err) } + s := OAuthSessionRow{ + ID: id, + DID: r.Did, + Handle: r.Handle, + PDS: r.Pds, + Email: r.Email, + AccessToken: r.AccessToken, + RefreshToken: r.RefreshToken, + TokenEndpoint: r.TokenEndpoint, + Issuer: r.Issuer, + DPoP: DPoPKey{Crv: r.DpopCrv, D: r.DpopD, X: r.DpopX, Y: r.DpopY}, + AccessTokenExpiresAt: r.AccessTokenExpiresAt.Time, + ExpiresAt: r.ExpiresAt.Time, + CreatedAt: r.CreatedAt.Time, + } if s.RefreshToken, err = db.crypter.decrypt(s.RefreshToken); err != nil { return OAuthSessionRow{}, fmt.Errorf("get oauth session: decrypt refresh: %w", err) } @@ -163,38 +180,63 @@ func (db *DB) GetOAuthSessionByID(id string) (OAuthSessionRow, error) { return s, nil } -func (db *DB) UpdateOAuthSessionTokens(id, accessToken, refreshToken string, accessTokenExpiresAt time.Time) error { +func (db *DB) UpdateOAuthSessionTokens(ctx context.Context, id, accessToken, refreshToken string, accessTokenExpiresAt time.Time) error { refresh, err := db.crypter.encrypt(refreshToken) if err != nil { return fmt.Errorf("update oauth session tokens: encrypt refresh: %w", err) } - _, err = db.Exec(` - UPDATE oauth_sessions - SET access_token = $2, refresh_token = $3, access_token_expires_at = $4 - WHERE id = $1 - `, id, accessToken, refresh, accessTokenExpiresAt) - return err + return db.q.UpdateOAuthSessionTokens(ctx, sqlcdb.UpdateOAuthSessionTokensParams{ + ID: id, + AccessToken: accessToken, + RefreshToken: refresh, + AccessTokenExpiresAt: tstz(accessTokenExpiresAt), + }) +} + +func (db *DB) DeleteOAuthSession(ctx context.Context, id string) error { + return db.q.DeleteOAuthSession(ctx, id) } -func (db *DB) DeleteOAuthSession(id string) error { - _, err := db.Exec(`DELETE FROM oauth_sessions WHERE id = $1`, id) - return err +func (db *DB) DeleteOAuthSessionsByDID(ctx context.Context, did string) error { + return db.q.DeleteOAuthSessionsByDID(ctx, did) } -func (db *DB) DeleteOAuthSessionsByDID(did string) error { - _, err := db.Exec(`DELETE FROM oauth_sessions WHERE did = $1`, did) - return err +func (db *DB) DeleteExpiredOAuthSessions(ctx context.Context) error { + return db.q.DeleteExpiredOAuthSessions(ctx) } -func (db *DB) DeleteExpiredOAuthSessions() error { - _, err := db.Exec(`DELETE FROM oauth_sessions WHERE expires_at <= NOW()`) - return err +func (db *DB) GetLatestOAuthSessionByDID(ctx context.Context, did string) (OAuthSessionRow, error) { + r, err := db.q.GetLatestOAuthSessionByDID(ctx, did) + if err != nil { + return OAuthSessionRow{}, fmt.Errorf("get oauth session by did: %w", err) + } + s := OAuthSessionRow{ + ID: r.ID, + DID: r.Did, + Handle: r.Handle, + PDS: r.Pds, + Email: r.Email, + AccessToken: r.AccessToken, + RefreshToken: r.RefreshToken, + TokenEndpoint: r.TokenEndpoint, + Issuer: r.Issuer, + DPoP: DPoPKey{Crv: r.DpopCrv, D: r.DpopD, X: r.DpopX, Y: r.DpopY}, + AccessTokenExpiresAt: r.AccessTokenExpiresAt.Time, + ExpiresAt: r.ExpiresAt.Time, + CreatedAt: r.CreatedAt.Time, + } + if s.RefreshToken, err = db.crypter.decrypt(s.RefreshToken); err != nil { + return OAuthSessionRow{}, fmt.Errorf("get oauth session by did: decrypt refresh: %w", err) + } + if s.DPoP.D, err = db.crypter.decrypt(s.DPoP.D); err != nil { + return OAuthSessionRow{}, fmt.Errorf("get oauth session by did: decrypt dpop: %w", err) + } + return s, nil } -func (db *DB) CountOAuthSessionsByDID(did string) (int, error) { - var n int - err := db.QueryRow(`SELECT COUNT(*) FROM oauth_sessions WHERE did = $1`, did).Scan(&n) - return n, err +func (db *DB) CountOAuthSessionsByDID(ctx context.Context, did string) (int, error) { + n, err := db.q.CountOAuthSessionsByDID(ctx, did) + return int(n), err } func hashLockKey(name string) int64 { @@ -209,14 +251,14 @@ func hashLockKey(name string) int64 { func (db *DB) WithAdvisoryLock(ctx context.Context, name string, fn func(context.Context) error) error { key := hashLockKey(name) - conn, err := db.Conn(ctx) + conn, err := db.pool.Acquire(ctx) if err != nil { return fmt.Errorf("advisory lock: acquire conn: %w", err) } - defer conn.Close() - if _, err := conn.ExecContext(ctx, "SELECT pg_advisory_lock($1)", key); err != nil { + defer conn.Release() + if _, err := conn.Exec(ctx, "SELECT pg_advisory_lock($1)", key); err != nil { return fmt.Errorf("advisory lock: %w", err) } - defer conn.ExecContext(context.Background(), "SELECT pg_advisory_unlock($1)", key) //nolint:errcheck + defer conn.Exec(context.Background(), "SELECT pg_advisory_unlock($1)", key) //nolint:errcheck return fn(ctx) } diff --git a/backend/internal/db/queries_profiles.go b/backend/internal/db/queries_profiles.go index 3772da5..a536bae 100644 --- a/backend/internal/db/queries_profiles.go +++ b/backend/internal/db/queries_profiles.go @@ -1,180 +1,173 @@ package db import ( - "database/sql" - "fmt" - "strings" + "context" + "errors" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" + "margin.at/internal/db/sqlcdb" ) -func (db *DB) GetProfile(did string) (*Profile, error) { - var p Profile - err := db.QueryRow( - `SELECT uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at - FROM profiles WHERE author_did = $1`, did, - ).Scan(&p.URI, &p.AuthorDID, &p.DisplayName, &p.Avatar, &p.Bio, &p.Website, &p.LinksJSON, &p.CreatedAt, &p.IndexedAt) +func (db *DB) GetProfile(ctx context.Context, did string) (*Profile, error) { + r, err := db.q.GetProfile(ctx, did) switch err { case nil: - return &p, nil - case sql.ErrNoRows: + return &Profile{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + DisplayName: r.DisplayName, + Avatar: r.Avatar, + Bio: r.Bio, + Website: r.Website, + LinksJSON: r.LinksJson, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + }, nil + case pgx.ErrNoRows: return nil, nil default: return nil, err } } -func (db *DB) GetProfilesByDIDs(dids []string) (map[string]*Profile, error) { +func (db *DB) GetProfilesByDIDs(ctx context.Context, dids []string) (map[string]*Profile, error) { if len(dids) == 0 { return nil, nil } - placeholders := make([]string, len(dids)) - args := make([]interface{}, len(dids)) - for i, did := range dids { - placeholders[i] = fmt.Sprintf("$%d", i+1) - args[i] = did - } - - rows, err := db.Query( - `SELECT uri, author_did, display_name, bio, avatar, website, links_json, created_at, indexed_at - FROM profiles WHERE author_did IN (`+strings.Join(placeholders, ",")+`)`, - args..., - ) + rows, err := db.q.GetProfilesByDIDs(ctx, dids) if err != nil { return nil, err } - defer rows.Close() profiles := make(map[string]*Profile) - for rows.Next() { - var p Profile - if err := rows.Scan(&p.URI, &p.AuthorDID, &p.DisplayName, &p.Bio, &p.Avatar, &p.Website, &p.LinksJSON, &p.CreatedAt, &p.IndexedAt); err != nil { - continue + for _, r := range rows { + p := Profile{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + DisplayName: r.DisplayName, + Bio: r.Bio, + Avatar: r.Avatar, + Website: r.Website, + LinksJSON: r.LinksJson, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, } profiles[p.AuthorDID] = &p } - return profiles, rows.Err() + return profiles, nil } -func (db *DB) UpsertProfile(p *Profile) error { - _, err := db.Exec(` - INSERT INTO profiles (uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) - ON CONFLICT(uri) DO UPDATE SET - display_name = EXCLUDED.display_name, - avatar = EXCLUDED.avatar, - bio = EXCLUDED.bio, - website = EXCLUDED.website, - links_json = EXCLUDED.links_json, - indexed_at = EXCLUDED.indexed_at - `, p.URI, p.AuthorDID, p.DisplayName, p.Avatar, p.Bio, p.Website, p.LinksJSON, p.CreatedAt, p.IndexedAt) - return err +func (db *DB) UpsertProfile(ctx context.Context, p *Profile) error { + return db.q.UpsertProfile(ctx, sqlcdb.UpsertProfileParams{ + Uri: p.URI, + AuthorDid: p.AuthorDID, + DisplayName: p.DisplayName, + Avatar: p.Avatar, + Bio: p.Bio, + Website: p.Website, + LinksJson: p.LinksJSON, + CreatedAt: p.CreatedAt, + IndexedAt: p.IndexedAt, + }) } -func (db *DB) DeleteProfile(uri string) error { - _, err := db.Exec("DELETE FROM profiles WHERE uri = $1", uri) - return err +func (db *DB) DeleteProfile(ctx context.Context, uri string) error { + return db.q.DeleteProfile(ctx, uri) } -func (db *DB) GetPreferences(did string) (*Preferences, error) { - var p Preferences - err := db.QueryRow( - `SELECT uri, author_did, external_link_skipped_hostnames, subscribed_labelers, - label_preferences, disable_external_link_warning, enable_community_bookmarks, - created_at, indexed_at, cid - FROM preferences WHERE author_did = $1`, did, - ).Scan( - &p.URI, &p.AuthorDID, &p.ExternalLinkSkippedHostnames, &p.SubscribedLabelers, - &p.LabelPreferences, &p.DisableExternalLinkWarning, &p.EnableCommunityBookmarks, - &p.CreatedAt, &p.IndexedAt, &p.CID, - ) +func (db *DB) GetPreferences(ctx context.Context, did string) (*Preferences, error) { + r, err := db.q.GetPreferences(ctx, did) switch err { case nil: - return &p, nil - case sql.ErrNoRows: + p := &Preferences{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + ExternalLinkSkippedHostnames: r.ExternalLinkSkippedHostnames, + SubscribedLabelers: r.SubscribedLabelers, + LabelPreferences: r.LabelPreferences, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + CID: r.Cid, + } + if r.DisableExternalLinkWarning.Valid { + v := r.DisableExternalLinkWarning.Bool + p.DisableExternalLinkWarning = &v + } + if r.EnableCommunityBookmarks.Valid { + v := r.EnableCommunityBookmarks.Bool + p.EnableCommunityBookmarks = &v + } + return p, nil + case pgx.ErrNoRows: return nil, nil default: return nil, err } } -func (db *DB) UpsertPreferences(p *Preferences) error { - _, err := db.Exec(` - INSERT INTO preferences ( - uri, author_did, external_link_skipped_hostnames, subscribed_labelers, - label_preferences, disable_external_link_warning, enable_community_bookmarks, - created_at, indexed_at, cid - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) - ON CONFLICT(uri) DO UPDATE SET - external_link_skipped_hostnames = EXCLUDED.external_link_skipped_hostnames, - subscribed_labelers = EXCLUDED.subscribed_labelers, - label_preferences = EXCLUDED.label_preferences, - disable_external_link_warning = EXCLUDED.disable_external_link_warning, - enable_community_bookmarks = EXCLUDED.enable_community_bookmarks, - indexed_at = EXCLUDED.indexed_at, - cid = EXCLUDED.cid - `, p.URI, p.AuthorDID, p.ExternalLinkSkippedHostnames, p.SubscribedLabelers, - p.LabelPreferences, p.DisableExternalLinkWarning, p.EnableCommunityBookmarks, - p.CreatedAt, p.IndexedAt, p.CID) - return err +func (db *DB) UpsertPreferences(ctx context.Context, p *Preferences) error { + var disableWarning, enableBookmarks pgtype.Bool + if p.DisableExternalLinkWarning != nil { + disableWarning = pgtype.Bool{Bool: *p.DisableExternalLinkWarning, Valid: true} + } + if p.EnableCommunityBookmarks != nil { + enableBookmarks = pgtype.Bool{Bool: *p.EnableCommunityBookmarks, Valid: true} + } + return db.q.UpsertPreferences(ctx, sqlcdb.UpsertPreferencesParams{ + Uri: p.URI, + AuthorDid: p.AuthorDID, + ExternalLinkSkippedHostnames: p.ExternalLinkSkippedHostnames, + SubscribedLabelers: p.SubscribedLabelers, + LabelPreferences: p.LabelPreferences, + DisableExternalLinkWarning: disableWarning, + EnableCommunityBookmarks: enableBookmarks, + CreatedAt: p.CreatedAt, + IndexedAt: p.IndexedAt, + Cid: p.CID, + }) } -func (db *DB) DeletePreferences(uri string) error { - _, err := db.Exec("DELETE FROM preferences WHERE uri = $1", uri) - return err +func (db *DB) DeletePreferences(ctx context.Context, uri string) error { + return db.q.DeletePreferences(ctx, uri) } -func (db *DB) GetPreferenceURIs(did string) ([]string, error) { - rows, err := db.Query( - "SELECT uri FROM preferences WHERE author_did = $1 AND uri IS NOT NULL AND uri != ''", did, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var uris []string - for rows.Next() { - var uri string - if err := rows.Scan(&uri); err != nil { - return nil, err - } - uris = append(uris, uri) - } - return uris, rows.Err() +func (db *DB) GetPreferenceURIs(ctx context.Context, did string) ([]string, error) { + return db.q.GetPreferenceURIs(ctx, did) } -func (db *DB) DeleteAPIKey(id, ownerDID string) (string, error) { - var uri string - err := db.QueryRow("SELECT uri FROM api_keys WHERE id = $1 AND owner_did = $2", id, ownerDID).Scan(&uri) +func (db *DB) DeleteAPIKey(ctx context.Context, id, ownerDID string) (string, error) { + uri, err := db.q.DeleteAPIKeyReturningURI(ctx, sqlcdb.DeleteAPIKeyReturningURIParams{ + ID: id, + OwnerDid: ownerDID, + }) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return "", nil } return "", err } - _, err = db.Exec("DELETE FROM api_keys WHERE id = $1 AND owner_did = $2", id, ownerDID) - return uri, err + if uri == nil { + return "", nil + } + return *uri, nil } -func (db *DB) DeleteAPIKeyByURI(uri string) error { - _, err := db.Exec("DELETE FROM api_keys WHERE uri = $1", uri) - return err +func (db *DB) DeleteAPIKeyByURI(ctx context.Context, uri string) error { + return db.q.DeleteAPIKeyByURI(ctx, &uri) } -func (db *DB) GetAPIKeyURIs(ownerDID string) ([]string, error) { - rows, err := db.Query( - "SELECT uri FROM api_keys WHERE owner_did = $1 AND uri IS NOT NULL AND uri != ''", ownerDID, - ) +func (db *DB) GetAPIKeyURIs(ctx context.Context, ownerDID string) ([]string, error) { + rows, err := db.q.GetAPIKeyURIs(ctx, ownerDID) if err != nil { return nil, err } - defer rows.Close() var uris []string - for rows.Next() { - var uri string - if err := rows.Scan(&uri); err != nil { - return nil, err + for _, uri := range rows { + if uri != nil { + uris = append(uris, *uri) } - uris = append(uris, uri) } - return uris, rows.Err() + return uris, nil } diff --git a/backend/internal/db/queries_recommendations.go b/backend/internal/db/queries_recommendations.go index 862ac14..656ce58 100644 --- a/backend/internal/db/queries_recommendations.go +++ b/backend/internal/db/queries_recommendations.go @@ -1,11 +1,14 @@ package db import ( + "context" "encoding/json" "fmt" "strconv" "strings" "time" + + "margin.at/internal/db/sqlcdb" ) type Document struct { @@ -54,114 +57,120 @@ type UserProfile struct { UpdatedAt time.Time `json:"updatedAt"` } -func (db *DB) UpsertPublication(p *Publication) error { - query := ` - INSERT INTO publications (uri, author_did, url, name, description, show_in_discover, indexed_at) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT(uri) DO UPDATE SET - name = EXCLUDED.name, - description = EXCLUDED.description, - show_in_discover = EXCLUDED.show_in_discover, - indexed_at = EXCLUDED.indexed_at - ` - _, err := db.Exec(query, p.URI, p.AuthorDID, p.URL, p.Name, p.Description, p.ShowInDiscover, p.IndexedAt) - return err +func (db *DB) UpsertPublication(ctx context.Context, p *Publication) error { + return db.q.UpsertPublication(ctx, sqlcdb.UpsertPublicationParams{ + Uri: p.URI, + AuthorDid: p.AuthorDID, + Url: p.URL, + Name: p.Name, + Description: p.Description, + ShowInDiscover: p.ShowInDiscover, + IndexedAt: p.IndexedAt, + }) } -func (db *DB) DeletePublication(uri string) error { - _, err := db.Exec("DELETE FROM publications WHERE uri = $1", uri) - return err +func (db *DB) DeletePublication(ctx context.Context, uri string) error { + return db.q.DeletePublication(ctx, uri) } -func (db *DB) GetPublicationByURL(url string) (*Publication, error) { - var p Publication - err := db.QueryRow( - "SELECT uri, author_did, url, name, description, show_in_discover, indexed_at FROM publications WHERE url = $1", - url, - ).Scan(&p.URI, &p.AuthorDID, &p.URL, &p.Name, &p.Description, &p.ShowInDiscover, &p.IndexedAt) +func (db *DB) GetPublicationByURL(ctx context.Context, url string) (*Publication, error) { + r, err := db.q.GetPublicationByURL(ctx, url) if err != nil { return nil, err } - return &p, nil -} - -func (db *DB) UpsertDocument(d *Document) error { - query := ` - INSERT INTO documents (uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) - ON CONFLICT(uri) DO UPDATE SET - title = EXCLUDED.title, - description = EXCLUDED.description, - text_content = EXCLUDED.text_content, - tags_json = EXCLUDED.tags_json, - canonical_url = EXCLUDED.canonical_url, - indexed_at = EXCLUDED.indexed_at - ` - _, err := db.Exec(query, d.URI, d.AuthorDID, d.Site, d.Path, d.Title, d.Description, d.TextContent, d.TagsJSON, d.CanonicalURL, d.PublishedAt, d.IndexedAt) - return err -} - -func (db *DB) DeleteDocument(uri string) error { - _, err := db.Exec("DELETE FROM documents WHERE uri = $1", uri) - return err -} - -func (db *DB) GetDocumentByCanonicalURL(canonicalURL string) (*Document, error) { - var d Document - err := db.QueryRow( - `SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at - FROM documents WHERE canonical_url = $1`, - canonicalURL, - ).Scan(&d.URI, &d.AuthorDID, &d.Site, &d.Path, &d.Title, &d.Description, &d.TextContent, &d.TagsJSON, &d.CanonicalURL, &d.PublishedAt, &d.IndexedAt) + return &Publication{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + URL: r.Url, + Name: r.Name, + Description: r.Description, + ShowInDiscover: r.ShowInDiscover, + IndexedAt: r.IndexedAt, + }, nil +} + +func (db *DB) UpsertDocument(ctx context.Context, d *Document) error { + var canonicalURL *string + if d.CanonicalURL != "" { + canonicalURL = &d.CanonicalURL + } + return db.q.UpsertDocument(ctx, sqlcdb.UpsertDocumentParams{ + Uri: d.URI, + AuthorDid: d.AuthorDID, + Site: d.Site, + Path: d.Path, + Title: d.Title, + Description: d.Description, + TextContent: d.TextContent, + TagsJson: d.TagsJSON, + CanonicalUrl: canonicalURL, + PublishedAt: d.PublishedAt, + IndexedAt: d.IndexedAt, + }) +} + +func (db *DB) DeleteDocument(ctx context.Context, uri string) error { + return db.q.DeleteDocument(ctx, uri) +} + +func (db *DB) GetDocumentByCanonicalURL(ctx context.Context, canonicalURL string) (*Document, error) { + r, err := db.q.GetDocumentByCanonicalURL(ctx, &canonicalURL) if err != nil { return nil, err } + d := mapDocument(r) return &d, nil } -func (db *DB) GetDocumentByURI(uri string) (*Document, error) { - var d Document - err := db.QueryRow( - `SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at - FROM documents WHERE uri = $1`, - uri, - ).Scan(&d.URI, &d.AuthorDID, &d.Site, &d.Path, &d.Title, &d.Description, &d.TextContent, &d.TagsJSON, &d.CanonicalURL, &d.PublishedAt, &d.IndexedAt) +func (db *DB) GetDocumentByURI(ctx context.Context, uri string) (*Document, error) { + r, err := db.q.GetDocumentByURI(ctx, uri) if err != nil { return nil, err } + d := mapDocument(r) return &d, nil } -func (db *DB) GetDocumentsWithoutEmbeddings(limit int) ([]Document, error) { - rows, err := db.Query(` - SELECT d.uri, d.author_did, d.site, d.path, d.title, d.description, d.text_content, d.tags_json, d.canonical_url, d.published_at, d.indexed_at - FROM documents d - LEFT JOIN document_embeddings de ON d.uri = de.document_uri - WHERE de.document_uri IS NULL - ORDER BY d.indexed_at DESC - LIMIT $1 - `, limit) +func (db *DB) GetDocumentsWithoutEmbeddings(ctx context.Context, limit int) ([]Document, error) { + rows, err := db.q.GetDocumentsWithoutEmbeddings(ctx, int32(limit)) if err != nil { return nil, err } - defer rows.Close() - return scanDocuments(rows) -} - -func (db *DB) GetAnnotationsWithoutEmbeddings(limit int) ([]Annotation, error) { - rows, err := db.Query(` - SELECT a.uri, a.author_did, a.motivation, a.body_value, a.body_format, a.body_uri, a.target_source, a.target_hash, a.target_title, a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid - FROM annotations a - LEFT JOIN annotation_embeddings ae ON a.uri = ae.annotation_uri - WHERE ae.annotation_uri IS NULL AND a.motivation IN ('commenting', 'highlighting') - ORDER BY a.created_at DESC - LIMIT $1 - `, limit) + return mapDocuments(rows), nil +} + +func (db *DB) GetAnnotationsWithoutEmbeddings(ctx context.Context, limit int) ([]Annotation, error) { + rows, err := db.q.GetAnnotationsWithoutEmbeddings(ctx, int32(limit)) if err != nil { return nil, err } - defer rows.Close() - return scanAnnotations(rows) + var results []Annotation + for _, r := range rows { + a := Annotation{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + BodyValue: r.BodyValue, + BodyFormat: r.BodyFormat, + BodyURI: r.BodyUri, + TargetTitle: r.TargetTitle, + SelectorJSON: r.SelectorJson, + TagsJSON: r.TagsJson, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + CID: r.Cid, + } + if r.Motivation != nil { + a.Motivation = *r.Motivation + } + if r.TargetSource != nil { + a.TargetSource = *r.TargetSource + } + if r.TargetHash != nil { + a.TargetHash = *r.TargetHash + } + results = append(results, a) + } + return results, nil } type HighlightForEmbedding struct { @@ -173,191 +182,172 @@ type HighlightForEmbedding struct { TagsJSON *string } -func (db *DB) GetHighlightsWithoutEmbeddings(limit int) ([]HighlightForEmbedding, error) { - rows, err := db.Query(` - SELECT h.uri, h.author_did, h.target_source, h.target_title, h.selector_json, h.tags_json - FROM highlights h - LEFT JOIN annotation_embeddings ae ON h.uri = ae.annotation_uri - WHERE ae.annotation_uri IS NULL - ORDER BY h.created_at DESC - LIMIT $1 - `, limit) +func (db *DB) GetHighlightsWithoutEmbeddings(ctx context.Context, limit int) ([]HighlightForEmbedding, error) { + rows, err := db.q.GetHighlightsWithoutEmbeddings(ctx, int32(limit)) if err != nil { return nil, err } - defer rows.Close() - var results []HighlightForEmbedding - for rows.Next() { - var h HighlightForEmbedding - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetTitle, &h.SelectorJSON, &h.TagsJSON); err != nil { - return nil, err - } - results = append(results, h) + for _, r := range rows { + results = append(results, HighlightForEmbedding{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + TargetSource: r.TargetSource, + TargetTitle: r.TargetTitle, + SelectorJSON: r.SelectorJson, + TagsJSON: r.TagsJson, + }) } return results, nil } -func (db *DB) GetDistinctAnnotationAuthors() ([]string, error) { - rows, err := db.Query(`SELECT DISTINCT author_did FROM annotation_embeddings`) - if err != nil { - return nil, err +func (db *DB) GetDistinctAnnotationAuthors(ctx context.Context) ([]string, error) { + return db.q.GetDistinctAnnotationAuthors(ctx) +} + +func mapDocument(r sqlcdb.Document) Document { + d := Document{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + Site: r.Site, + Path: r.Path, + Title: r.Title, + Description: r.Description, + TextContent: r.TextContent, + TagsJSON: r.TagsJson, + PublishedAt: r.PublishedAt, + IndexedAt: r.IndexedAt, } - defer rows.Close() - var dids []string - for rows.Next() { - var did string - if err := rows.Scan(&did); err != nil { - return nil, err - } - dids = append(dids, did) + if r.CanonicalUrl != nil { + d.CanonicalURL = *r.CanonicalUrl } - return dids, nil + return d } -func scanDocuments(rows interface { - Next() bool - Scan(...interface{}) error -}) ([]Document, error) { +func mapDocuments(rows []sqlcdb.Document) []Document { var docs []Document - for rows.Next() { - var d Document - if err := rows.Scan(&d.URI, &d.AuthorDID, &d.Site, &d.Path, &d.Title, &d.Description, &d.TextContent, &d.TagsJSON, &d.CanonicalURL, &d.PublishedAt, &d.IndexedAt); err != nil { - return nil, err - } - docs = append(docs, d) + for _, r := range rows { + docs = append(docs, mapDocument(r)) } - return docs, nil + return docs } -func (db *DB) GetRecentDocuments(limit, offset int) ([]Document, error) { - rows, err := db.Query(` - SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at - FROM documents - ORDER BY published_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) +func (db *DB) GetRecentDocuments(ctx context.Context, limit, offset int) ([]Document, error) { + rows, err := db.q.GetRecentDocuments(ctx, sqlcdb.GetRecentDocumentsParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - return scanDocuments(rows) -} - -func (db *DB) GetPopularDocuments(limit, offset int) ([]Document, error) { - rows, err := db.Query(` - SELECT d.uri, d.author_did, d.site, d.path, d.title, d.description, d.text_content, d.tags_json, d.canonical_url, d.published_at, d.indexed_at - FROM documents d - LEFT JOIN annotations a ON a.target_source = d.canonical_url - GROUP BY d.uri - ORDER BY COUNT(a.uri) DESC, d.published_at DESC - LIMIT $1 OFFSET $2 - `, limit, offset) + return mapDocuments(rows), nil +} + +func (db *DB) GetPopularDocuments(ctx context.Context, limit, offset int) ([]Document, error) { + rows, err := db.q.GetPopularDocuments(ctx, sqlcdb.GetPopularDocumentsParams{ + Limit: int32(limit), + Offset: int32(offset), + }) if err != nil { return nil, err } - defer rows.Close() - return scanDocuments(rows) + return mapDocuments(rows), nil } -func (db *DB) GetDocumentCount() (int, error) { - var count int - err := db.QueryRow("SELECT COUNT(*) FROM documents").Scan(&count) - return count, err +func (db *DB) GetDocumentCount(ctx context.Context) (int, error) { + n, err := db.q.GetDocumentCount(ctx) + return int(n), err } -func (db *DB) UpsertDocumentEmbedding(documentURI string, embedding []float32) error { +func (db *DB) UpsertDocumentEmbedding(ctx context.Context, documentURI string, embedding []float32) error { vecStr := float32SliceToVectorString(embedding) - _, err := db.Exec( - `INSERT INTO document_embeddings (document_uri, embedding, updated_at) VALUES ($1, $2, $3) - ON CONFLICT(document_uri) DO UPDATE SET embedding = EXCLUDED.embedding, updated_at = EXCLUDED.updated_at`, - documentURI, vecStr, time.Now(), - ) - return err + return db.q.UpsertDocumentEmbedding(ctx, sqlcdb.UpsertDocumentEmbeddingParams{ + DocumentUri: documentURI, + Embedding: vecStr, + UpdatedAt: time.Now(), + }) } -func (db *DB) UpsertAnnotationEmbedding(annotationURI, authorDID string, documentURI *string, embedding []float32) error { +func (db *DB) UpsertAnnotationEmbedding(ctx context.Context, annotationURI, authorDID string, documentURI *string, embedding []float32) error { vecStr := float32SliceToVectorString(embedding) - _, err := db.Exec( - `INSERT INTO annotation_embeddings (annotation_uri, author_did, document_uri, embedding, updated_at) VALUES ($1, $2, $3, $4, $5) - ON CONFLICT(annotation_uri) DO UPDATE SET embedding = EXCLUDED.embedding, document_uri = EXCLUDED.document_uri, updated_at = EXCLUDED.updated_at`, - annotationURI, authorDID, documentURI, vecStr, time.Now(), - ) - return err + return db.q.UpsertAnnotationEmbedding(ctx, sqlcdb.UpsertAnnotationEmbeddingParams{ + AnnotationUri: annotationURI, + AuthorDid: authorDID, + DocumentUri: documentURI, + Embedding: vecStr, + UpdatedAt: time.Now(), + }) } -func (db *DB) DeleteAnnotationEmbedding(annotationURI string) error { - _, err := db.Exec("DELETE FROM annotation_embeddings WHERE annotation_uri = $1", annotationURI) - return err +func (db *DB) DeleteAnnotationEmbedding(ctx context.Context, annotationURI string) error { + return db.q.DeleteAnnotationEmbedding(ctx, annotationURI) } -func (db *DB) UpsertUserProfile(authorDID string, embedding []float32, tagAffinities map[string]float64, annotationCount int) error { +func (db *DB) UpsertUserProfile(ctx context.Context, authorDID string, embedding []float32, tagAffinities map[string]float64, annotationCount int) error { vecStr := float32SliceToVectorString(embedding) tagsJSON, _ := json.Marshal(tagAffinities) - _, err := db.Exec( - `INSERT INTO user_profiles (author_did, embedding, tag_affinities, annotation_count, updated_at) VALUES ($1, $2, $3, $4, $5) - ON CONFLICT(author_did) DO UPDATE SET embedding = EXCLUDED.embedding, tag_affinities = EXCLUDED.tag_affinities, annotation_count = EXCLUDED.annotation_count, updated_at = EXCLUDED.updated_at`, - authorDID, vecStr, string(tagsJSON), annotationCount, time.Now(), - ) - return err -} - -func (db *DB) GetUserProfile(authorDID string) (*UserProfile, error) { - var p UserProfile - var embStr string - err := db.QueryRow( - `SELECT author_did, embedding, tag_affinities, annotation_count, updated_at FROM user_profiles WHERE author_did = $1`, - authorDID, - ).Scan(&p.AuthorDID, &embStr, &p.TagAffinities, &p.AnnotationCount, &p.UpdatedAt) + tags := string(tagsJSON) + return db.q.UpsertUserProfile(ctx, sqlcdb.UpsertUserProfileParams{ + AuthorDid: authorDID, + Embedding: vecStr, + TagAffinities: &tags, + AnnotationCount: int32(annotationCount), + UpdatedAt: time.Now(), + }) +} + +func (db *DB) GetUserProfile(ctx context.Context, authorDID string) (*UserProfile, error) { + r, err := db.q.GetUserProfile(ctx, authorDID) if err != nil { return nil, err } - p.Embedding = parseVectorString(embStr) + p := UserProfile{ + AuthorDID: r.AuthorDid, + Embedding: parseVectorString(r.Embedding), + AnnotationCount: int(r.AnnotationCount), + UpdatedAt: r.UpdatedAt, + } + if r.TagAffinities != nil { + p.TagAffinities = *r.TagAffinities + } return &p, nil } -func (db *DB) GetAnnotationEmbeddingsByAuthor(authorDID string) ([]AnnotationEmbedding, error) { - rows, err := db.Query( - `SELECT annotation_uri, author_did, document_uri, embedding, updated_at FROM annotation_embeddings WHERE author_did = $1`, - authorDID, - ) +func (db *DB) GetAnnotationEmbeddingsByAuthor(ctx context.Context, authorDID string) ([]AnnotationEmbedding, error) { + rows, err := db.q.GetAnnotationEmbeddingsByAuthor(ctx, authorDID) if err != nil { return nil, err } - defer rows.Close() - var results []AnnotationEmbedding - for rows.Next() { - var ae AnnotationEmbedding - var embStr string - if err := rows.Scan(&ae.AnnotationURI, &ae.AuthorDID, &ae.DocumentURI, &embStr, &ae.UpdatedAt); err != nil { - return nil, err - } - ae.Embedding = parseVectorString(embStr) - results = append(results, ae) + for _, r := range rows { + results = append(results, AnnotationEmbedding{ + AnnotationURI: r.AnnotationUri, + AuthorDID: r.AuthorDid, + DocumentURI: r.DocumentUri, + Embedding: parseVectorString(r.Embedding), + UpdatedAt: r.UpdatedAt, + }) } return results, nil } -func (db *DB) GetRecentAnnotationEmbeddingsByAuthor(authorDID string, limit int) ([]AnnotationEmbedding, error) { - rows, err := db.Query( - `SELECT annotation_uri, author_did, document_uri, embedding, updated_at FROM annotation_embeddings WHERE author_did = $1 ORDER BY updated_at DESC LIMIT $2`, - authorDID, limit, - ) +func (db *DB) GetRecentAnnotationEmbeddingsByAuthor(ctx context.Context, authorDID string, limit int) ([]AnnotationEmbedding, error) { + rows, err := db.q.GetRecentAnnotationEmbeddingsByAuthor(ctx, sqlcdb.GetRecentAnnotationEmbeddingsByAuthorParams{ + AuthorDid: authorDID, + Limit: int32(limit), + }) if err != nil { return nil, err } - defer rows.Close() - var results []AnnotationEmbedding - for rows.Next() { - var ae AnnotationEmbedding - var embStr string - if err := rows.Scan(&ae.AnnotationURI, &ae.AuthorDID, &ae.DocumentURI, &embStr, &ae.UpdatedAt); err != nil { - return nil, err - } - ae.Embedding = parseVectorString(embStr) - results = append(results, ae) + for _, r := range rows { + results = append(results, AnnotationEmbedding{ + AnnotationURI: r.AnnotationUri, + AuthorDID: r.AuthorDid, + DocumentURI: r.DocumentUri, + Embedding: parseVectorString(r.Embedding), + UpdatedAt: r.UpdatedAt, + }) } return results, nil } @@ -376,64 +366,39 @@ type CandidateDocument struct { Engagement int `json:"engagement"` } -func (db *DB) GetCandidateDocuments(userDID string, limit int) ([]CandidateDocument, error) { - rows, err := db.Query(` - SELECT - d.uri, d.author_did, d.site, d.path, d.title, d.description, d.tags_json, - d.canonical_url, d.published_at, de.embedding, - COALESCE(eng.cnt, 0) AS engagement - FROM documents d - JOIN document_embeddings de ON d.uri = de.document_uri - LEFT JOIN ( - SELECT document_uri, COUNT(DISTINCT author_did) AS cnt - FROM annotation_embeddings - WHERE document_uri IS NOT NULL - GROUP BY document_uri - ) eng ON eng.document_uri = d.uri - LEFT JOIN publications p ON d.site = p.uri OR d.site = p.url - WHERE d.author_did != $1 - AND (p.show_in_discover IS NULL OR p.show_in_discover = true) - AND LENGTH(d.title) > 15 - AND (LENGTH(COALESCE(d.description, '')) >= 30 OR LENGTH(COALESCE(d.text_content, '')) >= 100) - AND LOWER(d.title) NOT LIKE '%test%' - AND LOWER(d.title) NOT LIKE '%testing%' - AND LOWER(d.title) NOT LIKE '%hello world%' - AND LOWER(d.title) NOT LIKE '%untitled%' - AND LOWER(d.title) NOT LIKE '%draft%' - AND LOWER(d.title) NOT LIKE '%asdf%' - AND LOWER(d.title) NOT LIKE '%lorem%' - AND LOWER(d.title) NOT LIKE '%placeholder%' - AND d.uri NOT IN ( - SELECT DISTINCT document_uri FROM annotation_embeddings - WHERE author_did = $2 AND document_uri IS NOT NULL - ) - ORDER BY d.published_at DESC - LIMIT $3 - `, userDID, userDID, limit) +func (db *DB) GetCandidateDocuments(ctx context.Context, userDID string, limit int) ([]CandidateDocument, error) { + rows, err := db.q.GetCandidateDocuments(ctx, sqlcdb.GetCandidateDocumentsParams{ + AuthorDid: userDID, + AuthorDid_2: userDID, + Limit: int32(limit), + }) if err != nil { return nil, fmt.Errorf("candidate query: %w", err) } - defer rows.Close() - var results []CandidateDocument - for rows.Next() { - var c CandidateDocument - var embStr string - if err := rows.Scan( - &c.URI, &c.AuthorDID, &c.Site, &c.Path, &c.Title, &c.Description, - &c.TagsJSON, &c.CanonicalURL, &c.PublishedAt, &embStr, &c.Engagement, - ); err != nil { - return nil, err + for _, r := range rows { + c := CandidateDocument{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + Site: r.Site, + Path: r.Path, + Title: r.Title, + Description: r.Description, + TagsJSON: r.TagsJson, + PublishedAt: r.PublishedAt, + Embedding: parseVectorString(r.Embedding), + Engagement: int(r.Engagement), + } + if r.CanonicalUrl != nil { + c.CanonicalURL = *r.CanonicalUrl } - c.Embedding = parseVectorString(embStr) results = append(results, c) } return results, nil } -func (db *DB) MatchAnnotationToDocument(targetSource string) (*string, error) { - var uri string - err := db.QueryRow(`SELECT uri FROM documents WHERE canonical_url = $1`, targetSource).Scan(&uri) +func (db *DB) MatchAnnotationToDocument(ctx context.Context, targetSource string) (*string, error) { + uri, err := db.q.MatchAnnotationToDocument(ctx, &targetSource) if err != nil { return nil, err } diff --git a/backend/internal/db/queries_replies.go b/backend/internal/db/queries_replies.go index 7f154f9..62650e5 100644 --- a/backend/internal/db/queries_replies.go +++ b/backend/internal/db/queries_replies.go @@ -1,147 +1,115 @@ package db -func (db *DB) CreateReply(r *Reply) error { - _, err := db.Exec(` - INSERT INTO replies (uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) - ON CONFLICT(uri) DO UPDATE SET - text = EXCLUDED.text, - format = EXCLUDED.format, - indexed_at = EXCLUDED.indexed_at, - cid = EXCLUDED.cid - `, r.URI, r.AuthorDID, r.ParentURI, r.RootURI, r.Text, r.Format, r.CreatedAt, r.IndexedAt, r.CID) - return err +import ( + "context" + + "margin.at/internal/db/sqlcdb" +) + +func (db *DB) CreateReply(ctx context.Context, r *Reply) error { + return db.q.CreateReply(ctx, sqlcdb.CreateReplyParams{ + Uri: r.URI, + AuthorDid: r.AuthorDID, + ParentUri: r.ParentURI, + RootUri: r.RootURI, + Text: r.Text, + Format: r.Format, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + Cid: r.CID, + }) } -func (db *DB) GetRepliesByRoot(rootURI string) ([]Reply, error) { - rows, err := db.Query(` - SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid - FROM replies - WHERE root_uri = $1 - ORDER BY created_at ASC - `, rootURI) +func (db *DB) GetRepliesByRoot(ctx context.Context, rootURI string) ([]Reply, error) { + rows, err := db.q.GetRepliesByRoot(ctx, rootURI) if err != nil { return nil, err } - defer rows.Close() - - return scanReplies(rows) + return mapReplies(rows), nil } -func (db *DB) GetReplyByURI(uri string) (*Reply, error) { - var r Reply - err := db.QueryRow(` - SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid - FROM replies - WHERE uri = $1 - `, uri).Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID) +func (db *DB) GetReplyByURI(ctx context.Context, uri string) (*Reply, error) { + r, err := db.q.GetReplyByURI(ctx, uri) if err != nil { return nil, err } - return &r, nil + reply := mapReply(r) + return &reply, nil } -func (db *DB) DeleteReply(uri string) error { - _, err := db.Exec(`DELETE FROM replies WHERE uri = $1`, uri) - return err +func (db *DB) DeleteReply(ctx context.Context, uri string) error { + return db.q.DeleteReply(ctx, uri) } -func (db *DB) GetRepliesByAuthor(authorDID string) ([]Reply, error) { - rows, err := db.Query(` - SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid - FROM replies - WHERE author_did = $1 - ORDER BY created_at DESC - `, authorDID) +func (db *DB) GetRepliesByAuthor(ctx context.Context, authorDID string) ([]Reply, error) { + rows, err := db.q.GetRepliesByAuthor(ctx, authorDID) if err != nil { return nil, err } - defer rows.Close() - - return scanReplies(rows) + return mapReplies(rows), nil } -func (db *DB) GetOrphanedRepliesByAuthor(authorDID string) ([]Reply, error) { - rows, err := db.Query(` - SELECT r.uri, r.author_did, r.parent_uri, r.root_uri, r.text, r.format, r.created_at, r.indexed_at, r.cid - FROM replies r - LEFT JOIN annotations a ON r.root_uri = a.uri - WHERE r.author_did = $1 AND a.uri IS NULL - `, authorDID) +func (db *DB) GetOrphanedRepliesByAuthor(ctx context.Context, authorDID string) ([]Reply, error) { + rows, err := db.q.GetOrphanedRepliesByAuthor(ctx, authorDID) if err != nil { return nil, err } - defer rows.Close() - - return scanReplies(rows) + return mapReplies(rows), nil } -func (db *DB) GetReplyCount(rootURI string) (int, error) { - var count int - err := db.QueryRow(`SELECT COUNT(*) FROM replies WHERE root_uri = $1`, rootURI).Scan(&count) - return count, err +func (db *DB) GetReplyCount(ctx context.Context, rootURI string) (int, error) { + count, err := db.q.GetReplyCount(ctx, rootURI) + return int(count), err } -func (db *DB) GetReplyCounts(rootURIs []string) (map[string]int, error) { +func (db *DB) GetReplyCounts(ctx context.Context, rootURIs []string) (map[string]int, error) { if len(rootURIs) == 0 { return map[string]int{}, nil } - query := ` - SELECT root_uri, COUNT(*) - FROM replies - WHERE root_uri = ANY($1) - GROUP BY root_uri - ` - - rows, err := db.Query(query, pqStringArray(rootURIs)) + rows, err := db.q.GetReplyCounts(ctx, rootURIs) if err != nil { return nil, err } - defer rows.Close() counts := make(map[string]int) - for rows.Next() { - var uri string - var count int - if err := rows.Scan(&uri, &count); err != nil { - return nil, err - } - counts[uri] = count + for _, r := range rows { + counts[r.RootUri] = int(r.Count) } return counts, nil } -func (db *DB) GetRepliesByURIs(uris []string) ([]Reply, error) { +func (db *DB) GetRepliesByURIs(ctx context.Context, uris []string) ([]Reply, error) { if len(uris) == 0 { return []Reply{}, nil } - rows, err := db.Query(` - SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid - FROM replies - WHERE uri = ANY($1) - `, pqStringArray(uris)) + rows, err := db.q.GetRepliesByURIs(ctx, uris) if err != nil { return nil, err } - defer rows.Close() + return mapReplies(rows), nil +} - return scanReplies(rows) +func mapReply(r sqlcdb.Reply) Reply { + return Reply{ + URI: r.Uri, + AuthorDID: r.AuthorDid, + ParentURI: r.ParentUri, + RootURI: r.RootUri, + Text: r.Text, + Format: r.Format, + CreatedAt: r.CreatedAt, + IndexedAt: r.IndexedAt, + CID: r.Cid, + } } -func scanReplies(rows interface { - Next() bool - Scan(...interface{}) error -}) ([]Reply, error) { +func mapReplies(rows []sqlcdb.Reply) []Reply { var replies []Reply - for rows.Next() { - var r Reply - if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { - return nil, err - } - replies = append(replies, r) + for _, r := range rows { + replies = append(replies, mapReply(r)) } - return replies, nil + return replies } diff --git a/backend/internal/db/queries_search.go b/backend/internal/db/queries_search.go index 0c89060..8b67974 100644 --- a/backend/internal/db/queries_search.go +++ b/backend/internal/db/queries_search.go @@ -1,6 +1,9 @@ package db -import "strings" +import ( + "context" + "strings" +) func escapeLike(s string) string { s = strings.ReplaceAll(s, "\\", "\\\\") @@ -9,7 +12,7 @@ func escapeLike(s string) string { return s } -func (db *DB) SearchAnnotations(query string, authorDID string, limit, offset int) ([]Annotation, error) { +func (db *DB) SearchAnnotations(ctx context.Context, query string, authorDID string, limit, offset int) ([]Annotation, error) { pattern := "%" + escapeLike(query) + "%" var baseQuery string @@ -36,7 +39,7 @@ func (db *DB) SearchAnnotations(query string, authorDID string, limit, offset in args = []interface{}{pattern, pattern, pattern, pattern, pattern, limit, offset} } - rows, err := db.Query(baseQuery, args...) + rows, err := db.pool.Query(ctx, baseQuery, args...) if err != nil { return nil, err } @@ -45,7 +48,7 @@ func (db *DB) SearchAnnotations(query string, authorDID string, limit, offset in return scanAnnotations(rows) } -func (db *DB) SearchHighlights(query string, authorDID string, limit, offset int) ([]Highlight, error) { +func (db *DB) SearchHighlights(ctx context.Context, query string, authorDID string, limit, offset int) ([]Highlight, error) { pattern := "%" + escapeLike(query) + "%" var baseQuery string @@ -72,7 +75,7 @@ func (db *DB) SearchHighlights(query string, authorDID string, limit, offset int args = []interface{}{pattern, pattern, pattern, pattern, limit, offset} } - rows, err := db.Query(baseQuery, args...) + rows, err := db.pool.Query(ctx, baseQuery, args...) if err != nil { return nil, err } @@ -81,7 +84,7 @@ func (db *DB) SearchHighlights(query string, authorDID string, limit, offset int return scanHighlights(rows) } -func (db *DB) SearchBookmarks(query string, authorDID string, limit, offset int) ([]Bookmark, error) { +func (db *DB) SearchBookmarks(ctx context.Context, query string, authorDID string, limit, offset int) ([]Bookmark, error) { pattern := "%" + escapeLike(query) + "%" var baseQuery string @@ -108,7 +111,7 @@ func (db *DB) SearchBookmarks(query string, authorDID string, limit, offset int) args = []interface{}{pattern, pattern, pattern, pattern, limit, offset} } - rows, err := db.Query(baseQuery, args...) + rows, err := db.pool.Query(ctx, baseQuery, args...) if err != nil { return nil, err } @@ -116,3 +119,18 @@ func (db *DB) SearchBookmarks(query string, authorDID string, limit, offset int) return scanBookmarks(rows) } + +func scanBookmarks(rows interface { + Next() bool + Scan(...interface{}) error +}) ([]Bookmark, error) { + var bookmarks []Bookmark + for rows.Next() { + var b Bookmark + if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { + return nil, err + } + bookmarks = append(bookmarks, b) + } + return bookmarks, nil +} diff --git a/backend/internal/db/sqlc_queries/annotations.sql b/backend/internal/db/sqlc_queries/annotations.sql new file mode 100644 index 0000000..c569d65 --- /dev/null +++ b/backend/internal/db/sqlc_queries/annotations.sql @@ -0,0 +1,168 @@ +-- name: CreateAnnotation :exec +INSERT INTO annotations (uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) +ON CONFLICT(uri) DO UPDATE SET + motivation = EXCLUDED.motivation, + body_value = EXCLUDED.body_value, + body_format = EXCLUDED.body_format, + body_uri = EXCLUDED.body_uri, + target_source = EXCLUDED.target_source, + target_hash = EXCLUDED.target_hash, + target_title = EXCLUDED.target_title, + selector_json = EXCLUDED.selector_json, + tags_json = EXCLUDED.tags_json, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid; + +-- name: GetAnnotationByURI :one +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE uri = $1; + +-- name: GetAnnotationsByTargetHash :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE target_hash = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetAnnotationsByAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetMarginAnnotationsByAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetSembleAnnotationsByAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetAnnotationsByMotivation :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE motivation = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetRecentAnnotations :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +ORDER BY created_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetPopularAnnotations :many +SELECT + a.uri, a.author_did, a.motivation, a.body_value, a.body_format, + a.body_uri, a.target_source, a.target_hash, a.target_title, + a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid +FROM all_annotations a +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = a.uri +) l ON true +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = a.uri +) r ON true +WHERE a.created_at > $1 AND (l.cnt + r.cnt) > 0 +ORDER BY (l.cnt + r.cnt) DESC, a.created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetShelvedAnnotations :many +SELECT + a.uri, a.author_did, a.motivation, a.body_value, a.body_format, + a.body_uri, a.target_source, a.target_hash, a.target_title, + a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid +FROM all_annotations a +WHERE a.created_at < $1 AND a.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = a.uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = a.uri) +ORDER BY RANDOM() +LIMIT $3 OFFSET $4; + +-- name: GetMarginAnnotations :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetSembleAnnotations :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetAnnotationsByTag :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE lower(tags_json)::jsonb ? $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetMarginAnnotationsByTag :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE lower(tags_json)::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetSembleAnnotationsByTag :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE lower(tags_json)::jsonb ? $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: DeleteAnnotation :exec +DELETE FROM annotations WHERE uri = $1; + +-- name: UpdateAnnotation :exec +UPDATE annotations +SET body_value = $1, tags_json = $2, cid = $3, indexed_at = $4 +WHERE uri = $5; + +-- name: GetAnnotationsByTagAndAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 +ORDER BY created_at DESC +LIMIT $3 OFFSET $4; + +-- name: GetMarginAnnotationsByTagAndAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4; + +-- name: GetSembleAnnotationsByTagAndAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4; + +-- name: GetAnnotationsByAuthorAndTargetHash :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND target_hash = $2 +ORDER BY created_at DESC +LIMIT $3 OFFSET $4; + +-- name: GetAnnotationsByURIs :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE uri = ANY($1::text[]); + +-- name: GetAnnotationURIs :many +SELECT uri FROM all_annotations WHERE author_did = $1; diff --git a/backend/internal/db/sqlc_queries/api_keys.sql b/backend/internal/db/sqlc_queries/api_keys.sql new file mode 100644 index 0000000..d01f0fd --- /dev/null +++ b/backend/internal/db/sqlc_queries/api_keys.sql @@ -0,0 +1,25 @@ +-- name: CreateAPIKey :exec +INSERT INTO api_keys (id, owner_did, name, key_hash, created_at, uri, cid) +VALUES ($1, $2, $3, $4, $5, $6, $7) +ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + key_hash = EXCLUDED.key_hash, + uri = EXCLUDED.uri, + cid = EXCLUDED.cid; + +-- name: GetAPIKeysByOwner :many +SELECT id, owner_did, name, key_hash, created_at, last_used_at +FROM api_keys +WHERE owner_did = $1 +ORDER BY created_at DESC; + +-- name: GetAPIKeyByHash :one +SELECT id, owner_did, name, key_hash, created_at, last_used_at +FROM api_keys +WHERE key_hash = $1; + +-- name: UpdateAPIKeyLastUsed :exec +UPDATE api_keys SET last_used_at = $1 WHERE id = $2; + +-- name: DeleteAPIKeyReturningURI :one +DELETE FROM api_keys WHERE id = $1 AND owner_did = $2 RETURNING uri; diff --git a/backend/internal/db/sqlc_queries/bookmarks.sql b/backend/internal/db/sqlc_queries/bookmarks.sql new file mode 100644 index 0000000..82e41c7 --- /dev/null +++ b/backend/internal/db/sqlc_queries/bookmarks.sql @@ -0,0 +1,148 @@ +-- name: CreateBookmark :exec +INSERT INTO bookmarks (uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) +ON CONFLICT(uri) DO UPDATE SET + source = EXCLUDED.source, + source_hash = EXCLUDED.source_hash, + title = EXCLUDED.title, + description = EXCLUDED.description, + tags_json = EXCLUDED.tags_json, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid; + +-- name: GetBookmarkByURI :one +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE uri = $1; + +-- name: GetRecentBookmarks :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +ORDER BY created_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetPopularBookmarks :many +SELECT + b.uri, b.author_did, b.source, b.source_hash, b.title, + b.description, b.tags_json, b.created_at, b.indexed_at, b.cid +FROM all_bookmarks b +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = b.uri +) l ON true +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = b.uri +) r ON true +WHERE b.created_at > $1 AND (l.cnt + r.cnt) > 0 +ORDER BY (l.cnt + r.cnt) DESC, b.created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetShelvedBookmarks :many +SELECT + b.uri, b.author_did, b.source, b.source_hash, b.title, + b.description, b.tags_json, b.created_at, b.indexed_at, b.cid +FROM all_bookmarks b +WHERE b.created_at < $1 AND b.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = b.uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = b.uri) +ORDER BY RANDOM() +LIMIT $3 OFFSET $4; + +-- name: GetMarginBookmarks :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetSembleBookmarks :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetBookmarksByTag :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE lower(tags_json)::jsonb ? $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetMarginBookmarksByTag :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE lower(tags_json)::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetSembleBookmarksByTag :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE lower(tags_json)::jsonb ? $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetBookmarksByTagAndAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 +ORDER BY created_at DESC +LIMIT $3 OFFSET $4; + +-- name: GetMarginBookmarksByTagAndAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4; + +-- name: GetSembleBookmarksByTagAndAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4; + +-- name: GetBookmarksByAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetMarginBookmarksByAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetSembleBookmarksByAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: DeleteBookmark :exec +DELETE FROM bookmarks WHERE uri = $1; + +-- name: UpdateBookmark :exec +UPDATE bookmarks +SET title = $1, description = $2, tags_json = $3, cid = $4, indexed_at = $5 +WHERE uri = $6; + +-- name: GetBookmarksByURIs :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE uri = ANY($1::text[]); + +-- name: GetBookmarkURIs :many +SELECT uri FROM all_bookmarks WHERE author_did = $1; + +-- name: GetBookmarksByTargetHash :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE source_hash = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; diff --git a/backend/internal/db/sqlc_queries/collections.sql b/backend/internal/db/sqlc_queries/collections.sql new file mode 100644 index 0000000..4c49664 --- /dev/null +++ b/backend/internal/db/sqlc_queries/collections.sql @@ -0,0 +1,102 @@ +-- name: CreateCollection :exec +INSERT INTO collections (uri, author_did, name, description, icon, created_at, indexed_at) +VALUES ($1, $2, $3, $4, $5, $6, $7) +ON CONFLICT(uri) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + icon = EXCLUDED.icon, + indexed_at = EXCLUDED.indexed_at; + +-- name: GetCollectionsByAuthor :many +SELECT uri, author_did, name, description, icon, created_at, indexed_at +FROM collections +WHERE author_did = $1 +ORDER BY created_at DESC; + +-- name: GetCollectionByURI :one +SELECT uri, author_did, name, description, icon, created_at, indexed_at +FROM collections +WHERE uri = $1; + +-- name: DeleteCollectionItemsByCollection :exec +DELETE FROM collection_items WHERE collection_uri = $1; + +-- name: DeleteCollection :exec +DELETE FROM collections WHERE uri = $1; + +-- name: AddToCollection :exec +INSERT INTO collection_items (uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at) +VALUES ($1, $2, $3, $4, $5, $6, $7) +ON CONFLICT(uri) DO UPDATE SET + position = EXCLUDED.position, + indexed_at = EXCLUDED.indexed_at; + +-- name: GetCollectionItems :many +SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at +FROM collection_items +WHERE collection_uri = $1 +ORDER BY position ASC, created_at DESC; + +-- name: RemoveFromCollection :exec +DELETE FROM collection_items WHERE uri = $1; + +-- name: GetRecentCollectionItems :many +SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at +FROM collection_items +ORDER BY created_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetPopularCollectionItems :many +SELECT + c.uri, c.author_did, c.collection_uri, c.annotation_uri, + c.position, c.created_at, c.indexed_at +FROM collection_items c +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = c.annotation_uri +) l ON true +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = c.annotation_uri +) r ON true +WHERE c.created_at > $1 AND (l.cnt + r.cnt) > 0 +ORDER BY (l.cnt + r.cnt) DESC, c.created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetShelvedCollectionItems :many +SELECT + c.uri, c.author_did, c.collection_uri, c.annotation_uri, + c.position, c.created_at, c.indexed_at +FROM collection_items c +WHERE c.created_at < $1 AND c.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = c.annotation_uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = c.annotation_uri) +ORDER BY RANDOM() +LIMIT $3 OFFSET $4; + +-- name: GetCollectionItemsByAuthor :many +SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at +FROM collection_items +WHERE author_did = $1 +ORDER BY created_at DESC; + +-- name: GetCollectionURIsForAnnotation :many +SELECT collection_uri FROM collection_items WHERE annotation_uri = $1; + +-- name: GetCollectionItemCounts :many +SELECT collection_uri, COUNT(*) +FROM collection_items +WHERE collection_uri = ANY($1::text[]) +GROUP BY collection_uri; + +-- name: GetCollectionsForNoteURIs :many +SELECT DISTINCT ON (ci.annotation_uri) + ci.annotation_uri, + c.uri, c.author_did, c.name, c.description, c.icon, c.created_at, c.indexed_at +FROM collection_items ci +JOIN collections c ON c.uri = ci.collection_uri +WHERE ci.annotation_uri = ANY($1::text[]) +ORDER BY ci.annotation_uri, ci.created_at ASC; + +-- name: GetCollectionsByURIs :many +SELECT uri, author_did, name, description, icon, created_at, indexed_at +FROM collections +WHERE uri = ANY($1::text[]); diff --git a/backend/internal/db/sqlc_queries/highlights.sql b/backend/internal/db/sqlc_queries/highlights.sql new file mode 100644 index 0000000..e8a8558 --- /dev/null +++ b/backend/internal/db/sqlc_queries/highlights.sql @@ -0,0 +1,156 @@ +-- name: CreateHighlight :exec +INSERT INTO highlights (uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +ON CONFLICT(uri) DO UPDATE SET + target_source = EXCLUDED.target_source, + target_hash = EXCLUDED.target_hash, + target_title = EXCLUDED.target_title, + selector_json = EXCLUDED.selector_json, + color = EXCLUDED.color, + tags_json = EXCLUDED.tags_json, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid; + +-- name: GetHighlightByURI :one +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE uri = $1; + +-- name: GetRecentHighlights :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +ORDER BY created_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetPopularHighlights :many +SELECT + h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, + h.selector_json, h.color, h.tags_json, h.created_at, h.indexed_at, h.cid +FROM all_highlights h +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = h.uri +) l ON true +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = h.uri +) r ON true +WHERE h.created_at > $1 AND (l.cnt + r.cnt) > 0 +ORDER BY (l.cnt + r.cnt) DESC, h.created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetShelvedHighlights :many +SELECT + h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, + h.selector_json, h.color, h.tags_json, h.created_at, h.indexed_at, h.cid +FROM all_highlights h +WHERE h.created_at < $1 AND h.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = h.uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = h.uri) +ORDER BY RANDOM() +LIMIT $3 OFFSET $4; + +-- name: GetMarginHighlights :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetSembleHighlights :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetHighlightsByTag :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE lower(tags_json)::jsonb ? $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetMarginHighlightsByTag :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE lower(tags_json)::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetSembleHighlightsByTag :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE lower(tags_json)::jsonb ? $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetHighlightsByTagAndAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 +ORDER BY created_at DESC +LIMIT $3 OFFSET $4; + +-- name: GetMarginHighlightsByTagAndAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4; + +-- name: GetSembleHighlightsByTagAndAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4; + +-- name: GetHighlightsByTargetHash :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE target_hash = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetHighlightsByAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetMarginHighlightsByAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetSembleHighlightsByAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetHighlightsByAuthorAndTargetHash :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND target_hash = $2 +ORDER BY created_at DESC +LIMIT $3 OFFSET $4; + +-- name: DeleteHighlight :exec +DELETE FROM highlights WHERE uri = $1; + +-- name: UpdateHighlight :exec +UPDATE highlights +SET color = $1, tags_json = $2, cid = $3, indexed_at = $4 +WHERE uri = $5; + +-- name: GetHighlightsByURIs :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE uri = ANY($1::text[]); + +-- name: GetHighlightURIs :many +SELECT uri FROM all_highlights WHERE author_did = $1; diff --git a/backend/internal/db/sqlc_queries/history.sql b/backend/internal/db/sqlc_queries/history.sql new file mode 100644 index 0000000..3ee82a0 --- /dev/null +++ b/backend/internal/db/sqlc_queries/history.sql @@ -0,0 +1,15 @@ +-- name: SaveEditHistory :exec +INSERT INTO edit_history (uri, record_type, previous_content, previous_cid, edited_at) +VALUES ($1, $2, $3, $4, $5); + +-- name: GetEditHistory :many +SELECT id, uri, record_type, previous_content, previous_cid, edited_at +FROM edit_history +WHERE uri = $1 +ORDER BY edited_at DESC; + +-- name: GetLatestEditTimes :many +SELECT uri, MAX(edited_at) as edited_at +FROM edit_history +WHERE uri = ANY($1::text[]) +GROUP BY uri; diff --git a/backend/internal/db/sqlc_queries/kv.sql b/backend/internal/db/sqlc_queries/kv.sql new file mode 100644 index 0000000..67ae4d8 --- /dev/null +++ b/backend/internal/db/sqlc_queries/kv.sql @@ -0,0 +1,5 @@ +-- name: InsertKVIgnore :exec +INSERT INTO kv_store (key, value, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (key) DO NOTHING; + +-- name: GetKVValue :one +SELECT value FROM kv_store WHERE key = $1; diff --git a/backend/internal/db/sqlc_queries/likes.sql b/backend/internal/db/sqlc_queries/likes.sql new file mode 100644 index 0000000..15757e7 --- /dev/null +++ b/backend/internal/db/sqlc_queries/likes.sql @@ -0,0 +1,32 @@ +-- name: CreateLike :exec +INSERT INTO likes (uri, author_did, subject_uri, created_at, indexed_at) +VALUES ($1, $2, $3, $4, $5) +ON CONFLICT(uri) DO NOTHING; + +-- name: DeleteLike :exec +DELETE FROM likes WHERE uri = $1; + +-- name: GetLikesByAuthor :many +SELECT uri, author_did, subject_uri, created_at, indexed_at +FROM likes +WHERE author_did = $1 +ORDER BY created_at DESC; + +-- name: GetLikeCount :one +SELECT COUNT(*) FROM likes WHERE subject_uri = $1; + +-- name: GetLikeByUserAndSubject :one +SELECT uri, author_did, subject_uri, created_at, indexed_at +FROM likes +WHERE author_did = $1 AND subject_uri = $2; + +-- name: GetLikeCounts :many +SELECT subject_uri, COUNT(*) +FROM likes +WHERE subject_uri = ANY($1::text[]) +GROUP BY subject_uri; + +-- name: GetViewerLikes :many +SELECT subject_uri +FROM likes +WHERE author_did = $1 AND subject_uri = ANY($2::text[]); diff --git a/backend/internal/db/sqlc_queries/misc.sql b/backend/internal/db/sqlc_queries/misc.sql new file mode 100644 index 0000000..82014c3 --- /dev/null +++ b/backend/internal/db/sqlc_queries/misc.sql @@ -0,0 +1,51 @@ +-- name: GetAnnotationAuthorByURI :one +SELECT author_did FROM annotations WHERE uri = $1; + +-- name: GetHighlightAuthorByURI :one +SELECT author_did FROM highlights WHERE uri = $1; + +-- name: GetBookmarkAuthorByURI :one +SELECT author_did FROM bookmarks WHERE uri = $1; + +-- name: GetTrendingTags :many +SELECT tag, COUNT(*) as count FROM ( + SELECT value as tag, author_did + FROM annotations, json_array_elements_text(tags_json::json) as value + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + AND created_at > NOW() - INTERVAL '14 days' + UNION ALL + SELECT value as tag, author_did + FROM highlights, json_array_elements_text(tags_json::json) as value + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + AND created_at > NOW() - INTERVAL '14 days' + UNION ALL + SELECT value as tag, author_did + FROM bookmarks, json_array_elements_text(tags_json::json) as value + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + AND created_at > NOW() - INTERVAL '14 days' +) combined +GROUP BY tag +HAVING COUNT(DISTINCT author_did) >= 3 +ORDER BY count DESC +LIMIT $1; + +-- name: GetUserTags :many +SELECT tag, SUM(cnt) as count FROM ( + SELECT value as tag, COUNT(*) as cnt + FROM annotations, json_array_elements_text(tags_json::json) as value + WHERE annotations.author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + GROUP BY tag + UNION ALL + SELECT value as tag, COUNT(*) as cnt + FROM highlights, json_array_elements_text(tags_json::json) as value + WHERE highlights.author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + GROUP BY tag + UNION ALL + SELECT value as tag, COUNT(*) as cnt + FROM bookmarks, json_array_elements_text(tags_json::json) as value + WHERE bookmarks.author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + GROUP BY tag +) combined +GROUP BY tag +ORDER BY count DESC +LIMIT $2; diff --git a/backend/internal/db/sqlc_queries/moderation.sql b/backend/internal/db/sqlc_queries/moderation.sql new file mode 100644 index 0000000..932fa6b --- /dev/null +++ b/backend/internal/db/sqlc_queries/moderation.sql @@ -0,0 +1,120 @@ +-- name: CreateBlock :exec +INSERT INTO blocks (actor_did, subject_did, created_at) VALUES ($1, $2, $3) +ON CONFLICT(actor_did, subject_did) DO NOTHING; + +-- name: DeleteBlock :exec +DELETE FROM blocks WHERE actor_did = $1 AND subject_did = $2; + +-- name: GetBlocks :many +SELECT id, actor_did, subject_did, created_at FROM blocks WHERE actor_did = $1 ORDER BY created_at DESC; + +-- name: IsBlocked :one +SELECT EXISTS(SELECT 1 FROM blocks WHERE actor_did = $1 AND subject_did = $2); + +-- name: IsBlockedEither :one +SELECT EXISTS(SELECT 1 FROM blocks WHERE (actor_did = $1 AND subject_did = $2) OR (actor_did = $2 AND subject_did = $1)); + +-- name: GetBlockedDIDs :many +SELECT subject_did FROM blocks WHERE actor_did = $1; + +-- name: GetBlockedByDIDs :many +SELECT actor_did FROM blocks WHERE subject_did = $1; + +-- name: CreateMute :exec +INSERT INTO mutes (actor_did, subject_did, created_at) VALUES ($1, $2, $3) +ON CONFLICT(actor_did, subject_did) DO NOTHING; + +-- name: DeleteMute :exec +DELETE FROM mutes WHERE actor_did = $1 AND subject_did = $2; + +-- name: GetMutes :many +SELECT id, actor_did, subject_did, created_at FROM mutes WHERE actor_did = $1 ORDER BY created_at DESC; + +-- name: IsMuted :one +SELECT EXISTS(SELECT 1 FROM mutes WHERE actor_did = $1 AND subject_did = $2); + +-- name: GetMutedDIDs :many +SELECT subject_did FROM mutes WHERE actor_did = $1; + +-- name: CreateReport :one +INSERT INTO moderation_reports (reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at) +VALUES ($1, $2, $3, $4, $5, 'pending', $6) +RETURNING id; + +-- name: GetReports :many +SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by +FROM moderation_reports +ORDER BY created_at DESC LIMIT $1 OFFSET $2; + +-- name: GetReportsByStatus :many +SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by +FROM moderation_reports +WHERE status = $1 +ORDER BY created_at DESC LIMIT $2 OFFSET $3; + +-- name: GetReport :one +SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by FROM moderation_reports WHERE id = $1; + +-- name: ResolveReport :exec +UPDATE moderation_reports SET status = $1, resolved_at = $2, resolved_by = $3 WHERE id = $4; + +-- name: CreateModerationAction :exec +INSERT INTO moderation_actions (report_id, actor_did, action, comment, created_at) VALUES ($1, $2, $3, $4, $5); + +-- name: GetReportActions :many +SELECT id, report_id, actor_did, action, comment, created_at FROM moderation_actions WHERE report_id = $1 ORDER BY created_at DESC; + +-- name: GetReportCount :one +SELECT COUNT(*) FROM moderation_reports; + +-- name: GetReportCountByStatus :one +SELECT COUNT(*) FROM moderation_reports WHERE status = $1; + +-- name: CreateContentLabel :exec +INSERT INTO content_labels (src, uri, val, neg, created_by, created_at) VALUES ($1, $2, $3, 0, $4, $5); + +-- name: DeleteSelfLabels :exec +DELETE FROM content_labels WHERE src = $1 AND uri = $2 AND created_by = $3; + +-- name: NegateContentLabel :exec +UPDATE content_labels SET neg = 1 WHERE id = $1; + +-- name: DeleteContentLabel :exec +DELETE FROM content_labels WHERE id = $1; + +-- name: GetContentLabelsForURIs :many +SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels +WHERE uri = ANY($1::text[]) AND neg = 0 +ORDER BY created_at DESC; + +-- name: GetContentLabelsForURIsBySrc :many +SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels +WHERE uri = ANY($1::text[]) AND neg = 0 AND src = ANY($2::text[]) +ORDER BY created_at DESC; + +-- name: GetAllContentLabels :many +SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels ORDER BY created_at DESC LIMIT $1 OFFSET $2; + +-- name: MarkTakenDown :exec +INSERT INTO taken_down_uris (uri, taken_down_at) VALUES ($1, $2) +ON CONFLICT(uri) DO NOTHING; + +-- name: IsTakenDown :one +SELECT EXISTS(SELECT 1 FROM taken_down_uris WHERE uri = $1); + +-- name: BanAccount :exec +INSERT INTO banned_accounts (did, reason, banned_by, banned_at) +VALUES ($1, $2, $3, $4) +ON CONFLICT(did) DO UPDATE SET reason = EXCLUDED.reason, banned_by = EXCLUDED.banned_by, banned_at = EXCLUDED.banned_at; + +-- name: UnbanAccount :exec +DELETE FROM banned_accounts WHERE did = $1; + +-- name: IsBanned :one +SELECT EXISTS(SELECT 1 FROM banned_accounts WHERE did = $1); + +-- name: GetBannedAccounts :many +SELECT did, reason, banned_by, banned_at FROM banned_accounts ORDER BY banned_at DESC; + +-- name: GetBannedDIDs :many +SELECT did FROM banned_accounts; diff --git a/backend/internal/db/sqlc_queries/notes.sql b/backend/internal/db/sqlc_queries/notes.sql new file mode 100644 index 0000000..6073841 --- /dev/null +++ b/backend/internal/db/sqlc_queries/notes.sql @@ -0,0 +1,74 @@ +-- name: CreateNote :exec +INSERT INTO notes ( + uri, author_did, motivation, color, description, body_value, body_format, body_uri, + target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16 +) ON CONFLICT (uri) DO UPDATE SET + motivation = EXCLUDED.motivation, + color = EXCLUDED.color, + description = EXCLUDED.description, + body_value = EXCLUDED.body_value, + body_format = EXCLUDED.body_format, + body_uri = EXCLUDED.body_uri, + target_source = EXCLUDED.target_source, + target_hash = EXCLUDED.target_hash, + target_title = EXCLUDED.target_title, + selector_json = EXCLUDED.selector_json, + tags_json = EXCLUDED.tags_json, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid; + +-- name: GetNoteByURI :one +SELECT uri, author_did, motivation, color, description, body_value, body_format, body_uri, + target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM notes WHERE uri = $1; + +-- name: MarginNoteBookmarkExists :one +SELECT 1 FROM notes +WHERE author_did = $1 + AND target_hash = $2 + AND motivation = 'bookmarking' + AND uri LIKE 'at://%/at.margin.note/%' +LIMIT 1; + +-- name: SaveCommunityBookmarkRef :exec +INSERT INTO community_bookmark_refs (note_uri, community_uri) +VALUES ($1, $2) +ON CONFLICT (note_uri) DO UPDATE SET community_uri = EXCLUDED.community_uri; + +-- name: GetCommunityBookmarkURI :one +SELECT community_uri FROM community_bookmark_refs WHERE note_uri = $1; + +-- name: DeleteCommunityBookmarkRef :exec +DELETE FROM community_bookmark_refs WHERE note_uri = $1; + +-- name: CommunityBookmarkExists :one +SELECT 1 FROM community_bookmark_refs cbr +JOIN notes n ON n.uri = cbr.note_uri +WHERE n.author_did = $1 + AND n.target_hash = $2 +LIMIT 1; + +-- name: GetNotesByURIs :many +SELECT uri, author_did, motivation, color, description, body_value, body_format, body_uri, + target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM notes WHERE uri = ANY($1::text[]); + +-- name: DeleteNote :exec +DELETE FROM notes WHERE uri = $1; + +-- name: UpdateNoteAnnotation :exec +UPDATE notes +SET body_value = $1, tags_json = NULLIF($2, ''), cid = $3, indexed_at = $4 +WHERE uri = $5; + +-- name: UpdateNoteHighlight :exec +UPDATE notes +SET color = NULLIF($1, ''), tags_json = NULLIF($2, ''), cid = $3, indexed_at = $4 +WHERE uri = $5; + +-- name: UpdateNoteBookmark :exec +UPDATE notes +SET target_title = NULLIF($1, ''), body_value = NULLIF($2, ''), tags_json = NULLIF($3, ''), cid = $4, indexed_at = $5 +WHERE uri = $6; diff --git a/backend/internal/db/sqlc_queries/notifications.sql b/backend/internal/db/sqlc_queries/notifications.sql new file mode 100644 index 0000000..17666c5 --- /dev/null +++ b/backend/internal/db/sqlc_queries/notifications.sql @@ -0,0 +1,16 @@ +-- name: CreateNotification :exec +INSERT INTO notifications (recipient_did, actor_did, type, subject_uri, created_at) +VALUES ($1, $2, $3, $4, $5); + +-- name: GetNotifications :many +SELECT id, recipient_did, actor_did, type, subject_uri, created_at, read_at +FROM notifications +WHERE recipient_did = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3; + +-- name: GetUnreadNotificationCount :one +SELECT COUNT(*) FROM notifications WHERE recipient_did = $1 AND read_at IS NULL; + +-- name: MarkNotificationsRead :exec +UPDATE notifications SET read_at = $1 WHERE recipient_did = $2 AND read_at IS NULL; diff --git a/backend/internal/db/sqlc_queries/oauth.sql b/backend/internal/db/sqlc_queries/oauth.sql new file mode 100644 index 0000000..2e3fd14 --- /dev/null +++ b/backend/internal/db/sqlc_queries/oauth.sql @@ -0,0 +1,65 @@ +-- name: SavePendingAuthOAuth :exec +INSERT INTO oauth_pending_auths + (state, did, handle, pds, issuer, token_endpoint, pkce_verifier, + dpop_crv, dpop_d, dpop_x, dpop_y, dpop_nonce, created_at) +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) +ON CONFLICT (state) DO UPDATE SET + did = EXCLUDED.did, handle = EXCLUDED.handle, pds = EXCLUDED.pds, + issuer = EXCLUDED.issuer, token_endpoint = EXCLUDED.token_endpoint, + pkce_verifier = EXCLUDED.pkce_verifier, + dpop_crv = EXCLUDED.dpop_crv, dpop_d = EXCLUDED.dpop_d, + dpop_x = EXCLUDED.dpop_x, dpop_y = EXCLUDED.dpop_y, + dpop_nonce = EXCLUDED.dpop_nonce, created_at = EXCLUDED.created_at; + +-- name: GetPendingAuthOAuth :one +SELECT did, handle, pds, issuer, token_endpoint, pkce_verifier, + dpop_crv, dpop_d, dpop_x, dpop_y, dpop_nonce, created_at +FROM oauth_pending_auths WHERE state = $1; + +-- name: DeletePendingAuthOAuth :exec +DELETE FROM oauth_pending_auths WHERE state = $1; + +-- name: DeleteExpiredPendingAuthsOAuth :exec +DELETE FROM oauth_pending_auths WHERE created_at < $1; + +-- name: CreateOAuthSession :exec +INSERT INTO oauth_sessions + (id, did, handle, pds, email, access_token, refresh_token, + token_endpoint, issuer, dpop_crv, dpop_d, dpop_x, dpop_y, + access_token_expires_at, expires_at, created_at) +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15, NOW()) +ON CONFLICT (id) DO UPDATE SET + access_token = EXCLUDED.access_token, + refresh_token = EXCLUDED.refresh_token, + access_token_expires_at = EXCLUDED.access_token_expires_at, + expires_at = EXCLUDED.expires_at; + +-- name: GetOAuthSessionByID :one +SELECT did, handle, pds, email, access_token, refresh_token, + token_endpoint, issuer, dpop_crv, dpop_d, dpop_x, dpop_y, + access_token_expires_at, expires_at, created_at +FROM oauth_sessions WHERE id = $1 AND expires_at > NOW(); + +-- name: UpdateOAuthSessionTokens :exec +UPDATE oauth_sessions +SET access_token = $2, refresh_token = $3, access_token_expires_at = $4 +WHERE id = $1; + +-- name: DeleteOAuthSession :exec +DELETE FROM oauth_sessions WHERE id = $1; + +-- name: DeleteOAuthSessionsByDID :exec +DELETE FROM oauth_sessions WHERE did = $1; + +-- name: DeleteExpiredOAuthSessions :exec +DELETE FROM oauth_sessions WHERE expires_at <= NOW(); + +-- name: GetLatestOAuthSessionByDID :one +SELECT id, did, handle, pds, email, access_token, refresh_token, + token_endpoint, issuer, dpop_crv, dpop_d, dpop_x, dpop_y, + access_token_expires_at, expires_at, created_at +FROM oauth_sessions WHERE did = $1 AND expires_at > NOW() +ORDER BY created_at DESC LIMIT 1; + +-- name: CountOAuthSessionsByDID :one +SELECT COUNT(*) FROM oauth_sessions WHERE did = $1; diff --git a/backend/internal/db/sqlc_queries/profiles.sql b/backend/internal/db/sqlc_queries/profiles.sql new file mode 100644 index 0000000..0901ffc --- /dev/null +++ b/backend/internal/db/sqlc_queries/profiles.sql @@ -0,0 +1,54 @@ +-- name: GetProfile :one +SELECT uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at +FROM profiles WHERE author_did = $1; + +-- name: GetProfilesByDIDs :many +SELECT uri, author_did, display_name, bio, avatar, website, links_json, created_at, indexed_at +FROM profiles WHERE author_did = ANY($1::text[]); + +-- name: UpsertProfile :exec +INSERT INTO profiles (uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +ON CONFLICT(uri) DO UPDATE SET + display_name = EXCLUDED.display_name, + avatar = EXCLUDED.avatar, + bio = EXCLUDED.bio, + website = EXCLUDED.website, + links_json = EXCLUDED.links_json, + indexed_at = EXCLUDED.indexed_at; + +-- name: DeleteProfile :exec +DELETE FROM profiles WHERE uri = $1; + +-- name: GetPreferences :one +SELECT uri, author_did, external_link_skipped_hostnames, subscribed_labelers, + label_preferences, disable_external_link_warning, enable_community_bookmarks, + created_at, indexed_at, cid +FROM preferences WHERE author_did = $1; + +-- name: UpsertPreferences :exec +INSERT INTO preferences ( + uri, author_did, external_link_skipped_hostnames, subscribed_labelers, + label_preferences, disable_external_link_warning, enable_community_bookmarks, + created_at, indexed_at, cid +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) +ON CONFLICT(uri) DO UPDATE SET + external_link_skipped_hostnames = EXCLUDED.external_link_skipped_hostnames, + subscribed_labelers = EXCLUDED.subscribed_labelers, + label_preferences = EXCLUDED.label_preferences, + disable_external_link_warning = EXCLUDED.disable_external_link_warning, + enable_community_bookmarks = EXCLUDED.enable_community_bookmarks, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid; + +-- name: DeletePreferences :exec +DELETE FROM preferences WHERE uri = $1; + +-- name: GetPreferenceURIs :many +SELECT uri FROM preferences WHERE author_did = $1 AND uri IS NOT NULL AND uri != ''; + +-- name: DeleteAPIKeyByURI :exec +DELETE FROM api_keys WHERE uri = $1; + +-- name: GetAPIKeyURIs :many +SELECT uri FROM api_keys WHERE owner_did = $1 AND uri IS NOT NULL AND uri != ''; diff --git a/backend/internal/db/sqlc_queries/recommendations.sql b/backend/internal/db/sqlc_queries/recommendations.sql new file mode 100644 index 0000000..bdf11d9 --- /dev/null +++ b/backend/internal/db/sqlc_queries/recommendations.sql @@ -0,0 +1,141 @@ +-- name: UpsertPublication :exec +INSERT INTO publications (uri, author_did, url, name, description, show_in_discover, indexed_at) +VALUES ($1, $2, $3, $4, $5, $6, $7) +ON CONFLICT(uri) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + show_in_discover = EXCLUDED.show_in_discover, + indexed_at = EXCLUDED.indexed_at; + +-- name: DeletePublication :exec +DELETE FROM publications WHERE uri = $1; + +-- name: GetPublicationByURL :one +SELECT uri, author_did, url, name, description, show_in_discover, indexed_at +FROM publications WHERE url = $1; + +-- name: UpsertDocument :exec +INSERT INTO documents (uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +ON CONFLICT(uri) DO UPDATE SET + title = EXCLUDED.title, + description = EXCLUDED.description, + text_content = EXCLUDED.text_content, + tags_json = EXCLUDED.tags_json, + canonical_url = EXCLUDED.canonical_url, + indexed_at = EXCLUDED.indexed_at; + +-- name: DeleteDocument :exec +DELETE FROM documents WHERE uri = $1; + +-- name: GetDocumentByCanonicalURL :one +SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at +FROM documents WHERE canonical_url = $1; + +-- name: GetDocumentByURI :one +SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at +FROM documents WHERE uri = $1; + +-- name: GetDocumentsWithoutEmbeddings :many +SELECT d.uri, d.author_did, d.site, d.path, d.title, d.description, d.text_content, d.tags_json, d.canonical_url, d.published_at, d.indexed_at +FROM documents d +LEFT JOIN document_embeddings de ON d.uri = de.document_uri +WHERE de.document_uri IS NULL +ORDER BY d.indexed_at DESC +LIMIT $1; + +-- name: GetAnnotationsWithoutEmbeddings :many +SELECT a.uri, a.author_did, a.motivation, a.body_value, a.body_format, a.body_uri, a.target_source, a.target_hash, a.target_title, a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid +FROM annotations a +LEFT JOIN annotation_embeddings ae ON a.uri = ae.annotation_uri +WHERE ae.annotation_uri IS NULL AND a.motivation IN ('commenting', 'highlighting') +ORDER BY a.created_at DESC +LIMIT $1; + +-- name: GetHighlightsWithoutEmbeddings :many +SELECT h.uri, h.author_did, h.target_source, h.target_title, h.selector_json, h.tags_json +FROM highlights h +LEFT JOIN annotation_embeddings ae ON h.uri = ae.annotation_uri +WHERE ae.annotation_uri IS NULL +ORDER BY h.created_at DESC +LIMIT $1; + +-- name: GetDistinctAnnotationAuthors :many +SELECT DISTINCT author_did FROM annotation_embeddings; + +-- name: GetRecentDocuments :many +SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at +FROM documents +ORDER BY published_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetPopularDocuments :many +SELECT d.uri, d.author_did, d.site, d.path, d.title, d.description, d.text_content, d.tags_json, d.canonical_url, d.published_at, d.indexed_at +FROM documents d +LEFT JOIN annotations a ON a.target_source = d.canonical_url +GROUP BY d.uri +ORDER BY COUNT(a.uri) DESC, d.published_at DESC +LIMIT $1 OFFSET $2; + +-- name: GetDocumentCount :one +SELECT COUNT(*) FROM documents; + +-- name: UpsertDocumentEmbedding :exec +INSERT INTO document_embeddings (document_uri, embedding, updated_at) VALUES ($1, $2, $3) +ON CONFLICT(document_uri) DO UPDATE SET embedding = EXCLUDED.embedding, updated_at = EXCLUDED.updated_at; + +-- name: UpsertAnnotationEmbedding :exec +INSERT INTO annotation_embeddings (annotation_uri, author_did, document_uri, embedding, updated_at) VALUES ($1, $2, $3, $4, $5) +ON CONFLICT(annotation_uri) DO UPDATE SET embedding = EXCLUDED.embedding, document_uri = EXCLUDED.document_uri, updated_at = EXCLUDED.updated_at; + +-- name: DeleteAnnotationEmbedding :exec +DELETE FROM annotation_embeddings WHERE annotation_uri = $1; + +-- name: UpsertUserProfile :exec +INSERT INTO user_profiles (author_did, embedding, tag_affinities, annotation_count, updated_at) VALUES ($1, $2, $3, $4, $5) +ON CONFLICT(author_did) DO UPDATE SET embedding = EXCLUDED.embedding, tag_affinities = EXCLUDED.tag_affinities, annotation_count = EXCLUDED.annotation_count, updated_at = EXCLUDED.updated_at; + +-- name: GetUserProfile :one +SELECT author_did, embedding, tag_affinities, annotation_count, updated_at FROM user_profiles WHERE author_did = $1; + +-- name: GetAnnotationEmbeddingsByAuthor :many +SELECT annotation_uri, author_did, document_uri, embedding, updated_at FROM annotation_embeddings WHERE author_did = $1; + +-- name: GetRecentAnnotationEmbeddingsByAuthor :many +SELECT annotation_uri, author_did, document_uri, embedding, updated_at FROM annotation_embeddings WHERE author_did = $1 ORDER BY updated_at DESC LIMIT $2; + +-- name: GetCandidateDocuments :many +SELECT + d.uri, d.author_did, d.site, d.path, d.title, d.description, d.tags_json, + d.canonical_url, d.published_at, de.embedding, + COALESCE(eng.cnt, 0) AS engagement +FROM documents d +JOIN document_embeddings de ON d.uri = de.document_uri +LEFT JOIN ( + SELECT document_uri, COUNT(DISTINCT author_did) AS cnt + FROM annotation_embeddings + WHERE document_uri IS NOT NULL + GROUP BY document_uri +) eng ON eng.document_uri = d.uri +LEFT JOIN publications p ON d.site = p.uri OR d.site = p.url +WHERE d.author_did != $1 + AND (p.show_in_discover IS NULL OR p.show_in_discover = true) + AND LENGTH(d.title) > 15 + AND (LENGTH(COALESCE(d.description, '')) >= 30 OR LENGTH(COALESCE(d.text_content, '')) >= 100) + AND LOWER(d.title) NOT LIKE '%test%' + AND LOWER(d.title) NOT LIKE '%testing%' + AND LOWER(d.title) NOT LIKE '%hello world%' + AND LOWER(d.title) NOT LIKE '%untitled%' + AND LOWER(d.title) NOT LIKE '%draft%' + AND LOWER(d.title) NOT LIKE '%asdf%' + AND LOWER(d.title) NOT LIKE '%lorem%' + AND LOWER(d.title) NOT LIKE '%placeholder%' + AND d.uri NOT IN ( + SELECT DISTINCT ae2.document_uri FROM annotation_embeddings ae2 + WHERE ae2.author_did = $2 AND ae2.document_uri IS NOT NULL + ) +ORDER BY d.published_at DESC +LIMIT $3; + +-- name: MatchAnnotationToDocument :one +SELECT uri FROM documents WHERE canonical_url = $1; diff --git a/backend/internal/db/sqlc_queries/replies.sql b/backend/internal/db/sqlc_queries/replies.sql new file mode 100644 index 0000000..784b8c7 --- /dev/null +++ b/backend/internal/db/sqlc_queries/replies.sql @@ -0,0 +1,48 @@ +-- name: CreateReply :exec +INSERT INTO replies (uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +ON CONFLICT(uri) DO UPDATE SET + text = EXCLUDED.text, + format = EXCLUDED.format, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid; + +-- name: GetRepliesByRoot :many +SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid +FROM replies +WHERE root_uri = $1 +ORDER BY created_at ASC; + +-- name: GetReplyByURI :one +SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid +FROM replies +WHERE uri = $1; + +-- name: DeleteReply :exec +DELETE FROM replies WHERE uri = $1; + +-- name: GetRepliesByAuthor :many +SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid +FROM replies +WHERE author_did = $1 +ORDER BY created_at DESC; + +-- name: GetOrphanedRepliesByAuthor :many +SELECT r.uri, r.author_did, r.parent_uri, r.root_uri, r.text, r.format, r.created_at, r.indexed_at, r.cid +FROM replies r +LEFT JOIN annotations a ON r.root_uri = a.uri +WHERE r.author_did = $1 AND a.uri IS NULL; + +-- name: GetReplyCount :one +SELECT COUNT(*) FROM replies WHERE root_uri = $1; + +-- name: GetReplyCounts :many +SELECT root_uri, COUNT(*) +FROM replies +WHERE root_uri = ANY($1::text[]) +GROUP BY root_uri; + +-- name: GetRepliesByURIs :many +SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid +FROM replies +WHERE uri = ANY($1::text[]); diff --git a/backend/internal/db/sqlcdb/_test_jsonb.sql.go b/backend/internal/db/sqlcdb/_test_jsonb.sql.go new file mode 100644 index 0000000..7cf2d10 --- /dev/null +++ b/backend/internal/db/sqlcdb/_test_jsonb.sql.go @@ -0,0 +1,34 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: _test_jsonb.sql + +package sqlcdb + +import ( + "context" +) + +const testJsonb = `-- name: TestJsonb :many +SELECT uri FROM all_bookmarks WHERE lower(tags_json)::jsonb ? $1 +` + +func (q *Queries) TestJsonb(ctx context.Context, tagsJson *string) ([]string, error) { + rows, err := q.db.Query(ctx, testJsonb, tagsJson) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var uri string + if err := rows.Scan(&uri); err != nil { + return nil, err + } + items = append(items, uri) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/backend/internal/db/sqlcdb/annotations.sql.go b/backend/internal/db/sqlcdb/annotations.sql.go new file mode 100644 index 0000000..8cff28a --- /dev/null +++ b/backend/internal/db/sqlcdb/annotations.sql.go @@ -0,0 +1,1065 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: annotations.sql + +package sqlcdb + +import ( + "context" + "time" +) + +const createAnnotation = `-- name: CreateAnnotation :exec +INSERT INTO annotations (uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) +ON CONFLICT(uri) DO UPDATE SET + motivation = EXCLUDED.motivation, + body_value = EXCLUDED.body_value, + body_format = EXCLUDED.body_format, + body_uri = EXCLUDED.body_uri, + target_source = EXCLUDED.target_source, + target_hash = EXCLUDED.target_hash, + target_title = EXCLUDED.target_title, + selector_json = EXCLUDED.selector_json, + tags_json = EXCLUDED.tags_json, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid +` + +type CreateAnnotationParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Motivation *string `json:"motivation"` + BodyValue *string `json:"bodyValue"` + BodyFormat *string `json:"bodyFormat"` + BodyUri *string `json:"bodyUri"` + TargetSource *string `json:"targetSource"` + TargetHash *string `json:"targetHash"` + TargetTitle *string `json:"targetTitle"` + SelectorJson *string `json:"selectorJson"` + TagsJson *string `json:"tagsJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +func (q *Queries) CreateAnnotation(ctx context.Context, arg CreateAnnotationParams) error { + _, err := q.db.Exec(ctx, createAnnotation, + arg.Uri, + arg.AuthorDid, + arg.Motivation, + arg.BodyValue, + arg.BodyFormat, + arg.BodyUri, + arg.TargetSource, + arg.TargetHash, + arg.TargetTitle, + arg.SelectorJson, + arg.TagsJson, + arg.CreatedAt, + arg.IndexedAt, + arg.Cid, + ) + return err +} + +const deleteAnnotation = `-- name: DeleteAnnotation :exec +DELETE FROM annotations WHERE uri = $1 +` + +func (q *Queries) DeleteAnnotation(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, deleteAnnotation, uri) + return err +} + +const getAnnotationByURI = `-- name: GetAnnotationByURI :one +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE uri = $1 +` + +func (q *Queries) GetAnnotationByURI(ctx context.Context, uri string) (AllAnnotation, error) { + row := q.db.QueryRow(ctx, getAnnotationByURI, uri) + var i AllAnnotation + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ) + return i, err +} + +const getAnnotationURIs = `-- name: GetAnnotationURIs :many +SELECT uri FROM all_annotations WHERE author_did = $1 +` + +func (q *Queries) GetAnnotationURIs(ctx context.Context, authorDid string) ([]string, error) { + rows, err := q.db.Query(ctx, getAnnotationURIs, authorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var uri string + if err := rows.Scan(&uri); err != nil { + return nil, err + } + items = append(items, uri) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAnnotationsByAuthor = `-- name: GetAnnotationsByAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetAnnotationsByAuthorParams struct { + AuthorDid string `json:"authorDid"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetAnnotationsByAuthor(ctx context.Context, arg GetAnnotationsByAuthorParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getAnnotationsByAuthor, arg.AuthorDid, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAnnotationsByAuthorAndTargetHash = `-- name: GetAnnotationsByAuthorAndTargetHash :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND target_hash = $2 +ORDER BY created_at DESC +LIMIT $3 OFFSET $4 +` + +type GetAnnotationsByAuthorAndTargetHashParams struct { + AuthorDid string `json:"authorDid"` + TargetHash *string `json:"targetHash"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetAnnotationsByAuthorAndTargetHash(ctx context.Context, arg GetAnnotationsByAuthorAndTargetHashParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getAnnotationsByAuthorAndTargetHash, + arg.AuthorDid, + arg.TargetHash, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAnnotationsByMotivation = `-- name: GetAnnotationsByMotivation :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE motivation = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetAnnotationsByMotivationParams struct { + Motivation *string `json:"motivation"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetAnnotationsByMotivation(ctx context.Context, arg GetAnnotationsByMotivationParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getAnnotationsByMotivation, arg.Motivation, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAnnotationsByTag = `-- name: GetAnnotationsByTag :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE lower(tags_json)::jsonb ? $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetAnnotationsByTagParams struct { + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetAnnotationsByTag(ctx context.Context, arg GetAnnotationsByTagParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getAnnotationsByTag, arg.TagsJson, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAnnotationsByTagAndAuthor = `-- name: GetAnnotationsByTagAndAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 +ORDER BY created_at DESC +LIMIT $3 OFFSET $4 +` + +type GetAnnotationsByTagAndAuthorParams struct { + AuthorDid string `json:"authorDid"` + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetAnnotationsByTagAndAuthor(ctx context.Context, arg GetAnnotationsByTagAndAuthorParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getAnnotationsByTagAndAuthor, + arg.AuthorDid, + arg.TagsJson, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAnnotationsByTargetHash = `-- name: GetAnnotationsByTargetHash :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE target_hash = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetAnnotationsByTargetHashParams struct { + TargetHash *string `json:"targetHash"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetAnnotationsByTargetHash(ctx context.Context, arg GetAnnotationsByTargetHashParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getAnnotationsByTargetHash, arg.TargetHash, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAnnotationsByURIs = `-- name: GetAnnotationsByURIs :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE uri = ANY($1::text[]) +` + +func (q *Queries) GetAnnotationsByURIs(ctx context.Context, dollar_1 []string) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getAnnotationsByURIs, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginAnnotations = `-- name: GetMarginAnnotations :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2 +` + +type GetMarginAnnotationsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginAnnotations(ctx context.Context, arg GetMarginAnnotationsParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getMarginAnnotations, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginAnnotationsByAuthor = `-- name: GetMarginAnnotationsByAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetMarginAnnotationsByAuthorParams struct { + AuthorDid string `json:"authorDid"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginAnnotationsByAuthor(ctx context.Context, arg GetMarginAnnotationsByAuthorParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getMarginAnnotationsByAuthor, arg.AuthorDid, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginAnnotationsByTag = `-- name: GetMarginAnnotationsByTag :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE lower(tags_json)::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetMarginAnnotationsByTagParams struct { + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginAnnotationsByTag(ctx context.Context, arg GetMarginAnnotationsByTagParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getMarginAnnotationsByTag, arg.TagsJson, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginAnnotationsByTagAndAuthor = `-- name: GetMarginAnnotationsByTagAndAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4 +` + +type GetMarginAnnotationsByTagAndAuthorParams struct { + AuthorDid string `json:"authorDid"` + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginAnnotationsByTagAndAuthor(ctx context.Context, arg GetMarginAnnotationsByTagAndAuthorParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getMarginAnnotationsByTagAndAuthor, + arg.AuthorDid, + arg.TagsJson, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getPopularAnnotations = `-- name: GetPopularAnnotations :many +SELECT + a.uri, a.author_did, a.motivation, a.body_value, a.body_format, + a.body_uri, a.target_source, a.target_hash, a.target_title, + a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid +FROM all_annotations a +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = a.uri +) l ON true +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = a.uri +) r ON true +WHERE a.created_at > $1 AND (l.cnt + r.cnt) > 0 +ORDER BY (l.cnt + r.cnt) DESC, a.created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetPopularAnnotationsParams struct { + CreatedAt time.Time `json:"createdAt"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetPopularAnnotations(ctx context.Context, arg GetPopularAnnotationsParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getPopularAnnotations, arg.CreatedAt, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getRecentAnnotations = `-- name: GetRecentAnnotations :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +ORDER BY created_at DESC +LIMIT $1 OFFSET $2 +` + +type GetRecentAnnotationsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetRecentAnnotations(ctx context.Context, arg GetRecentAnnotationsParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getRecentAnnotations, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleAnnotations = `-- name: GetSembleAnnotations :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2 +` + +type GetSembleAnnotationsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleAnnotations(ctx context.Context, arg GetSembleAnnotationsParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getSembleAnnotations, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleAnnotationsByAuthor = `-- name: GetSembleAnnotationsByAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetSembleAnnotationsByAuthorParams struct { + AuthorDid string `json:"authorDid"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleAnnotationsByAuthor(ctx context.Context, arg GetSembleAnnotationsByAuthorParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getSembleAnnotationsByAuthor, arg.AuthorDid, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleAnnotationsByTag = `-- name: GetSembleAnnotationsByTag :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE lower(tags_json)::jsonb ? $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetSembleAnnotationsByTagParams struct { + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleAnnotationsByTag(ctx context.Context, arg GetSembleAnnotationsByTagParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getSembleAnnotationsByTag, arg.TagsJson, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleAnnotationsByTagAndAuthor = `-- name: GetSembleAnnotationsByTagAndAuthor :many +SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM all_annotations +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4 +` + +type GetSembleAnnotationsByTagAndAuthorParams struct { + AuthorDid string `json:"authorDid"` + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleAnnotationsByTagAndAuthor(ctx context.Context, arg GetSembleAnnotationsByTagAndAuthorParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getSembleAnnotationsByTagAndAuthor, + arg.AuthorDid, + arg.TagsJson, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getShelvedAnnotations = `-- name: GetShelvedAnnotations :many +SELECT + a.uri, a.author_did, a.motivation, a.body_value, a.body_format, + a.body_uri, a.target_source, a.target_hash, a.target_title, + a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid +FROM all_annotations a +WHERE a.created_at < $1 AND a.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = a.uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = a.uri) +ORDER BY RANDOM() +LIMIT $3 OFFSET $4 +` + +type GetShelvedAnnotationsParams struct { + CreatedAt time.Time `json:"createdAt"` + CreatedAt_2 time.Time `json:"createdAt2"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetShelvedAnnotations(ctx context.Context, arg GetShelvedAnnotationsParams) ([]AllAnnotation, error) { + rows, err := q.db.Query(ctx, getShelvedAnnotations, + arg.CreatedAt, + arg.CreatedAt_2, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllAnnotation + for rows.Next() { + var i AllAnnotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateAnnotation = `-- name: UpdateAnnotation :exec +UPDATE annotations +SET body_value = $1, tags_json = $2, cid = $3, indexed_at = $4 +WHERE uri = $5 +` + +type UpdateAnnotationParams struct { + BodyValue *string `json:"bodyValue"` + TagsJson *string `json:"tagsJson"` + Cid *string `json:"cid"` + IndexedAt time.Time `json:"indexedAt"` + Uri string `json:"uri"` +} + +func (q *Queries) UpdateAnnotation(ctx context.Context, arg UpdateAnnotationParams) error { + _, err := q.db.Exec(ctx, updateAnnotation, + arg.BodyValue, + arg.TagsJson, + arg.Cid, + arg.IndexedAt, + arg.Uri, + ) + return err +} diff --git a/backend/internal/db/sqlcdb/api_keys.sql.go b/backend/internal/db/sqlcdb/api_keys.sql.go new file mode 100644 index 0000000..0294df8 --- /dev/null +++ b/backend/internal/db/sqlcdb/api_keys.sql.go @@ -0,0 +1,146 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: api_keys.sql + +package sqlcdb + +import ( + "context" + "time" +) + +const createAPIKey = `-- name: CreateAPIKey :exec +INSERT INTO api_keys (id, owner_did, name, key_hash, created_at, uri, cid) +VALUES ($1, $2, $3, $4, $5, $6, $7) +ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + key_hash = EXCLUDED.key_hash, + uri = EXCLUDED.uri, + cid = EXCLUDED.cid +` + +type CreateAPIKeyParams struct { + ID string `json:"id"` + OwnerDid string `json:"ownerDid"` + Name string `json:"name"` + KeyHash string `json:"keyHash"` + CreatedAt time.Time `json:"createdAt"` + Uri *string `json:"uri"` + Cid *string `json:"cid"` +} + +func (q *Queries) CreateAPIKey(ctx context.Context, arg CreateAPIKeyParams) error { + _, err := q.db.Exec(ctx, createAPIKey, + arg.ID, + arg.OwnerDid, + arg.Name, + arg.KeyHash, + arg.CreatedAt, + arg.Uri, + arg.Cid, + ) + return err +} + +const deleteAPIKeyReturningURI = `-- name: DeleteAPIKeyReturningURI :one +DELETE FROM api_keys WHERE id = $1 AND owner_did = $2 RETURNING uri +` + +type DeleteAPIKeyReturningURIParams struct { + ID string `json:"id"` + OwnerDid string `json:"ownerDid"` +} + +func (q *Queries) DeleteAPIKeyReturningURI(ctx context.Context, arg DeleteAPIKeyReturningURIParams) (*string, error) { + row := q.db.QueryRow(ctx, deleteAPIKeyReturningURI, arg.ID, arg.OwnerDid) + var uri *string + err := row.Scan(&uri) + return uri, err +} + +const getAPIKeyByHash = `-- name: GetAPIKeyByHash :one +SELECT id, owner_did, name, key_hash, created_at, last_used_at +FROM api_keys +WHERE key_hash = $1 +` + +type GetAPIKeyByHashRow struct { + ID string `json:"id"` + OwnerDid string `json:"ownerDid"` + Name string `json:"name"` + KeyHash string `json:"keyHash"` + CreatedAt time.Time `json:"createdAt"` + LastUsedAt *time.Time `json:"lastUsedAt"` +} + +func (q *Queries) GetAPIKeyByHash(ctx context.Context, keyHash string) (GetAPIKeyByHashRow, error) { + row := q.db.QueryRow(ctx, getAPIKeyByHash, keyHash) + var i GetAPIKeyByHashRow + err := row.Scan( + &i.ID, + &i.OwnerDid, + &i.Name, + &i.KeyHash, + &i.CreatedAt, + &i.LastUsedAt, + ) + return i, err +} + +const getAPIKeysByOwner = `-- name: GetAPIKeysByOwner :many +SELECT id, owner_did, name, key_hash, created_at, last_used_at +FROM api_keys +WHERE owner_did = $1 +ORDER BY created_at DESC +` + +type GetAPIKeysByOwnerRow struct { + ID string `json:"id"` + OwnerDid string `json:"ownerDid"` + Name string `json:"name"` + KeyHash string `json:"keyHash"` + CreatedAt time.Time `json:"createdAt"` + LastUsedAt *time.Time `json:"lastUsedAt"` +} + +func (q *Queries) GetAPIKeysByOwner(ctx context.Context, ownerDid string) ([]GetAPIKeysByOwnerRow, error) { + rows, err := q.db.Query(ctx, getAPIKeysByOwner, ownerDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetAPIKeysByOwnerRow + for rows.Next() { + var i GetAPIKeysByOwnerRow + if err := rows.Scan( + &i.ID, + &i.OwnerDid, + &i.Name, + &i.KeyHash, + &i.CreatedAt, + &i.LastUsedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateAPIKeyLastUsed = `-- name: UpdateAPIKeyLastUsed :exec +UPDATE api_keys SET last_used_at = $1 WHERE id = $2 +` + +type UpdateAPIKeyLastUsedParams struct { + LastUsedAt *time.Time `json:"lastUsedAt"` + ID string `json:"id"` +} + +func (q *Queries) UpdateAPIKeyLastUsed(ctx context.Context, arg UpdateAPIKeyLastUsedParams) error { + _, err := q.db.Exec(ctx, updateAPIKeyLastUsed, arg.LastUsedAt, arg.ID) + return err +} diff --git a/backend/internal/db/sqlcdb/bookmarks.sql.go b/backend/internal/db/sqlcdb/bookmarks.sql.go new file mode 100644 index 0000000..828b84c --- /dev/null +++ b/backend/internal/db/sqlcdb/bookmarks.sql.go @@ -0,0 +1,881 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: bookmarks.sql + +package sqlcdb + +import ( + "context" + "time" +) + +const createBookmark = `-- name: CreateBookmark :exec +INSERT INTO bookmarks (uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) +ON CONFLICT(uri) DO UPDATE SET + source = EXCLUDED.source, + source_hash = EXCLUDED.source_hash, + title = EXCLUDED.title, + description = EXCLUDED.description, + tags_json = EXCLUDED.tags_json, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid +` + +type CreateBookmarkParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Source string `json:"source"` + SourceHash string `json:"sourceHash"` + Title *string `json:"title"` + Description *string `json:"description"` + TagsJson *string `json:"tagsJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +func (q *Queries) CreateBookmark(ctx context.Context, arg CreateBookmarkParams) error { + _, err := q.db.Exec(ctx, createBookmark, + arg.Uri, + arg.AuthorDid, + arg.Source, + arg.SourceHash, + arg.Title, + arg.Description, + arg.TagsJson, + arg.CreatedAt, + arg.IndexedAt, + arg.Cid, + ) + return err +} + +const deleteBookmark = `-- name: DeleteBookmark :exec +DELETE FROM bookmarks WHERE uri = $1 +` + +func (q *Queries) DeleteBookmark(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, deleteBookmark, uri) + return err +} + +const getBookmarkByURI = `-- name: GetBookmarkByURI :one +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE uri = $1 +` + +func (q *Queries) GetBookmarkByURI(ctx context.Context, uri string) (AllBookmark, error) { + row := q.db.QueryRow(ctx, getBookmarkByURI, uri) + var i AllBookmark + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ) + return i, err +} + +const getBookmarkURIs = `-- name: GetBookmarkURIs :many +SELECT uri FROM all_bookmarks WHERE author_did = $1 +` + +func (q *Queries) GetBookmarkURIs(ctx context.Context, authorDid string) ([]string, error) { + rows, err := q.db.Query(ctx, getBookmarkURIs, authorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var uri string + if err := rows.Scan(&uri); err != nil { + return nil, err + } + items = append(items, uri) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getBookmarksByAuthor = `-- name: GetBookmarksByAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetBookmarksByAuthorParams struct { + AuthorDid string `json:"authorDid"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetBookmarksByAuthor(ctx context.Context, arg GetBookmarksByAuthorParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getBookmarksByAuthor, arg.AuthorDid, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getBookmarksByTag = `-- name: GetBookmarksByTag :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE lower(tags_json)::jsonb ? $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetBookmarksByTagParams struct { + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetBookmarksByTag(ctx context.Context, arg GetBookmarksByTagParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getBookmarksByTag, arg.TagsJson, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getBookmarksByTagAndAuthor = `-- name: GetBookmarksByTagAndAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 +ORDER BY created_at DESC +LIMIT $3 OFFSET $4 +` + +type GetBookmarksByTagAndAuthorParams struct { + AuthorDid string `json:"authorDid"` + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetBookmarksByTagAndAuthor(ctx context.Context, arg GetBookmarksByTagAndAuthorParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getBookmarksByTagAndAuthor, + arg.AuthorDid, + arg.TagsJson, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getBookmarksByTargetHash = `-- name: GetBookmarksByTargetHash :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE source_hash = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetBookmarksByTargetHashParams struct { + SourceHash string `json:"sourceHash"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetBookmarksByTargetHash(ctx context.Context, arg GetBookmarksByTargetHashParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getBookmarksByTargetHash, arg.SourceHash, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getBookmarksByURIs = `-- name: GetBookmarksByURIs :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE uri = ANY($1::text[]) +` + +func (q *Queries) GetBookmarksByURIs(ctx context.Context, dollar_1 []string) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getBookmarksByURIs, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginBookmarks = `-- name: GetMarginBookmarks :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2 +` + +type GetMarginBookmarksParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginBookmarks(ctx context.Context, arg GetMarginBookmarksParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getMarginBookmarks, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginBookmarksByAuthor = `-- name: GetMarginBookmarksByAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetMarginBookmarksByAuthorParams struct { + AuthorDid string `json:"authorDid"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginBookmarksByAuthor(ctx context.Context, arg GetMarginBookmarksByAuthorParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getMarginBookmarksByAuthor, arg.AuthorDid, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginBookmarksByTag = `-- name: GetMarginBookmarksByTag :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE lower(tags_json)::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetMarginBookmarksByTagParams struct { + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginBookmarksByTag(ctx context.Context, arg GetMarginBookmarksByTagParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getMarginBookmarksByTag, arg.TagsJson, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginBookmarksByTagAndAuthor = `-- name: GetMarginBookmarksByTagAndAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4 +` + +type GetMarginBookmarksByTagAndAuthorParams struct { + AuthorDid string `json:"authorDid"` + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginBookmarksByTagAndAuthor(ctx context.Context, arg GetMarginBookmarksByTagAndAuthorParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getMarginBookmarksByTagAndAuthor, + arg.AuthorDid, + arg.TagsJson, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getPopularBookmarks = `-- name: GetPopularBookmarks :many +SELECT + b.uri, b.author_did, b.source, b.source_hash, b.title, + b.description, b.tags_json, b.created_at, b.indexed_at, b.cid +FROM all_bookmarks b +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = b.uri +) l ON true +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = b.uri +) r ON true +WHERE b.created_at > $1 AND (l.cnt + r.cnt) > 0 +ORDER BY (l.cnt + r.cnt) DESC, b.created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetPopularBookmarksParams struct { + CreatedAt time.Time `json:"createdAt"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetPopularBookmarks(ctx context.Context, arg GetPopularBookmarksParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getPopularBookmarks, arg.CreatedAt, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getRecentBookmarks = `-- name: GetRecentBookmarks :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +ORDER BY created_at DESC +LIMIT $1 OFFSET $2 +` + +type GetRecentBookmarksParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetRecentBookmarks(ctx context.Context, arg GetRecentBookmarksParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getRecentBookmarks, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleBookmarks = `-- name: GetSembleBookmarks :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2 +` + +type GetSembleBookmarksParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleBookmarks(ctx context.Context, arg GetSembleBookmarksParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getSembleBookmarks, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleBookmarksByAuthor = `-- name: GetSembleBookmarksByAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetSembleBookmarksByAuthorParams struct { + AuthorDid string `json:"authorDid"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleBookmarksByAuthor(ctx context.Context, arg GetSembleBookmarksByAuthorParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getSembleBookmarksByAuthor, arg.AuthorDid, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleBookmarksByTag = `-- name: GetSembleBookmarksByTag :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE lower(tags_json)::jsonb ? $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetSembleBookmarksByTagParams struct { + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleBookmarksByTag(ctx context.Context, arg GetSembleBookmarksByTagParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getSembleBookmarksByTag, arg.TagsJson, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleBookmarksByTagAndAuthor = `-- name: GetSembleBookmarksByTagAndAuthor :many +SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid +FROM all_bookmarks +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4 +` + +type GetSembleBookmarksByTagAndAuthorParams struct { + AuthorDid string `json:"authorDid"` + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleBookmarksByTagAndAuthor(ctx context.Context, arg GetSembleBookmarksByTagAndAuthorParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getSembleBookmarksByTagAndAuthor, + arg.AuthorDid, + arg.TagsJson, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getShelvedBookmarks = `-- name: GetShelvedBookmarks :many +SELECT + b.uri, b.author_did, b.source, b.source_hash, b.title, + b.description, b.tags_json, b.created_at, b.indexed_at, b.cid +FROM all_bookmarks b +WHERE b.created_at < $1 AND b.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = b.uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = b.uri) +ORDER BY RANDOM() +LIMIT $3 OFFSET $4 +` + +type GetShelvedBookmarksParams struct { + CreatedAt time.Time `json:"createdAt"` + CreatedAt_2 time.Time `json:"createdAt2"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetShelvedBookmarks(ctx context.Context, arg GetShelvedBookmarksParams) ([]AllBookmark, error) { + rows, err := q.db.Query(ctx, getShelvedBookmarks, + arg.CreatedAt, + arg.CreatedAt_2, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllBookmark + for rows.Next() { + var i AllBookmark + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Source, + &i.SourceHash, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateBookmark = `-- name: UpdateBookmark :exec +UPDATE bookmarks +SET title = $1, description = $2, tags_json = $3, cid = $4, indexed_at = $5 +WHERE uri = $6 +` + +type UpdateBookmarkParams struct { + Title *string `json:"title"` + Description *string `json:"description"` + TagsJson *string `json:"tagsJson"` + Cid *string `json:"cid"` + IndexedAt time.Time `json:"indexedAt"` + Uri string `json:"uri"` +} + +func (q *Queries) UpdateBookmark(ctx context.Context, arg UpdateBookmarkParams) error { + _, err := q.db.Exec(ctx, updateBookmark, + arg.Title, + arg.Description, + arg.TagsJson, + arg.Cid, + arg.IndexedAt, + arg.Uri, + ) + return err +} diff --git a/backend/internal/db/sqlcdb/collections.sql.go b/backend/internal/db/sqlcdb/collections.sql.go new file mode 100644 index 0000000..d127cab --- /dev/null +++ b/backend/internal/db/sqlcdb/collections.sql.go @@ -0,0 +1,512 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: collections.sql + +package sqlcdb + +import ( + "context" + "time" + + "github.com/jackc/pgx/v5/pgtype" +) + +const addToCollection = `-- name: AddToCollection :exec +INSERT INTO collection_items (uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at) +VALUES ($1, $2, $3, $4, $5, $6, $7) +ON CONFLICT(uri) DO UPDATE SET + position = EXCLUDED.position, + indexed_at = EXCLUDED.indexed_at +` + +type AddToCollectionParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + CollectionUri string `json:"collectionUri"` + AnnotationUri string `json:"annotationUri"` + Position pgtype.Int4 `json:"position"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +func (q *Queries) AddToCollection(ctx context.Context, arg AddToCollectionParams) error { + _, err := q.db.Exec(ctx, addToCollection, + arg.Uri, + arg.AuthorDid, + arg.CollectionUri, + arg.AnnotationUri, + arg.Position, + arg.CreatedAt, + arg.IndexedAt, + ) + return err +} + +const createCollection = `-- name: CreateCollection :exec +INSERT INTO collections (uri, author_did, name, description, icon, created_at, indexed_at) +VALUES ($1, $2, $3, $4, $5, $6, $7) +ON CONFLICT(uri) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + icon = EXCLUDED.icon, + indexed_at = EXCLUDED.indexed_at +` + +type CreateCollectionParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Name string `json:"name"` + Description *string `json:"description"` + Icon *string `json:"icon"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +func (q *Queries) CreateCollection(ctx context.Context, arg CreateCollectionParams) error { + _, err := q.db.Exec(ctx, createCollection, + arg.Uri, + arg.AuthorDid, + arg.Name, + arg.Description, + arg.Icon, + arg.CreatedAt, + arg.IndexedAt, + ) + return err +} + +const deleteCollection = `-- name: DeleteCollection :exec +DELETE FROM collections WHERE uri = $1 +` + +func (q *Queries) DeleteCollection(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, deleteCollection, uri) + return err +} + +const deleteCollectionItemsByCollection = `-- name: DeleteCollectionItemsByCollection :exec +DELETE FROM collection_items WHERE collection_uri = $1 +` + +func (q *Queries) DeleteCollectionItemsByCollection(ctx context.Context, collectionUri string) error { + _, err := q.db.Exec(ctx, deleteCollectionItemsByCollection, collectionUri) + return err +} + +const getCollectionByURI = `-- name: GetCollectionByURI :one +SELECT uri, author_did, name, description, icon, created_at, indexed_at +FROM collections +WHERE uri = $1 +` + +func (q *Queries) GetCollectionByURI(ctx context.Context, uri string) (Collection, error) { + row := q.db.QueryRow(ctx, getCollectionByURI, uri) + var i Collection + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.Name, + &i.Description, + &i.Icon, + &i.CreatedAt, + &i.IndexedAt, + ) + return i, err +} + +const getCollectionItemCounts = `-- name: GetCollectionItemCounts :many +SELECT collection_uri, COUNT(*) +FROM collection_items +WHERE collection_uri = ANY($1::text[]) +GROUP BY collection_uri +` + +type GetCollectionItemCountsRow struct { + CollectionUri string `json:"collectionUri"` + Count int64 `json:"count"` +} + +func (q *Queries) GetCollectionItemCounts(ctx context.Context, dollar_1 []string) ([]GetCollectionItemCountsRow, error) { + rows, err := q.db.Query(ctx, getCollectionItemCounts, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetCollectionItemCountsRow + for rows.Next() { + var i GetCollectionItemCountsRow + if err := rows.Scan(&i.CollectionUri, &i.Count); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getCollectionItems = `-- name: GetCollectionItems :many +SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at +FROM collection_items +WHERE collection_uri = $1 +ORDER BY position ASC, created_at DESC +` + +func (q *Queries) GetCollectionItems(ctx context.Context, collectionUri string) ([]CollectionItem, error) { + rows, err := q.db.Query(ctx, getCollectionItems, collectionUri) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CollectionItem + for rows.Next() { + var i CollectionItem + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.CollectionUri, + &i.AnnotationUri, + &i.Position, + &i.CreatedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getCollectionItemsByAuthor = `-- name: GetCollectionItemsByAuthor :many +SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at +FROM collection_items +WHERE author_did = $1 +ORDER BY created_at DESC +` + +func (q *Queries) GetCollectionItemsByAuthor(ctx context.Context, authorDid string) ([]CollectionItem, error) { + rows, err := q.db.Query(ctx, getCollectionItemsByAuthor, authorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CollectionItem + for rows.Next() { + var i CollectionItem + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.CollectionUri, + &i.AnnotationUri, + &i.Position, + &i.CreatedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getCollectionURIsForAnnotation = `-- name: GetCollectionURIsForAnnotation :many +SELECT collection_uri FROM collection_items WHERE annotation_uri = $1 +` + +func (q *Queries) GetCollectionURIsForAnnotation(ctx context.Context, annotationUri string) ([]string, error) { + rows, err := q.db.Query(ctx, getCollectionURIsForAnnotation, annotationUri) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var collection_uri string + if err := rows.Scan(&collection_uri); err != nil { + return nil, err + } + items = append(items, collection_uri) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getCollectionsByAuthor = `-- name: GetCollectionsByAuthor :many +SELECT uri, author_did, name, description, icon, created_at, indexed_at +FROM collections +WHERE author_did = $1 +ORDER BY created_at DESC +` + +func (q *Queries) GetCollectionsByAuthor(ctx context.Context, authorDid string) ([]Collection, error) { + rows, err := q.db.Query(ctx, getCollectionsByAuthor, authorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Collection + for rows.Next() { + var i Collection + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Name, + &i.Description, + &i.Icon, + &i.CreatedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getCollectionsByURIs = `-- name: GetCollectionsByURIs :many +SELECT uri, author_did, name, description, icon, created_at, indexed_at +FROM collections +WHERE uri = ANY($1::text[]) +` + +func (q *Queries) GetCollectionsByURIs(ctx context.Context, dollar_1 []string) ([]Collection, error) { + rows, err := q.db.Query(ctx, getCollectionsByURIs, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Collection + for rows.Next() { + var i Collection + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Name, + &i.Description, + &i.Icon, + &i.CreatedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getCollectionsForNoteURIs = `-- name: GetCollectionsForNoteURIs :many +SELECT DISTINCT ON (ci.annotation_uri) + ci.annotation_uri, + c.uri, c.author_did, c.name, c.description, c.icon, c.created_at, c.indexed_at +FROM collection_items ci +JOIN collections c ON c.uri = ci.collection_uri +WHERE ci.annotation_uri = ANY($1::text[]) +ORDER BY ci.annotation_uri, ci.created_at ASC +` + +type GetCollectionsForNoteURIsRow struct { + AnnotationUri string `json:"annotationUri"` + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Name string `json:"name"` + Description *string `json:"description"` + Icon *string `json:"icon"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +func (q *Queries) GetCollectionsForNoteURIs(ctx context.Context, dollar_1 []string) ([]GetCollectionsForNoteURIsRow, error) { + rows, err := q.db.Query(ctx, getCollectionsForNoteURIs, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetCollectionsForNoteURIsRow + for rows.Next() { + var i GetCollectionsForNoteURIsRow + if err := rows.Scan( + &i.AnnotationUri, + &i.Uri, + &i.AuthorDid, + &i.Name, + &i.Description, + &i.Icon, + &i.CreatedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getPopularCollectionItems = `-- name: GetPopularCollectionItems :many +SELECT + c.uri, c.author_did, c.collection_uri, c.annotation_uri, + c.position, c.created_at, c.indexed_at +FROM collection_items c +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = c.annotation_uri +) l ON true +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = c.annotation_uri +) r ON true +WHERE c.created_at > $1 AND (l.cnt + r.cnt) > 0 +ORDER BY (l.cnt + r.cnt) DESC, c.created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetPopularCollectionItemsParams struct { + CreatedAt time.Time `json:"createdAt"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetPopularCollectionItems(ctx context.Context, arg GetPopularCollectionItemsParams) ([]CollectionItem, error) { + rows, err := q.db.Query(ctx, getPopularCollectionItems, arg.CreatedAt, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CollectionItem + for rows.Next() { + var i CollectionItem + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.CollectionUri, + &i.AnnotationUri, + &i.Position, + &i.CreatedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getRecentCollectionItems = `-- name: GetRecentCollectionItems :many +SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at +FROM collection_items +ORDER BY created_at DESC +LIMIT $1 OFFSET $2 +` + +type GetRecentCollectionItemsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetRecentCollectionItems(ctx context.Context, arg GetRecentCollectionItemsParams) ([]CollectionItem, error) { + rows, err := q.db.Query(ctx, getRecentCollectionItems, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CollectionItem + for rows.Next() { + var i CollectionItem + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.CollectionUri, + &i.AnnotationUri, + &i.Position, + &i.CreatedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getShelvedCollectionItems = `-- name: GetShelvedCollectionItems :many +SELECT + c.uri, c.author_did, c.collection_uri, c.annotation_uri, + c.position, c.created_at, c.indexed_at +FROM collection_items c +WHERE c.created_at < $1 AND c.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = c.annotation_uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = c.annotation_uri) +ORDER BY RANDOM() +LIMIT $3 OFFSET $4 +` + +type GetShelvedCollectionItemsParams struct { + CreatedAt time.Time `json:"createdAt"` + CreatedAt_2 time.Time `json:"createdAt2"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetShelvedCollectionItems(ctx context.Context, arg GetShelvedCollectionItemsParams) ([]CollectionItem, error) { + rows, err := q.db.Query(ctx, getShelvedCollectionItems, + arg.CreatedAt, + arg.CreatedAt_2, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CollectionItem + for rows.Next() { + var i CollectionItem + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.CollectionUri, + &i.AnnotationUri, + &i.Position, + &i.CreatedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const removeFromCollection = `-- name: RemoveFromCollection :exec +DELETE FROM collection_items WHERE uri = $1 +` + +func (q *Queries) RemoveFromCollection(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, removeFromCollection, uri) + return err +} diff --git a/backend/internal/db/sqlcdb/db.go b/backend/internal/db/sqlcdb/db.go new file mode 100644 index 0000000..eaed72c --- /dev/null +++ b/backend/internal/db/sqlcdb/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package sqlcdb + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/backend/internal/db/sqlcdb/highlights.sql.go b/backend/internal/db/sqlcdb/highlights.sql.go new file mode 100644 index 0000000..6e34cc5 --- /dev/null +++ b/backend/internal/db/sqlcdb/highlights.sql.go @@ -0,0 +1,951 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: highlights.sql + +package sqlcdb + +import ( + "context" + "time" +) + +const createHighlight = `-- name: CreateHighlight :exec +INSERT INTO highlights (uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +ON CONFLICT(uri) DO UPDATE SET + target_source = EXCLUDED.target_source, + target_hash = EXCLUDED.target_hash, + target_title = EXCLUDED.target_title, + selector_json = EXCLUDED.selector_json, + color = EXCLUDED.color, + tags_json = EXCLUDED.tags_json, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid +` + +type CreateHighlightParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + TargetSource string `json:"targetSource"` + TargetHash string `json:"targetHash"` + TargetTitle *string `json:"targetTitle"` + SelectorJson *string `json:"selectorJson"` + Color *string `json:"color"` + TagsJson *string `json:"tagsJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +func (q *Queries) CreateHighlight(ctx context.Context, arg CreateHighlightParams) error { + _, err := q.db.Exec(ctx, createHighlight, + arg.Uri, + arg.AuthorDid, + arg.TargetSource, + arg.TargetHash, + arg.TargetTitle, + arg.SelectorJson, + arg.Color, + arg.TagsJson, + arg.CreatedAt, + arg.IndexedAt, + arg.Cid, + ) + return err +} + +const deleteHighlight = `-- name: DeleteHighlight :exec +DELETE FROM highlights WHERE uri = $1 +` + +func (q *Queries) DeleteHighlight(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, deleteHighlight, uri) + return err +} + +const getHighlightByURI = `-- name: GetHighlightByURI :one +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE uri = $1 +` + +func (q *Queries) GetHighlightByURI(ctx context.Context, uri string) (AllHighlight, error) { + row := q.db.QueryRow(ctx, getHighlightByURI, uri) + var i AllHighlight + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ) + return i, err +} + +const getHighlightURIs = `-- name: GetHighlightURIs :many +SELECT uri FROM all_highlights WHERE author_did = $1 +` + +func (q *Queries) GetHighlightURIs(ctx context.Context, authorDid string) ([]string, error) { + rows, err := q.db.Query(ctx, getHighlightURIs, authorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var uri string + if err := rows.Scan(&uri); err != nil { + return nil, err + } + items = append(items, uri) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getHighlightsByAuthor = `-- name: GetHighlightsByAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetHighlightsByAuthorParams struct { + AuthorDid string `json:"authorDid"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetHighlightsByAuthor(ctx context.Context, arg GetHighlightsByAuthorParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getHighlightsByAuthor, arg.AuthorDid, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getHighlightsByAuthorAndTargetHash = `-- name: GetHighlightsByAuthorAndTargetHash :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND target_hash = $2 +ORDER BY created_at DESC +LIMIT $3 OFFSET $4 +` + +type GetHighlightsByAuthorAndTargetHashParams struct { + AuthorDid string `json:"authorDid"` + TargetHash string `json:"targetHash"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetHighlightsByAuthorAndTargetHash(ctx context.Context, arg GetHighlightsByAuthorAndTargetHashParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getHighlightsByAuthorAndTargetHash, + arg.AuthorDid, + arg.TargetHash, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getHighlightsByTag = `-- name: GetHighlightsByTag :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE lower(tags_json)::jsonb ? $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetHighlightsByTagParams struct { + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetHighlightsByTag(ctx context.Context, arg GetHighlightsByTagParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getHighlightsByTag, arg.TagsJson, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getHighlightsByTagAndAuthor = `-- name: GetHighlightsByTagAndAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 +ORDER BY created_at DESC +LIMIT $3 OFFSET $4 +` + +type GetHighlightsByTagAndAuthorParams struct { + AuthorDid string `json:"authorDid"` + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetHighlightsByTagAndAuthor(ctx context.Context, arg GetHighlightsByTagAndAuthorParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getHighlightsByTagAndAuthor, + arg.AuthorDid, + arg.TagsJson, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getHighlightsByTargetHash = `-- name: GetHighlightsByTargetHash :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE target_hash = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetHighlightsByTargetHashParams struct { + TargetHash string `json:"targetHash"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetHighlightsByTargetHash(ctx context.Context, arg GetHighlightsByTargetHashParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getHighlightsByTargetHash, arg.TargetHash, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getHighlightsByURIs = `-- name: GetHighlightsByURIs :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE uri = ANY($1::text[]) +` + +func (q *Queries) GetHighlightsByURIs(ctx context.Context, dollar_1 []string) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getHighlightsByURIs, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginHighlights = `-- name: GetMarginHighlights :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2 +` + +type GetMarginHighlightsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginHighlights(ctx context.Context, arg GetMarginHighlightsParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getMarginHighlights, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginHighlightsByAuthor = `-- name: GetMarginHighlightsByAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetMarginHighlightsByAuthorParams struct { + AuthorDid string `json:"authorDid"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginHighlightsByAuthor(ctx context.Context, arg GetMarginHighlightsByAuthorParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getMarginHighlightsByAuthor, arg.AuthorDid, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginHighlightsByTag = `-- name: GetMarginHighlightsByTag :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE lower(tags_json)::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetMarginHighlightsByTagParams struct { + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginHighlightsByTag(ctx context.Context, arg GetMarginHighlightsByTagParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getMarginHighlightsByTag, arg.TagsJson, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMarginHighlightsByTagAndAuthor = `-- name: GetMarginHighlightsByTagAndAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4 +` + +type GetMarginHighlightsByTagAndAuthorParams struct { + AuthorDid string `json:"authorDid"` + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetMarginHighlightsByTagAndAuthor(ctx context.Context, arg GetMarginHighlightsByTagAndAuthorParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getMarginHighlightsByTagAndAuthor, + arg.AuthorDid, + arg.TagsJson, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getPopularHighlights = `-- name: GetPopularHighlights :many +SELECT + h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, + h.selector_json, h.color, h.tags_json, h.created_at, h.indexed_at, h.cid +FROM all_highlights h +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = h.uri +) l ON true +LEFT JOIN LATERAL ( + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = h.uri +) r ON true +WHERE h.created_at > $1 AND (l.cnt + r.cnt) > 0 +ORDER BY (l.cnt + r.cnt) DESC, h.created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetPopularHighlightsParams struct { + CreatedAt time.Time `json:"createdAt"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetPopularHighlights(ctx context.Context, arg GetPopularHighlightsParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getPopularHighlights, arg.CreatedAt, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getRecentHighlights = `-- name: GetRecentHighlights :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +ORDER BY created_at DESC +LIMIT $1 OFFSET $2 +` + +type GetRecentHighlightsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetRecentHighlights(ctx context.Context, arg GetRecentHighlightsParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getRecentHighlights, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleHighlights = `-- name: GetSembleHighlights :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $1 OFFSET $2 +` + +type GetSembleHighlightsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleHighlights(ctx context.Context, arg GetSembleHighlightsParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getSembleHighlights, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleHighlightsByAuthor = `-- name: GetSembleHighlightsByAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetSembleHighlightsByAuthorParams struct { + AuthorDid string `json:"authorDid"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleHighlightsByAuthor(ctx context.Context, arg GetSembleHighlightsByAuthorParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getSembleHighlightsByAuthor, arg.AuthorDid, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleHighlightsByTag = `-- name: GetSembleHighlightsByTag :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE lower(tags_json)::jsonb ? $1 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetSembleHighlightsByTagParams struct { + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleHighlightsByTag(ctx context.Context, arg GetSembleHighlightsByTagParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getSembleHighlightsByTag, arg.TagsJson, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSembleHighlightsByTagAndAuthor = `-- name: GetSembleHighlightsByTagAndAuthor :many +SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid +FROM all_highlights +WHERE author_did = $1 AND lower(tags_json)::jsonb ? $2 AND uri LIKE '%network.cosmik%' +ORDER BY created_at DESC +LIMIT $3 OFFSET $4 +` + +type GetSembleHighlightsByTagAndAuthorParams struct { + AuthorDid string `json:"authorDid"` + TagsJson *string `json:"tagsJson"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSembleHighlightsByTagAndAuthor(ctx context.Context, arg GetSembleHighlightsByTagAndAuthorParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getSembleHighlightsByTagAndAuthor, + arg.AuthorDid, + arg.TagsJson, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getShelvedHighlights = `-- name: GetShelvedHighlights :many +SELECT + h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, + h.selector_json, h.color, h.tags_json, h.created_at, h.indexed_at, h.cid +FROM all_highlights h +WHERE h.created_at < $1 AND h.created_at > $2 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = h.uri) + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = h.uri) +ORDER BY RANDOM() +LIMIT $3 OFFSET $4 +` + +type GetShelvedHighlightsParams struct { + CreatedAt time.Time `json:"createdAt"` + CreatedAt_2 time.Time `json:"createdAt2"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetShelvedHighlights(ctx context.Context, arg GetShelvedHighlightsParams) ([]AllHighlight, error) { + rows, err := q.db.Query(ctx, getShelvedHighlights, + arg.CreatedAt, + arg.CreatedAt_2, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AllHighlight + for rows.Next() { + var i AllHighlight + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.Color, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateHighlight = `-- name: UpdateHighlight :exec +UPDATE highlights +SET color = $1, tags_json = $2, cid = $3, indexed_at = $4 +WHERE uri = $5 +` + +type UpdateHighlightParams struct { + Color *string `json:"color"` + TagsJson *string `json:"tagsJson"` + Cid *string `json:"cid"` + IndexedAt time.Time `json:"indexedAt"` + Uri string `json:"uri"` +} + +func (q *Queries) UpdateHighlight(ctx context.Context, arg UpdateHighlightParams) error { + _, err := q.db.Exec(ctx, updateHighlight, + arg.Color, + arg.TagsJson, + arg.Cid, + arg.IndexedAt, + arg.Uri, + ) + return err +} diff --git a/backend/internal/db/sqlcdb/history.sql.go b/backend/internal/db/sqlcdb/history.sql.go new file mode 100644 index 0000000..7f705ba --- /dev/null +++ b/backend/internal/db/sqlcdb/history.sql.go @@ -0,0 +1,101 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: history.sql + +package sqlcdb + +import ( + "context" + "time" +) + +const getEditHistory = `-- name: GetEditHistory :many +SELECT id, uri, record_type, previous_content, previous_cid, edited_at +FROM edit_history +WHERE uri = $1 +ORDER BY edited_at DESC +` + +func (q *Queries) GetEditHistory(ctx context.Context, uri string) ([]EditHistory, error) { + rows, err := q.db.Query(ctx, getEditHistory, uri) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EditHistory + for rows.Next() { + var i EditHistory + if err := rows.Scan( + &i.ID, + &i.Uri, + &i.RecordType, + &i.PreviousContent, + &i.PreviousCid, + &i.EditedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getLatestEditTimes = `-- name: GetLatestEditTimes :many +SELECT uri, MAX(edited_at) as edited_at +FROM edit_history +WHERE uri = ANY($1::text[]) +GROUP BY uri +` + +type GetLatestEditTimesRow struct { + Uri string `json:"uri"` + EditedAt interface{} `json:"editedAt"` +} + +func (q *Queries) GetLatestEditTimes(ctx context.Context, dollar_1 []string) ([]GetLatestEditTimesRow, error) { + rows, err := q.db.Query(ctx, getLatestEditTimes, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetLatestEditTimesRow + for rows.Next() { + var i GetLatestEditTimesRow + if err := rows.Scan(&i.Uri, &i.EditedAt); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const saveEditHistory = `-- name: SaveEditHistory :exec +INSERT INTO edit_history (uri, record_type, previous_content, previous_cid, edited_at) +VALUES ($1, $2, $3, $4, $5) +` + +type SaveEditHistoryParams struct { + Uri string `json:"uri"` + RecordType string `json:"recordType"` + PreviousContent string `json:"previousContent"` + PreviousCid *string `json:"previousCid"` + EditedAt time.Time `json:"editedAt"` +} + +func (q *Queries) SaveEditHistory(ctx context.Context, arg SaveEditHistoryParams) error { + _, err := q.db.Exec(ctx, saveEditHistory, + arg.Uri, + arg.RecordType, + arg.PreviousContent, + arg.PreviousCid, + arg.EditedAt, + ) + return err +} diff --git a/backend/internal/db/sqlcdb/kv.sql.go b/backend/internal/db/sqlcdb/kv.sql.go new file mode 100644 index 0000000..0673aab --- /dev/null +++ b/backend/internal/db/sqlcdb/kv.sql.go @@ -0,0 +1,35 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: kv.sql + +package sqlcdb + +import ( + "context" +) + +const getKVValue = `-- name: GetKVValue :one +SELECT value FROM kv_store WHERE key = $1 +` + +func (q *Queries) GetKVValue(ctx context.Context, key string) (string, error) { + row := q.db.QueryRow(ctx, getKVValue, key) + var value string + err := row.Scan(&value) + return value, err +} + +const insertKVIgnore = `-- name: InsertKVIgnore :exec +INSERT INTO kv_store (key, value, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (key) DO NOTHING +` + +type InsertKVIgnoreParams struct { + Key string `json:"key"` + Value string `json:"value"` +} + +func (q *Queries) InsertKVIgnore(ctx context.Context, arg InsertKVIgnoreParams) error { + _, err := q.db.Exec(ctx, insertKVIgnore, arg.Key, arg.Value) + return err +} diff --git a/backend/internal/db/sqlcdb/likes.sql.go b/backend/internal/db/sqlcdb/likes.sql.go new file mode 100644 index 0000000..7ca1042 --- /dev/null +++ b/backend/internal/db/sqlcdb/likes.sql.go @@ -0,0 +1,176 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: likes.sql + +package sqlcdb + +import ( + "context" + "time" +) + +const createLike = `-- name: CreateLike :exec +INSERT INTO likes (uri, author_did, subject_uri, created_at, indexed_at) +VALUES ($1, $2, $3, $4, $5) +ON CONFLICT(uri) DO NOTHING +` + +type CreateLikeParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + SubjectUri string `json:"subjectUri"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +func (q *Queries) CreateLike(ctx context.Context, arg CreateLikeParams) error { + _, err := q.db.Exec(ctx, createLike, + arg.Uri, + arg.AuthorDid, + arg.SubjectUri, + arg.CreatedAt, + arg.IndexedAt, + ) + return err +} + +const deleteLike = `-- name: DeleteLike :exec +DELETE FROM likes WHERE uri = $1 +` + +func (q *Queries) DeleteLike(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, deleteLike, uri) + return err +} + +const getLikeByUserAndSubject = `-- name: GetLikeByUserAndSubject :one +SELECT uri, author_did, subject_uri, created_at, indexed_at +FROM likes +WHERE author_did = $1 AND subject_uri = $2 +` + +type GetLikeByUserAndSubjectParams struct { + AuthorDid string `json:"authorDid"` + SubjectUri string `json:"subjectUri"` +} + +func (q *Queries) GetLikeByUserAndSubject(ctx context.Context, arg GetLikeByUserAndSubjectParams) (Like, error) { + row := q.db.QueryRow(ctx, getLikeByUserAndSubject, arg.AuthorDid, arg.SubjectUri) + var i Like + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.SubjectUri, + &i.CreatedAt, + &i.IndexedAt, + ) + return i, err +} + +const getLikeCount = `-- name: GetLikeCount :one +SELECT COUNT(*) FROM likes WHERE subject_uri = $1 +` + +func (q *Queries) GetLikeCount(ctx context.Context, subjectUri string) (int64, error) { + row := q.db.QueryRow(ctx, getLikeCount, subjectUri) + var count int64 + err := row.Scan(&count) + return count, err +} + +const getLikeCounts = `-- name: GetLikeCounts :many +SELECT subject_uri, COUNT(*) +FROM likes +WHERE subject_uri = ANY($1::text[]) +GROUP BY subject_uri +` + +type GetLikeCountsRow struct { + SubjectUri string `json:"subjectUri"` + Count int64 `json:"count"` +} + +func (q *Queries) GetLikeCounts(ctx context.Context, dollar_1 []string) ([]GetLikeCountsRow, error) { + rows, err := q.db.Query(ctx, getLikeCounts, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetLikeCountsRow + for rows.Next() { + var i GetLikeCountsRow + if err := rows.Scan(&i.SubjectUri, &i.Count); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getLikesByAuthor = `-- name: GetLikesByAuthor :many +SELECT uri, author_did, subject_uri, created_at, indexed_at +FROM likes +WHERE author_did = $1 +ORDER BY created_at DESC +` + +func (q *Queries) GetLikesByAuthor(ctx context.Context, authorDid string) ([]Like, error) { + rows, err := q.db.Query(ctx, getLikesByAuthor, authorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Like + for rows.Next() { + var i Like + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.SubjectUri, + &i.CreatedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getViewerLikes = `-- name: GetViewerLikes :many +SELECT subject_uri +FROM likes +WHERE author_did = $1 AND subject_uri = ANY($2::text[]) +` + +type GetViewerLikesParams struct { + AuthorDid string `json:"authorDid"` + Column2 []string `json:"column2"` +} + +func (q *Queries) GetViewerLikes(ctx context.Context, arg GetViewerLikesParams) ([]string, error) { + rows, err := q.db.Query(ctx, getViewerLikes, arg.AuthorDid, arg.Column2) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var subject_uri string + if err := rows.Scan(&subject_uri); err != nil { + return nil, err + } + items = append(items, subject_uri) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/backend/internal/db/sqlcdb/misc.sql.go b/backend/internal/db/sqlcdb/misc.sql.go new file mode 100644 index 0000000..bcb0359 --- /dev/null +++ b/backend/internal/db/sqlcdb/misc.sql.go @@ -0,0 +1,143 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: misc.sql + +package sqlcdb + +import ( + "context" +) + +const getAnnotationAuthorByURI = `-- name: GetAnnotationAuthorByURI :one +SELECT author_did FROM annotations WHERE uri = $1 +` + +func (q *Queries) GetAnnotationAuthorByURI(ctx context.Context, uri string) (string, error) { + row := q.db.QueryRow(ctx, getAnnotationAuthorByURI, uri) + var author_did string + err := row.Scan(&author_did) + return author_did, err +} + +const getBookmarkAuthorByURI = `-- name: GetBookmarkAuthorByURI :one +SELECT author_did FROM bookmarks WHERE uri = $1 +` + +func (q *Queries) GetBookmarkAuthorByURI(ctx context.Context, uri string) (string, error) { + row := q.db.QueryRow(ctx, getBookmarkAuthorByURI, uri) + var author_did string + err := row.Scan(&author_did) + return author_did, err +} + +const getHighlightAuthorByURI = `-- name: GetHighlightAuthorByURI :one +SELECT author_did FROM highlights WHERE uri = $1 +` + +func (q *Queries) GetHighlightAuthorByURI(ctx context.Context, uri string) (string, error) { + row := q.db.QueryRow(ctx, getHighlightAuthorByURI, uri) + var author_did string + err := row.Scan(&author_did) + return author_did, err +} + +const getTrendingTags = `-- name: GetTrendingTags :many +SELECT tag, COUNT(*) as count FROM ( + SELECT value as tag, author_did + FROM annotations, json_array_elements_text(tags_json::json) as value + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + AND created_at > NOW() - INTERVAL '14 days' + UNION ALL + SELECT value as tag, author_did + FROM highlights, json_array_elements_text(tags_json::json) as value + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + AND created_at > NOW() - INTERVAL '14 days' + UNION ALL + SELECT value as tag, author_did + FROM bookmarks, json_array_elements_text(tags_json::json) as value + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + AND created_at > NOW() - INTERVAL '14 days' +) combined +GROUP BY tag +HAVING COUNT(DISTINCT author_did) >= 3 +ORDER BY count DESC +LIMIT $1 +` + +type GetTrendingTagsRow struct { + Tag *string `json:"tag"` + Count int64 `json:"count"` +} + +func (q *Queries) GetTrendingTags(ctx context.Context, limit int32) ([]GetTrendingTagsRow, error) { + rows, err := q.db.Query(ctx, getTrendingTags, limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetTrendingTagsRow + for rows.Next() { + var i GetTrendingTagsRow + if err := rows.Scan(&i.Tag, &i.Count); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getUserTags = `-- name: GetUserTags :many +SELECT tag, SUM(cnt) as count FROM ( + SELECT value as tag, COUNT(*) as cnt + FROM annotations, json_array_elements_text(tags_json::json) as value + WHERE annotations.author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + GROUP BY tag + UNION ALL + SELECT value as tag, COUNT(*) as cnt + FROM highlights, json_array_elements_text(tags_json::json) as value + WHERE highlights.author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + GROUP BY tag + UNION ALL + SELECT value as tag, COUNT(*) as cnt + FROM bookmarks, json_array_elements_text(tags_json::json) as value + WHERE bookmarks.author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' + GROUP BY tag +) combined +GROUP BY tag +ORDER BY count DESC +LIMIT $2 +` + +type GetUserTagsParams struct { + AuthorDid string `json:"authorDid"` + Limit int32 `json:"limit"` +} + +type GetUserTagsRow struct { + Tag *string `json:"tag"` + Count int64 `json:"count"` +} + +func (q *Queries) GetUserTags(ctx context.Context, arg GetUserTagsParams) ([]GetUserTagsRow, error) { + rows, err := q.db.Query(ctx, getUserTags, arg.AuthorDid, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetUserTagsRow + for rows.Next() { + var i GetUserTagsRow + if err := rows.Scan(&i.Tag, &i.Count); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/backend/internal/db/sqlcdb/models.go b/backend/internal/db/sqlcdb/models.go new file mode 100644 index 0000000..b625947 --- /dev/null +++ b/backend/internal/db/sqlcdb/models.go @@ -0,0 +1,395 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package sqlcdb + +import ( + "time" + + "github.com/jackc/pgx/v5/pgtype" +) + +type AllAnnotation struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Motivation *string `json:"motivation"` + BodyValue *string `json:"bodyValue"` + BodyFormat *string `json:"bodyFormat"` + BodyUri *string `json:"bodyUri"` + TargetSource *string `json:"targetSource"` + TargetHash *string `json:"targetHash"` + TargetTitle *string `json:"targetTitle"` + SelectorJson *string `json:"selectorJson"` + TagsJson *string `json:"tagsJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +type AllBookmark struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Source string `json:"source"` + SourceHash string `json:"sourceHash"` + Title *string `json:"title"` + Description *string `json:"description"` + TagsJson *string `json:"tagsJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +type AllHighlight struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + TargetSource string `json:"targetSource"` + TargetHash string `json:"targetHash"` + TargetTitle *string `json:"targetTitle"` + SelectorJson *string `json:"selectorJson"` + Color *string `json:"color"` + TagsJson *string `json:"tagsJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +type Annotation struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Motivation *string `json:"motivation"` + BodyValue *string `json:"bodyValue"` + BodyFormat *string `json:"bodyFormat"` + BodyUri *string `json:"bodyUri"` + TargetSource *string `json:"targetSource"` + TargetHash *string `json:"targetHash"` + TargetTitle *string `json:"targetTitle"` + SelectorJson *string `json:"selectorJson"` + TagsJson *string `json:"tagsJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +type AnnotationEmbedding struct { + AnnotationUri string `json:"annotationUri"` + AuthorDid string `json:"authorDid"` + DocumentUri *string `json:"documentUri"` + Embedding string `json:"embedding"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type ApiKey struct { + ID string `json:"id"` + OwnerDid string `json:"ownerDid"` + Name string `json:"name"` + KeyHash string `json:"keyHash"` + CreatedAt time.Time `json:"createdAt"` + LastUsedAt *time.Time `json:"lastUsedAt"` + Uri *string `json:"uri"` + Cid *string `json:"cid"` + IndexedAt *time.Time `json:"indexedAt"` +} + +type BannedAccount struct { + Did string `json:"did"` + Reason *string `json:"reason"` + BannedBy string `json:"bannedBy"` + BannedAt time.Time `json:"bannedAt"` +} + +type Block struct { + ID int32 `json:"id"` + ActorDid string `json:"actorDid"` + SubjectDid string `json:"subjectDid"` + CreatedAt time.Time `json:"createdAt"` +} + +type Bookmark struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Source string `json:"source"` + SourceHash string `json:"sourceHash"` + Title *string `json:"title"` + Description *string `json:"description"` + TagsJson *string `json:"tagsJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +type Collection struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Name string `json:"name"` + Description *string `json:"description"` + Icon *string `json:"icon"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +type CollectionItem struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + CollectionUri string `json:"collectionUri"` + AnnotationUri string `json:"annotationUri"` + Position pgtype.Int4 `json:"position"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +type CommunityBookmarkRef struct { + NoteUri string `json:"noteUri"` + CommunityUri string `json:"communityUri"` +} + +type ContentLabel struct { + ID int32 `json:"id"` + Src string `json:"src"` + Uri string `json:"uri"` + Val string `json:"val"` + Neg int32 `json:"neg"` + CreatedBy string `json:"createdBy"` + CreatedAt time.Time `json:"createdAt"` +} + +type Cursor struct { + ID string `json:"id"` + LastCursor int64 `json:"lastCursor"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type Document struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Site string `json:"site"` + Path *string `json:"path"` + Title string `json:"title"` + Description *string `json:"description"` + TextContent *string `json:"textContent"` + TagsJson *string `json:"tagsJson"` + CanonicalUrl *string `json:"canonicalUrl"` + PublishedAt time.Time `json:"publishedAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +type DocumentEmbedding struct { + DocumentUri string `json:"documentUri"` + Embedding string `json:"embedding"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type EditHistory struct { + ID int32 `json:"id"` + Uri string `json:"uri"` + RecordType string `json:"recordType"` + PreviousContent string `json:"previousContent"` + PreviousCid *string `json:"previousCid"` + EditedAt time.Time `json:"editedAt"` +} + +type Highlight struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + TargetSource string `json:"targetSource"` + TargetHash string `json:"targetHash"` + TargetTitle *string `json:"targetTitle"` + SelectorJson *string `json:"selectorJson"` + Color *string `json:"color"` + TagsJson *string `json:"tagsJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +type KvStore struct { + Key string `json:"key"` + Value string `json:"value"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type Like struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + SubjectUri string `json:"subjectUri"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +type ModerationAction struct { + ID int32 `json:"id"` + ReportID int32 `json:"reportId"` + ActorDid string `json:"actorDid"` + Action string `json:"action"` + Comment *string `json:"comment"` + CreatedAt time.Time `json:"createdAt"` +} + +type ModerationReport struct { + ID int32 `json:"id"` + ReporterDid string `json:"reporterDid"` + SubjectDid string `json:"subjectDid"` + SubjectUri *string `json:"subjectUri"` + ReasonType string `json:"reasonType"` + ReasonText *string `json:"reasonText"` + Status string `json:"status"` + CreatedAt time.Time `json:"createdAt"` + ResolvedAt *time.Time `json:"resolvedAt"` + ResolvedBy *string `json:"resolvedBy"` +} + +type Mute struct { + ID int32 `json:"id"` + ActorDid string `json:"actorDid"` + SubjectDid string `json:"subjectDid"` + CreatedAt time.Time `json:"createdAt"` +} + +type Note struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Motivation *string `json:"motivation"` + Color *string `json:"color"` + Description *string `json:"description"` + BodyValue *string `json:"bodyValue"` + BodyFormat *string `json:"bodyFormat"` + BodyUri *string `json:"bodyUri"` + TargetSource string `json:"targetSource"` + TargetHash string `json:"targetHash"` + TargetTitle *string `json:"targetTitle"` + SelectorJson *string `json:"selectorJson"` + TagsJson *string `json:"tagsJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +type Notification struct { + ID int32 `json:"id"` + RecipientDid string `json:"recipientDid"` + ActorDid string `json:"actorDid"` + Type string `json:"type"` + SubjectUri string `json:"subjectUri"` + CreatedAt time.Time `json:"createdAt"` + ReadAt *time.Time `json:"readAt"` +} + +type OauthPendingAuth struct { + State string `json:"state"` + Did string `json:"did"` + Handle string `json:"handle"` + Pds string `json:"pds"` + Issuer string `json:"issuer"` + TokenEndpoint string `json:"tokenEndpoint"` + PkceVerifier string `json:"pkceVerifier"` + DpopCrv string `json:"dpopCrv"` + DpopD string `json:"dpopD"` + DpopX string `json:"dpopX"` + DpopY string `json:"dpopY"` + DpopNonce string `json:"dpopNonce"` + CreatedAt pgtype.Timestamptz `json:"createdAt"` +} + +type OauthSession struct { + ID string `json:"id"` + Did string `json:"did"` + Handle string `json:"handle"` + Pds string `json:"pds"` + Email string `json:"email"` + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + TokenEndpoint string `json:"tokenEndpoint"` + Issuer string `json:"issuer"` + DpopCrv string `json:"dpopCrv"` + DpopD string `json:"dpopD"` + DpopX string `json:"dpopX"` + DpopY string `json:"dpopY"` + AccessTokenExpiresAt pgtype.Timestamptz `json:"accessTokenExpiresAt"` + ExpiresAt pgtype.Timestamptz `json:"expiresAt"` + CreatedAt pgtype.Timestamptz `json:"createdAt"` +} + +type PendingAuth struct { + State string `json:"state"` + Did string `json:"did"` + Handle string `json:"handle"` + Pds string `json:"pds"` + AuthServer string `json:"authServer"` + Issuer string `json:"issuer"` + PkceVerifier string `json:"pkceVerifier"` + DpopKeyPem string `json:"dpopKeyPem"` + DpopNonce string `json:"dpopNonce"` + CreatedAt time.Time `json:"createdAt"` +} + +type Preference struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + ExternalLinkSkippedHostnames *string `json:"externalLinkSkippedHostnames"` + SubscribedLabelers *string `json:"subscribedLabelers"` + LabelPreferences *string `json:"labelPreferences"` + DisableExternalLinkWarning pgtype.Bool `json:"disableExternalLinkWarning"` + EnableCommunityBookmarks pgtype.Bool `json:"enableCommunityBookmarks"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +type Profile struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + DisplayName *string `json:"displayName"` + Avatar *string `json:"avatar"` + Bio *string `json:"bio"` + Website *string `json:"website"` + LinksJson *string `json:"linksJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +type Publication struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Url string `json:"url"` + Name string `json:"name"` + Description *string `json:"description"` + ShowInDiscover bool `json:"showInDiscover"` + IndexedAt time.Time `json:"indexedAt"` +} + +type Reply struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + ParentUri string `json:"parentUri"` + RootUri string `json:"rootUri"` + Text string `json:"text"` + Format *string `json:"format"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +type Session struct { + ID string `json:"id"` + Did string `json:"did"` + Handle string `json:"handle"` + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + DpopKey *string `json:"dpopKey"` + CreatedAt time.Time `json:"createdAt"` + ExpiresAt time.Time `json:"expiresAt"` +} + +type TakenDownUri struct { + Uri string `json:"uri"` + TakenDownAt time.Time `json:"takenDownAt"` +} + +type UserProfile struct { + AuthorDid string `json:"authorDid"` + Embedding string `json:"embedding"` + TagAffinities *string `json:"tagAffinities"` + AnnotationCount int32 `json:"annotationCount"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/backend/internal/db/sqlcdb/moderation.sql.go b/backend/internal/db/sqlcdb/moderation.sql.go new file mode 100644 index 0000000..423e9c4 --- /dev/null +++ b/backend/internal/db/sqlcdb/moderation.sql.go @@ -0,0 +1,771 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: moderation.sql + +package sqlcdb + +import ( + "context" + "time" +) + +const banAccount = `-- name: BanAccount :exec +INSERT INTO banned_accounts (did, reason, banned_by, banned_at) +VALUES ($1, $2, $3, $4) +ON CONFLICT(did) DO UPDATE SET reason = EXCLUDED.reason, banned_by = EXCLUDED.banned_by, banned_at = EXCLUDED.banned_at +` + +type BanAccountParams struct { + Did string `json:"did"` + Reason *string `json:"reason"` + BannedBy string `json:"bannedBy"` + BannedAt time.Time `json:"bannedAt"` +} + +func (q *Queries) BanAccount(ctx context.Context, arg BanAccountParams) error { + _, err := q.db.Exec(ctx, banAccount, + arg.Did, + arg.Reason, + arg.BannedBy, + arg.BannedAt, + ) + return err +} + +const createBlock = `-- name: CreateBlock :exec +INSERT INTO blocks (actor_did, subject_did, created_at) VALUES ($1, $2, $3) +ON CONFLICT(actor_did, subject_did) DO NOTHING +` + +type CreateBlockParams struct { + ActorDid string `json:"actorDid"` + SubjectDid string `json:"subjectDid"` + CreatedAt time.Time `json:"createdAt"` +} + +func (q *Queries) CreateBlock(ctx context.Context, arg CreateBlockParams) error { + _, err := q.db.Exec(ctx, createBlock, arg.ActorDid, arg.SubjectDid, arg.CreatedAt) + return err +} + +const createContentLabel = `-- name: CreateContentLabel :exec +INSERT INTO content_labels (src, uri, val, neg, created_by, created_at) VALUES ($1, $2, $3, 0, $4, $5) +` + +type CreateContentLabelParams struct { + Src string `json:"src"` + Uri string `json:"uri"` + Val string `json:"val"` + CreatedBy string `json:"createdBy"` + CreatedAt time.Time `json:"createdAt"` +} + +func (q *Queries) CreateContentLabel(ctx context.Context, arg CreateContentLabelParams) error { + _, err := q.db.Exec(ctx, createContentLabel, + arg.Src, + arg.Uri, + arg.Val, + arg.CreatedBy, + arg.CreatedAt, + ) + return err +} + +const createModerationAction = `-- name: CreateModerationAction :exec +INSERT INTO moderation_actions (report_id, actor_did, action, comment, created_at) VALUES ($1, $2, $3, $4, $5) +` + +type CreateModerationActionParams struct { + ReportID int32 `json:"reportId"` + ActorDid string `json:"actorDid"` + Action string `json:"action"` + Comment *string `json:"comment"` + CreatedAt time.Time `json:"createdAt"` +} + +func (q *Queries) CreateModerationAction(ctx context.Context, arg CreateModerationActionParams) error { + _, err := q.db.Exec(ctx, createModerationAction, + arg.ReportID, + arg.ActorDid, + arg.Action, + arg.Comment, + arg.CreatedAt, + ) + return err +} + +const createMute = `-- name: CreateMute :exec +INSERT INTO mutes (actor_did, subject_did, created_at) VALUES ($1, $2, $3) +ON CONFLICT(actor_did, subject_did) DO NOTHING +` + +type CreateMuteParams struct { + ActorDid string `json:"actorDid"` + SubjectDid string `json:"subjectDid"` + CreatedAt time.Time `json:"createdAt"` +} + +func (q *Queries) CreateMute(ctx context.Context, arg CreateMuteParams) error { + _, err := q.db.Exec(ctx, createMute, arg.ActorDid, arg.SubjectDid, arg.CreatedAt) + return err +} + +const createReport = `-- name: CreateReport :one +INSERT INTO moderation_reports (reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at) +VALUES ($1, $2, $3, $4, $5, 'pending', $6) +RETURNING id +` + +type CreateReportParams struct { + ReporterDid string `json:"reporterDid"` + SubjectDid string `json:"subjectDid"` + SubjectUri *string `json:"subjectUri"` + ReasonType string `json:"reasonType"` + ReasonText *string `json:"reasonText"` + CreatedAt time.Time `json:"createdAt"` +} + +func (q *Queries) CreateReport(ctx context.Context, arg CreateReportParams) (int32, error) { + row := q.db.QueryRow(ctx, createReport, + arg.ReporterDid, + arg.SubjectDid, + arg.SubjectUri, + arg.ReasonType, + arg.ReasonText, + arg.CreatedAt, + ) + var id int32 + err := row.Scan(&id) + return id, err +} + +const deleteBlock = `-- name: DeleteBlock :exec +DELETE FROM blocks WHERE actor_did = $1 AND subject_did = $2 +` + +type DeleteBlockParams struct { + ActorDid string `json:"actorDid"` + SubjectDid string `json:"subjectDid"` +} + +func (q *Queries) DeleteBlock(ctx context.Context, arg DeleteBlockParams) error { + _, err := q.db.Exec(ctx, deleteBlock, arg.ActorDid, arg.SubjectDid) + return err +} + +const deleteContentLabel = `-- name: DeleteContentLabel :exec +DELETE FROM content_labels WHERE id = $1 +` + +func (q *Queries) DeleteContentLabel(ctx context.Context, id int32) error { + _, err := q.db.Exec(ctx, deleteContentLabel, id) + return err +} + +const deleteMute = `-- name: DeleteMute :exec +DELETE FROM mutes WHERE actor_did = $1 AND subject_did = $2 +` + +type DeleteMuteParams struct { + ActorDid string `json:"actorDid"` + SubjectDid string `json:"subjectDid"` +} + +func (q *Queries) DeleteMute(ctx context.Context, arg DeleteMuteParams) error { + _, err := q.db.Exec(ctx, deleteMute, arg.ActorDid, arg.SubjectDid) + return err +} + +const deleteSelfLabels = `-- name: DeleteSelfLabels :exec +DELETE FROM content_labels WHERE src = $1 AND uri = $2 AND created_by = $3 +` + +type DeleteSelfLabelsParams struct { + Src string `json:"src"` + Uri string `json:"uri"` + CreatedBy string `json:"createdBy"` +} + +func (q *Queries) DeleteSelfLabels(ctx context.Context, arg DeleteSelfLabelsParams) error { + _, err := q.db.Exec(ctx, deleteSelfLabels, arg.Src, arg.Uri, arg.CreatedBy) + return err +} + +const getAllContentLabels = `-- name: GetAllContentLabels :many +SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels ORDER BY created_at DESC LIMIT $1 OFFSET $2 +` + +type GetAllContentLabelsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetAllContentLabels(ctx context.Context, arg GetAllContentLabelsParams) ([]ContentLabel, error) { + rows, err := q.db.Query(ctx, getAllContentLabels, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ContentLabel + for rows.Next() { + var i ContentLabel + if err := rows.Scan( + &i.ID, + &i.Src, + &i.Uri, + &i.Val, + &i.Neg, + &i.CreatedBy, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getBannedAccounts = `-- name: GetBannedAccounts :many +SELECT did, reason, banned_by, banned_at FROM banned_accounts ORDER BY banned_at DESC +` + +func (q *Queries) GetBannedAccounts(ctx context.Context) ([]BannedAccount, error) { + rows, err := q.db.Query(ctx, getBannedAccounts) + if err != nil { + return nil, err + } + defer rows.Close() + var items []BannedAccount + for rows.Next() { + var i BannedAccount + if err := rows.Scan( + &i.Did, + &i.Reason, + &i.BannedBy, + &i.BannedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getBannedDIDs = `-- name: GetBannedDIDs :many +SELECT did FROM banned_accounts +` + +func (q *Queries) GetBannedDIDs(ctx context.Context) ([]string, error) { + rows, err := q.db.Query(ctx, getBannedDIDs) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var did string + if err := rows.Scan(&did); err != nil { + return nil, err + } + items = append(items, did) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getBlockedByDIDs = `-- name: GetBlockedByDIDs :many +SELECT actor_did FROM blocks WHERE subject_did = $1 +` + +func (q *Queries) GetBlockedByDIDs(ctx context.Context, subjectDid string) ([]string, error) { + rows, err := q.db.Query(ctx, getBlockedByDIDs, subjectDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var actor_did string + if err := rows.Scan(&actor_did); err != nil { + return nil, err + } + items = append(items, actor_did) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getBlockedDIDs = `-- name: GetBlockedDIDs :many +SELECT subject_did FROM blocks WHERE actor_did = $1 +` + +func (q *Queries) GetBlockedDIDs(ctx context.Context, actorDid string) ([]string, error) { + rows, err := q.db.Query(ctx, getBlockedDIDs, actorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var subject_did string + if err := rows.Scan(&subject_did); err != nil { + return nil, err + } + items = append(items, subject_did) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getBlocks = `-- name: GetBlocks :many +SELECT id, actor_did, subject_did, created_at FROM blocks WHERE actor_did = $1 ORDER BY created_at DESC +` + +func (q *Queries) GetBlocks(ctx context.Context, actorDid string) ([]Block, error) { + rows, err := q.db.Query(ctx, getBlocks, actorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Block + for rows.Next() { + var i Block + if err := rows.Scan( + &i.ID, + &i.ActorDid, + &i.SubjectDid, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getContentLabelsForURIs = `-- name: GetContentLabelsForURIs :many +SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels +WHERE uri = ANY($1::text[]) AND neg = 0 +ORDER BY created_at DESC +` + +func (q *Queries) GetContentLabelsForURIs(ctx context.Context, dollar_1 []string) ([]ContentLabel, error) { + rows, err := q.db.Query(ctx, getContentLabelsForURIs, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ContentLabel + for rows.Next() { + var i ContentLabel + if err := rows.Scan( + &i.ID, + &i.Src, + &i.Uri, + &i.Val, + &i.Neg, + &i.CreatedBy, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getContentLabelsForURIsBySrc = `-- name: GetContentLabelsForURIsBySrc :many +SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels +WHERE uri = ANY($1::text[]) AND neg = 0 AND src = ANY($2::text[]) +ORDER BY created_at DESC +` + +type GetContentLabelsForURIsBySrcParams struct { + Column1 []string `json:"column1"` + Column2 []string `json:"column2"` +} + +func (q *Queries) GetContentLabelsForURIsBySrc(ctx context.Context, arg GetContentLabelsForURIsBySrcParams) ([]ContentLabel, error) { + rows, err := q.db.Query(ctx, getContentLabelsForURIsBySrc, arg.Column1, arg.Column2) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ContentLabel + for rows.Next() { + var i ContentLabel + if err := rows.Scan( + &i.ID, + &i.Src, + &i.Uri, + &i.Val, + &i.Neg, + &i.CreatedBy, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMutedDIDs = `-- name: GetMutedDIDs :many +SELECT subject_did FROM mutes WHERE actor_did = $1 +` + +func (q *Queries) GetMutedDIDs(ctx context.Context, actorDid string) ([]string, error) { + rows, err := q.db.Query(ctx, getMutedDIDs, actorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var subject_did string + if err := rows.Scan(&subject_did); err != nil { + return nil, err + } + items = append(items, subject_did) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getMutes = `-- name: GetMutes :many +SELECT id, actor_did, subject_did, created_at FROM mutes WHERE actor_did = $1 ORDER BY created_at DESC +` + +func (q *Queries) GetMutes(ctx context.Context, actorDid string) ([]Mute, error) { + rows, err := q.db.Query(ctx, getMutes, actorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Mute + for rows.Next() { + var i Mute + if err := rows.Scan( + &i.ID, + &i.ActorDid, + &i.SubjectDid, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getReport = `-- name: GetReport :one +SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by FROM moderation_reports WHERE id = $1 +` + +func (q *Queries) GetReport(ctx context.Context, id int32) (ModerationReport, error) { + row := q.db.QueryRow(ctx, getReport, id) + var i ModerationReport + err := row.Scan( + &i.ID, + &i.ReporterDid, + &i.SubjectDid, + &i.SubjectUri, + &i.ReasonType, + &i.ReasonText, + &i.Status, + &i.CreatedAt, + &i.ResolvedAt, + &i.ResolvedBy, + ) + return i, err +} + +const getReportActions = `-- name: GetReportActions :many +SELECT id, report_id, actor_did, action, comment, created_at FROM moderation_actions WHERE report_id = $1 ORDER BY created_at DESC +` + +func (q *Queries) GetReportActions(ctx context.Context, reportID int32) ([]ModerationAction, error) { + rows, err := q.db.Query(ctx, getReportActions, reportID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ModerationAction + for rows.Next() { + var i ModerationAction + if err := rows.Scan( + &i.ID, + &i.ReportID, + &i.ActorDid, + &i.Action, + &i.Comment, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getReportCount = `-- name: GetReportCount :one +SELECT COUNT(*) FROM moderation_reports +` + +func (q *Queries) GetReportCount(ctx context.Context) (int64, error) { + row := q.db.QueryRow(ctx, getReportCount) + var count int64 + err := row.Scan(&count) + return count, err +} + +const getReportCountByStatus = `-- name: GetReportCountByStatus :one +SELECT COUNT(*) FROM moderation_reports WHERE status = $1 +` + +func (q *Queries) GetReportCountByStatus(ctx context.Context, status string) (int64, error) { + row := q.db.QueryRow(ctx, getReportCountByStatus, status) + var count int64 + err := row.Scan(&count) + return count, err +} + +const getReports = `-- name: GetReports :many +SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by +FROM moderation_reports +ORDER BY created_at DESC LIMIT $1 OFFSET $2 +` + +type GetReportsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetReports(ctx context.Context, arg GetReportsParams) ([]ModerationReport, error) { + rows, err := q.db.Query(ctx, getReports, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ModerationReport + for rows.Next() { + var i ModerationReport + if err := rows.Scan( + &i.ID, + &i.ReporterDid, + &i.SubjectDid, + &i.SubjectUri, + &i.ReasonType, + &i.ReasonText, + &i.Status, + &i.CreatedAt, + &i.ResolvedAt, + &i.ResolvedBy, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getReportsByStatus = `-- name: GetReportsByStatus :many +SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by +FROM moderation_reports +WHERE status = $1 +ORDER BY created_at DESC LIMIT $2 OFFSET $3 +` + +type GetReportsByStatusParams struct { + Status string `json:"status"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetReportsByStatus(ctx context.Context, arg GetReportsByStatusParams) ([]ModerationReport, error) { + rows, err := q.db.Query(ctx, getReportsByStatus, arg.Status, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ModerationReport + for rows.Next() { + var i ModerationReport + if err := rows.Scan( + &i.ID, + &i.ReporterDid, + &i.SubjectDid, + &i.SubjectUri, + &i.ReasonType, + &i.ReasonText, + &i.Status, + &i.CreatedAt, + &i.ResolvedAt, + &i.ResolvedBy, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const isBanned = `-- name: IsBanned :one +SELECT EXISTS(SELECT 1 FROM banned_accounts WHERE did = $1) +` + +func (q *Queries) IsBanned(ctx context.Context, did string) (bool, error) { + row := q.db.QueryRow(ctx, isBanned, did) + var exists bool + err := row.Scan(&exists) + return exists, err +} + +const isBlocked = `-- name: IsBlocked :one +SELECT EXISTS(SELECT 1 FROM blocks WHERE actor_did = $1 AND subject_did = $2) +` + +type IsBlockedParams struct { + ActorDid string `json:"actorDid"` + SubjectDid string `json:"subjectDid"` +} + +func (q *Queries) IsBlocked(ctx context.Context, arg IsBlockedParams) (bool, error) { + row := q.db.QueryRow(ctx, isBlocked, arg.ActorDid, arg.SubjectDid) + var exists bool + err := row.Scan(&exists) + return exists, err +} + +const isBlockedEither = `-- name: IsBlockedEither :one +SELECT EXISTS(SELECT 1 FROM blocks WHERE (actor_did = $1 AND subject_did = $2) OR (actor_did = $2 AND subject_did = $1)) +` + +type IsBlockedEitherParams struct { + ActorDid string `json:"actorDid"` + SubjectDid string `json:"subjectDid"` +} + +func (q *Queries) IsBlockedEither(ctx context.Context, arg IsBlockedEitherParams) (bool, error) { + row := q.db.QueryRow(ctx, isBlockedEither, arg.ActorDid, arg.SubjectDid) + var exists bool + err := row.Scan(&exists) + return exists, err +} + +const isMuted = `-- name: IsMuted :one +SELECT EXISTS(SELECT 1 FROM mutes WHERE actor_did = $1 AND subject_did = $2) +` + +type IsMutedParams struct { + ActorDid string `json:"actorDid"` + SubjectDid string `json:"subjectDid"` +} + +func (q *Queries) IsMuted(ctx context.Context, arg IsMutedParams) (bool, error) { + row := q.db.QueryRow(ctx, isMuted, arg.ActorDid, arg.SubjectDid) + var exists bool + err := row.Scan(&exists) + return exists, err +} + +const isTakenDown = `-- name: IsTakenDown :one +SELECT EXISTS(SELECT 1 FROM taken_down_uris WHERE uri = $1) +` + +func (q *Queries) IsTakenDown(ctx context.Context, uri string) (bool, error) { + row := q.db.QueryRow(ctx, isTakenDown, uri) + var exists bool + err := row.Scan(&exists) + return exists, err +} + +const markTakenDown = `-- name: MarkTakenDown :exec +INSERT INTO taken_down_uris (uri, taken_down_at) VALUES ($1, $2) +ON CONFLICT(uri) DO NOTHING +` + +type MarkTakenDownParams struct { + Uri string `json:"uri"` + TakenDownAt time.Time `json:"takenDownAt"` +} + +func (q *Queries) MarkTakenDown(ctx context.Context, arg MarkTakenDownParams) error { + _, err := q.db.Exec(ctx, markTakenDown, arg.Uri, arg.TakenDownAt) + return err +} + +const negateContentLabel = `-- name: NegateContentLabel :exec +UPDATE content_labels SET neg = 1 WHERE id = $1 +` + +func (q *Queries) NegateContentLabel(ctx context.Context, id int32) error { + _, err := q.db.Exec(ctx, negateContentLabel, id) + return err +} + +const resolveReport = `-- name: ResolveReport :exec +UPDATE moderation_reports SET status = $1, resolved_at = $2, resolved_by = $3 WHERE id = $4 +` + +type ResolveReportParams struct { + Status string `json:"status"` + ResolvedAt *time.Time `json:"resolvedAt"` + ResolvedBy *string `json:"resolvedBy"` + ID int32 `json:"id"` +} + +func (q *Queries) ResolveReport(ctx context.Context, arg ResolveReportParams) error { + _, err := q.db.Exec(ctx, resolveReport, + arg.Status, + arg.ResolvedAt, + arg.ResolvedBy, + arg.ID, + ) + return err +} + +const unbanAccount = `-- name: UnbanAccount :exec +DELETE FROM banned_accounts WHERE did = $1 +` + +func (q *Queries) UnbanAccount(ctx context.Context, did string) error { + _, err := q.db.Exec(ctx, unbanAccount, did) + return err +} diff --git a/backend/internal/db/sqlcdb/notes.sql.go b/backend/internal/db/sqlcdb/notes.sql.go new file mode 100644 index 0000000..be7c2af --- /dev/null +++ b/backend/internal/db/sqlcdb/notes.sql.go @@ -0,0 +1,310 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: notes.sql + +package sqlcdb + +import ( + "context" + "time" +) + +const communityBookmarkExists = `-- name: CommunityBookmarkExists :one +SELECT 1 FROM community_bookmark_refs cbr +JOIN notes n ON n.uri = cbr.note_uri +WHERE n.author_did = $1 + AND n.target_hash = $2 +LIMIT 1 +` + +type CommunityBookmarkExistsParams struct { + AuthorDid string `json:"authorDid"` + TargetHash string `json:"targetHash"` +} + +func (q *Queries) CommunityBookmarkExists(ctx context.Context, arg CommunityBookmarkExistsParams) (int32, error) { + row := q.db.QueryRow(ctx, communityBookmarkExists, arg.AuthorDid, arg.TargetHash) + var column_1 int32 + err := row.Scan(&column_1) + return column_1, err +} + +const createNote = `-- name: CreateNote :exec +INSERT INTO notes ( + uri, author_did, motivation, color, description, body_value, body_format, body_uri, + target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16 +) ON CONFLICT (uri) DO UPDATE SET + motivation = EXCLUDED.motivation, + color = EXCLUDED.color, + description = EXCLUDED.description, + body_value = EXCLUDED.body_value, + body_format = EXCLUDED.body_format, + body_uri = EXCLUDED.body_uri, + target_source = EXCLUDED.target_source, + target_hash = EXCLUDED.target_hash, + target_title = EXCLUDED.target_title, + selector_json = EXCLUDED.selector_json, + tags_json = EXCLUDED.tags_json, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid +` + +type CreateNoteParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Motivation *string `json:"motivation"` + Color *string `json:"color"` + Description *string `json:"description"` + BodyValue *string `json:"bodyValue"` + BodyFormat *string `json:"bodyFormat"` + BodyUri *string `json:"bodyUri"` + TargetSource string `json:"targetSource"` + TargetHash string `json:"targetHash"` + TargetTitle *string `json:"targetTitle"` + SelectorJson *string `json:"selectorJson"` + TagsJson *string `json:"tagsJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +func (q *Queries) CreateNote(ctx context.Context, arg CreateNoteParams) error { + _, err := q.db.Exec(ctx, createNote, + arg.Uri, + arg.AuthorDid, + arg.Motivation, + arg.Color, + arg.Description, + arg.BodyValue, + arg.BodyFormat, + arg.BodyUri, + arg.TargetSource, + arg.TargetHash, + arg.TargetTitle, + arg.SelectorJson, + arg.TagsJson, + arg.CreatedAt, + arg.IndexedAt, + arg.Cid, + ) + return err +} + +const deleteCommunityBookmarkRef = `-- name: DeleteCommunityBookmarkRef :exec +DELETE FROM community_bookmark_refs WHERE note_uri = $1 +` + +func (q *Queries) DeleteCommunityBookmarkRef(ctx context.Context, noteUri string) error { + _, err := q.db.Exec(ctx, deleteCommunityBookmarkRef, noteUri) + return err +} + +const deleteNote = `-- name: DeleteNote :exec +DELETE FROM notes WHERE uri = $1 +` + +func (q *Queries) DeleteNote(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, deleteNote, uri) + return err +} + +const getCommunityBookmarkURI = `-- name: GetCommunityBookmarkURI :one +SELECT community_uri FROM community_bookmark_refs WHERE note_uri = $1 +` + +func (q *Queries) GetCommunityBookmarkURI(ctx context.Context, noteUri string) (string, error) { + row := q.db.QueryRow(ctx, getCommunityBookmarkURI, noteUri) + var community_uri string + err := row.Scan(&community_uri) + return community_uri, err +} + +const getNoteByURI = `-- name: GetNoteByURI :one +SELECT uri, author_did, motivation, color, description, body_value, body_format, body_uri, + target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM notes WHERE uri = $1 +` + +func (q *Queries) GetNoteByURI(ctx context.Context, uri string) (Note, error) { + row := q.db.QueryRow(ctx, getNoteByURI, uri) + var i Note + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.Color, + &i.Description, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ) + return i, err +} + +const getNotesByURIs = `-- name: GetNotesByURIs :many +SELECT uri, author_did, motivation, color, description, body_value, body_format, body_uri, + target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid +FROM notes WHERE uri = ANY($1::text[]) +` + +func (q *Queries) GetNotesByURIs(ctx context.Context, dollar_1 []string) ([]Note, error) { + rows, err := q.db.Query(ctx, getNotesByURIs, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Note + for rows.Next() { + var i Note + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.Color, + &i.Description, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const marginNoteBookmarkExists = `-- name: MarginNoteBookmarkExists :one +SELECT 1 FROM notes +WHERE author_did = $1 + AND target_hash = $2 + AND motivation = 'bookmarking' + AND uri LIKE 'at://%/at.margin.note/%' +LIMIT 1 +` + +type MarginNoteBookmarkExistsParams struct { + AuthorDid string `json:"authorDid"` + TargetHash string `json:"targetHash"` +} + +func (q *Queries) MarginNoteBookmarkExists(ctx context.Context, arg MarginNoteBookmarkExistsParams) (int32, error) { + row := q.db.QueryRow(ctx, marginNoteBookmarkExists, arg.AuthorDid, arg.TargetHash) + var column_1 int32 + err := row.Scan(&column_1) + return column_1, err +} + +const saveCommunityBookmarkRef = `-- name: SaveCommunityBookmarkRef :exec +INSERT INTO community_bookmark_refs (note_uri, community_uri) +VALUES ($1, $2) +ON CONFLICT (note_uri) DO UPDATE SET community_uri = EXCLUDED.community_uri +` + +type SaveCommunityBookmarkRefParams struct { + NoteUri string `json:"noteUri"` + CommunityUri string `json:"communityUri"` +} + +func (q *Queries) SaveCommunityBookmarkRef(ctx context.Context, arg SaveCommunityBookmarkRefParams) error { + _, err := q.db.Exec(ctx, saveCommunityBookmarkRef, arg.NoteUri, arg.CommunityUri) + return err +} + +const updateNoteAnnotation = `-- name: UpdateNoteAnnotation :exec +UPDATE notes +SET body_value = $1, tags_json = NULLIF($2, ''), cid = $3, indexed_at = $4 +WHERE uri = $5 +` + +type UpdateNoteAnnotationParams struct { + BodyValue *string `json:"bodyValue"` + Column2 interface{} `json:"column2"` + Cid *string `json:"cid"` + IndexedAt time.Time `json:"indexedAt"` + Uri string `json:"uri"` +} + +func (q *Queries) UpdateNoteAnnotation(ctx context.Context, arg UpdateNoteAnnotationParams) error { + _, err := q.db.Exec(ctx, updateNoteAnnotation, + arg.BodyValue, + arg.Column2, + arg.Cid, + arg.IndexedAt, + arg.Uri, + ) + return err +} + +const updateNoteBookmark = `-- name: UpdateNoteBookmark :exec +UPDATE notes +SET target_title = NULLIF($1, ''), body_value = NULLIF($2, ''), tags_json = NULLIF($3, ''), cid = $4, indexed_at = $5 +WHERE uri = $6 +` + +type UpdateNoteBookmarkParams struct { + Column1 interface{} `json:"column1"` + Column2 interface{} `json:"column2"` + Column3 interface{} `json:"column3"` + Cid *string `json:"cid"` + IndexedAt time.Time `json:"indexedAt"` + Uri string `json:"uri"` +} + +func (q *Queries) UpdateNoteBookmark(ctx context.Context, arg UpdateNoteBookmarkParams) error { + _, err := q.db.Exec(ctx, updateNoteBookmark, + arg.Column1, + arg.Column2, + arg.Column3, + arg.Cid, + arg.IndexedAt, + arg.Uri, + ) + return err +} + +const updateNoteHighlight = `-- name: UpdateNoteHighlight :exec +UPDATE notes +SET color = NULLIF($1, ''), tags_json = NULLIF($2, ''), cid = $3, indexed_at = $4 +WHERE uri = $5 +` + +type UpdateNoteHighlightParams struct { + Column1 interface{} `json:"column1"` + Column2 interface{} `json:"column2"` + Cid *string `json:"cid"` + IndexedAt time.Time `json:"indexedAt"` + Uri string `json:"uri"` +} + +func (q *Queries) UpdateNoteHighlight(ctx context.Context, arg UpdateNoteHighlightParams) error { + _, err := q.db.Exec(ctx, updateNoteHighlight, + arg.Column1, + arg.Column2, + arg.Cid, + arg.IndexedAt, + arg.Uri, + ) + return err +} diff --git a/backend/internal/db/sqlcdb/notifications.sql.go b/backend/internal/db/sqlcdb/notifications.sql.go new file mode 100644 index 0000000..b436a9f --- /dev/null +++ b/backend/internal/db/sqlcdb/notifications.sql.go @@ -0,0 +1,102 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: notifications.sql + +package sqlcdb + +import ( + "context" + "time" +) + +const createNotification = `-- name: CreateNotification :exec +INSERT INTO notifications (recipient_did, actor_did, type, subject_uri, created_at) +VALUES ($1, $2, $3, $4, $5) +` + +type CreateNotificationParams struct { + RecipientDid string `json:"recipientDid"` + ActorDid string `json:"actorDid"` + Type string `json:"type"` + SubjectUri string `json:"subjectUri"` + CreatedAt time.Time `json:"createdAt"` +} + +func (q *Queries) CreateNotification(ctx context.Context, arg CreateNotificationParams) error { + _, err := q.db.Exec(ctx, createNotification, + arg.RecipientDid, + arg.ActorDid, + arg.Type, + arg.SubjectUri, + arg.CreatedAt, + ) + return err +} + +const getNotifications = `-- name: GetNotifications :many +SELECT id, recipient_did, actor_did, type, subject_uri, created_at, read_at +FROM notifications +WHERE recipient_did = $1 +ORDER BY created_at DESC +LIMIT $2 OFFSET $3 +` + +type GetNotificationsParams struct { + RecipientDid string `json:"recipientDid"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetNotifications(ctx context.Context, arg GetNotificationsParams) ([]Notification, error) { + rows, err := q.db.Query(ctx, getNotifications, arg.RecipientDid, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Notification + for rows.Next() { + var i Notification + if err := rows.Scan( + &i.ID, + &i.RecipientDid, + &i.ActorDid, + &i.Type, + &i.SubjectUri, + &i.CreatedAt, + &i.ReadAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getUnreadNotificationCount = `-- name: GetUnreadNotificationCount :one +SELECT COUNT(*) FROM notifications WHERE recipient_did = $1 AND read_at IS NULL +` + +func (q *Queries) GetUnreadNotificationCount(ctx context.Context, recipientDid string) (int64, error) { + row := q.db.QueryRow(ctx, getUnreadNotificationCount, recipientDid) + var count int64 + err := row.Scan(&count) + return count, err +} + +const markNotificationsRead = `-- name: MarkNotificationsRead :exec +UPDATE notifications SET read_at = $1 WHERE recipient_did = $2 AND read_at IS NULL +` + +type MarkNotificationsReadParams struct { + ReadAt *time.Time `json:"readAt"` + RecipientDid string `json:"recipientDid"` +} + +func (q *Queries) MarkNotificationsRead(ctx context.Context, arg MarkNotificationsReadParams) error { + _, err := q.db.Exec(ctx, markNotificationsRead, arg.ReadAt, arg.RecipientDid) + return err +} diff --git a/backend/internal/db/sqlcdb/oauth.sql.go b/backend/internal/db/sqlcdb/oauth.sql.go new file mode 100644 index 0000000..4f03e73 --- /dev/null +++ b/backend/internal/db/sqlcdb/oauth.sql.go @@ -0,0 +1,313 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: oauth.sql + +package sqlcdb + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const countOAuthSessionsByDID = `-- name: CountOAuthSessionsByDID :one +SELECT COUNT(*) FROM oauth_sessions WHERE did = $1 +` + +func (q *Queries) CountOAuthSessionsByDID(ctx context.Context, did string) (int64, error) { + row := q.db.QueryRow(ctx, countOAuthSessionsByDID, did) + var count int64 + err := row.Scan(&count) + return count, err +} + +const createOAuthSession = `-- name: CreateOAuthSession :exec +INSERT INTO oauth_sessions + (id, did, handle, pds, email, access_token, refresh_token, + token_endpoint, issuer, dpop_crv, dpop_d, dpop_x, dpop_y, + access_token_expires_at, expires_at, created_at) +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15, NOW()) +ON CONFLICT (id) DO UPDATE SET + access_token = EXCLUDED.access_token, + refresh_token = EXCLUDED.refresh_token, + access_token_expires_at = EXCLUDED.access_token_expires_at, + expires_at = EXCLUDED.expires_at +` + +type CreateOAuthSessionParams struct { + ID string `json:"id"` + Did string `json:"did"` + Handle string `json:"handle"` + Pds string `json:"pds"` + Email string `json:"email"` + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + TokenEndpoint string `json:"tokenEndpoint"` + Issuer string `json:"issuer"` + DpopCrv string `json:"dpopCrv"` + DpopD string `json:"dpopD"` + DpopX string `json:"dpopX"` + DpopY string `json:"dpopY"` + AccessTokenExpiresAt pgtype.Timestamptz `json:"accessTokenExpiresAt"` + ExpiresAt pgtype.Timestamptz `json:"expiresAt"` +} + +func (q *Queries) CreateOAuthSession(ctx context.Context, arg CreateOAuthSessionParams) error { + _, err := q.db.Exec(ctx, createOAuthSession, + arg.ID, + arg.Did, + arg.Handle, + arg.Pds, + arg.Email, + arg.AccessToken, + arg.RefreshToken, + arg.TokenEndpoint, + arg.Issuer, + arg.DpopCrv, + arg.DpopD, + arg.DpopX, + arg.DpopY, + arg.AccessTokenExpiresAt, + arg.ExpiresAt, + ) + return err +} + +const deleteExpiredOAuthSessions = `-- name: DeleteExpiredOAuthSessions :exec +DELETE FROM oauth_sessions WHERE expires_at <= NOW() +` + +func (q *Queries) DeleteExpiredOAuthSessions(ctx context.Context) error { + _, err := q.db.Exec(ctx, deleteExpiredOAuthSessions) + return err +} + +const deleteExpiredPendingAuthsOAuth = `-- name: DeleteExpiredPendingAuthsOAuth :exec +DELETE FROM oauth_pending_auths WHERE created_at < $1 +` + +func (q *Queries) DeleteExpiredPendingAuthsOAuth(ctx context.Context, createdAt pgtype.Timestamptz) error { + _, err := q.db.Exec(ctx, deleteExpiredPendingAuthsOAuth, createdAt) + return err +} + +const deleteOAuthSession = `-- name: DeleteOAuthSession :exec +DELETE FROM oauth_sessions WHERE id = $1 +` + +func (q *Queries) DeleteOAuthSession(ctx context.Context, id string) error { + _, err := q.db.Exec(ctx, deleteOAuthSession, id) + return err +} + +const deleteOAuthSessionsByDID = `-- name: DeleteOAuthSessionsByDID :exec +DELETE FROM oauth_sessions WHERE did = $1 +` + +func (q *Queries) DeleteOAuthSessionsByDID(ctx context.Context, did string) error { + _, err := q.db.Exec(ctx, deleteOAuthSessionsByDID, did) + return err +} + +const deletePendingAuthOAuth = `-- name: DeletePendingAuthOAuth :exec +DELETE FROM oauth_pending_auths WHERE state = $1 +` + +func (q *Queries) DeletePendingAuthOAuth(ctx context.Context, state string) error { + _, err := q.db.Exec(ctx, deletePendingAuthOAuth, state) + return err +} + +const getLatestOAuthSessionByDID = `-- name: GetLatestOAuthSessionByDID :one +SELECT id, did, handle, pds, email, access_token, refresh_token, + token_endpoint, issuer, dpop_crv, dpop_d, dpop_x, dpop_y, + access_token_expires_at, expires_at, created_at +FROM oauth_sessions WHERE did = $1 AND expires_at > NOW() +ORDER BY created_at DESC LIMIT 1 +` + +func (q *Queries) GetLatestOAuthSessionByDID(ctx context.Context, did string) (OauthSession, error) { + row := q.db.QueryRow(ctx, getLatestOAuthSessionByDID, did) + var i OauthSession + err := row.Scan( + &i.ID, + &i.Did, + &i.Handle, + &i.Pds, + &i.Email, + &i.AccessToken, + &i.RefreshToken, + &i.TokenEndpoint, + &i.Issuer, + &i.DpopCrv, + &i.DpopD, + &i.DpopX, + &i.DpopY, + &i.AccessTokenExpiresAt, + &i.ExpiresAt, + &i.CreatedAt, + ) + return i, err +} + +const getOAuthSessionByID = `-- name: GetOAuthSessionByID :one +SELECT did, handle, pds, email, access_token, refresh_token, + token_endpoint, issuer, dpop_crv, dpop_d, dpop_x, dpop_y, + access_token_expires_at, expires_at, created_at +FROM oauth_sessions WHERE id = $1 AND expires_at > NOW() +` + +type GetOAuthSessionByIDRow struct { + Did string `json:"did"` + Handle string `json:"handle"` + Pds string `json:"pds"` + Email string `json:"email"` + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + TokenEndpoint string `json:"tokenEndpoint"` + Issuer string `json:"issuer"` + DpopCrv string `json:"dpopCrv"` + DpopD string `json:"dpopD"` + DpopX string `json:"dpopX"` + DpopY string `json:"dpopY"` + AccessTokenExpiresAt pgtype.Timestamptz `json:"accessTokenExpiresAt"` + ExpiresAt pgtype.Timestamptz `json:"expiresAt"` + CreatedAt pgtype.Timestamptz `json:"createdAt"` +} + +func (q *Queries) GetOAuthSessionByID(ctx context.Context, id string) (GetOAuthSessionByIDRow, error) { + row := q.db.QueryRow(ctx, getOAuthSessionByID, id) + var i GetOAuthSessionByIDRow + err := row.Scan( + &i.Did, + &i.Handle, + &i.Pds, + &i.Email, + &i.AccessToken, + &i.RefreshToken, + &i.TokenEndpoint, + &i.Issuer, + &i.DpopCrv, + &i.DpopD, + &i.DpopX, + &i.DpopY, + &i.AccessTokenExpiresAt, + &i.ExpiresAt, + &i.CreatedAt, + ) + return i, err +} + +const getPendingAuthOAuth = `-- name: GetPendingAuthOAuth :one +SELECT did, handle, pds, issuer, token_endpoint, pkce_verifier, + dpop_crv, dpop_d, dpop_x, dpop_y, dpop_nonce, created_at +FROM oauth_pending_auths WHERE state = $1 +` + +type GetPendingAuthOAuthRow struct { + Did string `json:"did"` + Handle string `json:"handle"` + Pds string `json:"pds"` + Issuer string `json:"issuer"` + TokenEndpoint string `json:"tokenEndpoint"` + PkceVerifier string `json:"pkceVerifier"` + DpopCrv string `json:"dpopCrv"` + DpopD string `json:"dpopD"` + DpopX string `json:"dpopX"` + DpopY string `json:"dpopY"` + DpopNonce string `json:"dpopNonce"` + CreatedAt pgtype.Timestamptz `json:"createdAt"` +} + +func (q *Queries) GetPendingAuthOAuth(ctx context.Context, state string) (GetPendingAuthOAuthRow, error) { + row := q.db.QueryRow(ctx, getPendingAuthOAuth, state) + var i GetPendingAuthOAuthRow + err := row.Scan( + &i.Did, + &i.Handle, + &i.Pds, + &i.Issuer, + &i.TokenEndpoint, + &i.PkceVerifier, + &i.DpopCrv, + &i.DpopD, + &i.DpopX, + &i.DpopY, + &i.DpopNonce, + &i.CreatedAt, + ) + return i, err +} + +const savePendingAuthOAuth = `-- name: SavePendingAuthOAuth :exec +INSERT INTO oauth_pending_auths + (state, did, handle, pds, issuer, token_endpoint, pkce_verifier, + dpop_crv, dpop_d, dpop_x, dpop_y, dpop_nonce, created_at) +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) +ON CONFLICT (state) DO UPDATE SET + did = EXCLUDED.did, handle = EXCLUDED.handle, pds = EXCLUDED.pds, + issuer = EXCLUDED.issuer, token_endpoint = EXCLUDED.token_endpoint, + pkce_verifier = EXCLUDED.pkce_verifier, + dpop_crv = EXCLUDED.dpop_crv, dpop_d = EXCLUDED.dpop_d, + dpop_x = EXCLUDED.dpop_x, dpop_y = EXCLUDED.dpop_y, + dpop_nonce = EXCLUDED.dpop_nonce, created_at = EXCLUDED.created_at +` + +type SavePendingAuthOAuthParams struct { + State string `json:"state"` + Did string `json:"did"` + Handle string `json:"handle"` + Pds string `json:"pds"` + Issuer string `json:"issuer"` + TokenEndpoint string `json:"tokenEndpoint"` + PkceVerifier string `json:"pkceVerifier"` + DpopCrv string `json:"dpopCrv"` + DpopD string `json:"dpopD"` + DpopX string `json:"dpopX"` + DpopY string `json:"dpopY"` + DpopNonce string `json:"dpopNonce"` + CreatedAt pgtype.Timestamptz `json:"createdAt"` +} + +func (q *Queries) SavePendingAuthOAuth(ctx context.Context, arg SavePendingAuthOAuthParams) error { + _, err := q.db.Exec(ctx, savePendingAuthOAuth, + arg.State, + arg.Did, + arg.Handle, + arg.Pds, + arg.Issuer, + arg.TokenEndpoint, + arg.PkceVerifier, + arg.DpopCrv, + arg.DpopD, + arg.DpopX, + arg.DpopY, + arg.DpopNonce, + arg.CreatedAt, + ) + return err +} + +const updateOAuthSessionTokens = `-- name: UpdateOAuthSessionTokens :exec +UPDATE oauth_sessions +SET access_token = $2, refresh_token = $3, access_token_expires_at = $4 +WHERE id = $1 +` + +type UpdateOAuthSessionTokensParams struct { + ID string `json:"id"` + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + AccessTokenExpiresAt pgtype.Timestamptz `json:"accessTokenExpiresAt"` +} + +func (q *Queries) UpdateOAuthSessionTokens(ctx context.Context, arg UpdateOAuthSessionTokensParams) error { + _, err := q.db.Exec(ctx, updateOAuthSessionTokens, + arg.ID, + arg.AccessToken, + arg.RefreshToken, + arg.AccessTokenExpiresAt, + ) + return err +} diff --git a/backend/internal/db/sqlcdb/profiles.sql.go b/backend/internal/db/sqlcdb/profiles.sql.go new file mode 100644 index 0000000..453338c --- /dev/null +++ b/backend/internal/db/sqlcdb/profiles.sql.go @@ -0,0 +1,278 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: profiles.sql + +package sqlcdb + +import ( + "context" + "time" + + "github.com/jackc/pgx/v5/pgtype" +) + +const deleteAPIKeyByURI = `-- name: DeleteAPIKeyByURI :exec +DELETE FROM api_keys WHERE uri = $1 +` + +func (q *Queries) DeleteAPIKeyByURI(ctx context.Context, uri *string) error { + _, err := q.db.Exec(ctx, deleteAPIKeyByURI, uri) + return err +} + +const deletePreferences = `-- name: DeletePreferences :exec +DELETE FROM preferences WHERE uri = $1 +` + +func (q *Queries) DeletePreferences(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, deletePreferences, uri) + return err +} + +const deleteProfile = `-- name: DeleteProfile :exec +DELETE FROM profiles WHERE uri = $1 +` + +func (q *Queries) DeleteProfile(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, deleteProfile, uri) + return err +} + +const getAPIKeyURIs = `-- name: GetAPIKeyURIs :many +SELECT uri FROM api_keys WHERE owner_did = $1 AND uri IS NOT NULL AND uri != '' +` + +func (q *Queries) GetAPIKeyURIs(ctx context.Context, ownerDid string) ([]*string, error) { + rows, err := q.db.Query(ctx, getAPIKeyURIs, ownerDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []*string + for rows.Next() { + var uri *string + if err := rows.Scan(&uri); err != nil { + return nil, err + } + items = append(items, uri) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getPreferenceURIs = `-- name: GetPreferenceURIs :many +SELECT uri FROM preferences WHERE author_did = $1 AND uri IS NOT NULL AND uri != '' +` + +func (q *Queries) GetPreferenceURIs(ctx context.Context, authorDid string) ([]string, error) { + rows, err := q.db.Query(ctx, getPreferenceURIs, authorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var uri string + if err := rows.Scan(&uri); err != nil { + return nil, err + } + items = append(items, uri) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getPreferences = `-- name: GetPreferences :one +SELECT uri, author_did, external_link_skipped_hostnames, subscribed_labelers, + label_preferences, disable_external_link_warning, enable_community_bookmarks, + created_at, indexed_at, cid +FROM preferences WHERE author_did = $1 +` + +func (q *Queries) GetPreferences(ctx context.Context, authorDid string) (Preference, error) { + row := q.db.QueryRow(ctx, getPreferences, authorDid) + var i Preference + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.ExternalLinkSkippedHostnames, + &i.SubscribedLabelers, + &i.LabelPreferences, + &i.DisableExternalLinkWarning, + &i.EnableCommunityBookmarks, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ) + return i, err +} + +const getProfile = `-- name: GetProfile :one +SELECT uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at +FROM profiles WHERE author_did = $1 +` + +type GetProfileRow struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + DisplayName *string `json:"displayName"` + Avatar *string `json:"avatar"` + Bio *string `json:"bio"` + Website *string `json:"website"` + LinksJson *string `json:"linksJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +func (q *Queries) GetProfile(ctx context.Context, authorDid string) (GetProfileRow, error) { + row := q.db.QueryRow(ctx, getProfile, authorDid) + var i GetProfileRow + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.DisplayName, + &i.Avatar, + &i.Bio, + &i.Website, + &i.LinksJson, + &i.CreatedAt, + &i.IndexedAt, + ) + return i, err +} + +const getProfilesByDIDs = `-- name: GetProfilesByDIDs :many +SELECT uri, author_did, display_name, bio, avatar, website, links_json, created_at, indexed_at +FROM profiles WHERE author_did = ANY($1::text[]) +` + +type GetProfilesByDIDsRow struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + DisplayName *string `json:"displayName"` + Bio *string `json:"bio"` + Avatar *string `json:"avatar"` + Website *string `json:"website"` + LinksJson *string `json:"linksJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +func (q *Queries) GetProfilesByDIDs(ctx context.Context, dollar_1 []string) ([]GetProfilesByDIDsRow, error) { + rows, err := q.db.Query(ctx, getProfilesByDIDs, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetProfilesByDIDsRow + for rows.Next() { + var i GetProfilesByDIDsRow + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.DisplayName, + &i.Bio, + &i.Avatar, + &i.Website, + &i.LinksJson, + &i.CreatedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const upsertPreferences = `-- name: UpsertPreferences :exec +INSERT INTO preferences ( + uri, author_did, external_link_skipped_hostnames, subscribed_labelers, + label_preferences, disable_external_link_warning, enable_community_bookmarks, + created_at, indexed_at, cid +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) +ON CONFLICT(uri) DO UPDATE SET + external_link_skipped_hostnames = EXCLUDED.external_link_skipped_hostnames, + subscribed_labelers = EXCLUDED.subscribed_labelers, + label_preferences = EXCLUDED.label_preferences, + disable_external_link_warning = EXCLUDED.disable_external_link_warning, + enable_community_bookmarks = EXCLUDED.enable_community_bookmarks, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid +` + +type UpsertPreferencesParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + ExternalLinkSkippedHostnames *string `json:"externalLinkSkippedHostnames"` + SubscribedLabelers *string `json:"subscribedLabelers"` + LabelPreferences *string `json:"labelPreferences"` + DisableExternalLinkWarning pgtype.Bool `json:"disableExternalLinkWarning"` + EnableCommunityBookmarks pgtype.Bool `json:"enableCommunityBookmarks"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +func (q *Queries) UpsertPreferences(ctx context.Context, arg UpsertPreferencesParams) error { + _, err := q.db.Exec(ctx, upsertPreferences, + arg.Uri, + arg.AuthorDid, + arg.ExternalLinkSkippedHostnames, + arg.SubscribedLabelers, + arg.LabelPreferences, + arg.DisableExternalLinkWarning, + arg.EnableCommunityBookmarks, + arg.CreatedAt, + arg.IndexedAt, + arg.Cid, + ) + return err +} + +const upsertProfile = `-- name: UpsertProfile :exec +INSERT INTO profiles (uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +ON CONFLICT(uri) DO UPDATE SET + display_name = EXCLUDED.display_name, + avatar = EXCLUDED.avatar, + bio = EXCLUDED.bio, + website = EXCLUDED.website, + links_json = EXCLUDED.links_json, + indexed_at = EXCLUDED.indexed_at +` + +type UpsertProfileParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + DisplayName *string `json:"displayName"` + Avatar *string `json:"avatar"` + Bio *string `json:"bio"` + Website *string `json:"website"` + LinksJson *string `json:"linksJson"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +func (q *Queries) UpsertProfile(ctx context.Context, arg UpsertProfileParams) error { + _, err := q.db.Exec(ctx, upsertProfile, + arg.Uri, + arg.AuthorDid, + arg.DisplayName, + arg.Avatar, + arg.Bio, + arg.Website, + arg.LinksJson, + arg.CreatedAt, + arg.IndexedAt, + ) + return err +} diff --git a/backend/internal/db/sqlcdb/querier.go b/backend/internal/db/sqlcdb/querier.go new file mode 100644 index 0000000..0b3b060 --- /dev/null +++ b/backend/internal/db/sqlcdb/querier.go @@ -0,0 +1,225 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package sqlcdb + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +type Querier interface { + AddToCollection(ctx context.Context, arg AddToCollectionParams) error + BanAccount(ctx context.Context, arg BanAccountParams) error + CommunityBookmarkExists(ctx context.Context, arg CommunityBookmarkExistsParams) (int32, error) + CountOAuthSessionsByDID(ctx context.Context, did string) (int64, error) + CreateAPIKey(ctx context.Context, arg CreateAPIKeyParams) error + CreateAnnotation(ctx context.Context, arg CreateAnnotationParams) error + CreateBlock(ctx context.Context, arg CreateBlockParams) error + CreateBookmark(ctx context.Context, arg CreateBookmarkParams) error + CreateCollection(ctx context.Context, arg CreateCollectionParams) error + CreateContentLabel(ctx context.Context, arg CreateContentLabelParams) error + CreateHighlight(ctx context.Context, arg CreateHighlightParams) error + CreateLike(ctx context.Context, arg CreateLikeParams) error + CreateModerationAction(ctx context.Context, arg CreateModerationActionParams) error + CreateMute(ctx context.Context, arg CreateMuteParams) error + CreateNote(ctx context.Context, arg CreateNoteParams) error + CreateNotification(ctx context.Context, arg CreateNotificationParams) error + CreateOAuthSession(ctx context.Context, arg CreateOAuthSessionParams) error + CreateReply(ctx context.Context, arg CreateReplyParams) error + CreateReport(ctx context.Context, arg CreateReportParams) (int32, error) + DeleteAPIKeyByURI(ctx context.Context, uri *string) error + DeleteAPIKeyReturningURI(ctx context.Context, arg DeleteAPIKeyReturningURIParams) (*string, error) + DeleteAnnotation(ctx context.Context, uri string) error + DeleteAnnotationEmbedding(ctx context.Context, annotationUri string) error + DeleteBlock(ctx context.Context, arg DeleteBlockParams) error + DeleteBookmark(ctx context.Context, uri string) error + DeleteCollection(ctx context.Context, uri string) error + DeleteCollectionItemsByCollection(ctx context.Context, collectionUri string) error + DeleteCommunityBookmarkRef(ctx context.Context, noteUri string) error + DeleteContentLabel(ctx context.Context, id int32) error + DeleteDocument(ctx context.Context, uri string) error + DeleteExpiredOAuthSessions(ctx context.Context) error + DeleteExpiredPendingAuthsOAuth(ctx context.Context, createdAt pgtype.Timestamptz) error + DeleteHighlight(ctx context.Context, uri string) error + DeleteLike(ctx context.Context, uri string) error + DeleteMute(ctx context.Context, arg DeleteMuteParams) error + DeleteNote(ctx context.Context, uri string) error + DeleteOAuthSession(ctx context.Context, id string) error + DeleteOAuthSessionsByDID(ctx context.Context, did string) error + DeletePendingAuthOAuth(ctx context.Context, state string) error + DeletePreferences(ctx context.Context, uri string) error + DeleteProfile(ctx context.Context, uri string) error + DeletePublication(ctx context.Context, uri string) error + DeleteReply(ctx context.Context, uri string) error + DeleteSelfLabels(ctx context.Context, arg DeleteSelfLabelsParams) error + GetAPIKeyByHash(ctx context.Context, keyHash string) (GetAPIKeyByHashRow, error) + GetAPIKeyURIs(ctx context.Context, ownerDid string) ([]*string, error) + GetAPIKeysByOwner(ctx context.Context, ownerDid string) ([]GetAPIKeysByOwnerRow, error) + GetAllContentLabels(ctx context.Context, arg GetAllContentLabelsParams) ([]ContentLabel, error) + GetAnnotationAuthorByURI(ctx context.Context, uri string) (string, error) + GetAnnotationByURI(ctx context.Context, uri string) (AllAnnotation, error) + GetAnnotationEmbeddingsByAuthor(ctx context.Context, authorDid string) ([]AnnotationEmbedding, error) + GetAnnotationURIs(ctx context.Context, authorDid string) ([]string, error) + GetAnnotationsByAuthor(ctx context.Context, arg GetAnnotationsByAuthorParams) ([]AllAnnotation, error) + GetAnnotationsByAuthorAndTargetHash(ctx context.Context, arg GetAnnotationsByAuthorAndTargetHashParams) ([]AllAnnotation, error) + GetAnnotationsByMotivation(ctx context.Context, arg GetAnnotationsByMotivationParams) ([]AllAnnotation, error) + GetAnnotationsByTag(ctx context.Context, arg GetAnnotationsByTagParams) ([]AllAnnotation, error) + GetAnnotationsByTagAndAuthor(ctx context.Context, arg GetAnnotationsByTagAndAuthorParams) ([]AllAnnotation, error) + GetAnnotationsByTargetHash(ctx context.Context, arg GetAnnotationsByTargetHashParams) ([]AllAnnotation, error) + GetAnnotationsByURIs(ctx context.Context, dollar_1 []string) ([]AllAnnotation, error) + GetAnnotationsWithoutEmbeddings(ctx context.Context, limit int32) ([]Annotation, error) + GetBannedAccounts(ctx context.Context) ([]BannedAccount, error) + GetBannedDIDs(ctx context.Context) ([]string, error) + GetBlockedByDIDs(ctx context.Context, subjectDid string) ([]string, error) + GetBlockedDIDs(ctx context.Context, actorDid string) ([]string, error) + GetBlocks(ctx context.Context, actorDid string) ([]Block, error) + GetBookmarkAuthorByURI(ctx context.Context, uri string) (string, error) + GetBookmarkByURI(ctx context.Context, uri string) (AllBookmark, error) + GetBookmarkURIs(ctx context.Context, authorDid string) ([]string, error) + GetBookmarksByAuthor(ctx context.Context, arg GetBookmarksByAuthorParams) ([]AllBookmark, error) + GetBookmarksByTag(ctx context.Context, arg GetBookmarksByTagParams) ([]AllBookmark, error) + GetBookmarksByTagAndAuthor(ctx context.Context, arg GetBookmarksByTagAndAuthorParams) ([]AllBookmark, error) + GetBookmarksByTargetHash(ctx context.Context, arg GetBookmarksByTargetHashParams) ([]AllBookmark, error) + GetBookmarksByURIs(ctx context.Context, dollar_1 []string) ([]AllBookmark, error) + GetCandidateDocuments(ctx context.Context, arg GetCandidateDocumentsParams) ([]GetCandidateDocumentsRow, error) + GetCollectionByURI(ctx context.Context, uri string) (Collection, error) + GetCollectionItemCounts(ctx context.Context, dollar_1 []string) ([]GetCollectionItemCountsRow, error) + GetCollectionItems(ctx context.Context, collectionUri string) ([]CollectionItem, error) + GetCollectionItemsByAuthor(ctx context.Context, authorDid string) ([]CollectionItem, error) + GetCollectionURIsForAnnotation(ctx context.Context, annotationUri string) ([]string, error) + GetCollectionsByAuthor(ctx context.Context, authorDid string) ([]Collection, error) + GetCollectionsByURIs(ctx context.Context, dollar_1 []string) ([]Collection, error) + GetCollectionsForNoteURIs(ctx context.Context, dollar_1 []string) ([]GetCollectionsForNoteURIsRow, error) + GetCommunityBookmarkURI(ctx context.Context, noteUri string) (string, error) + GetContentLabelsForURIs(ctx context.Context, dollar_1 []string) ([]ContentLabel, error) + GetContentLabelsForURIsBySrc(ctx context.Context, arg GetContentLabelsForURIsBySrcParams) ([]ContentLabel, error) + GetDistinctAnnotationAuthors(ctx context.Context) ([]string, error) + GetDocumentByCanonicalURL(ctx context.Context, canonicalUrl *string) (Document, error) + GetDocumentByURI(ctx context.Context, uri string) (Document, error) + GetDocumentCount(ctx context.Context) (int64, error) + GetDocumentsWithoutEmbeddings(ctx context.Context, limit int32) ([]Document, error) + GetEditHistory(ctx context.Context, uri string) ([]EditHistory, error) + GetHighlightAuthorByURI(ctx context.Context, uri string) (string, error) + GetHighlightByURI(ctx context.Context, uri string) (AllHighlight, error) + GetHighlightURIs(ctx context.Context, authorDid string) ([]string, error) + GetHighlightsByAuthor(ctx context.Context, arg GetHighlightsByAuthorParams) ([]AllHighlight, error) + GetHighlightsByAuthorAndTargetHash(ctx context.Context, arg GetHighlightsByAuthorAndTargetHashParams) ([]AllHighlight, error) + GetHighlightsByTag(ctx context.Context, arg GetHighlightsByTagParams) ([]AllHighlight, error) + GetHighlightsByTagAndAuthor(ctx context.Context, arg GetHighlightsByTagAndAuthorParams) ([]AllHighlight, error) + GetHighlightsByTargetHash(ctx context.Context, arg GetHighlightsByTargetHashParams) ([]AllHighlight, error) + GetHighlightsByURIs(ctx context.Context, dollar_1 []string) ([]AllHighlight, error) + GetHighlightsWithoutEmbeddings(ctx context.Context, limit int32) ([]GetHighlightsWithoutEmbeddingsRow, error) + GetKVValue(ctx context.Context, key string) (string, error) + GetLatestEditTimes(ctx context.Context, dollar_1 []string) ([]GetLatestEditTimesRow, error) + GetLatestOAuthSessionByDID(ctx context.Context, did string) (OauthSession, error) + GetLikeByUserAndSubject(ctx context.Context, arg GetLikeByUserAndSubjectParams) (Like, error) + GetLikeCount(ctx context.Context, subjectUri string) (int64, error) + GetLikeCounts(ctx context.Context, dollar_1 []string) ([]GetLikeCountsRow, error) + GetLikesByAuthor(ctx context.Context, authorDid string) ([]Like, error) + GetMarginAnnotations(ctx context.Context, arg GetMarginAnnotationsParams) ([]AllAnnotation, error) + GetMarginAnnotationsByAuthor(ctx context.Context, arg GetMarginAnnotationsByAuthorParams) ([]AllAnnotation, error) + GetMarginAnnotationsByTag(ctx context.Context, arg GetMarginAnnotationsByTagParams) ([]AllAnnotation, error) + GetMarginAnnotationsByTagAndAuthor(ctx context.Context, arg GetMarginAnnotationsByTagAndAuthorParams) ([]AllAnnotation, error) + GetMarginBookmarks(ctx context.Context, arg GetMarginBookmarksParams) ([]AllBookmark, error) + GetMarginBookmarksByAuthor(ctx context.Context, arg GetMarginBookmarksByAuthorParams) ([]AllBookmark, error) + GetMarginBookmarksByTag(ctx context.Context, arg GetMarginBookmarksByTagParams) ([]AllBookmark, error) + GetMarginBookmarksByTagAndAuthor(ctx context.Context, arg GetMarginBookmarksByTagAndAuthorParams) ([]AllBookmark, error) + GetMarginHighlights(ctx context.Context, arg GetMarginHighlightsParams) ([]AllHighlight, error) + GetMarginHighlightsByAuthor(ctx context.Context, arg GetMarginHighlightsByAuthorParams) ([]AllHighlight, error) + GetMarginHighlightsByTag(ctx context.Context, arg GetMarginHighlightsByTagParams) ([]AllHighlight, error) + GetMarginHighlightsByTagAndAuthor(ctx context.Context, arg GetMarginHighlightsByTagAndAuthorParams) ([]AllHighlight, error) + GetMutedDIDs(ctx context.Context, actorDid string) ([]string, error) + GetMutes(ctx context.Context, actorDid string) ([]Mute, error) + GetNoteByURI(ctx context.Context, uri string) (Note, error) + GetNotesByURIs(ctx context.Context, dollar_1 []string) ([]Note, error) + GetNotifications(ctx context.Context, arg GetNotificationsParams) ([]Notification, error) + GetOAuthSessionByID(ctx context.Context, id string) (GetOAuthSessionByIDRow, error) + GetOrphanedRepliesByAuthor(ctx context.Context, authorDid string) ([]Reply, error) + GetPendingAuthOAuth(ctx context.Context, state string) (GetPendingAuthOAuthRow, error) + GetPopularAnnotations(ctx context.Context, arg GetPopularAnnotationsParams) ([]AllAnnotation, error) + GetPopularBookmarks(ctx context.Context, arg GetPopularBookmarksParams) ([]AllBookmark, error) + GetPopularCollectionItems(ctx context.Context, arg GetPopularCollectionItemsParams) ([]CollectionItem, error) + GetPopularDocuments(ctx context.Context, arg GetPopularDocumentsParams) ([]Document, error) + GetPopularHighlights(ctx context.Context, arg GetPopularHighlightsParams) ([]AllHighlight, error) + GetPreferenceURIs(ctx context.Context, authorDid string) ([]string, error) + GetPreferences(ctx context.Context, authorDid string) (Preference, error) + GetProfile(ctx context.Context, authorDid string) (GetProfileRow, error) + GetProfilesByDIDs(ctx context.Context, dollar_1 []string) ([]GetProfilesByDIDsRow, error) + GetPublicationByURL(ctx context.Context, url string) (Publication, error) + GetRecentAnnotationEmbeddingsByAuthor(ctx context.Context, arg GetRecentAnnotationEmbeddingsByAuthorParams) ([]AnnotationEmbedding, error) + GetRecentAnnotations(ctx context.Context, arg GetRecentAnnotationsParams) ([]AllAnnotation, error) + GetRecentBookmarks(ctx context.Context, arg GetRecentBookmarksParams) ([]AllBookmark, error) + GetRecentCollectionItems(ctx context.Context, arg GetRecentCollectionItemsParams) ([]CollectionItem, error) + GetRecentDocuments(ctx context.Context, arg GetRecentDocumentsParams) ([]Document, error) + GetRecentHighlights(ctx context.Context, arg GetRecentHighlightsParams) ([]AllHighlight, error) + GetRepliesByAuthor(ctx context.Context, authorDid string) ([]Reply, error) + GetRepliesByRoot(ctx context.Context, rootUri string) ([]Reply, error) + GetRepliesByURIs(ctx context.Context, dollar_1 []string) ([]Reply, error) + GetReplyByURI(ctx context.Context, uri string) (Reply, error) + GetReplyCount(ctx context.Context, rootUri string) (int64, error) + GetReplyCounts(ctx context.Context, dollar_1 []string) ([]GetReplyCountsRow, error) + GetReport(ctx context.Context, id int32) (ModerationReport, error) + GetReportActions(ctx context.Context, reportID int32) ([]ModerationAction, error) + GetReportCount(ctx context.Context) (int64, error) + GetReportCountByStatus(ctx context.Context, status string) (int64, error) + GetReports(ctx context.Context, arg GetReportsParams) ([]ModerationReport, error) + GetReportsByStatus(ctx context.Context, arg GetReportsByStatusParams) ([]ModerationReport, error) + GetSembleAnnotations(ctx context.Context, arg GetSembleAnnotationsParams) ([]AllAnnotation, error) + GetSembleAnnotationsByAuthor(ctx context.Context, arg GetSembleAnnotationsByAuthorParams) ([]AllAnnotation, error) + GetSembleAnnotationsByTag(ctx context.Context, arg GetSembleAnnotationsByTagParams) ([]AllAnnotation, error) + GetSembleAnnotationsByTagAndAuthor(ctx context.Context, arg GetSembleAnnotationsByTagAndAuthorParams) ([]AllAnnotation, error) + GetSembleBookmarks(ctx context.Context, arg GetSembleBookmarksParams) ([]AllBookmark, error) + GetSembleBookmarksByAuthor(ctx context.Context, arg GetSembleBookmarksByAuthorParams) ([]AllBookmark, error) + GetSembleBookmarksByTag(ctx context.Context, arg GetSembleBookmarksByTagParams) ([]AllBookmark, error) + GetSembleBookmarksByTagAndAuthor(ctx context.Context, arg GetSembleBookmarksByTagAndAuthorParams) ([]AllBookmark, error) + GetSembleHighlights(ctx context.Context, arg GetSembleHighlightsParams) ([]AllHighlight, error) + GetSembleHighlightsByAuthor(ctx context.Context, arg GetSembleHighlightsByAuthorParams) ([]AllHighlight, error) + GetSembleHighlightsByTag(ctx context.Context, arg GetSembleHighlightsByTagParams) ([]AllHighlight, error) + GetSembleHighlightsByTagAndAuthor(ctx context.Context, arg GetSembleHighlightsByTagAndAuthorParams) ([]AllHighlight, error) + GetShelvedAnnotations(ctx context.Context, arg GetShelvedAnnotationsParams) ([]AllAnnotation, error) + GetShelvedBookmarks(ctx context.Context, arg GetShelvedBookmarksParams) ([]AllBookmark, error) + GetShelvedCollectionItems(ctx context.Context, arg GetShelvedCollectionItemsParams) ([]CollectionItem, error) + GetShelvedHighlights(ctx context.Context, arg GetShelvedHighlightsParams) ([]AllHighlight, error) + GetTrendingTags(ctx context.Context, limit int32) ([]GetTrendingTagsRow, error) + GetUnreadNotificationCount(ctx context.Context, recipientDid string) (int64, error) + GetUserProfile(ctx context.Context, authorDid string) (UserProfile, error) + GetUserTags(ctx context.Context, arg GetUserTagsParams) ([]GetUserTagsRow, error) + GetViewerLikes(ctx context.Context, arg GetViewerLikesParams) ([]string, error) + InsertKVIgnore(ctx context.Context, arg InsertKVIgnoreParams) error + IsBanned(ctx context.Context, did string) (bool, error) + IsBlocked(ctx context.Context, arg IsBlockedParams) (bool, error) + IsBlockedEither(ctx context.Context, arg IsBlockedEitherParams) (bool, error) + IsMuted(ctx context.Context, arg IsMutedParams) (bool, error) + IsTakenDown(ctx context.Context, uri string) (bool, error) + MarginNoteBookmarkExists(ctx context.Context, arg MarginNoteBookmarkExistsParams) (int32, error) + MarkNotificationsRead(ctx context.Context, arg MarkNotificationsReadParams) error + MarkTakenDown(ctx context.Context, arg MarkTakenDownParams) error + MatchAnnotationToDocument(ctx context.Context, canonicalUrl *string) (string, error) + NegateContentLabel(ctx context.Context, id int32) error + RemoveFromCollection(ctx context.Context, uri string) error + ResolveReport(ctx context.Context, arg ResolveReportParams) error + SaveCommunityBookmarkRef(ctx context.Context, arg SaveCommunityBookmarkRefParams) error + SaveEditHistory(ctx context.Context, arg SaveEditHistoryParams) error + SavePendingAuthOAuth(ctx context.Context, arg SavePendingAuthOAuthParams) error + UnbanAccount(ctx context.Context, did string) error + UpdateAPIKeyLastUsed(ctx context.Context, arg UpdateAPIKeyLastUsedParams) error + UpdateAnnotation(ctx context.Context, arg UpdateAnnotationParams) error + UpdateBookmark(ctx context.Context, arg UpdateBookmarkParams) error + UpdateHighlight(ctx context.Context, arg UpdateHighlightParams) error + UpdateNoteAnnotation(ctx context.Context, arg UpdateNoteAnnotationParams) error + UpdateNoteBookmark(ctx context.Context, arg UpdateNoteBookmarkParams) error + UpdateNoteHighlight(ctx context.Context, arg UpdateNoteHighlightParams) error + UpdateOAuthSessionTokens(ctx context.Context, arg UpdateOAuthSessionTokensParams) error + UpsertAnnotationEmbedding(ctx context.Context, arg UpsertAnnotationEmbeddingParams) error + UpsertDocument(ctx context.Context, arg UpsertDocumentParams) error + UpsertDocumentEmbedding(ctx context.Context, arg UpsertDocumentEmbeddingParams) error + UpsertPreferences(ctx context.Context, arg UpsertPreferencesParams) error + UpsertProfile(ctx context.Context, arg UpsertProfileParams) error + UpsertPublication(ctx context.Context, arg UpsertPublicationParams) error + UpsertUserProfile(ctx context.Context, arg UpsertUserProfileParams) error +} + +var _ Querier = (*Queries)(nil) diff --git a/backend/internal/db/sqlcdb/recommendations.sql.go b/backend/internal/db/sqlcdb/recommendations.sql.go new file mode 100644 index 0000000..5425105 --- /dev/null +++ b/backend/internal/db/sqlcdb/recommendations.sql.go @@ -0,0 +1,680 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: recommendations.sql + +package sqlcdb + +import ( + "context" + "time" +) + +const deleteAnnotationEmbedding = `-- name: DeleteAnnotationEmbedding :exec +DELETE FROM annotation_embeddings WHERE annotation_uri = $1 +` + +func (q *Queries) DeleteAnnotationEmbedding(ctx context.Context, annotationUri string) error { + _, err := q.db.Exec(ctx, deleteAnnotationEmbedding, annotationUri) + return err +} + +const deleteDocument = `-- name: DeleteDocument :exec +DELETE FROM documents WHERE uri = $1 +` + +func (q *Queries) DeleteDocument(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, deleteDocument, uri) + return err +} + +const deletePublication = `-- name: DeletePublication :exec +DELETE FROM publications WHERE uri = $1 +` + +func (q *Queries) DeletePublication(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, deletePublication, uri) + return err +} + +const getAnnotationEmbeddingsByAuthor = `-- name: GetAnnotationEmbeddingsByAuthor :many +SELECT annotation_uri, author_did, document_uri, embedding, updated_at FROM annotation_embeddings WHERE author_did = $1 +` + +func (q *Queries) GetAnnotationEmbeddingsByAuthor(ctx context.Context, authorDid string) ([]AnnotationEmbedding, error) { + rows, err := q.db.Query(ctx, getAnnotationEmbeddingsByAuthor, authorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AnnotationEmbedding + for rows.Next() { + var i AnnotationEmbedding + if err := rows.Scan( + &i.AnnotationUri, + &i.AuthorDid, + &i.DocumentUri, + &i.Embedding, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAnnotationsWithoutEmbeddings = `-- name: GetAnnotationsWithoutEmbeddings :many +SELECT a.uri, a.author_did, a.motivation, a.body_value, a.body_format, a.body_uri, a.target_source, a.target_hash, a.target_title, a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid +FROM annotations a +LEFT JOIN annotation_embeddings ae ON a.uri = ae.annotation_uri +WHERE ae.annotation_uri IS NULL AND a.motivation IN ('commenting', 'highlighting') +ORDER BY a.created_at DESC +LIMIT $1 +` + +func (q *Queries) GetAnnotationsWithoutEmbeddings(ctx context.Context, limit int32) ([]Annotation, error) { + rows, err := q.db.Query(ctx, getAnnotationsWithoutEmbeddings, limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Annotation + for rows.Next() { + var i Annotation + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Motivation, + &i.BodyValue, + &i.BodyFormat, + &i.BodyUri, + &i.TargetSource, + &i.TargetHash, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getCandidateDocuments = `-- name: GetCandidateDocuments :many +SELECT + d.uri, d.author_did, d.site, d.path, d.title, d.description, d.tags_json, + d.canonical_url, d.published_at, de.embedding, + COALESCE(eng.cnt, 0) AS engagement +FROM documents d +JOIN document_embeddings de ON d.uri = de.document_uri +LEFT JOIN ( + SELECT document_uri, COUNT(DISTINCT author_did) AS cnt + FROM annotation_embeddings + WHERE document_uri IS NOT NULL + GROUP BY document_uri +) eng ON eng.document_uri = d.uri +LEFT JOIN publications p ON d.site = p.uri OR d.site = p.url +WHERE d.author_did != $1 + AND (p.show_in_discover IS NULL OR p.show_in_discover = true) + AND LENGTH(d.title) > 15 + AND (LENGTH(COALESCE(d.description, '')) >= 30 OR LENGTH(COALESCE(d.text_content, '')) >= 100) + AND LOWER(d.title) NOT LIKE '%test%' + AND LOWER(d.title) NOT LIKE '%testing%' + AND LOWER(d.title) NOT LIKE '%hello world%' + AND LOWER(d.title) NOT LIKE '%untitled%' + AND LOWER(d.title) NOT LIKE '%draft%' + AND LOWER(d.title) NOT LIKE '%asdf%' + AND LOWER(d.title) NOT LIKE '%lorem%' + AND LOWER(d.title) NOT LIKE '%placeholder%' + AND d.uri NOT IN ( + SELECT DISTINCT ae2.document_uri FROM annotation_embeddings ae2 + WHERE ae2.author_did = $2 AND ae2.document_uri IS NOT NULL + ) +ORDER BY d.published_at DESC +LIMIT $3 +` + +type GetCandidateDocumentsParams struct { + AuthorDid string `json:"authorDid"` + AuthorDid_2 string `json:"authorDid2"` + Limit int32 `json:"limit"` +} + +type GetCandidateDocumentsRow struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Site string `json:"site"` + Path *string `json:"path"` + Title string `json:"title"` + Description *string `json:"description"` + TagsJson *string `json:"tagsJson"` + CanonicalUrl *string `json:"canonicalUrl"` + PublishedAt time.Time `json:"publishedAt"` + Embedding string `json:"embedding"` + Engagement int64 `json:"engagement"` +} + +func (q *Queries) GetCandidateDocuments(ctx context.Context, arg GetCandidateDocumentsParams) ([]GetCandidateDocumentsRow, error) { + rows, err := q.db.Query(ctx, getCandidateDocuments, arg.AuthorDid, arg.AuthorDid_2, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetCandidateDocumentsRow + for rows.Next() { + var i GetCandidateDocumentsRow + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Site, + &i.Path, + &i.Title, + &i.Description, + &i.TagsJson, + &i.CanonicalUrl, + &i.PublishedAt, + &i.Embedding, + &i.Engagement, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getDistinctAnnotationAuthors = `-- name: GetDistinctAnnotationAuthors :many +SELECT DISTINCT author_did FROM annotation_embeddings +` + +func (q *Queries) GetDistinctAnnotationAuthors(ctx context.Context) ([]string, error) { + rows, err := q.db.Query(ctx, getDistinctAnnotationAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var author_did string + if err := rows.Scan(&author_did); err != nil { + return nil, err + } + items = append(items, author_did) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getDocumentByCanonicalURL = `-- name: GetDocumentByCanonicalURL :one +SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at +FROM documents WHERE canonical_url = $1 +` + +func (q *Queries) GetDocumentByCanonicalURL(ctx context.Context, canonicalUrl *string) (Document, error) { + row := q.db.QueryRow(ctx, getDocumentByCanonicalURL, canonicalUrl) + var i Document + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.Site, + &i.Path, + &i.Title, + &i.Description, + &i.TextContent, + &i.TagsJson, + &i.CanonicalUrl, + &i.PublishedAt, + &i.IndexedAt, + ) + return i, err +} + +const getDocumentByURI = `-- name: GetDocumentByURI :one +SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at +FROM documents WHERE uri = $1 +` + +func (q *Queries) GetDocumentByURI(ctx context.Context, uri string) (Document, error) { + row := q.db.QueryRow(ctx, getDocumentByURI, uri) + var i Document + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.Site, + &i.Path, + &i.Title, + &i.Description, + &i.TextContent, + &i.TagsJson, + &i.CanonicalUrl, + &i.PublishedAt, + &i.IndexedAt, + ) + return i, err +} + +const getDocumentCount = `-- name: GetDocumentCount :one +SELECT COUNT(*) FROM documents +` + +func (q *Queries) GetDocumentCount(ctx context.Context) (int64, error) { + row := q.db.QueryRow(ctx, getDocumentCount) + var count int64 + err := row.Scan(&count) + return count, err +} + +const getDocumentsWithoutEmbeddings = `-- name: GetDocumentsWithoutEmbeddings :many +SELECT d.uri, d.author_did, d.site, d.path, d.title, d.description, d.text_content, d.tags_json, d.canonical_url, d.published_at, d.indexed_at +FROM documents d +LEFT JOIN document_embeddings de ON d.uri = de.document_uri +WHERE de.document_uri IS NULL +ORDER BY d.indexed_at DESC +LIMIT $1 +` + +func (q *Queries) GetDocumentsWithoutEmbeddings(ctx context.Context, limit int32) ([]Document, error) { + rows, err := q.db.Query(ctx, getDocumentsWithoutEmbeddings, limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Document + for rows.Next() { + var i Document + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Site, + &i.Path, + &i.Title, + &i.Description, + &i.TextContent, + &i.TagsJson, + &i.CanonicalUrl, + &i.PublishedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getHighlightsWithoutEmbeddings = `-- name: GetHighlightsWithoutEmbeddings :many +SELECT h.uri, h.author_did, h.target_source, h.target_title, h.selector_json, h.tags_json +FROM highlights h +LEFT JOIN annotation_embeddings ae ON h.uri = ae.annotation_uri +WHERE ae.annotation_uri IS NULL +ORDER BY h.created_at DESC +LIMIT $1 +` + +type GetHighlightsWithoutEmbeddingsRow struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + TargetSource string `json:"targetSource"` + TargetTitle *string `json:"targetTitle"` + SelectorJson *string `json:"selectorJson"` + TagsJson *string `json:"tagsJson"` +} + +func (q *Queries) GetHighlightsWithoutEmbeddings(ctx context.Context, limit int32) ([]GetHighlightsWithoutEmbeddingsRow, error) { + rows, err := q.db.Query(ctx, getHighlightsWithoutEmbeddings, limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetHighlightsWithoutEmbeddingsRow + for rows.Next() { + var i GetHighlightsWithoutEmbeddingsRow + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.TargetSource, + &i.TargetTitle, + &i.SelectorJson, + &i.TagsJson, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getPopularDocuments = `-- name: GetPopularDocuments :many +SELECT d.uri, d.author_did, d.site, d.path, d.title, d.description, d.text_content, d.tags_json, d.canonical_url, d.published_at, d.indexed_at +FROM documents d +LEFT JOIN annotations a ON a.target_source = d.canonical_url +GROUP BY d.uri +ORDER BY COUNT(a.uri) DESC, d.published_at DESC +LIMIT $1 OFFSET $2 +` + +type GetPopularDocumentsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetPopularDocuments(ctx context.Context, arg GetPopularDocumentsParams) ([]Document, error) { + rows, err := q.db.Query(ctx, getPopularDocuments, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Document + for rows.Next() { + var i Document + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Site, + &i.Path, + &i.Title, + &i.Description, + &i.TextContent, + &i.TagsJson, + &i.CanonicalUrl, + &i.PublishedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getPublicationByURL = `-- name: GetPublicationByURL :one +SELECT uri, author_did, url, name, description, show_in_discover, indexed_at +FROM publications WHERE url = $1 +` + +func (q *Queries) GetPublicationByURL(ctx context.Context, url string) (Publication, error) { + row := q.db.QueryRow(ctx, getPublicationByURL, url) + var i Publication + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.Url, + &i.Name, + &i.Description, + &i.ShowInDiscover, + &i.IndexedAt, + ) + return i, err +} + +const getRecentAnnotationEmbeddingsByAuthor = `-- name: GetRecentAnnotationEmbeddingsByAuthor :many +SELECT annotation_uri, author_did, document_uri, embedding, updated_at FROM annotation_embeddings WHERE author_did = $1 ORDER BY updated_at DESC LIMIT $2 +` + +type GetRecentAnnotationEmbeddingsByAuthorParams struct { + AuthorDid string `json:"authorDid"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetRecentAnnotationEmbeddingsByAuthor(ctx context.Context, arg GetRecentAnnotationEmbeddingsByAuthorParams) ([]AnnotationEmbedding, error) { + rows, err := q.db.Query(ctx, getRecentAnnotationEmbeddingsByAuthor, arg.AuthorDid, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AnnotationEmbedding + for rows.Next() { + var i AnnotationEmbedding + if err := rows.Scan( + &i.AnnotationUri, + &i.AuthorDid, + &i.DocumentUri, + &i.Embedding, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getRecentDocuments = `-- name: GetRecentDocuments :many +SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at +FROM documents +ORDER BY published_at DESC +LIMIT $1 OFFSET $2 +` + +type GetRecentDocumentsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetRecentDocuments(ctx context.Context, arg GetRecentDocumentsParams) ([]Document, error) { + rows, err := q.db.Query(ctx, getRecentDocuments, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Document + for rows.Next() { + var i Document + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.Site, + &i.Path, + &i.Title, + &i.Description, + &i.TextContent, + &i.TagsJson, + &i.CanonicalUrl, + &i.PublishedAt, + &i.IndexedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getUserProfile = `-- name: GetUserProfile :one +SELECT author_did, embedding, tag_affinities, annotation_count, updated_at FROM user_profiles WHERE author_did = $1 +` + +func (q *Queries) GetUserProfile(ctx context.Context, authorDid string) (UserProfile, error) { + row := q.db.QueryRow(ctx, getUserProfile, authorDid) + var i UserProfile + err := row.Scan( + &i.AuthorDid, + &i.Embedding, + &i.TagAffinities, + &i.AnnotationCount, + &i.UpdatedAt, + ) + return i, err +} + +const matchAnnotationToDocument = `-- name: MatchAnnotationToDocument :one +SELECT uri FROM documents WHERE canonical_url = $1 +` + +func (q *Queries) MatchAnnotationToDocument(ctx context.Context, canonicalUrl *string) (string, error) { + row := q.db.QueryRow(ctx, matchAnnotationToDocument, canonicalUrl) + var uri string + err := row.Scan(&uri) + return uri, err +} + +const upsertAnnotationEmbedding = `-- name: UpsertAnnotationEmbedding :exec +INSERT INTO annotation_embeddings (annotation_uri, author_did, document_uri, embedding, updated_at) VALUES ($1, $2, $3, $4, $5) +ON CONFLICT(annotation_uri) DO UPDATE SET embedding = EXCLUDED.embedding, document_uri = EXCLUDED.document_uri, updated_at = EXCLUDED.updated_at +` + +type UpsertAnnotationEmbeddingParams struct { + AnnotationUri string `json:"annotationUri"` + AuthorDid string `json:"authorDid"` + DocumentUri *string `json:"documentUri"` + Embedding string `json:"embedding"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func (q *Queries) UpsertAnnotationEmbedding(ctx context.Context, arg UpsertAnnotationEmbeddingParams) error { + _, err := q.db.Exec(ctx, upsertAnnotationEmbedding, + arg.AnnotationUri, + arg.AuthorDid, + arg.DocumentUri, + arg.Embedding, + arg.UpdatedAt, + ) + return err +} + +const upsertDocument = `-- name: UpsertDocument :exec +INSERT INTO documents (uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +ON CONFLICT(uri) DO UPDATE SET + title = EXCLUDED.title, + description = EXCLUDED.description, + text_content = EXCLUDED.text_content, + tags_json = EXCLUDED.tags_json, + canonical_url = EXCLUDED.canonical_url, + indexed_at = EXCLUDED.indexed_at +` + +type UpsertDocumentParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Site string `json:"site"` + Path *string `json:"path"` + Title string `json:"title"` + Description *string `json:"description"` + TextContent *string `json:"textContent"` + TagsJson *string `json:"tagsJson"` + CanonicalUrl *string `json:"canonicalUrl"` + PublishedAt time.Time `json:"publishedAt"` + IndexedAt time.Time `json:"indexedAt"` +} + +func (q *Queries) UpsertDocument(ctx context.Context, arg UpsertDocumentParams) error { + _, err := q.db.Exec(ctx, upsertDocument, + arg.Uri, + arg.AuthorDid, + arg.Site, + arg.Path, + arg.Title, + arg.Description, + arg.TextContent, + arg.TagsJson, + arg.CanonicalUrl, + arg.PublishedAt, + arg.IndexedAt, + ) + return err +} + +const upsertDocumentEmbedding = `-- name: UpsertDocumentEmbedding :exec +INSERT INTO document_embeddings (document_uri, embedding, updated_at) VALUES ($1, $2, $3) +ON CONFLICT(document_uri) DO UPDATE SET embedding = EXCLUDED.embedding, updated_at = EXCLUDED.updated_at +` + +type UpsertDocumentEmbeddingParams struct { + DocumentUri string `json:"documentUri"` + Embedding string `json:"embedding"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func (q *Queries) UpsertDocumentEmbedding(ctx context.Context, arg UpsertDocumentEmbeddingParams) error { + _, err := q.db.Exec(ctx, upsertDocumentEmbedding, arg.DocumentUri, arg.Embedding, arg.UpdatedAt) + return err +} + +const upsertPublication = `-- name: UpsertPublication :exec +INSERT INTO publications (uri, author_did, url, name, description, show_in_discover, indexed_at) +VALUES ($1, $2, $3, $4, $5, $6, $7) +ON CONFLICT(uri) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + show_in_discover = EXCLUDED.show_in_discover, + indexed_at = EXCLUDED.indexed_at +` + +type UpsertPublicationParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + Url string `json:"url"` + Name string `json:"name"` + Description *string `json:"description"` + ShowInDiscover bool `json:"showInDiscover"` + IndexedAt time.Time `json:"indexedAt"` +} + +func (q *Queries) UpsertPublication(ctx context.Context, arg UpsertPublicationParams) error { + _, err := q.db.Exec(ctx, upsertPublication, + arg.Uri, + arg.AuthorDid, + arg.Url, + arg.Name, + arg.Description, + arg.ShowInDiscover, + arg.IndexedAt, + ) + return err +} + +const upsertUserProfile = `-- name: UpsertUserProfile :exec +INSERT INTO user_profiles (author_did, embedding, tag_affinities, annotation_count, updated_at) VALUES ($1, $2, $3, $4, $5) +ON CONFLICT(author_did) DO UPDATE SET embedding = EXCLUDED.embedding, tag_affinities = EXCLUDED.tag_affinities, annotation_count = EXCLUDED.annotation_count, updated_at = EXCLUDED.updated_at +` + +type UpsertUserProfileParams struct { + AuthorDid string `json:"authorDid"` + Embedding string `json:"embedding"` + TagAffinities *string `json:"tagAffinities"` + AnnotationCount int32 `json:"annotationCount"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func (q *Queries) UpsertUserProfile(ctx context.Context, arg UpsertUserProfileParams) error { + _, err := q.db.Exec(ctx, upsertUserProfile, + arg.AuthorDid, + arg.Embedding, + arg.TagAffinities, + arg.AnnotationCount, + arg.UpdatedAt, + ) + return err +} diff --git a/backend/internal/db/sqlcdb/replies.sql.go b/backend/internal/db/sqlcdb/replies.sql.go new file mode 100644 index 0000000..7c927c7 --- /dev/null +++ b/backend/internal/db/sqlcdb/replies.sql.go @@ -0,0 +1,270 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: replies.sql + +package sqlcdb + +import ( + "context" + "time" +) + +const createReply = `-- name: CreateReply :exec +INSERT INTO replies (uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +ON CONFLICT(uri) DO UPDATE SET + text = EXCLUDED.text, + format = EXCLUDED.format, + indexed_at = EXCLUDED.indexed_at, + cid = EXCLUDED.cid +` + +type CreateReplyParams struct { + Uri string `json:"uri"` + AuthorDid string `json:"authorDid"` + ParentUri string `json:"parentUri"` + RootUri string `json:"rootUri"` + Text string `json:"text"` + Format *string `json:"format"` + CreatedAt time.Time `json:"createdAt"` + IndexedAt time.Time `json:"indexedAt"` + Cid *string `json:"cid"` +} + +func (q *Queries) CreateReply(ctx context.Context, arg CreateReplyParams) error { + _, err := q.db.Exec(ctx, createReply, + arg.Uri, + arg.AuthorDid, + arg.ParentUri, + arg.RootUri, + arg.Text, + arg.Format, + arg.CreatedAt, + arg.IndexedAt, + arg.Cid, + ) + return err +} + +const deleteReply = `-- name: DeleteReply :exec +DELETE FROM replies WHERE uri = $1 +` + +func (q *Queries) DeleteReply(ctx context.Context, uri string) error { + _, err := q.db.Exec(ctx, deleteReply, uri) + return err +} + +const getOrphanedRepliesByAuthor = `-- name: GetOrphanedRepliesByAuthor :many +SELECT r.uri, r.author_did, r.parent_uri, r.root_uri, r.text, r.format, r.created_at, r.indexed_at, r.cid +FROM replies r +LEFT JOIN annotations a ON r.root_uri = a.uri +WHERE r.author_did = $1 AND a.uri IS NULL +` + +func (q *Queries) GetOrphanedRepliesByAuthor(ctx context.Context, authorDid string) ([]Reply, error) { + rows, err := q.db.Query(ctx, getOrphanedRepliesByAuthor, authorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Reply + for rows.Next() { + var i Reply + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.ParentUri, + &i.RootUri, + &i.Text, + &i.Format, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getRepliesByAuthor = `-- name: GetRepliesByAuthor :many +SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid +FROM replies +WHERE author_did = $1 +ORDER BY created_at DESC +` + +func (q *Queries) GetRepliesByAuthor(ctx context.Context, authorDid string) ([]Reply, error) { + rows, err := q.db.Query(ctx, getRepliesByAuthor, authorDid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Reply + for rows.Next() { + var i Reply + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.ParentUri, + &i.RootUri, + &i.Text, + &i.Format, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getRepliesByRoot = `-- name: GetRepliesByRoot :many +SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid +FROM replies +WHERE root_uri = $1 +ORDER BY created_at ASC +` + +func (q *Queries) GetRepliesByRoot(ctx context.Context, rootUri string) ([]Reply, error) { + rows, err := q.db.Query(ctx, getRepliesByRoot, rootUri) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Reply + for rows.Next() { + var i Reply + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.ParentUri, + &i.RootUri, + &i.Text, + &i.Format, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getRepliesByURIs = `-- name: GetRepliesByURIs :many +SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid +FROM replies +WHERE uri = ANY($1::text[]) +` + +func (q *Queries) GetRepliesByURIs(ctx context.Context, dollar_1 []string) ([]Reply, error) { + rows, err := q.db.Query(ctx, getRepliesByURIs, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Reply + for rows.Next() { + var i Reply + if err := rows.Scan( + &i.Uri, + &i.AuthorDid, + &i.ParentUri, + &i.RootUri, + &i.Text, + &i.Format, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getReplyByURI = `-- name: GetReplyByURI :one +SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid +FROM replies +WHERE uri = $1 +` + +func (q *Queries) GetReplyByURI(ctx context.Context, uri string) (Reply, error) { + row := q.db.QueryRow(ctx, getReplyByURI, uri) + var i Reply + err := row.Scan( + &i.Uri, + &i.AuthorDid, + &i.ParentUri, + &i.RootUri, + &i.Text, + &i.Format, + &i.CreatedAt, + &i.IndexedAt, + &i.Cid, + ) + return i, err +} + +const getReplyCount = `-- name: GetReplyCount :one +SELECT COUNT(*) FROM replies WHERE root_uri = $1 +` + +func (q *Queries) GetReplyCount(ctx context.Context, rootUri string) (int64, error) { + row := q.db.QueryRow(ctx, getReplyCount, rootUri) + var count int64 + err := row.Scan(&count) + return count, err +} + +const getReplyCounts = `-- name: GetReplyCounts :many +SELECT root_uri, COUNT(*) +FROM replies +WHERE root_uri = ANY($1::text[]) +GROUP BY root_uri +` + +type GetReplyCountsRow struct { + RootUri string `json:"rootUri"` + Count int64 `json:"count"` +} + +func (q *Queries) GetReplyCounts(ctx context.Context, dollar_1 []string) ([]GetReplyCountsRow, error) { + rows, err := q.db.Query(ctx, getReplyCounts, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetReplyCountsRow + for rows.Next() { + var i GetReplyCountsRow + if err := rows.Scan(&i.RootUri, &i.Count); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/backend/internal/db/tags.go b/backend/internal/db/tags.go index 8152a0e..6bb7c95 100644 --- a/backend/internal/db/tags.go +++ b/backend/internal/db/tags.go @@ -1,101 +1,43 @@ package db -func (db *DB) GetTrendingTags(limit int) ([]TrendingTag, error) { - query := ` - SELECT tag, COUNT(*) as count FROM ( - SELECT value as tag, author_did - FROM annotations, json_array_elements_text(tags_json::json) as value - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - AND created_at > NOW() - INTERVAL '14 days' - UNION ALL - SELECT value as tag, author_did - FROM highlights, json_array_elements_text(tags_json::json) as value - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - AND created_at > NOW() - INTERVAL '14 days' - UNION ALL - SELECT value as tag, author_did - FROM bookmarks, json_array_elements_text(tags_json::json) as value - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - AND created_at > NOW() - INTERVAL '14 days' - ) combined - GROUP BY tag - HAVING COUNT(DISTINCT author_did) >= 3 - ORDER BY count DESC - LIMIT $1 - ` +import ( + "context" - rows, err := db.Query(query, limit) + "margin.at/internal/db/sqlcdb" +) + +func (db *DB) GetTrendingTags(ctx context.Context, limit int) ([]TrendingTag, error) { + rows, err := db.q.GetTrendingTags(ctx, int32(limit)) if err != nil { return nil, err } - defer rows.Close() - - var tags []TrendingTag - for rows.Next() { - var t TrendingTag - if err := rows.Scan(&t.Tag, &t.Count); err != nil { - return nil, err + tags := make([]TrendingTag, 0, len(rows)) + for _, r := range rows { + var tag string + if r.Tag != nil { + tag = *r.Tag } - tags = append(tags, t) - } - - if err = rows.Err(); err != nil { - return nil, err - } - - if tags == nil { - return []TrendingTag{}, nil + tags = append(tags, TrendingTag{Tag: tag, Count: int(r.Count)}) } - return tags, nil } -func (db *DB) GetUserTags(did string, limit int) ([]TrendingTag, error) { - query := ` - SELECT tag, SUM(cnt) as count FROM ( - SELECT value as tag, COUNT(*) as cnt - FROM annotations, json_array_elements_text(tags_json::json) as value - WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - GROUP BY tag - UNION ALL - SELECT value as tag, COUNT(*) as cnt - FROM highlights, json_array_elements_text(tags_json::json) as value - WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - GROUP BY tag - UNION ALL - SELECT value as tag, COUNT(*) as cnt - FROM bookmarks, json_array_elements_text(tags_json::json) as value - WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' - GROUP BY tag - ) combined - GROUP BY tag - ORDER BY count DESC - LIMIT $2 - ` - - rows, err := db.Query(query, did, limit) +func (db *DB) GetUserTags(ctx context.Context, did string, limit int) ([]TrendingTag, error) { + rows, err := db.q.GetUserTags(ctx, sqlcdb.GetUserTagsParams{ + AuthorDid: did, + Limit: int32(limit), + }) if err != nil { return nil, err } - defer rows.Close() - - var tags []TrendingTag - for rows.Next() { - var t TrendingTag - if err := rows.Scan(&t.Tag, &t.Count); err != nil { - return nil, err + tags := make([]TrendingTag, 0, len(rows)) + for _, r := range rows { + var tag string + if r.Tag != nil { + tag = *r.Tag } - tags = append(tags, t) - } - - if err = rows.Err(); err != nil { - return nil, err - } - - if tags == nil { - return []TrendingTag{}, nil + tags = append(tags, TrendingTag{Tag: tag, Count: int(r.Count)}) } - return tags, nil } diff --git a/backend/internal/domain/interfaces.go b/backend/internal/domain/interfaces.go index 0488222..4b7dad6 100644 --- a/backend/internal/domain/interfaces.go +++ b/backend/internal/domain/interfaces.go @@ -56,10 +56,6 @@ type NotificationRepository interface { CreateNotification(ctx context.Context, n *Notification) error } -type SessionRepository interface { - GetSession(ctx context.Context, id string) (did, handle, accessToken, refreshToken, dpopKey string, err error) -} - type CollectionRepository interface { GetCollectionsForNoteURIs(ctx context.Context, noteURIs []string) (map[string]Collection, error) } diff --git a/backend/internal/firehose/ingester.go b/backend/internal/firehose/ingester.go index 2552d75..0b784a4 100644 --- a/backend/internal/firehose/ingester.go +++ b/backend/internal/firehose/ingester.go @@ -300,40 +300,40 @@ func (i *Ingester) triggerLazySync(did string) { func (i *Ingester) handleDelete(collection, uri string) { switch collection { case CollectionAnnotation: - i.db.DeleteAnnotation(uri) + i.db.DeleteAnnotation(context.Background(), uri) case CollectionHighlight: - i.db.DeleteHighlight(uri) + i.db.DeleteHighlight(context.Background(), uri) case CollectionBookmark: - i.db.DeleteBookmark(uri) + i.db.DeleteBookmark(context.Background(), uri) case CollectionReply: - i.db.DeleteReply(uri) + i.db.DeleteReply(context.Background(), uri) case CollectionLike: - i.db.DeleteLike(uri) + i.db.DeleteLike(context.Background(), uri) case CollectionCollection: - i.db.DeleteCollection(uri) + i.db.DeleteCollection(context.Background(), uri) case CollectionCollectionItem: - i.db.RemoveFromCollection(uri) + i.db.RemoveFromCollection(context.Background(), uri) case CollectionProfile: - i.db.DeleteProfile(uri) + i.db.DeleteProfile(context.Background(), uri) case CollectionAPIKey: - i.db.DeleteAPIKeyByURI(uri) + i.db.DeleteAPIKeyByURI(context.Background(), uri) case CollectionPreferences: - i.db.DeletePreferences(uri) + i.db.DeletePreferences(context.Background(), uri) case CollectionSembleCard: - i.db.DeleteAnnotation(uri) - i.db.DeleteBookmark(uri) + i.db.DeleteAnnotation(context.Background(), uri) + i.db.DeleteBookmark(context.Background(), uri) case CollectionSembleCollection: - i.db.DeleteCollection(uri) + i.db.DeleteCollection(context.Background(), uri) case xrpc.CollectionSembleCollectionLink: - i.db.RemoveFromCollection(uri) + i.db.RemoveFromCollection(context.Background(), uri) case CollectionDocument: - i.db.DeleteDocument(uri) + i.db.DeleteDocument(context.Background(), uri) case xrpc.CollectionNote: - i.db.DeleteNote(uri) + i.db.DeleteNote(context.Background(), uri) case xrpc.CollectionCommunityBookmark: - i.db.DeleteNote(uri) + i.db.DeleteNote(context.Background(), uri) case xrpc.CollectionLichenBookmark: - i.db.DeleteBookmark(uri) + i.db.DeleteBookmark(context.Background(), uri) } } @@ -455,7 +455,7 @@ func (i *Ingester) handleAnnotation(event *FirehoseEvent) { IndexedAt: time.Now(), } - if err := i.db.CreateAnnotation(annotation); err != nil { + if err := i.db.CreateAnnotation(context.Background(), annotation); err != nil { logger.Error("Failed to index annotation: %v", err) } else { logger.Info("Indexed annotation from %s on %s", event.Repo, targetSource) @@ -559,7 +559,7 @@ func (i *Ingester) handleNote(event *FirehoseEvent) { IndexedAt: time.Now(), } - if err := i.db.CreateNote(note); err != nil { + if err := i.db.CreateNote(context.Background(), note); err != nil { logger.Error("Failed to index note: %v", err) } else { logger.Info("Indexed note from %s on %s", event.Repo, targetSource) @@ -590,7 +590,7 @@ func (i *Ingester) handleCommunityBookmark(event *FirehoseEvent) { targetHash := db.HashURL(record.Subject) - if exists, err := i.db.MarginNoteBookmarkExists(event.Repo, targetHash); err == nil && exists { + if exists, err := i.db.MarginNoteBookmarkExists(context.Background(), event.Repo, targetHash); err == nil && exists { return } @@ -612,7 +612,7 @@ func (i *Ingester) handleCommunityBookmark(event *FirehoseEvent) { IndexedAt: time.Now(), } - if err := i.db.CreateNote(note); err != nil { + if err := i.db.CreateNote(context.Background(), note); err != nil { logger.Error("Failed to index community bookmark: %v", err) } else { logger.Info("Indexed community bookmark from %s on %s", event.Repo, record.Subject) @@ -652,7 +652,7 @@ func (i *Ingester) handleReply(event *FirehoseEvent) { IndexedAt: time.Now(), } - i.db.CreateReply(reply) + i.db.CreateReply(context.Background(), reply) } func (i *Ingester) handleLike(event *FirehoseEvent) { @@ -682,7 +682,7 @@ func (i *Ingester) handleLike(event *FirehoseEvent) { IndexedAt: time.Now(), } - i.db.CreateLike(like) + i.db.CreateLike(context.Background(), like) } func (i *Ingester) handleHighlight(event *FirehoseEvent) { @@ -744,7 +744,7 @@ func (i *Ingester) handleHighlight(event *FirehoseEvent) { IndexedAt: time.Now(), } - if err := i.db.CreateHighlight(highlight); err != nil { + if err := i.db.CreateHighlight(context.Background(), highlight); err != nil { logger.Error("Failed to index highlight: %v", err) } else { logger.Info("Indexed highlight from %s on %s", event.Repo, record.Target.Source) @@ -805,7 +805,7 @@ func (i *Ingester) handleBookmark(event *FirehoseEvent) { IndexedAt: time.Now(), } - if err := i.db.CreateBookmark(bookmark); err != nil { + if err := i.db.CreateBookmark(context.Background(), bookmark); err != nil { logger.Error("Failed to index bookmark: %v", err) } else { logger.Info("Indexed bookmark from %s: %s", event.Repo, record.Source) @@ -849,7 +849,7 @@ func (i *Ingester) handleCollection(event *FirehoseEvent) { IndexedAt: time.Now(), } - if err := i.db.CreateCollection(collection); err != nil { + if err := i.db.CreateCollection(context.Background(), collection); err != nil { logger.Error("Failed to index collection: %v", err) } else { logger.Info("Indexed collection from %s: %s", event.Repo, record.Name) @@ -885,7 +885,7 @@ func (i *Ingester) handleCollectionItem(event *FirehoseEvent) { IndexedAt: time.Now(), } - if err := i.db.AddToCollection(item); err != nil { + if err := i.db.AddToCollection(context.Background(), item); err != nil { logger.Error("Failed to index collection item: %v", err) } else { logger.Info("Indexed collection item from %s", event.Repo) @@ -943,7 +943,7 @@ func (i *Ingester) handleProfile(event *FirehoseEvent) { IndexedAt: time.Now(), } - if err := i.db.UpsertProfile(profile); err != nil { + if err := i.db.UpsertProfile(context.Background(), profile); err != nil { logger.Error("Failed to index profile: %v", err) } else { logger.Info("Indexed profile from %s", event.Repo) @@ -984,7 +984,7 @@ func (i *Ingester) handleAPIKey(event *FirehoseEvent) { IndexedAt: time.Now(), } - if err := i.db.CreateAPIKey(apiKey); err != nil { + if err := i.db.CreateAPIKey(context.Background(), apiKey); err != nil { logger.Error("Failed to index API key: %v", err) } else { logger.Info("Indexed API key from %s: %s", event.Repo, record.Name) @@ -1053,7 +1053,7 @@ func (i *Ingester) handlePreferences(event *FirehoseEvent) { CID: cidPtr, } - if err := i.db.UpsertPreferences(prefs); err != nil { + if err := i.db.UpsertPreferences(context.Background(), prefs); err != nil { logger.Error("Failed to index preferences: %v", err) } else { logger.Info("Indexed preferences from %s", event.Repo) @@ -1122,7 +1122,7 @@ func (i *Ingester) handleSembleCard(event *FirehoseEvent) { CreatedAt: createdAt, IndexedAt: time.Now(), } - if err := i.db.CreateAnnotation(annotation); err != nil { + if err := i.db.CreateAnnotation(context.Background(), annotation); err != nil { logger.Error("Failed to index Semble NOTE as annotation: %v", err) } else { if card.ParentCard != nil { @@ -1159,7 +1159,7 @@ func (i *Ingester) handleSembleCard(event *FirehoseEvent) { CreatedAt: createdAt, IndexedAt: time.Now(), } - if err := i.db.CreateBookmark(bookmark); err != nil { + if err := i.db.CreateBookmark(context.Background(), bookmark); err != nil { logger.Error("Failed to index Semble URL as bookmark: %v", err) } else { logger.Info("Indexed Semble URL from %s: %s", event.Repo, source) @@ -1196,7 +1196,7 @@ func (i *Ingester) handleSembleCollection(event *FirehoseEvent) { IndexedAt: time.Now(), } - if err := i.db.CreateCollection(collection); err != nil { + if err := i.db.CreateCollection(context.Background(), collection); err != nil { logger.Error("Failed to index Semble collection: %v", err) } else { logger.Info("Indexed Semble collection from %s: %s", event.Repo, record.Name) @@ -1225,7 +1225,7 @@ func (i *Ingester) handleSembleCollectionLink(event *FirehoseEvent) { IndexedAt: time.Now(), } - if err := i.db.AddToCollection(item); err != nil { + if err := i.db.AddToCollection(context.Background(), item); err != nil { logger.Error("Failed to index Semble collection link: %v", err) } else { logger.Info("Indexed Semble collection link from %s", event.Repo) @@ -1295,7 +1295,7 @@ func (i *Ingester) handleDocument(event *FirehoseEvent) { } verification.VerifyDocumentAsync(canonicalURL, uri, func(verifiedURI string) { - if err := i.db.UpsertDocument(doc); err != nil { + if err := i.db.UpsertDocument(context.Background(), doc); err != nil { logger.Error("Failed to index document: %v", err) } else { logger.Info("Indexed verified document from %s: %s", event.Repo, record.Title) @@ -1331,7 +1331,7 @@ func (i *Ingester) handleLichenBookmark(event *FirehoseEvent) { CreatedAt: createdAt, IndexedAt: time.Now(), } - if err := i.db.CreateBookmark(bookmark); err != nil { + if err := i.db.CreateBookmark(context.Background(), bookmark); err != nil { logger.Error("Failed to index Lichen bookmark: %v", err) } else { logger.Info("Indexed Lichen bookmark from %s: %s", event.Repo, source) diff --git a/backend/internal/oauth/client.go b/backend/internal/oauth/client.go index a2ec925..81655a3 100644 --- a/backend/internal/oauth/client.go +++ b/backend/internal/oauth/client.go @@ -82,10 +82,10 @@ func GenerateSigningKey() (*ecdsa.PrivateKey, error) { const SigningKeyKV = "oauth_signing_key" type KVStore interface { - GetOrCreateEncryptedKV(key, value string) (string, error) + GetOrCreateEncryptedKV(ctx context.Context, key, value string) (string, error) } -func LoadSigningKey(kv KVStore) (*ecdsa.PrivateKey, error) { +func LoadSigningKey(ctx context.Context, kv KVStore) (*ecdsa.PrivateKey, error) { candidate, err := GenerateSigningKey() if err != nil { return nil, err @@ -94,7 +94,7 @@ func LoadSigningKey(kv KVStore) (*ecdsa.PrivateKey, error) { if err != nil { return nil, err } - stored, err := kv.GetOrCreateEncryptedKV(SigningKeyKV, string(raw)) + stored, err := kv.GetOrCreateEncryptedKV(ctx, SigningKeyKV, string(raw)) if err != nil { return nil, err } diff --git a/backend/internal/oauth/handler.go b/backend/internal/oauth/handler.go index 3b00819..a06bba3 100644 --- a/backend/internal/oauth/handler.go +++ b/backend/internal/oauth/handler.go @@ -30,14 +30,19 @@ type Handler struct { } func NewHandler(database *db.DB, syncService *internal_sync.Service, ac *analytics.Client) (*Handler, error) { - signingKey, err := LoadSigningKey(database) + base := getBaseURLEnv() + if u, err := url.Parse(base); err != nil || u.Scheme == "" || u.Host == "" { + return nil, fmt.Errorf("BASE_URL=%q must be a valid absolute URL (e.g. https://margin.at); the OAuth client_id and redirect_uri are derived from it and must be identical across all replicas", base) + } + + signingKey, err := LoadSigningKey(context.Background(), database) if err != nil { return nil, fmt.Errorf("failed to load signing key: %w", err) } return &Handler{ db: database, - configuredBaseURL: getBaseURLEnv(), + configuredBaseURL: base, signingKey: signingKey, syncService: syncService, analytics: ac, @@ -48,23 +53,12 @@ func getBaseURLEnv() string { return strings.TrimRight(strings.TrimSpace(os.Getenv("BASE_URL")), "/") } -func (h *Handler) baseURL(r *http.Request) string { - if h.configuredBaseURL != "" { - return h.configuredBaseURL - } - scheme := "http" - if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { - scheme = "https" - } - host := r.Header.Get("X-Forwarded-Host") - if host == "" { - host = r.Host - } - return strings.TrimRight(fmt.Sprintf("%s://%s", scheme, host), "/") +func (h *Handler) baseURL() string { + return h.configuredBaseURL } -func (h *Handler) oauthClient(r *http.Request) *Client { - base := h.baseURL(r) +func (h *Handler) oauthClient() *Client { + base := h.baseURL() return NewClient(base+"/oauth-client-metadata.json", base+"/auth/callback", h.signingKey) } @@ -76,7 +70,7 @@ func (h *Handler) HandleLogin(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - client := h.oauthClient(r) + client := h.oauthClient() did, err := client.ResolveHandle(ctx, handle) if err != nil { @@ -106,7 +100,7 @@ func (h *Handler) HandleLogin(w http.ResponseWriter, r *http.Request) { return } - if err := h.savePending(state, did, handle, pdsURL, meta, verifier, dpopKey, nonce); err != nil { + if err := h.savePending(ctx, state, did, handle, pdsURL, meta, verifier, dpopKey, nonce); err != nil { http.Error(w, "Failed to save pending auth", http.StatusInternalServerError) return } @@ -133,7 +127,7 @@ func (h *Handler) HandleStart(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - client := h.oauthClient(r) + client := h.oauthClient() did, err := client.ResolveHandle(ctx, handle) if err != nil { @@ -164,7 +158,7 @@ func (h *Handler) HandleStart(w http.ResponseWriter, r *http.Request) { return } - if err := h.savePending(state, did, handle, pdsURL, meta, verifier, dpopKey, nonce); err != nil { + if err := h.savePending(ctx, state, did, handle, pdsURL, meta, verifier, dpopKey, nonce); err != nil { writeJSONError(w, http.StatusInternalServerError, "Failed to save pending auth") return } @@ -191,7 +185,7 @@ func (h *Handler) HandleSignup(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - client := h.oauthClient(r) + client := h.oauthClient() meta, err := client.GetAuthServerMetadataForSignup(ctx, pdsURL) if err != nil { @@ -216,7 +210,7 @@ func (h *Handler) HandleSignup(w http.ResponseWriter, r *http.Request) { } } - if err := h.savePending(state, "", "", pdsURL, meta, verifier, dpopKey, nonce); err != nil { + if err := h.savePending(ctx, state, "", "", pdsURL, meta, verifier, dpopKey, nonce); err != nil { writeJSONError(w, http.StatusInternalServerError, "Failed to save pending auth") return } @@ -224,9 +218,9 @@ func (h *Handler) HandleSignup(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{"authorizationUrl": client.AuthorizeURL(meta, par.RequestURI)}) } -func (h *Handler) savePending(state, did, handle, pdsURL string, meta *AuthServerMetadata, verifier string, dpopKey *ecdsa.PrivateKey, nonce string) error { +func (h *Handler) savePending(ctx context.Context, state, did, handle, pdsURL string, meta *AuthServerMetadata, verifier string, dpopKey *ecdsa.PrivateKey, nonce string) error { jwk := PrivateJWK(dpopKey) - return h.db.SavePendingAuthOAuth(db.PendingAuth{ + return h.db.SavePendingAuthOAuth(ctx, db.PendingAuth{ State: state, DID: did, Handle: handle, @@ -242,13 +236,13 @@ func (h *Handler) savePending(state, did, handle, pdsURL string, meta *AuthServe func (h *Handler) HandleCallback(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - client := h.oauthClient(r) + client := h.oauthClient() if oauthErr := r.URL.Query().Get("error"); oauthErr != "" { errDesc := r.URL.Query().Get("error_description") logger.Error("OAuth callback error: %s - %s", oauthErr, errDesc) if state := r.URL.Query().Get("state"); state != "" { - h.db.DeletePendingAuthOAuth(state) + h.db.DeletePendingAuthOAuth(ctx, state) } http.Redirect(w, r, "/login?error="+url.QueryEscape(errDesc), http.StatusFound) return @@ -262,12 +256,12 @@ func (h *Handler) HandleCallback(w http.ResponseWriter, r *http.Request) { return } - pending, err := h.db.GetPendingAuthOAuth(state) + pending, err := h.db.GetPendingAuthOAuth(ctx, state) if err != nil { http.Error(w, "Invalid or expired state", http.StatusBadRequest) return } - h.db.DeletePendingAuthOAuth(state) + h.db.DeletePendingAuthOAuth(ctx, state) if time.Since(pending.CreatedAt) > 10*time.Minute { http.Error(w, "Authentication request expired", http.StatusBadRequest) @@ -318,7 +312,7 @@ func (h *Handler) HandleCallback(w http.ResponseWriter, r *http.Request) { handle = did } - if banned, berr := h.db.IsBanned(did); berr == nil && banned { + if banned, berr := h.db.IsBanned(ctx, did); berr == nil && banned { http.Redirect(w, r, "/login?error=banned", http.StatusFound) return } @@ -330,7 +324,7 @@ func (h *Handler) HandleCallback(w http.ResponseWriter, r *http.Request) { } sessionExpiry := time.Now().Add(7 * 24 * time.Hour) - if err := h.db.CreateOAuthSession(db.OAuthSessionRow{ + if err := h.db.CreateOAuthSession(ctx, db.OAuthSessionRow{ ID: sessionID, DID: did, Handle: handle, @@ -376,7 +370,7 @@ func (h *Handler) HandleCallback(w http.ResponseWriter, r *http.Request) { if h.analytics == nil { return } - existingCount, _ := h.db.CountOAuthSessionsByDID(did) + existingCount, _ := h.db.CountOAuthSessionsByDID(context.Background(), did) if existingCount <= 1 { h.analytics.Capture(did, "account_created", map[string]interface{}{"pds": pdsURL}) } else { @@ -386,7 +380,8 @@ func (h *Handler) HandleCallback(w http.ResponseWriter, r *http.Request) { } func (h *Handler) cleanupOrphanedReplies(did, accessToken string, dpopKey *ecdsa.PrivateKey, pds string) { - orphans, err := h.db.GetOrphanedRepliesByAuthor(did) + ctx := context.Background() + orphans, err := h.db.GetOrphanedRepliesByAuthor(ctx, did) if err != nil || len(orphans) == 0 { return } @@ -397,7 +392,7 @@ func (h *Handler) cleanupOrphanedReplies(did, accessToken string, dpopKey *ecdsa } rkey := uriParts[len(uriParts)-1] deleteFromPDS(pds, accessToken, dpopKey, "at.margin.reply", did, rkey) - h.db.DeleteReply(reply.URI) + h.db.DeleteReply(ctx, reply.URI) } } @@ -431,7 +426,7 @@ func deleteFromPDS(pds, accessToken string, dpopKey *ecdsa.PrivateKey, collectio func (h *Handler) HandleLogout(w http.ResponseWriter, r *http.Request) { if cookie, err := r.Cookie("margin_session"); err == nil { - h.db.DeleteOAuthSession(cookie.Value) + h.db.DeleteOAuthSession(r.Context(), cookie.Value) } for _, secure := range []bool{true, false} { http.SetCookie(w, &http.Cookie{ @@ -459,7 +454,7 @@ func (h *Handler) HandleSession(w http.ResponseWriter, r *http.Request) { return } - sess, err := h.db.GetOAuthSessionByID(sessionID) + sess, err := h.db.GetOAuthSessionByID(r.Context(), sessionID) if err != nil { writeJSON(w, http.StatusOK, map[string]any{"authenticated": false}) return @@ -473,7 +468,7 @@ func (h *Handler) HandleSession(w http.ResponseWriter, r *http.Request) { } func (h *Handler) HandleClientMetadata(w http.ResponseWriter, r *http.Request) { - base := h.baseURL(r) + base := h.baseURL() writeJSON(w, http.StatusOK, map[string]any{ "client_id": base + "/oauth-client-metadata.json", "client_name": "Margin", @@ -494,7 +489,7 @@ func (h *Handler) HandleClientMetadata(w http.ResponseWriter, r *http.Request) { } func (h *Handler) HandleJWKS(w http.ResponseWriter, r *http.Request) { - writeJSON(w, http.StatusOK, h.oauthClient(r).PublicJWKS()) + writeJSON(w, http.StatusOK, h.oauthClient().PublicJWKS()) } func (h *Handler) GetSigningKey() *ecdsa.PrivateKey { diff --git a/backend/internal/recommendations/service.go b/backend/internal/recommendations/service.go index a428864..09cac7d 100644 --- a/backend/internal/recommendations/service.go +++ b/backend/internal/recommendations/service.go @@ -1,7 +1,7 @@ package recommendations import ( - "database/sql" + "context" "encoding/json" "math" "sort" @@ -10,6 +10,7 @@ import ( "time" "unicode" + "github.com/jackc/pgx/v5" "margin.at/internal/db" "margin.at/internal/embeddings" "margin.at/internal/logger" @@ -49,12 +50,12 @@ func (s *Service) OnAnnotation(uri, authorDID, targetSource string, bodyValue, s } var documentURI *string - docURI, err := s.db.MatchAnnotationToDocument(targetSource) + docURI, err := s.db.MatchAnnotationToDocument(context.Background(), targetSource) if err == nil && docURI != nil { documentURI = docURI } - if err := s.db.UpsertAnnotationEmbedding(uri, authorDID, documentURI, embedding); err != nil { + if err := s.db.UpsertAnnotationEmbedding(context.Background(), uri, authorDID, documentURI, embedding); err != nil { logger.Error("Failed to store annotation embedding %s: %v", uri, err) return } @@ -67,7 +68,7 @@ func (s *Service) OnDocument(documentURI string) { return } - doc, err := s.db.GetDocumentByURI(documentURI) + doc, err := s.db.GetDocumentByURI(context.Background(), documentURI) if err != nil { logger.Error("Failed to fetch document %s for embedding: %v", documentURI, err) return @@ -97,7 +98,7 @@ func (s *Service) OnDocument(documentURI string) { return } - if err := s.db.UpsertDocumentEmbedding(documentURI, embedding); err != nil { + if err := s.db.UpsertDocumentEmbedding(context.Background(), documentURI, embedding); err != nil { logger.Error("Failed to store document embedding %s: %v", documentURI, err) } } @@ -107,8 +108,8 @@ func (s *Service) GetRecommendations(authorDID string, limit int) ([]Recommended return nil, nil } - profile, err := s.db.GetUserProfile(authorDID) - if err == sql.ErrNoRows { + profile, err := s.db.GetUserProfile(context.Background(), authorDID) + if err == pgx.ErrNoRows { return nil, nil } if err != nil { @@ -119,7 +120,7 @@ func (s *Service) GetRecommendations(authorDID string, limit int) ([]Recommended return nil, nil } - candidates, err := s.db.GetCandidateDocuments(authorDID, 500) + candidates, err := s.db.GetCandidateDocuments(context.Background(), authorDID, 500) if err != nil { return nil, err } @@ -165,7 +166,7 @@ func (s *Service) GetRecommendations(authorDID string, limit int) ([]Recommended preList = preList[:shortlistSize] } - annEmbeddings, _ := s.db.GetRecentAnnotationEmbeddingsByAuthor(authorDID, 30) + annEmbeddings, _ := s.db.GetRecentAnnotationEmbeddingsByAuthor(context.Background(), authorDID, 30) topK := 3 if len(annEmbeddings) < topK { @@ -258,7 +259,7 @@ func (s *Service) updateUserProfile(authorDID string) { s.mu.Lock() defer s.mu.Unlock() - annEmbeddings, err := s.db.GetAnnotationEmbeddingsByAuthor(authorDID) + annEmbeddings, err := s.db.GetAnnotationEmbeddingsByAuthor(context.Background(), authorDID) if err != nil || len(annEmbeddings) == 0 { return } @@ -288,7 +289,7 @@ func (s *Service) updateUserProfile(authorDID string) { result[i] = float32(centroid[i] / totalWeight) } - annotations, _ := s.db.GetAnnotationsByAuthor(authorDID, 500, 0) + annotations, _ := s.db.GetAnnotationsByAuthor(context.Background(), authorDID, 500, 0) for _, ann := range annotations { if ann.TagsJSON != nil { var tags []string @@ -298,7 +299,7 @@ func (s *Service) updateUserProfile(authorDID string) { } } } - highlights, _ := s.db.GetHighlightsByAuthor(authorDID, 500, 0) + highlights, _ := s.db.GetHighlightsByAuthor(context.Background(), authorDID, 500, 0) for _, h := range highlights { if h.TagsJSON != nil { var tags []string @@ -321,7 +322,7 @@ func (s *Service) updateUserProfile(authorDID string) { } } - if err := s.db.UpsertUserProfile(authorDID, result, tagCounts, len(annEmbeddings)); err != nil { + if err := s.db.UpsertUserProfile(context.Background(), authorDID, result, tagCounts, len(annEmbeddings)); err != nil { logger.Error("Failed to update user profile for %s: %v", authorDID, err) } } @@ -333,7 +334,7 @@ func (s *Service) BackfillDocumentEmbeddings(batchSize int) error { total := 0 for { - docs, err := s.db.GetDocumentsWithoutEmbeddings(batchSize) + docs, err := s.db.GetDocumentsWithoutEmbeddings(context.Background(), batchSize) if err != nil { return err } @@ -365,7 +366,7 @@ func (s *Service) BackfillDocumentEmbeddings(batchSize int) error { } for i, doc := range docs { - if err := s.db.UpsertDocumentEmbedding(doc.URI, vecs[i]); err != nil { + if err := s.db.UpsertDocumentEmbedding(context.Background(), doc.URI, vecs[i]); err != nil { logger.Error("Failed to store embedding for doc %s: %v", doc.URI, err) } } @@ -391,7 +392,7 @@ func (s *Service) BackfillAnnotationEmbeddings(batchSize int) (int, error) { total := 0 for { - anns, err := s.db.GetAnnotationsWithoutEmbeddings(batchSize) + anns, err := s.db.GetAnnotationsWithoutEmbeddings(context.Background(), batchSize) if err != nil { return total, err } @@ -417,10 +418,10 @@ func (s *Service) BackfillAnnotationEmbeddings(batchSize int) (int, error) { continue } var documentURI *string - if docURI, err := s.db.MatchAnnotationToDocument(a.TargetSource); err == nil && docURI != nil { + if docURI, err := s.db.MatchAnnotationToDocument(context.Background(), a.TargetSource); err == nil && docURI != nil { documentURI = docURI } - if err := s.db.UpsertAnnotationEmbedding(a.URI, a.AuthorDID, documentURI, vecs[i]); err != nil { + if err := s.db.UpsertAnnotationEmbedding(context.Background(), a.URI, a.AuthorDID, documentURI, vecs[i]); err != nil { logger.Error("Failed to store embedding for annotation %s: %v", a.URI, err) } else { batch++ @@ -448,7 +449,7 @@ func (s *Service) BackfillHighlightEmbeddings(batchSize int) (int, error) { total := 0 for { - highlights, err := s.db.GetHighlightsWithoutEmbeddings(batchSize) + highlights, err := s.db.GetHighlightsWithoutEmbeddings(context.Background(), batchSize) if err != nil { return total, err } @@ -474,10 +475,10 @@ func (s *Service) BackfillHighlightEmbeddings(batchSize int) (int, error) { continue } var documentURI *string - if docURI, err := s.db.MatchAnnotationToDocument(h.TargetSource); err == nil && docURI != nil { + if docURI, err := s.db.MatchAnnotationToDocument(context.Background(), h.TargetSource); err == nil && docURI != nil { documentURI = docURI } - if err := s.db.UpsertAnnotationEmbedding(h.URI, h.AuthorDID, documentURI, vecs[i]); err != nil { + if err := s.db.UpsertAnnotationEmbedding(context.Background(), h.URI, h.AuthorDID, documentURI, vecs[i]); err != nil { logger.Error("Failed to store embedding for highlight %s: %v", h.URI, err) } else { batch++ @@ -503,7 +504,7 @@ func (s *Service) RebuildAllProfiles() (int, error) { return 0, nil } - dids, err := s.db.GetDistinctAnnotationAuthors() + dids, err := s.db.GetDistinctAnnotationAuthors(context.Background()) if err != nil { return 0, err } @@ -665,8 +666,8 @@ func detectScript(text string) string { } func (s *Service) detectUserScript(authorDID string) string { - annotations, _ := s.db.GetAnnotationsByAuthor(authorDID, 100, 0) - highlights, _ := s.db.GetHighlightsByAuthor(authorDID, 100, 0) + annotations, _ := s.db.GetAnnotationsByAuthor(context.Background(), authorDID, 100, 0) + highlights, _ := s.db.GetHighlightsByAuthor(context.Background(), authorDID, 100, 0) scriptCounts := make(map[string]int) for _, a := range annotations { diff --git a/backend/internal/repository/postgres/pg_collection_repo.go b/backend/internal/repository/postgres/pg_collection_repo.go index 5fc9c00..a7143b7 100644 --- a/backend/internal/repository/postgres/pg_collection_repo.go +++ b/backend/internal/repository/postgres/pg_collection_repo.go @@ -19,7 +19,7 @@ func (r *CollectionRepository) GetCollectionsForNoteURIs(ctx context.Context, no if len(noteURIs) == 0 { return map[string]domain.Collection{}, nil } - rows, err := r.db.QueryContext(ctx, ` + rows, err := r.db.Query(ctx, ` SELECT DISTINCT ON (ci.annotation_uri) ci.annotation_uri, c.uri, c.author_did, c.name, c.description, c.icon, c.created_at, c.indexed_at diff --git a/backend/internal/repository/postgres/pg_engagement_repo.go b/backend/internal/repository/postgres/pg_engagement_repo.go index 8e95759..60f10b7 100644 --- a/backend/internal/repository/postgres/pg_engagement_repo.go +++ b/backend/internal/repository/postgres/pg_engagement_repo.go @@ -20,7 +20,7 @@ func NewEngagementRepository(db DB) *EngagementRepository { func (r *EngagementRepository) GetLikeCount(ctx context.Context, uri string) (int, error) { var count int - err := r.db.QueryRowContext(ctx, + err := r.db.QueryRow(ctx, `SELECT COUNT(*) FROM likes WHERE subject_uri = $1`, uri, ).Scan(&count) return count, err @@ -30,7 +30,7 @@ func (r *EngagementRepository) GetLikeCounts(ctx context.Context, uris []string) if len(uris) == 0 { return map[string]int{}, nil } - rows, err := r.db.QueryContext(ctx, + rows, err := r.db.Query(ctx, `SELECT subject_uri, COUNT(*) FROM likes WHERE subject_uri = ANY($1) GROUP BY subject_uri`, pqArray(uris), ) @@ -55,7 +55,7 @@ func (r *EngagementRepository) GetReplyCounts(ctx context.Context, uris []string if len(uris) == 0 { return map[string]int{}, nil } - rows, err := r.db.QueryContext(ctx, + rows, err := r.db.Query(ctx, `SELECT root_uri, COUNT(*) FROM replies WHERE root_uri = ANY($1) GROUP BY root_uri`, pqArray(uris), ) @@ -89,7 +89,7 @@ func (r *EngagementRepository) GetViewerLikes(ctx context.Context, viewerDID str args[i+1] = uri } - rows, err := r.db.QueryContext(ctx, + rows, err := r.db.Query(ctx, `SELECT subject_uri FROM likes WHERE author_did = $1 AND subject_uri IN (`+ strings.Join(placeholders, ", ")+`)`, args..., @@ -134,7 +134,7 @@ func (r *EngagementRepository) queryLabels(ctx context.Context, subjects []strin } query += ` ORDER BY created_at DESC` - rows, err := r.db.QueryContext(ctx, query, args...) + rows, err := r.db.Query(ctx, query, args...) if err != nil { return result, err } @@ -162,7 +162,7 @@ func (r *EngagementRepository) GetLatestEditTimes(ctx context.Context, uris []st args[i] = uri } - rows, err := r.db.QueryContext(ctx, + rows, err := r.db.Query(ctx, `SELECT uri, MAX(edited_at) FROM edit_history WHERE uri IN (`+ strings.Join(placeholders, ",")+`) GROUP BY uri`, args..., diff --git a/backend/internal/repository/postgres/pg_note_repo.go b/backend/internal/repository/postgres/pg_note_repo.go index dfa6a12..df01efa 100644 --- a/backend/internal/repository/postgres/pg_note_repo.go +++ b/backend/internal/repository/postgres/pg_note_repo.go @@ -2,19 +2,16 @@ package postgres import ( "context" - "database/sql" "fmt" "strings" "time" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" "margin.at/internal/domain" ) -type DB interface { - ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) - QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) - QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row -} +type DB = *pgxpool.Pool type NoteRepository struct { db DB @@ -128,7 +125,7 @@ func (r *NoteRepository) List(ctx context.Context, f domain.NoteFilter) ([]domai args = append(args, limit, f.Offset) - rows, err := r.db.QueryContext(ctx, query, args...) + rows, err := r.db.Query(ctx, query, args...) if err != nil { return nil, err } @@ -144,13 +141,13 @@ func (r *NoteRepository) GetByURI(ctx context.Context, uri string) (*domain.Note WHERE uri = $1 ` var note domain.Note - err := r.db.QueryRowContext(ctx, query, uri).Scan( + err := r.db.QueryRow(ctx, query, uri).Scan( ¬e.URI, ¬e.AuthorDID, ¬e.Motivation, ¬e.Color, ¬e.Description, ¬e.BodyValue, ¬e.BodyFormat, ¬e.BodyURI, ¬e.TargetSource, ¬e.TargetHash, ¬e.TargetTitle, ¬e.SelectorJSON, ¬e.TagsJSON, ¬e.CreatedAt, ¬e.IndexedAt, ¬e.CID, ) - if err == sql.ErrNoRows { + if err == pgx.ErrNoRows { return nil, nil } if err != nil { @@ -181,7 +178,7 @@ func (r *NoteRepository) CreateNote(ctx context.Context, n *domain.Note) error { indexed_at = EXCLUDED.indexed_at, cid = EXCLUDED.cid ` - _, err := r.db.ExecContext(ctx, query, + _, err := r.db.Exec(ctx, query, n.URI, n.AuthorDID, n.Motivation, n.Color, n.Description, n.BodyValue, n.BodyFormat, n.BodyURI, n.TargetSource, n.TargetHash, n.TargetTitle, n.SelectorJSON, n.TagsJSON, n.CreatedAt, n.IndexedAt, n.CID, ) @@ -189,12 +186,12 @@ func (r *NoteRepository) CreateNote(ctx context.Context, n *domain.Note) error { } func (r *NoteRepository) DeleteNote(ctx context.Context, uri string) error { - _, err := r.db.ExecContext(ctx, "DELETE FROM notes WHERE uri = $1", uri) + _, err := r.db.Exec(ctx, "DELETE FROM notes WHERE uri = $1", uri) return err } func (r *NoteRepository) UpdateNoteAnnotation(ctx context.Context, uri, bodyValue, tagsJSON string, cid *string) error { - _, err := r.db.ExecContext(ctx, ` + _, err := r.db.Exec(ctx, ` UPDATE notes SET body_value = $1, tags_json = NULLIF($2, ''), cid = $3, indexed_at = $4 WHERE uri = $5 @@ -205,10 +202,10 @@ func (r *NoteRepository) UpdateNoteAnnotation(ctx context.Context, uri, bodyValu func (r *NoteRepository) GetLikeByUserAndSubject(ctx context.Context, did, subjectURI string) (*domain.Like, error) { query := "SELECT uri, author_did, subject_uri, created_at, indexed_at FROM likes WHERE author_did = $1 AND subject_uri = $2" var l domain.Like - err := r.db.QueryRowContext(ctx, query, did, subjectURI).Scan( + err := r.db.QueryRow(ctx, query, did, subjectURI).Scan( &l.URI, &l.AuthorDID, &l.SubjectURI, &l.CreatedAt, &l.IndexedAt, ) - if err == sql.ErrNoRows { + if err == pgx.ErrNoRows { return nil, nil } if err != nil { @@ -218,7 +215,7 @@ func (r *NoteRepository) GetLikeByUserAndSubject(ctx context.Context, did, subje } func (r *NoteRepository) CreateLike(ctx context.Context, l *domain.Like) error { - _, err := r.db.ExecContext(ctx, ` + _, err := r.db.Exec(ctx, ` INSERT INTO likes (uri, author_did, subject_uri, created_at, indexed_at) VALUES ($1, $2, $3, $4, $5) ON CONFLICT(uri) DO NOTHING @@ -227,12 +224,12 @@ func (r *NoteRepository) CreateLike(ctx context.Context, l *domain.Like) error { } func (r *NoteRepository) DeleteLike(ctx context.Context, uri string) error { - _, err := r.db.ExecContext(ctx, "DELETE FROM likes WHERE uri = $1", uri) + _, err := r.db.Exec(ctx, "DELETE FROM likes WHERE uri = $1", uri) return err } func (r *NoteRepository) CreateReply(ctx context.Context, rep *domain.Reply) error { - _, err := r.db.ExecContext(ctx, ` + _, err := r.db.Exec(ctx, ` INSERT INTO replies (uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT(uri) DO NOTHING @@ -243,37 +240,37 @@ func (r *NoteRepository) CreateReply(ctx context.Context, rep *domain.Reply) err func (r *NoteRepository) GetReplyByURI(ctx context.Context, uri string) (*domain.Reply, error) { query := "SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid FROM replies WHERE uri = $1" var rep domain.Reply - err := r.db.QueryRowContext(ctx, query, uri).Scan( + err := r.db.QueryRow(ctx, query, uri).Scan( &rep.URI, &rep.AuthorDID, &rep.ParentURI, &rep.RootURI, &rep.Text, &rep.Format, &rep.CreatedAt, &rep.IndexedAt, &rep.CID, ) - if err == sql.ErrNoRows { + if err == pgx.ErrNoRows { return nil, nil } return &rep, err } func (r *NoteRepository) DeleteReply(ctx context.Context, uri string) error { - _, err := r.db.ExecContext(ctx, "DELETE FROM replies WHERE uri = $1", uri) + _, err := r.db.Exec(ctx, "DELETE FROM replies WHERE uri = $1", uri) return err } func (r *NoteRepository) DeleteAnnotation(ctx context.Context, uri string) error { - _, err := r.db.ExecContext(ctx, "DELETE FROM annotations WHERE uri = $1", uri) + _, err := r.db.Exec(ctx, "DELETE FROM annotations WHERE uri = $1", uri) return err } func (r *NoteRepository) DeleteHighlight(ctx context.Context, uri string) error { - _, err := r.db.ExecContext(ctx, "DELETE FROM highlights WHERE uri = $1", uri) + _, err := r.db.Exec(ctx, "DELETE FROM highlights WHERE uri = $1", uri) return err } func (r *NoteRepository) DeleteBookmark(ctx context.Context, uri string) error { - _, err := r.db.ExecContext(ctx, "DELETE FROM bookmarks WHERE uri = $1", uri) + _, err := r.db.Exec(ctx, "DELETE FROM bookmarks WHERE uri = $1", uri) return err } func (r *NoteRepository) UpdateAnnotation(ctx context.Context, uri, bodyValue, tagsJSON string, cid *string) error { - _, err := r.db.ExecContext(ctx, ` + _, err := r.db.Exec(ctx, ` UPDATE annotations SET body_value = $1, tags_json = NULLIF($2, ''), cid = $3, indexed_at = $4 WHERE uri = $5 @@ -288,11 +285,11 @@ func (r *NoteRepository) GetAnnotationByURI(ctx context.Context, uri string) (*d FROM annotations WHERE uri = $1 ` var a domain.Annotation - err := r.db.QueryRowContext(ctx, query, uri).Scan( + err := r.db.QueryRow(ctx, query, uri).Scan( &a.URI, &a.AuthorDID, &a.Motivation, &a.BodyValue, &a.BodyFormat, &a.BodyURI, &a.TargetSource, &a.TargetHash, &a.TargetTitle, &a.SelectorJSON, &a.TagsJSON, &a.CreatedAt, &a.IndexedAt, &a.CID, ) - if err == sql.ErrNoRows { + if err == pgx.ErrNoRows { return nil, nil } return &a, err @@ -301,8 +298,8 @@ func (r *NoteRepository) GetAnnotationByURI(ctx context.Context, uri string) (*d func (r *NoteRepository) CheckDuplicateAnnotation(ctx context.Context, did, url, text string) (*domain.Annotation, error) { query := "SELECT uri, cid FROM annotations WHERE author_did = $1 AND target_source = $2 AND body_value = $3 LIMIT 1" var a domain.Annotation - err := r.db.QueryRowContext(ctx, query, did, url, text).Scan(&a.URI, &a.CID) - if err == sql.ErrNoRows { + err := r.db.QueryRow(ctx, query, did, url, text).Scan(&a.URI, &a.CID) + if err == pgx.ErrNoRows { return nil, nil } return &a, err @@ -311,14 +308,14 @@ func (r *NoteRepository) CheckDuplicateAnnotation(ctx context.Context, did, url, func (r *NoteRepository) CheckDuplicateHighlight(ctx context.Context, did, url string, selector []byte) (*domain.Highlight, error) { query := "SELECT uri, cid FROM highlights WHERE author_did = $1 AND target_source = $2 AND selector_json = $3 LIMIT 1" var h domain.Highlight - err := r.db.QueryRowContext(ctx, query, did, url, selector).Scan(&h.URI, &h.CID) - if err == sql.ErrNoRows { + err := r.db.QueryRow(ctx, query, did, url, selector).Scan(&h.URI, &h.CID) + if err == pgx.ErrNoRows { return nil, nil } return &h, err } -func scanNotes(rows *sql.Rows) ([]domain.Note, error) { +func scanNotes(rows pgx.Rows) ([]domain.Note, error) { var notes []domain.Note for rows.Next() { var note domain.Note diff --git a/backend/internal/repository/postgres/pg_notification_repo.go b/backend/internal/repository/postgres/pg_notification_repo.go index c24cc00..077466c 100644 --- a/backend/internal/repository/postgres/pg_notification_repo.go +++ b/backend/internal/repository/postgres/pg_notification_repo.go @@ -16,7 +16,7 @@ func NewNotificationRepository(db DB) *NotificationRepository { } func (r *NotificationRepository) CreateNotification(ctx context.Context, n *domain.Notification) error { - _, err := r.db.ExecContext(ctx, ` + _, err := r.db.Exec(ctx, ` INSERT INTO notifications (recipient_did, actor_did, type, subject_uri, created_at) VALUES ($1, $2, $3, $4, $5) `, n.RecipientDID, n.ActorDID, n.Type, n.SubjectURI, n.CreatedAt) @@ -24,7 +24,7 @@ func (r *NotificationRepository) CreateNotification(ctx context.Context, n *doma } func (r *NotificationRepository) GetNotifications(ctx context.Context, recipientDID string, limit, offset int) ([]domain.Notification, error) { - rows, err := r.db.QueryContext(ctx, ` + rows, err := r.db.Query(ctx, ` SELECT id, recipient_did, actor_did, type, subject_uri, created_at, read_at FROM notifications WHERE recipient_did = $1 @@ -49,14 +49,14 @@ func (r *NotificationRepository) GetNotifications(ctx context.Context, recipient func (r *NotificationRepository) GetUnreadNotificationCount(ctx context.Context, recipientDID string) (int, error) { var count int - err := r.db.QueryRowContext(ctx, ` + err := r.db.QueryRow(ctx, ` SELECT COUNT(*) FROM notifications WHERE recipient_did = $1 AND read_at IS NULL `, recipientDID).Scan(&count) return count, err } func (r *NotificationRepository) MarkNotificationsRead(ctx context.Context, recipientDID string) error { - _, err := r.db.ExecContext(ctx, ` + _, err := r.db.Exec(ctx, ` UPDATE notifications SET read_at = $1 WHERE recipient_did = $2 AND read_at IS NULL `, time.Now(), recipientDID) return err diff --git a/backend/internal/repository/postgres/pg_profile_repo.go b/backend/internal/repository/postgres/pg_profile_repo.go index 9f67b42..492312c 100644 --- a/backend/internal/repository/postgres/pg_profile_repo.go +++ b/backend/internal/repository/postgres/pg_profile_repo.go @@ -2,10 +2,10 @@ package postgres import ( "context" - "database/sql" "fmt" "strings" + "github.com/jackc/pgx/v5" "margin.at/internal/domain" ) @@ -32,7 +32,7 @@ func (r *ProfileRepository) GetProfiles(ctx context.Context, dids []string) (map query := `SELECT author_did, display_name, avatar FROM profiles WHERE author_did IN (` + strings.Join(placeholders, ",") + `)` - rows, err := r.db.QueryContext(ctx, query, args...) + rows, err := r.db.Query(ctx, query, args...) if err != nil { return nil, err } @@ -70,10 +70,10 @@ func (r *ProfileRepository) GetProfile(ctx context.Context, did string) (*domain query := `SELECT uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at FROM profiles WHERE author_did = $1` var p domain.Profile - err := r.db.QueryRowContext(ctx, query, did).Scan( + err := r.db.QueryRow(ctx, query, did).Scan( &p.URI, &p.AuthorDID, &p.DisplayName, &p.Avatar, &p.Bio, &p.Website, &p.LinksJSON, &p.CreatedAt, &p.IndexedAt, ) - if err == sql.ErrNoRows { + if err == pgx.ErrNoRows { return nil, nil } if err != nil { @@ -83,7 +83,7 @@ func (r *ProfileRepository) GetProfile(ctx context.Context, did string) (*domain } func (r *ProfileRepository) UpsertProfile(ctx context.Context, p *domain.Profile) error { - _, err := r.db.ExecContext(ctx, ` + _, err := r.db.Exec(ctx, ` INSERT INTO profiles (uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT(uri) DO UPDATE SET diff --git a/backend/internal/repository/postgres/pg_session_repo.go b/backend/internal/repository/postgres/pg_session_repo.go deleted file mode 100644 index 06a9fc5..0000000 --- a/backend/internal/repository/postgres/pg_session_repo.go +++ /dev/null @@ -1,23 +0,0 @@ -package postgres - -import ( - "context" - "time" -) - -type SessionRepository struct { - db DB -} - -func NewSessionRepository(db DB) *SessionRepository { - return &SessionRepository{db: db} -} - -func (r *SessionRepository) GetSession(ctx context.Context, id string) (did, handle, accessToken, refreshToken, dpopKey string, err error) { - err = r.db.QueryRowContext(ctx, ` - SELECT did, handle, access_token, refresh_token, COALESCE(dpop_key, '') - FROM sessions - WHERE id = $1 AND expires_at > $2 - `, id, time.Now()).Scan(&did, &handle, &accessToken, &refreshToken, &dpopKey) - return -} diff --git a/backend/internal/service/feed.go b/backend/internal/service/feed.go index 2583a84..749c896 100644 --- a/backend/internal/service/feed.go +++ b/backend/internal/service/feed.go @@ -26,8 +26,8 @@ type FeedService struct { notes domain.NoteRepository hydration *HydrationService database interface { - GetAllHiddenDIDs(actorDID string) (map[string]bool, error) - GetBannedDIDs() ([]string, error) + GetAllHiddenDIDs(ctx context.Context, actorDID string) (map[string]bool, error) + GetBannedDIDs(ctx context.Context) ([]string, error) } } @@ -35,8 +35,8 @@ func NewFeedService( notes domain.NoteRepository, hydration *HydrationService, db interface { - GetAllHiddenDIDs(actorDID string) (map[string]bool, error) - GetBannedDIDs() ([]string, error) + GetAllHiddenDIDs(ctx context.Context, actorDID string) (map[string]bool, error) + GetBannedDIDs(ctx context.Context) ([]string, error) }, ) *FeedService { return &FeedService{ @@ -66,7 +66,7 @@ func (s *FeedService) GetFeed(ctx context.Context, req FeedRequest) (*FeedRespon return nil, err } - if bannedDIDs, err := s.database.GetBannedDIDs(); err == nil && len(bannedDIDs) > 0 { + if bannedDIDs, err := s.database.GetBannedDIDs(ctx); err == nil && len(bannedDIDs) > 0 { banned := make(map[string]bool, len(bannedDIDs)) for _, did := range bannedDIDs { banned[did] = true @@ -81,7 +81,7 @@ func (s *FeedService) GetFeed(ctx context.Context, req FeedRequest) (*FeedRespon } if req.ViewerDID != "" { - hidden, _ := s.database.GetAllHiddenDIDs(req.ViewerDID) + hidden, _ := s.database.GetAllHiddenDIDs(ctx, req.ViewerDID) if len(hidden) > 0 { filtered := notes[:0] for _, n := range notes { diff --git a/backend/internal/sync/service.go b/backend/internal/sync/service.go index 81cc090..2b4e123 100644 --- a/backend/internal/sync/service.go +++ b/backend/internal/sync/service.go @@ -121,16 +121,16 @@ func (s *Service) PerformSync(ctx context.Context, did string, getClient func(co switch collectionNSID { case xrpc.CollectionAnnotation: - localURIs, err = s.db.GetAnnotationURIs(did) + localURIs, err = s.db.GetAnnotationURIs(ctx, did) localURIs = filterURIsByCollection(localURIs, xrpc.CollectionAnnotation) case xrpc.CollectionHighlight: - localURIs, err = s.db.GetHighlightURIs(did) + localURIs, err = s.db.GetHighlightURIs(ctx, did) localURIs = filterURIsByCollection(localURIs, xrpc.CollectionHighlight) case xrpc.CollectionBookmark: - localURIs, err = s.db.GetBookmarkURIs(did) + localURIs, err = s.db.GetBookmarkURIs(ctx, did) localURIs = filterURIsByCollection(localURIs, xrpc.CollectionBookmark) case xrpc.CollectionCollection: - cols, e := s.db.GetCollectionsByAuthor(did) + cols, e := s.db.GetCollectionsByAuthor(ctx, did) if e == nil { for _, c := range cols { localURIs = append(localURIs, c.URI) @@ -140,7 +140,7 @@ func (s *Service) PerformSync(ctx context.Context, did string, getClient func(co err = e } case xrpc.CollectionCollectionItem: - items, e := s.db.GetCollectionItemsByAuthor(did) + items, e := s.db.GetCollectionItemsByAuthor(ctx, did) if e == nil { for _, item := range items { localURIs = append(localURIs, item.URI) @@ -150,7 +150,7 @@ func (s *Service) PerformSync(ctx context.Context, did string, getClient func(co err = e } case xrpc.CollectionReply: - replies, e := s.db.GetRepliesByAuthor(did) + replies, e := s.db.GetRepliesByAuthor(ctx, did) if e == nil { for _, r := range replies { localURIs = append(localURIs, r.URI) @@ -160,7 +160,7 @@ func (s *Service) PerformSync(ctx context.Context, did string, getClient func(co err = e } case xrpc.CollectionLike: - likes, e := s.db.GetLikesByAuthor(did) + likes, e := s.db.GetLikesByAuthor(ctx, did) if e == nil { for _, l := range likes { localURIs = append(localURIs, l.URI) @@ -170,8 +170,8 @@ func (s *Service) PerformSync(ctx context.Context, did string, getClient func(co err = e } case xrpc.CollectionSembleCard: - annos, e1 := s.db.GetAnnotationURIs(did) - books, e2 := s.db.GetBookmarkURIs(did) + annos, e1 := s.db.GetAnnotationURIs(ctx, did) + books, e2 := s.db.GetBookmarkURIs(ctx, did) if e1 != nil { err = e1 break @@ -184,7 +184,7 @@ func (s *Service) PerformSync(ctx context.Context, did string, getClient func(co localURIs = append(localURIs, books...) localURIs = filterURIsByCollection(localURIs, xrpc.CollectionSembleCard) case xrpc.CollectionSembleCollection: - cols, e := s.db.GetCollectionsByAuthor(did) + cols, e := s.db.GetCollectionsByAuthor(ctx, did) if e == nil { for _, c := range cols { localURIs = append(localURIs, c.URI) @@ -194,13 +194,13 @@ func (s *Service) PerformSync(ctx context.Context, did string, getClient func(co err = e } case xrpc.CollectionAPIKey: - localURIs, err = s.db.GetAPIKeyURIs(did) + localURIs, err = s.db.GetAPIKeyURIs(ctx, did) localURIs = filterURIsByCollection(localURIs, xrpc.CollectionAPIKey) case xrpc.CollectionPreferences: - localURIs, err = s.db.GetPreferenceURIs(did) + localURIs, err = s.db.GetPreferenceURIs(ctx, did) localURIs = filterURIsByCollection(localURIs, xrpc.CollectionPreferences) case xrpc.CollectionSembleCollectionLink: - items, e := s.db.GetCollectionItemsByAuthor(did) + items, e := s.db.GetCollectionItemsByAuthor(ctx, did) if e == nil { for _, item := range items { localURIs = append(localURIs, item.URI) @@ -210,7 +210,7 @@ func (s *Service) PerformSync(ctx context.Context, did string, getClient func(co err = e } case xrpc.CollectionLichenBookmark: - localURIs, err = s.db.GetBookmarkURIs(did) + localURIs, err = s.db.GetBookmarkURIs(ctx, did) localURIs = filterURIsByCollection(localURIs, xrpc.CollectionLichenBookmark) } @@ -219,32 +219,32 @@ func (s *Service) PerformSync(ctx context.Context, did string, getClient func(co if !fetchedURIs[uri] { switch collectionNSID { case xrpc.CollectionAnnotation: - _ = s.db.DeleteAnnotation(uri) + _ = s.db.DeleteAnnotation(ctx, uri) case xrpc.CollectionHighlight: - _ = s.db.DeleteHighlight(uri) + _ = s.db.DeleteHighlight(ctx, uri) case xrpc.CollectionBookmark: - _ = s.db.DeleteBookmark(uri) + _ = s.db.DeleteBookmark(ctx, uri) case xrpc.CollectionCollection: - _ = s.db.DeleteCollection(uri) + _ = s.db.DeleteCollection(ctx, uri) case xrpc.CollectionCollectionItem: - _ = s.db.RemoveFromCollection(uri) + _ = s.db.RemoveFromCollection(ctx, uri) case xrpc.CollectionReply: - _ = s.db.DeleteReply(uri) + _ = s.db.DeleteReply(ctx, uri) case xrpc.CollectionLike: - _ = s.db.DeleteLike(uri) + _ = s.db.DeleteLike(ctx, uri) case xrpc.CollectionSembleCard: - _ = s.db.DeleteAnnotation(uri) - _ = s.db.DeleteBookmark(uri) + _ = s.db.DeleteAnnotation(ctx, uri) + _ = s.db.DeleteBookmark(ctx, uri) case xrpc.CollectionSembleCollection: - _ = s.db.DeleteCollection(uri) + _ = s.db.DeleteCollection(ctx, uri) case xrpc.CollectionSembleCollectionLink: - _ = s.db.RemoveFromCollection(uri) + _ = s.db.RemoveFromCollection(ctx, uri) case xrpc.CollectionAPIKey: - _ = s.db.DeleteAPIKeyByURI(uri) + _ = s.db.DeleteAPIKeyByURI(ctx, uri) case xrpc.CollectionPreferences: - _ = s.db.DeletePreferences(uri) + _ = s.db.DeletePreferences(ctx, uri) case xrpc.CollectionLichenBookmark: - _ = s.db.DeleteBookmark(uri) + _ = s.db.DeleteBookmark(ctx, uri) } deletedCount++ } @@ -331,7 +331,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM tagsJSONPtr = &tagsStr } - return s.db.CreateAnnotation(&db.Annotation{ + return s.db.CreateAnnotation(context.Background(), &db.Annotation{ URI: uri, AuthorDID: did, Motivation: motivation, @@ -383,7 +383,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM tagsJSONPtr = &tagsStr } - return s.db.CreateHighlight(&db.Highlight{ + return s.db.CreateHighlight(context.Background(), &db.Highlight{ URI: uri, AuthorDID: did, TargetSource: record.Target.Source, @@ -425,7 +425,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM tagsJSONPtr = &tagsStr } - return s.db.CreateBookmark(&db.Bookmark{ + return s.db.CreateBookmark(context.Background(), &db.Bookmark{ URI: uri, AuthorDID: did, Source: record.Source, @@ -455,7 +455,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM iconPtr = &i } - return s.db.CreateCollection(&db.Collection{ + return s.db.CreateCollection(context.Background(), &db.Collection{ URI: uri, AuthorDID: did, Name: record.Name, @@ -472,7 +472,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM } createdAt, _ := time.Parse(time.RFC3339, record.CreatedAt) - return s.db.AddToCollection(&db.CollectionItem{ + return s.db.AddToCollection(context.Background(), &db.CollectionItem{ URI: uri, AuthorDID: did, CollectionURI: record.Collection, @@ -495,7 +495,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM formatPtr = &f } - return s.db.CreateReply(&db.Reply{ + return s.db.CreateReply(context.Background(), &db.Reply{ URI: uri, AuthorDID: did, ParentURI: record.Parent.URI, @@ -514,7 +514,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM } createdAt, _ := time.Parse(time.RFC3339, record.CreatedAt) - return s.db.CreateLike(&db.Like{ + return s.db.CreateLike(context.Background(), &db.Like{ URI: uri, AuthorDID: did, SubjectURI: record.Subject.URI, @@ -551,7 +551,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM motivation := "commenting" bodyValue := note.Text - return s.db.CreateAnnotation(&db.Annotation{ + return s.db.CreateAnnotation(context.Background(), &db.Annotation{ URI: uri, AuthorDID: did, Motivation: motivation, @@ -581,7 +581,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM titlePtr = &t } - return s.db.CreateBookmark(&db.Bookmark{ + return s.db.CreateBookmark(context.Background(), &db.Bookmark{ URI: uri, AuthorDID: did, Source: source, @@ -608,7 +608,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM icon := "icon:semble" iconPtr = &icon - return s.db.CreateCollection(&db.Collection{ + return s.db.CreateCollection(context.Background(), &db.Collection{ URI: uri, AuthorDID: did, Name: record.Name, @@ -625,7 +625,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM } createdAt, _ := time.Parse(time.RFC3339, record.CreatedAt) - return s.db.AddToCollection(&db.CollectionItem{ + return s.db.AddToCollection(context.Background(), &db.CollectionItem{ URI: uri, AuthorDID: did, CollectionURI: record.Collection.URI, @@ -646,7 +646,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM } createdAt, _ := time.Parse(time.RFC3339, record.CreatedAt) - return s.db.CreateBookmark(&db.Bookmark{ + return s.db.CreateBookmark(context.Background(), &db.Bookmark{ URI: uri, AuthorDID: did, Source: source, @@ -666,7 +666,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM parts := strings.Split(uri, "/") rkey := parts[len(parts)-1] - return s.db.CreateAPIKey(&db.APIKey{ + return s.db.CreateAPIKey(context.Background(), &db.APIKey{ ID: rkey, OwnerDID: did, Name: record.Name, @@ -720,7 +720,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM if err := verification.VerifyDocument(canonicalURL, uri); err != nil { return nil } - return s.db.UpsertDocument(&db.Document{ + return s.db.UpsertDocument(context.Background(), &db.Document{ URI: uri, AuthorDID: did, Site: record.Site, @@ -762,7 +762,7 @@ func (s *Service) upsertRecord(did, collection, uri, cid string, value json.RawM labelPrefsPtr = &s } - return s.db.UpsertPreferences(&db.Preferences{ + return s.db.UpsertPreferences(context.Background(), &db.Preferences{ URI: uri, AuthorDID: did, ExternalLinkSkippedHostnames: skippedHostnamesPtr, diff --git a/backend/sqlc.yaml b/backend/sqlc.yaml new file mode 100644 index 0000000..cca2479 --- /dev/null +++ b/backend/sqlc.yaml @@ -0,0 +1,28 @@ +version: "2" +sql: + - engine: postgresql + queries: "internal/db/sqlc_queries" + schema: "internal/db/migrations" + gen: + go: + package: "sqlcdb" + out: "internal/db/sqlcdb" + sql_package: "pgx/v5" + emit_interface: true + emit_json_tags: true + json_tags_case_style: "camel" + overrides: + - db_type: "pg_catalog.timestamp" + go_type: "time.Time" + nullable: false + - db_type: "pg_catalog.timestamp" + go_type: + type: "Time" + import: "time" + pointer: true + nullable: true + - db_type: "text" + go_type: + type: "string" + pointer: true + nullable: true diff --git a/web/astro.config.mjs b/web/astro.config.mjs index 400691a..26d2437 100644 --- a/web/astro.config.mjs +++ b/web/astro.config.mjs @@ -16,9 +16,11 @@ function i18nResourcesPlugin() { const resolvedId = "\0" + virtualId; return { name: "i18n-resources", + /** @param {string} id */ resolveId(id) { if (id === virtualId) return resolvedId; }, + /** @param {string} id */ load(id) { if (id !== resolvedId) return; const localesDir = join(__dirname, "public/locales"); @@ -46,9 +48,11 @@ function i18nLanguagesPlugin() { const resolvedId = "\0" + virtualId; return { name: "i18n-languages", + /** @param {string} id */ resolveId(id) { if (id === virtualId) return resolvedId; }, + /** @param {string} id */ load(id) { if (id !== resolvedId) return; const localesDir = join(__dirname, "public/locales"); diff --git a/web/src/lib/analytics.ts b/web/src/lib/analytics.ts index a9b3d05..278072e 100644 --- a/web/src/lib/analytics.ts +++ b/web/src/lib/analytics.ts @@ -31,12 +31,13 @@ export type AnalyticsEvents = { }; item_deleted: { type: "annotation" | "highlight" | "bookmark" }; item_shared: { - method: "copy" | "bluesky" | "twitter" | "mastodon" | "email"; + method: "copy_link" | "social_app"; + destination?: string; item_type?: string; }; item_added_to_collection: Record; - collection_created: { name: string }; + collection_created: { has_description: boolean }; collection_deleted: Record; extension_installed: { version: string; browser: string }; diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index db8141f..6f22cdb 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -11,21 +11,10 @@ function serverFetch(path: string, cookie?: string): Promise { return fetch(`${API_URL}${path}`, { headers }); } -const sessionCache = new Map(); - -export function clearSessionCacheForCookie(cookie: string) { - const cacheKey = cookie.match(/margin_session=([^;]+)/)?.[1] || ""; - if (cacheKey) sessionCache.delete(cacheKey); -} +export function clearSessionCacheForCookie(_cookie: string) {} export async function getSession(cookie: string): Promise { try { - const cacheKey = cookie.match(/margin_session=([^;]+)/)?.[1] || ""; - const cached = sessionCache.get(cacheKey); - if (cached && Date.now() < cached.expires) { - return cached.user; - } - const res = await serverFetch("/auth/session", cookie); if (!res.ok) return null; const data = await res.json(); @@ -44,19 +33,6 @@ export async function getSession(cookie: string): Promise { postsCount: data.postsCount, }; - if (cacheKey) { - sessionCache.set(cacheKey, { - user: profile, - expires: Date.now() + 5 * 60_000, - }); - if (sessionCache.size > 100) { - const now = Date.now(); - for (const [k, v] of sessionCache) { - if (now > v.expires) sessionCache.delete(k); - } - } - } - const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 3000); diff --git a/web/src/middleware.ts b/web/src/middleware.ts index 28c9b11..0ac5b0e 100644 --- a/web/src/middleware.ts +++ b/web/src/middleware.ts @@ -92,14 +92,9 @@ async function proxyToBackend(request: Request, url: URL): Promise { const target = new URL(url.pathname + url.search, API_URL); const headers = new Headers(request.headers); - const host = headers.get("host"); headers.delete("host"); headers.delete("origin"); headers.delete("referer"); - if (host) { - headers.set("X-Forwarded-Host", host); - headers.set("X-Forwarded-Proto", url.protocol.replace(":", "")); - } const init: RequestInit = { method: request.method,