collection of golang services under the Red Dwarf umbrella server.reddwarf.app
bluesky reddwarf microcosm appview
15
fork

Configure Feed

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

aturilist mattress experiment

+150 -7
+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
··· 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 + }