diff options
| author | Adam Soltys <[email protected]> | 2025-01-14 15:50:49 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2025-01-14 15:50:49 -0800 |
| commit | b21b536817449b88c028a6cbe8ca6f21f4abcb46 (patch) | |
| tree | eb174dc4b27ebf61f654143fff18adb2ca8bc801 | |
| parent | 6560cc552aa8fecff14292bfa31e8369c42c2c73 (diff) | |
| download | coinos-server-b21b536817449b88c028a6cbe8ca6f21f4abcb46.tar.gz coinos-server-b21b536817449b88c028a6cbe8ca6f21f4abcb46.zip | |
use strfry scan
| -rwxr-xr-x | bun.lockb | bin | 209041 -> 209423 bytes | |||
| -rw-r--r-- | index.ts | 2 | ||||
| -rw-r--r-- | lib/nostr.ts | 57 | ||||
| -rw-r--r-- | lib/strfry.ts | 16 | ||||
| -rw-r--r-- | lib/utils.ts | 22 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | routes/nostr.ts | 121 | ||||
| -rw-r--r-- | routes/users.ts | 70 | ||||
| -rwxr-xr-x | server | bin | 0 -> 16960 bytes | |||
| -rw-r--r-- | server.c | 2 |
10 files changed, 118 insertions, 173 deletions
| Binary files differ @@ -1,5 +1,3 @@ -Error.stackTraceLimit = Infinity; - import app from "$lib/app"; import { admin, auth, optional } from "$lib/auth"; diff --git a/lib/nostr.ts b/lib/nostr.ts index f3befa03..5ef8af3d 100644 --- a/lib/nostr.ts +++ b/lib/nostr.ts @@ -1,6 +1,7 @@ import config from "$config"; import { g, s } from "$lib/db"; import { l } from "$lib/logging"; +import { count, scan } from "$lib/strfry"; import { fail } from "$lib/utils"; import { finalizeEvent, getPublicKey, nip19 } from "nostr-tools"; import { AbstractSimplePool } from "nostr-tools/abstract-pool"; @@ -18,7 +19,7 @@ export const pool = new AbstractSimplePool({ verifyEvent: alwaysTrue }); const opts = { maxWait: 2000 }; export const anon = (pubkey) => ({ - name: pubkey.substr(0, 6), + username: pubkey.substr(0, 6), pubkey, anon: true, follows: [], @@ -101,15 +102,16 @@ export async function handleZap(invoice) { export const getRelays = async (pubkey): Promise<any> => { const { relays } = config; const filter = { authors: [pubkey], kinds: [10002] }; - const event = await pool.get([config.nostr], filter, opts); + const result = await scan(filter); let read = relays; let write = relays; - if (event) { + if (result.length) { + const { tags } = result[0]; read = []; write = []; - for (const r of event.tags) { + for (const r of tags) { if (r[0] !== "r") continue; if (!r[2] || r[2] === "write") write.push(r[1]); if (!r[2] || r[2] === "read") read.push(r[1]); @@ -157,44 +159,15 @@ export const getProfile = async (pubkey, relays = [config.nostr]) => { }; export const getCount = async (pubkey) => { - let follows = await g(`${pubkey}:follows:n`); - let followers = await g(`${pubkey}:followers:n`); - - if (follows === null) { - const filter: any = { kinds: [3], authors: [pubkey] }; - let ev = await pool.get([config.nostr], filter, opts); - - if (!ev) { - let { relays } = config; - ({ write: relays } = await getRelays(pubkey)); - ev = await pool.get(relays, filter, opts); - send(ev); - } - - if (ev) { - follows = ev.tags - .map((t) => t[0] === "p" && t[1]) - .filter((p) => p && p.length === 64).length; - } - - follows ||= 0; - await s(`${pubkey}:follows:n`, follows); - } - - if (followers === null) { - const filter: any = { cache: ["user_infos", { pubkeys: [pubkey] }] }; - const infos = await pool.querySync([config.cache], filter, opts); - - const f = infos.find((e) => e.kind === 10000133); - let counts = {}; - if (f?.content) { - counts = JSON.parse(f.content); - followers = counts[pubkey]; - } + try { + const result = await scan({ authors: [pubkey], kinds: [3] }); + const follows = result.length + ? result[0].tags.filter((t) => t[0] === "p").length + : 0; - followers ||= 0; - await s(`${pubkey}:followers:n`, followers); + const [followers] = await count({ "#p": [pubkey], kinds: [3] }); + return { follows, followers }; + } catch (e) { + console.log(e); } - - return { follows, followers }; }; diff --git a/lib/strfry.ts b/lib/strfry.ts index 9caa539e..231e3533 100644 --- a/lib/strfry.ts +++ b/lib/strfry.ts @@ -1,5 +1,5 @@ -import { createServer, connect } from "net"; import { randomUUID } from "crypto"; +import { connect, createServer } from "net"; const exec = async (cmd) => new Promise((resolve, reject) => { @@ -13,7 +13,6 @@ const exec = async (cmd) => const results = []; socket.on("data", (data) => { resultBuffer += data.toString(); - const parts = resultBuffer.split("\n"); for (let i = 0; i < parts.length - 1; i++) { @@ -44,16 +43,17 @@ const exec = async (cmd) => controlClient.end(); }); - controlClient.on("error", (e) => - reject(`Control socket error: ${e.message}`), - ); + controlClient.on("error", (e) => { + reject(`Control socket error: ${e.message}`); + }); }); - resultServer.on("error", (e) => - reject(`Result server error: ${e.message}`), - ); + resultServer.on("error", (e) => { + reject(`Result server error: ${e.message}`); + }); }); +export const count = (f) => exec(`scan --count '${JSON.stringify(f)}'\n`); export const scan = (f) => exec(`scan '${JSON.stringify(f)}'\n`); export const sync = (r, f) => exec(`sync ${r} --dir down --filter '${JSON.stringify(f)}'\n`); diff --git a/lib/utils.ts b/lib/utils.ts index 7dc6b9ef..9cb6a690 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -2,6 +2,7 @@ import config from "$config"; import { g, s } from "$lib/db"; import locales from "$lib/locales/index"; import migrate from "$lib/migrate"; +import whitelist from "$lib/whitelist"; import { bytesToHex, randomBytes } from "@noble/hashes/utils"; import { getPublicKey } from "nostr"; @@ -68,7 +69,7 @@ export const fields = [ "banner", ]; -export const getUser = async (username) => { +export const getUser = async (username, fields = whitelist) => { let update; if (username === "undefined") fail("invalid user"); const user = await migrate(username); @@ -206,3 +207,22 @@ export const fmt = (sats) => )}`; export const link = (id) => `${URL}/payment/${id}`; + +export const publicFields = [ + "about", + "anon", + "banner", + "banner", + "currency", + "display", + "id", + "hidepay", + "lud16", + "memoPrompt", + "npub", + "picture", + "prompt", + "pubkey", + "username", + "website", +]; diff --git a/package.json b/package.json index 115aa6e7..4a726347 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "readable-stream-clone": "^0.0.7", "redis": "^4.6.10", "sharp": "^0.33.5", + "strfry-rpc": "^0.0.1", "uuid": "8", "web-push": "^3.6.7", "ws": "^7.4.6" diff --git a/routes/nostr.ts b/routes/nostr.ts index b909f1ab..6c252927 100644 --- a/routes/nostr.ts +++ b/routes/nostr.ts @@ -9,7 +9,8 @@ import { send, serverPubkey, } from "$lib/nostr"; -import { bail, fail, getUser } from "$lib/utils"; +import { count, scan } from "$lib/strfry"; +import { bail, fail, getUser, publicFields } from "$lib/utils"; import { decode } from "nostr-tools/nip19"; import type { ProfilePointer } from "nostr-tools/nip19"; @@ -59,65 +60,41 @@ export default { async follows(req, res) { const { params: { pubkey }, - query: { pubkeysOnly, nocache }, + query: { limit = 20, offset = 0, pubkeysOnly }, } = req; try { - let created_at; - let follows = []; - - let pubkeys = await g(`${pubkey}:pubkeys`); - if (!pubkeys || !pubkeysOnly || nocache) { - let filter = { kinds: [3], authors: [pubkey] }; - let ev = await pool.get([config.nostr], filter, opts); - - if (!ev) { - let { relays } = config; - ({ write: relays } = await getRelays(pubkey)); - - filter = { kinds: [3], authors: [pubkey] }; - ev = await pool.get(relays, filter, opts); - send(ev); - } + const events = await scan({ authors: [pubkey], kinds: [3] }); + if (!events.length) return res.send([]); - let tags = []; + const event = events[0]; + const pubkeys = event.tags + .filter((tag) => tag[0] === "p") + .map((tag) => tag[1]) + .slice(offset, limit); - if (ev) ({ created_at, tags } = ev); - pubkeys = tags - .map((t) => t[0] === "p" && t[1]) - .filter((p) => p && p.length === 64); - - await s(`${pubkey}:pubkeys`, pubkeys); - } - if (!pubkeys.length || pubkeysOnly) return res.send(pubkeys); - - const cache = await g(`${pubkey}:follows`); - if (cache && cache.t >= created_at) ({ follows } = cache); + let follows = []; + if (pubkeysOnly) follows = pubkeys; else { - const filter: any = { cache: ["user_infos", { pubkeys }] }; - const infos = await pool.querySync([config.cache], filter, opts); - const profiles = infos.filter((e) => e.kind === 0); - - const f = infos.find((e) => e.kind === 10000133); - let counts = {}; - if (f) counts = JSON.parse(f.content); - + const profiles = await scan({ authors: pubkeys, kinds: [0] }); for (const p of profiles) { const { content, pubkey } = p; - const user = JSON.parse(content); - user.count = counts[pubkey]; + let user = await getUser(pubkey, publicFields); + user = { ...user, ...JSON.parse(content) }; user.pubkey = pubkey; follows.push(user); } - const followKeys = follows.map((f) => f.pubkey); - const missing = pubkeys - .filter((p) => !followKeys.includes(p)) - .map((pubkey) => anon(pubkey)); + const keys = follows.map((f) => f.pubkey); + const missing = await Promise.all( + pubkeys + .filter((p) => !keys.includes(p)) + .map( + async (pubkey) => + (await getUser(pubkey, publicFields)) || anon(pubkey), + ), + ); - follows.sort((a: any, b: any) => b.count - a.count); follows.push(...missing); - - s(`${pubkey}:follows`, { follows, t: created_at }); } res.send(follows); @@ -130,38 +107,36 @@ export default { async followers(req, res) { const { params: { pubkey }, + query: { limit = 20, offset = 0 }, } = req; try { - let filter: any = { kinds: [3], "#p": [pubkey] }; - const ev = await pool.get([config.nostr], filter, opts); - let created_at; - if (ev) ({ created_at } = ev); + const filter = { kinds: [3], "#p": [pubkey] }; + const events = await scan({ ...filter, limit }); + if (!events.length) return res.send([]); - let followers = []; - const cache = await g(`${pubkey}:followers`); - if (cache && cache.t >= created_at) ({ followers } = cache); - else { - filter = { cache: ["user_followers", { pubkey }] }; - const data = await pool.querySync([config.cache], filter, opts); - for (const ev of data.filter((f) => f.kind === 0)) { - const { content, pubkey } = ev; - const user = JSON.parse(content); - user.pubkey = pubkey; - followers.push(user); - } + const pubkeys = events.map((e) => e.pubkey).slice(offset, limit); - const pubkeys = followers.map((f) => f.pubkey); - filter = { cache: ["user_infos", { pubkeys }] }; - const infos = await pool.querySync([config.cache], filter, opts); + const followers = []; + const profiles = await scan({ authors: pubkeys, kinds: [0] }); + for (const p of profiles) { + const { content, pubkey } = p; + let user = await getUser(pubkey, publicFields); + user = { ...user, ...JSON.parse(content) }; + user.pubkey = pubkey; + followers.push(user); + } - let counts = {}; - const f = infos.find((e) => e.kind === 10000133); - if (f) counts = JSON.parse(f.content); - followers.map((f) => (f.count = counts[f.pubkey])); - followers.sort((a: any, b: any) => b.count - a.count); + const keys = followers.map((f) => f.pubkey); + const missing = await Promise.all( + pubkeys + .filter((p) => !keys.includes(p)) + .map( + async (pubkey) => + (await getUser(pubkey, publicFields)) || anon(pubkey), + ), + ); - await s(`${pubkey}:followers`, { followers, t: created_at }); - } + followers.push(...missing); res.send(followers); } catch (e) { diff --git a/routes/users.ts b/routes/users.ts index af5794d5..dc3636e8 100644 --- a/routes/users.ts +++ b/routes/users.ts @@ -10,7 +10,7 @@ import { reconcile, types } from "$lib/payments"; import register from "$lib/register"; import { emit } from "$lib/sockets"; import upload from "$lib/upload"; -import { bail, fail, getUser, pick } from "$lib/utils"; +import { bail, fail, publicFields, getUser, pick } from "$lib/utils"; import whitelist from "$lib/whitelist"; import rpc from "@coinos/rpc"; import { bytesToHex, randomBytes } from "@noble/hashes/utils"; @@ -114,29 +114,10 @@ export default { if (!user) return res.code(500).send("User not found"); - const whitelist = [ - "about", - "anon", - "banner", - "banner", - "currency", - "display", - "id", - "hidepay", - "lud16", - "memoPrompt", - "npub", - "picture", - "prompt", - "pubkey", - "username", - "website", - ]; - if (user.pubkey) user.npub = nip19.npubEncode(user.pubkey); user.prompt = !!user.prompt; - res.send(pick(user, whitelist)); + res.send(pick(user, publicFields)); } catch (e) { err("problem getting user", e.message); res.code(500).send(e.message); @@ -213,9 +194,17 @@ export default { l("updating user", user.username); const { confirm, password, pin, newpin, username } = body; + let exists; let { pubkey } = body; if (pubkey) { + exists = await getUser(pubkey); + if (exists && ![username].includes(exists.username)) { + warn("key in use", pubkey, exists.username); + if (exists.anon) await db.del(`user:${pubkey}`); + else fail("Key in use by another account"); + } + const { challenge } = body; const event = JSON.parse(body.event); const c = await g(`challenge:${challenge}`); @@ -241,32 +230,21 @@ export default { user.pin = newpin; if (user.pin === "delete") user.pin = undefined; - let exists; - if (username) exists = await getUser(username); - - let token; - let oldname; - if ( - username && - user.username.toLowerCase() !== username.toLowerCase() && - exists - ) { - err("username taken", username, user.username, exists.username); - fail("Username taken"); - } else if (username) { - if (user.username.toLowerCase() !== username.toLowerCase()) - l("changing username", user.username, username); + if (username) { + let oldname; + exists = await getUser(username); - await db.del(`user:${user.username}`); - oldname = user.username; - user.username = username; - } + if (user.username.toLowerCase() !== username.toLowerCase() && exists) { + err("username taken", username, user.username, exists.username); + fail("Username taken"); + } else if (username) { + if (user.username.toLowerCase() !== username.toLowerCase()) + l("changing username", user.username, username); - if (pubkey) exists = await getUser(pubkey); - if (exists && ![oldname, username].includes(exists.username)) { - warn("key in use", pubkey, exists.username); - if (exists.anon) await db.del(`user:${pubkey}`); - else fail("Key in use by another account"); + await db.del(`user:${user.username}`); + oldname = user.username; + user.username = username; + } } const attributes = [ @@ -319,7 +297,7 @@ export default { if (user.nip5) await db.sAdd("nip5", `${user.username}:${user.pubkey}`); emit(user.id, "user", user); - res.send({ user, token }); + res.send({ user }); } catch (e) { warn("failed to update", user.username, e.message); bail(res, e.message); Binary files differ@@ -6,7 +6,7 @@ #include <unistd.h> #define SOCKET_PATH "/sockets/ctrl" -#define BUFFER_SIZE 1024 +#define BUFFER_SIZE 262144 void handle_client(const char *result_socket, const char *command); |
