|
| 1 | +// MongoDB layer for the beacon. |
| 2 | +// |
| 3 | +// Schema matches nostr-beacon (for regression): each collection stores the |
| 4 | +// RAW Nostr event, keyed by `pubkey`, latest-wins by `created_at`. |
| 5 | +// kind 0 -> profiles (collection: beacon) |
| 6 | +// kind 3 -> social graph (collection: follows) |
| 7 | +// kind 10002 -> relay lists (collection: relay_lists) |
| 8 | +import { MongoClient } from 'mongodb'; |
| 9 | +import 'dotenv/config'; |
| 10 | + |
| 11 | +const uri = process.env.MONGODB_URI || process.env.MONGO_URL || 'mongodb://localhost:27017'; |
| 12 | +const dbName = process.env.MONGO_DB || process.env.DB_NAME || 'nostr'; |
| 13 | + |
| 14 | +// Collection names default to the live nostr-beacon values so the same Mongo |
| 15 | +// can be read/written interchangeably for regression. |
| 16 | +export const COLLECTIONS = { |
| 17 | + 0: process.env.MONGO_COLLECTION || 'beacon', |
| 18 | + 3: process.env.MONGO_FOLLOWS_COLLECTION || 'follows', |
| 19 | + 10002: process.env.MONGO_RELAYS_COLLECTION || 'relay_lists', |
| 20 | +}; |
| 21 | + |
| 22 | +let db, client; |
| 23 | + |
| 24 | +export async function connect() { |
| 25 | + if (db) return db; |
| 26 | + client = new MongoClient(uri); |
| 27 | + await client.connect(); |
| 28 | + db = client.db(dbName); |
| 29 | + // non-unique index for query/dedupe perf — does not change the doc shape |
| 30 | + for (const name of new Set(Object.values(COLLECTIONS))) { |
| 31 | + await db.collection(name).createIndex({ pubkey: 1 }); |
| 32 | + } |
| 33 | + return db; |
| 34 | +} |
| 35 | + |
| 36 | +export async function close() { |
| 37 | + if (client) await client.close(); |
| 38 | + db = client = undefined; |
| 39 | +} |
| 40 | + |
| 41 | +/** Latest-wins upsert of a raw event into the collection for its kind. */ |
| 42 | +export async function upsertEvent(event) { |
| 43 | + const name = COLLECTIONS[event.kind]; |
| 44 | + if (!name || !event.pubkey) return false; |
| 45 | + const col = (await connect()).collection(name); |
| 46 | + const existing = await col.findOne({ pubkey: event.pubkey }, { projection: { created_at: 1 } }); |
| 47 | + if (existing && existing.created_at >= event.created_at) return false; |
| 48 | + await col.updateOne({ pubkey: event.pubkey }, { $set: { ...event } }, { upsert: true }); |
| 49 | + return true; |
| 50 | +} |
| 51 | + |
| 52 | +const one = async (kind, pubkey) => (await connect()).collection(COLLECTIONS[kind]).findOne({ pubkey }); |
| 53 | +export const getProfile = (pubkey) => one(0, pubkey); |
| 54 | +export const getFollows = (pubkey) => one(3, pubkey); |
| 55 | +export const getRelays = (pubkey) => one(10002, pubkey); |
| 56 | + |
| 57 | +export async function recentProfiles(limit = 10) { |
| 58 | + return (await connect()).collection(COLLECTIONS[0]).find().sort({ created_at: -1 }).limit(limit).toArray(); |
| 59 | +} |
0 commit comments