diff options
| author | Adam Soltys <[email protected]> | 2024-10-10 12:16:41 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-10-10 12:17:05 -0700 |
| commit | 0de0e3ba3719333f3884aeff26992adf1688c6ec (patch) | |
| tree | c0ed293eb942ca34c412f952cb20f049e4b50300 | |
| parent | d734c424f19bbec89274d828c3a2b70dd3ef221a (diff) | |
| download | coinos-server-0de0e3ba3719333f3884aeff26992adf1688c6ec.tar.gz coinos-server-0de0e3ba3719333f3884aeff26992adf1688c6ec.zip | |
2s maxwait
| -rw-r--r-- | index.ts | 2 | ||||
| -rw-r--r-- | lib/nostr.ts | 53 | ||||
| -rw-r--r-- | lib/utils.ts | 6 | ||||
| -rw-r--r-- | routes/nostr.ts | 108 | ||||
| -rw-r--r-- | routes/users.ts | 4 |
5 files changed, 134 insertions, 39 deletions
@@ -47,9 +47,11 @@ app.get("/invoice/:id", invoices.get); app.post("/invoice", optional, invoices.create); app.get("/nostr.json", nostr.identities); +app.get("/:pubkey/count", nostr.count); app.get("/:pubkey/followers", nostr.followers); app.get("/:pubkey/follows", nostr.follows); app.get("/event/:id", nostr.event); +app.post("/event", auth, nostr.publish); app.get("/info", payments.info); app.post("/payments", auth, payments.create); diff --git a/lib/nostr.ts b/lib/nostr.ts index e4d87b42..234312df 100644 --- a/lib/nostr.ts +++ b/lib/nostr.ts @@ -1,5 +1,5 @@ import config from "$config"; -import { db } from "$lib/db"; +import { g, s, db } from "$lib/db"; import { l } from "$lib/logging"; import { fail } from "$lib/utils"; import { nip19, finalizeEvent, getPublicKey } from "nostr-tools"; @@ -15,16 +15,13 @@ let alwaysTrue: any = (t: Event) => { return true; }; export let pool = new AbstractSimplePool({ verifyEvent: alwaysTrue }); +let opts = { maxWait: 2000 }; export async function send(ev, url = config.nostr) { if (!(ev && ev.id)) return; - try { - let r = await Relay.connect(url); - await r.publish(ev); - r.close(); - } catch (e) { - console.log("nostr send error", e); - } + let r = await Relay.connect(url); + await r.publish(ev); + r.close(); } export async function handleZap(invoice) { @@ -92,7 +89,7 @@ export async function handleZap(invoice) { export let getRelays = async (pubkey): Promise<any> => { let { relays } = config; let filter = { authors: [pubkey], kinds: [10002] }; - let event = await pool.get([config.nostr], filter); + let event = await pool.get([config.nostr], filter, opts); let read = relays; let write = relays; @@ -104,7 +101,11 @@ export let getRelays = async (pubkey): Promise<any> => { if (!r[2] || r[2] === "write") write.push(r[1]); if (!r[2] || r[2] === "read") read.push(r[1]); } - } else pool.get(relays, filter).then(send); + } else + pool + .get(relays, filter) + .then(send) + .catch(() => {}); return { read, write }; }; @@ -116,18 +117,18 @@ export let getProfile = async (pubkey) => { let relays = [config.nostr]; let filter: any = { authors: pubkeys, kinds: [0] }; - let ev = await pool.get(relays, filter); + let ev = await pool.get(relays, filter, opts); if (!ev) { filter = { cache: ["user_infos", { pubkeys }] }; - ev = (await pool.querySync([config.cache], filter)).find( + ev = (await pool.querySync([config.cache], filter, opts)).find( (e) => e.kind === 0, ); } if (!ev) { ({ write: relays } = await getRelays(pubkey)); - ev = await pool.get(relays, filter); + ev = await pool.get(relays, filter, opts); } if (ev) { @@ -143,3 +144,29 @@ export let anon = (pubkey) => ({ follows: [], followers: [], }); + +export let getCount = async (pubkey) => { + let filter: any = { kinds: [3], "#p": [pubkey] }; + let ev = await pool.get([config.nostr], filter, opts); + let created_at; + if (ev) ({ created_at } = ev); + + let count = 0; + let cache = await g(`${pubkey}:followers:n`); + + if (cache && cache.t >= created_at) ({ count } = cache); + else { + let filter: any = { cache: ["user_infos", { pubkeys: [pubkey] }] }; + let infos = await pool.querySync([config.cache], filter, opts); + + let f = infos.find((e) => e.kind === 10000133); + let counts = {}; + if (f) { + counts = JSON.parse(f.content); + count = counts[pubkey]; + await s(`${pubkey}:count`, { count, t: created_at }); + } + } + + return count; +}; diff --git a/lib/utils.ts b/lib/utils.ts index 7a663c60..a805f343 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -168,9 +168,3 @@ export function formatReceipt(items, currency) { } export let t = ({ language = "en" }) => locales[language]; - -export let chunk = (array, chunkSize) => array.reduce((all, one, i) => { - const chunkIndex = Math.floor(i / chunkSize); - (all[chunkIndex] = all[chunkIndex] || []).push(one); - return all; -}, []); diff --git a/routes/nostr.ts b/routes/nostr.ts index 573424e3..fa20aa43 100644 --- a/routes/nostr.ts +++ b/routes/nostr.ts @@ -1,7 +1,16 @@ import config from "$config"; -import { bail, chunk, getUser, uniq } from "$lib/utils"; +import { bail, fail, getUser } from "$lib/utils"; import { g, s, db } from "$lib/db"; -import { anon, send, getRelays, serverPubkey, pool } from "$lib/nostr"; +import { + anon, + send, + getCount, + getRelays, + serverPubkey, + pool, +} from "$lib/nostr"; + +let opts = { maxWait: 2000 }; export default { async event(req, res) { @@ -18,6 +27,32 @@ export default { res.send(event); }, + async publish(req, res) { + try { + let { event } = req.body; + let { pubkey } = req.user; + let { write: relays } = await getRelays(pubkey); + if (!relays.includes(config.nostr)) relays.push(config.nostr); + + let ok; + for (let url of relays) { + try { + await send(event, url); + ok = true; + } catch (e) { + console.log("failed to publish to", url); + } + } + + if (!ok) fail("failed to publish"); + if (event.kind === 3) db.del(`${pubkey}:follows`); + + res.send({}); + } catch (e) { + bail(res, e.message); + } + }, + async follows(req, res) { let { params: { pubkey }, @@ -25,38 +60,42 @@ export default { } = req; try { let filter = { kinds: [3], authors: [pubkey] }; - let ev = await pool.get([config.nostr], filter); + let ev = await pool.get([config.nostr], filter, opts); - if (!ev) { + if (!ev || tagsonly) { let { relays } = config; ({ write: relays } = await getRelays(pubkey)); filter = { kinds: [3], authors: [pubkey] }; - pool.get(relays, filter).then(send); + ev = await pool.get(relays, filter, opts); + send(ev); } let created_at; let follows = []; let tags = []; + if (ev) ({ created_at, tags } = ev); if (!tags.length || tagsonly) return res.send(tags); let cache = await g(`${pubkey}:follows`); if (cache && cache.t >= created_at) ({ follows } = cache); else { - let pubkeys = tags.map((t) => t[1]).filter((p) => p.length === 64); + let pubkeys = tags + .map((t) => t[0] === "p" && t[1]) + .filter((p) => p && p.length === 64); let filter: any = { cache: ["user_infos", { pubkeys }] }; - let infos = await pool.querySync([config.cache], filter); + let infos = await pool.querySync([config.cache], filter, opts); let profiles = infos.filter((e) => e.kind === 0); let f = infos.find((e) => e.kind === 10000133); - let followers = {}; - if (f) followers = JSON.parse(f.content); + let counts = {}; + if (f) counts = JSON.parse(f.content); for (let p of profiles) { let { content, pubkey } = p; let user = JSON.parse(content); - user.followers = followers[pubkey]; + user.count = counts[pubkey]; user.pubkey = pubkey; follows.push(user); } @@ -66,7 +105,7 @@ export default { .filter((p) => !followKeys.includes(p)) .map((pubkey) => anon(pubkey)); - follows.sort((a: any, b: any) => b.followers - a.followers); + follows.sort((a: any, b: any) => b.count - a.count); follows.push(...missing); s(`${pubkey}:follows`, { follows, t: created_at }); @@ -84,20 +123,51 @@ export default { params: { pubkey }, } = req; try { - let pubkeys: any = await q({ "#p": [pubkey] }); - pubkeys = pubkeys.map((e) => e.pubkey); + let filter: any = { kinds: [3], "#p": [pubkey] }; + let ev = await pool.get([config.nostr], filter, opts); + let created_at; + if (ev) ({ created_at } = ev); - let followers: any = await q({ authors: [pubkeys], kinds: [0] }); + let followers = []; + let cache = await g(`${pubkey}:followers`); + if (cache && cache.t >= created_at) ({ followers } = cache); + else { + filter = { cache: ["user_followers", { pubkey }] }; + let data = await pool.querySync([config.cache], filter, opts); + for (let ev of data.filter((f) => f.kind === 0)) { + let { content, pubkey } = ev; + let user = JSON.parse(content); + user.pubkey = pubkey; + followers.push(user); + } + + let pubkeys = followers.map((f) => f.pubkey); + filter = { cache: ["user_infos", { pubkeys }] }; + let infos = await pool.querySync([config.cache], filter, opts); - followers = uniq(followers, (e) => e.pubkey); - followers.sort( - (a, b) => a.username && a.username.localeCompare(b.username), - ); + let counts = {}; + let 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); + + await s(`${pubkey}:followers`, { followers, t: created_at }); + } res.send(followers); } catch (e) { console.log(e); - res.code(500).send(e && e.message); + bail(res, e.message); + } + }, + + async count(req, res) { + try { + let { pubkey } = req.params; + let count = await getCount(pubkey); + res.send(count); + } catch (e) { + bail(res, e.message); } }, diff --git a/routes/users.ts b/routes/users.ts index e1971e78..06eb75ab 100644 --- a/routes/users.ts +++ b/routes/users.ts @@ -15,7 +15,7 @@ import { mail, templates } from "$lib/mail"; import upload from "$lib/upload"; import rpc from "@coinos/rpc"; import { nip19 } from "nostr-tools"; -import { getProfile } from "$lib/nostr"; +import { getCount, getProfile } from "$lib/nostr"; export default { upload, @@ -111,6 +111,7 @@ export default { if (!user) return res.code(500).send("User not found"); let whitelist = [ + "about", "address", "anon", "banner", @@ -127,6 +128,7 @@ export default { "prompt", "pubkey", "username", + "website" ]; if (user.pubkey) user.npub = nip19.npubEncode(user.pubkey); |
