aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-10-10 12:16:41 -0700
committerAdam Soltys <[email protected]>2024-10-10 12:17:05 -0700
commit0de0e3ba3719333f3884aeff26992adf1688c6ec (patch)
treec0ed293eb942ca34c412f952cb20f049e4b50300
parentd734c424f19bbec89274d828c3a2b70dd3ef221a (diff)
downloadcoinos-server-0de0e3ba3719333f3884aeff26992adf1688c6ec.tar.gz
coinos-server-0de0e3ba3719333f3884aeff26992adf1688c6ec.zip
2s maxwait
-rw-r--r--index.ts2
-rw-r--r--lib/nostr.ts53
-rw-r--r--lib/utils.ts6
-rw-r--r--routes/nostr.ts108
-rw-r--r--routes/users.ts4
5 files changed, 134 insertions, 39 deletions
diff --git a/index.ts b/index.ts
index 5ce5747f..bec345ad 100644
--- a/index.ts
+++ b/index.ts
@@ -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);