+27
-5
cmd/aturilist/client/client.go
+27
-5
cmd/aturilist/client/client.go
···
13
13
14
14
// Constants for the XRPC methods
15
15
const (
16
-
MethodListRecords = "app.reddwarf.aturilist.listRecords"
17
-
MethodCountRecords = "app.reddwarf.aturilist.countRecords"
18
-
MethodIndexRecord = "app.reddwarf.aturilist.indexRecord"
19
-
MethodValidateRecord = "app.reddwarf.aturilist.validateRecord"
20
-
DefaultProductionHost = "https://aturilist.reddwarf.app"
16
+
MethodListRecords = "app.reddwarf.aturilist.listRecords"
17
+
MethodCountRecords = "app.reddwarf.aturilist.countRecords"
18
+
MethodIndexRecord = "app.reddwarf.aturilist.indexRecord"
19
+
MethodValidateRecord = "app.reddwarf.aturilist.validateRecord"
20
+
MethodQueryCollectionRkey = "app.reddwarf.aturilist.queryCollectionRkey"
21
+
DefaultProductionHost = "https://aturilist.reddwarf.app"
21
22
)
22
23
23
24
// Client is the API client for the Red Dwarf AtURI List Service.
···
53
54
Repo string `json:"repo"`
54
55
Collection string `json:"collection"`
55
56
Count int `json:"count"`
57
+
}
58
+
59
+
type QueryCollectionRkeyResponse struct {
60
+
Collection string `json:"collection"`
61
+
RKey string `json:"rkey"`
62
+
DIDs []string `json:"dids"`
63
+
Count int `json:"count"`
56
64
}
57
65
58
66
type ErrorResponse struct {
···
138
146
}
139
147
140
148
return true, nil
149
+
}
150
+
151
+
// QueryCollectionRkey returns a list of DIDs that have a specific collection and rkey pair.
152
+
func (c *Client) QueryCollectionRkey(ctx context.Context, collection, rkey string) (*QueryCollectionRkeyResponse, error) {
153
+
params := url.Values{}
154
+
params.Set("collection", collection)
155
+
params.Set("rkey", rkey)
156
+
157
+
var resp QueryCollectionRkeyResponse
158
+
if err := c.doRequest(ctx, http.MethodGet, MethodQueryCollectionRkey, params, nil, &resp); err != nil {
159
+
return nil, err
160
+
}
161
+
162
+
return &resp, nil
141
163
}
142
164
143
165
// --- Internal Helpers ---
+123
-2
cmd/aturilist/main.go
+123
-2
cmd/aturilist/main.go
···
2
2
3
3
import (
4
4
"context"
5
+
"encoding/json"
5
6
"errors"
6
7
"flag"
7
8
"fmt"
···
141
142
142
143
router.POST("/xrpc/app.reddwarf.aturilist.validateRecord", srv.handleValidateRecord)
143
144
145
+
router.GET("/xrpc/app.reddwarf.aturilist.queryCollectionRkey", srv.handleQueryCollectionRkey)
146
+
144
147
// router.GET("/xrpc/app.reddwarf.aturilist.requestBackfill", )
145
148
146
149
router.Run(":7155")
···
174
177
return parts[0], parts[1], parts[2], nil
175
178
}
176
179
180
+
func makeCollectionRkeyKey(collection, rkey string) []byte {
181
+
return []byte(fmt.Sprintf("cr|%s|%s|", collection, rkey))
182
+
}
183
+
184
+
func parseCollectionRkeyKey(key []byte) (collection, rkey string, err error) {
185
+
parts := strings.Split(string(key), "|")
186
+
if len(parts) < 3 || parts[0] != "cr" {
187
+
return "", "", errors.New("invalid collection+rkey key format")
188
+
}
189
+
return parts[1], parts[2], nil
190
+
}
191
+
177
192
func (s *Server) processRecord(repo, collection, rkey string, isDelete bool) {
178
193
key := makeKey(repo, collection, rkey)
194
+
crKey := makeCollectionRkeyKey(collection, rkey)
179
195
180
196
err := s.db.Update(func(txn *badger.Txn) error {
181
197
if isDelete {
182
-
return txn.Delete(key)
198
+
if err := txn.Delete(key); err != nil {
199
+
return err
200
+
}
201
+
return s.removeDidFromCollectionRkeyIndex(txn, crKey, repo)
183
202
}
184
-
return txn.Set(key, []byte(time.Now().Format(time.RFC3339)))
203
+
if err := txn.Set(key, []byte(time.Now().Format(time.RFC3339))); err != nil {
204
+
return err
205
+
}
206
+
return s.addDidToCollectionRkeyIndex(txn, crKey, repo)
185
207
})
186
208
187
209
if err != nil {
···
189
211
}
190
212
}
191
213
214
+
func (s *Server) addDidToCollectionRkeyIndex(txn *badger.Txn, crKey []byte, did string) error {
215
+
item, err := txn.Get(crKey)
216
+
if err == badger.ErrKeyNotFound {
217
+
var dids []string
218
+
dids = append(dids, did)
219
+
didsJSON, _ := json.Marshal(dids)
220
+
return txn.Set(crKey, didsJSON)
221
+
} else if err != nil {
222
+
return err
223
+
}
224
+
225
+
var dids []string
226
+
err = item.Value(func(val []byte) error {
227
+
return json.Unmarshal(val, &dids)
228
+
})
229
+
if err != nil {
230
+
return err
231
+
}
232
+
233
+
for _, existingDid := range dids {
234
+
if existingDid == did {
235
+
return nil
236
+
}
237
+
}
238
+
239
+
dids = append(dids, did)
240
+
didsJSON, _ := json.Marshal(dids)
241
+
return txn.Set(crKey, didsJSON)
242
+
}
243
+
244
+
func (s *Server) removeDidFromCollectionRkeyIndex(txn *badger.Txn, crKey []byte, did string) error {
245
+
item, err := txn.Get(crKey)
246
+
if err == badger.ErrKeyNotFound {
247
+
return nil
248
+
} else if err != nil {
249
+
return err
250
+
}
251
+
252
+
var dids []string
253
+
err = item.Value(func(val []byte) error {
254
+
return json.Unmarshal(val, &dids)
255
+
})
256
+
if err != nil {
257
+
return err
258
+
}
259
+
260
+
var newDids []string
261
+
for _, existingDid := range dids {
262
+
if existingDid != did {
263
+
newDids = append(newDids, existingDid)
264
+
}
265
+
}
266
+
267
+
if len(newDids) == 0 {
268
+
return txn.Delete(crKey)
269
+
}
270
+
271
+
didsJSON, _ := json.Marshal(newDids)
272
+
return txn.Set(crKey, didsJSON)
273
+
}
274
+
192
275
func (s *Server) handleListRecords(c *gin.Context) {
193
276
repo := c.Query("repo")
194
277
collection := c.Query("collection")
···
375
458
c.Status(404)
376
459
}
377
460
}
461
+
462
+
func (s *Server) handleQueryCollectionRkey(c *gin.Context) {
463
+
collection := c.Query("collection")
464
+
rkey := c.Query("rkey")
465
+
466
+
if collection == "" || rkey == "" {
467
+
c.JSON(400, gin.H{"error": "collection and rkey required"})
468
+
return
469
+
}
470
+
471
+
crKey := makeCollectionRkeyKey(collection, rkey)
472
+
var dids []string
473
+
474
+
err := s.db.View(func(txn *badger.Txn) error {
475
+
item, err := txn.Get(crKey)
476
+
if err == badger.ErrKeyNotFound {
477
+
return nil
478
+
} else if err != nil {
479
+
return err
480
+
}
481
+
482
+
return item.Value(func(val []byte) error {
483
+
return json.Unmarshal(val, &dids)
484
+
})
485
+
})
486
+
487
+
if err != nil {
488
+
c.JSON(500, gin.H{"error": err.Error()})
489
+
return
490
+
}
491
+
492
+
c.JSON(200, gin.H{
493
+
"collection": collection,
494
+
"rkey": rkey,
495
+
"dids": dids,
496
+
"count": len(dids),
497
+
})
498
+
}