feat(curating): add scanpubkeys API method to retroactively populate unclassified users
- Add ScanAllPubkeys method to scan SerialPubkey index for all pubkeys - Count events for each pubkey using the Pubkey index - Store event counts in CURATING_ACL_EVENT_COUNT_ prefix - Add NIP-86 "scanpubkeys" API endpoint to trigger the scan This allows the curation UI to show all existing users in the unclassified list, even if they had events before curating mode was enabled. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -143,6 +143,8 @@ func (s *Server) handleCuratingNIP86Method(request NIP86Request, curatingACL *ac
|
||||
return s.handleUnblockCuratingIP(request.Params, dbACL)
|
||||
case "isconfigured":
|
||||
return s.handleIsConfigured(dbACL)
|
||||
case "scanpubkeys":
|
||||
return s.handleScanPubkeys(dbACL)
|
||||
default:
|
||||
return NIP86Response{Error: "Unknown method: " + request.Method}
|
||||
}
|
||||
@@ -167,6 +169,7 @@ func (s *Server) handleCuratingSupportedMethods() NIP86Response {
|
||||
"listblockedips",
|
||||
"unblockip",
|
||||
"isconfigured",
|
||||
"scanpubkeys",
|
||||
}
|
||||
return NIP86Response{Result: methods}
|
||||
}
|
||||
@@ -606,3 +609,18 @@ func parseRange(s string, parts []int) (int, error) {
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// handleScanPubkeys scans the database for all pubkeys and populates event counts
|
||||
// This is used to retroactively populate the unclassified users list
|
||||
func (s *Server) handleScanPubkeys(dbACL *database.CuratingACL) NIP86Response {
|
||||
result, err := dbACL.ScanAllPubkeys()
|
||||
if chk.E(err) {
|
||||
return NIP86Response{Error: "Failed to scan pubkeys: " + err.Error()}
|
||||
}
|
||||
|
||||
return NIP86Response{Result: map[string]interface{}{
|
||||
"total_pubkeys": result.TotalPubkeys,
|
||||
"total_events": result.TotalEvents,
|
||||
"skipped": result.Skipped,
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -990,3 +990,140 @@ func kindInCategory(kind int, category string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ==================== Database Scanning ====================
|
||||
|
||||
// ScanResult contains the results of scanning all pubkeys in the database
|
||||
type ScanResult struct {
|
||||
TotalPubkeys int `json:"total_pubkeys"`
|
||||
TotalEvents int `json:"total_events"`
|
||||
Skipped int `json:"skipped"` // Trusted/blacklisted users skipped
|
||||
}
|
||||
|
||||
// ScanAllPubkeys scans the database to find all unique pubkeys and count their events.
|
||||
// This populates the event count data needed for the unclassified users list.
|
||||
// It uses the SerialPubkey index to find all pubkeys, then counts events for each.
|
||||
func (c *CuratingACL) ScanAllPubkeys() (*ScanResult, error) {
|
||||
result := &ScanResult{}
|
||||
|
||||
// First, get all trusted and blacklisted pubkeys to skip
|
||||
trusted, err := c.ListTrustedPubkeys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blacklisted, err := c.ListBlacklistedPubkeys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
excludeSet := make(map[string]struct{})
|
||||
for _, t := range trusted {
|
||||
excludeSet[t.Pubkey] = struct{}{}
|
||||
}
|
||||
for _, b := range blacklisted {
|
||||
excludeSet[b.Pubkey] = struct{}{}
|
||||
}
|
||||
|
||||
// Scan the SerialPubkey index to get all pubkeys
|
||||
pubkeys := make(map[string]struct{})
|
||||
|
||||
err = c.View(func(txn *badger.Txn) error {
|
||||
// SerialPubkey prefix is "spk"
|
||||
prefix := []byte("spk")
|
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: prefix})
|
||||
defer it.Close()
|
||||
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
// The value contains the 32-byte pubkey
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(val) == 32 {
|
||||
// Convert to hex
|
||||
pubkeyHex := fmt.Sprintf("%x", val)
|
||||
pubkeys[pubkeyHex] = struct{}{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.TotalPubkeys = len(pubkeys)
|
||||
|
||||
// For each pubkey, count events and store the count
|
||||
today := time.Now().Format("2006-01-02")
|
||||
|
||||
for pubkeyHex := range pubkeys {
|
||||
// Skip if trusted or blacklisted
|
||||
if _, excluded := excludeSet[pubkeyHex]; excluded {
|
||||
result.Skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
// Count events for this pubkey using the Pubkey index
|
||||
count, err := c.countEventsForPubkey(pubkeyHex)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
result.TotalEvents += count
|
||||
|
||||
// Store the event count
|
||||
ec := PubkeyEventCount{
|
||||
Pubkey: pubkeyHex,
|
||||
Date: today,
|
||||
Count: count,
|
||||
LastEvent: time.Now(),
|
||||
}
|
||||
|
||||
err = c.Update(func(txn *badger.Txn) error {
|
||||
key := c.getEventCountKey(pubkeyHex, today)
|
||||
data, err := json.Marshal(ec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txn.Set(key, data)
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// countEventsForPubkey counts events in the database for a given pubkey hex string
|
||||
func (c *CuratingACL) countEventsForPubkey(pubkeyHex string) (int, error) {
|
||||
count := 0
|
||||
|
||||
// Decode the pubkey hex to bytes
|
||||
pubkeyBytes := make([]byte, 32)
|
||||
for i := 0; i < 32 && i*2+1 < len(pubkeyHex); i++ {
|
||||
fmt.Sscanf(pubkeyHex[i*2:i*2+2], "%02x", &pubkeyBytes[i])
|
||||
}
|
||||
|
||||
// Scan the Pubkey index (prefix "pc-") for this pubkey
|
||||
err := c.View(func(txn *badger.Txn) error {
|
||||
// Build prefix: "pc-" + 8-byte pubkey hash
|
||||
// The pubkey hash is the first 8 bytes of the pubkey
|
||||
prefix := make([]byte, 3+8)
|
||||
copy(prefix[:3], []byte("pc-"))
|
||||
copy(prefix[3:], pubkeyBytes[:8])
|
||||
|
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: prefix})
|
||||
defer it.Close()
|
||||
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
count++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user