Monorepo for Tangled
tangled.org
1package db
2
3import (
4 "database/sql"
5 "log/slog"
6 "strconv"
7 "time"
8
9 "github.com/bluesky-social/indigo/atproto/syntax"
10 "tangled.org/core/api/tangled"
11)
12
13type PublicKey struct {
14 Did syntax.DID
15 Rkey syntax.RecordKey
16 tangled.PublicKey
17}
18
19func (d *DB) UpsertPublicKey(pk PublicKey) error {
20 tx, err := d.db.Begin()
21 if err != nil {
22 return err
23 }
24 defer tx.Rollback()
25
26 if pk.Rkey != "" {
27 if _, err := tx.Exec(`delete from public_keys where did = ? and rkey = ?`, pk.Did, pk.Rkey); err != nil {
28 return err
29 }
30 }
31
32 if err := insertPublicKey(tx, d.logger, pk); err != nil {
33 return err
34 }
35
36 return tx.Commit()
37}
38
39func insertPublicKey(tx *sql.Tx, logger *slog.Logger, pk PublicKey) error {
40 if pk.Key == "" {
41 logger.Warn("skipping public key with empty key value", "did", pk.Did, "rkey", pk.Rkey)
42 return nil
43 }
44
45 if pk.CreatedAt == "" {
46 pk.CreatedAt = time.Now().Format(time.RFC3339)
47 }
48
49 res, err := tx.Exec(
50 `insert or ignore into public_keys (did, key, rkey, created) values (?, ?, ?, ?)`,
51 pk.Did, pk.Key, pk.Rkey, pk.CreatedAt,
52 )
53 if err != nil {
54 return err
55 }
56
57 if rows, err := res.RowsAffected(); err == nil && rows == 0 {
58 logger.Warn("public key not stored, already registered to another did", "did", pk.Did, "rkey", pk.Rkey)
59 }
60
61 return nil
62}
63
64func (d *DB) DeletePublicKeyByRkey(did syntax.DID, rkey syntax.RecordKey) error {
65 if rkey == "" {
66 return nil
67 }
68
69 query := `delete from public_keys where did = ? and rkey = ?`
70 _, err := d.db.Exec(query, did, rkey)
71 return err
72}
73
74func (d *DB) ReplacePublicKeys(did syntax.DID, keys []PublicKey) error {
75 tx, err := d.db.Begin()
76 if err != nil {
77 return err
78 }
79 defer tx.Rollback()
80
81 if _, err := tx.Exec(`delete from public_keys where did = ?`, did); err != nil {
82 return err
83 }
84
85 if err := insertPublicKeys(tx, d.logger, keys); err != nil {
86 return err
87 }
88
89 return tx.Commit()
90}
91
92func insertPublicKeys(tx *sql.Tx, logger *slog.Logger, keys []PublicKey) error {
93 if len(keys) == 0 {
94 return nil
95 }
96
97 if err := insertPublicKey(tx, logger, keys[0]); err != nil {
98 return err
99 }
100
101 return insertPublicKeys(tx, logger, keys[1:])
102}
103
104func (pk *PublicKey) JSON() map[string]any {
105 return map[string]any{
106 "did": pk.Did,
107 "key": pk.Key,
108 "createdAt": pk.CreatedAt,
109 }
110}
111
112func (d *DB) GetAllPublicKeys() ([]PublicKey, error) {
113 var keys []PublicKey
114
115 rows, err := d.db.Query(`select key, did, created from public_keys`)
116 if err != nil {
117 return nil, err
118 }
119 defer rows.Close()
120
121 for rows.Next() {
122 var publicKey PublicKey
123 if err := rows.Scan(&publicKey.Key, &publicKey.Did, &publicKey.CreatedAt); err != nil {
124 return nil, err
125 }
126 keys = append(keys, publicKey)
127 }
128
129 if err := rows.Err(); err != nil {
130 return nil, err
131 }
132
133 return keys, nil
134}
135
136func (d *DB) GetPublicKeysPaginated(limit int, cursor string) ([]PublicKey, string, error) {
137 var keys []PublicKey
138
139 offset := 0
140 if cursor != "" {
141 if o, err := strconv.Atoi(cursor); err == nil && o >= 0 {
142 offset = o
143 }
144 }
145
146 query := `select key, did, created from public_keys order by created desc limit ? offset ?`
147 rows, err := d.db.Query(query, limit+1, offset) // +1 to check if there are more results
148 if err != nil {
149 return nil, "", err
150 }
151 defer rows.Close()
152
153 for rows.Next() {
154 var publicKey PublicKey
155 if err := rows.Scan(&publicKey.Key, &publicKey.Did, &publicKey.CreatedAt); err != nil {
156 return nil, "", err
157 }
158 keys = append(keys, publicKey)
159 }
160
161 if err := rows.Err(); err != nil {
162 return nil, "", err
163 }
164
165 // check if there are more results for pagination
166 var nextCursor string
167 if len(keys) > limit {
168 keys = keys[:limit] // remove the extra item
169 nextCursor = strconv.Itoa(offset + limit)
170 }
171
172 return keys, nextCursor, nil
173}