Eve-Relay/src/utils/invites.ts
2025-06-04 12:43:23 +02:00

76 lines
2 KiB
TypeScript

import type { Database } from 'jsr:@db/sqlite';
import { bytesToHex } from '@noble/ciphers/utils';
import { nip19 } from '@nostr/tools';
import { bech32m } from '@scure/base';
import { sql } from './queries.ts';
export class InviteTree {
public root: InviteNode;
constructor(npub: string) {
this.root = new InviteNode(npub);
}
public addChild(npub: string) {
const child = new InviteNode(npub);
this.root.children.push(child);
}
}
export class InviteNode {
public readonly npub: string;
public readonly children: InviteNode[];
constructor(npub: string) {
this.npub = npub;
this.children = [];
}
public addChild(npub: string) {
const child = new InviteNode(npub);
this.children.push(child);
}
}
export function buildInviteTree(db: Database, ccnPubkey: string) {
const ccnCreator = sql`
SELECT pubkey FROM allowed_writes WHERE ccn_pubkey = ${ccnPubkey} AND pubkey NOT IN (
SELECT invitee_pubkey FROM inviter_invitee WHERE ccn_pubkey = ${ccnPubkey}
)
`(db)[0]?.pubkey;
if (!ccnCreator) {
throw new Error('CCN creator not found');
}
const inviteTree = new InviteTree(ccnCreator);
const invitees = sql`
SELECT inviter_pubkey, invitee_pubkey FROM inviter_invitee WHERE ccn_pubkey = ${ccnPubkey}
`(db);
// populate the invite tree by traversing the inviters
for (const invitee of invitees) {
let inviterNode = inviteTree.root.children.find(
(child) => child.npub === invitee.inviter_pubkey,
);
if (!inviterNode) {
inviterNode = new InviteNode(invitee.inviter_pubkey);
inviteTree.root.children.push(inviterNode);
}
inviterNode.addChild(invitee.invitee_pubkey);
}
return inviteTree;
}
export function readInvite(invite: `${string}1${string}`) {
const decoded = bech32m.decode(invite, false);
if (decoded.prefix !== 'eveinvite') return false;
const hexBytes = bech32m.fromWords(decoded.words);
const npub = nip19.npubEncode(bytesToHex(hexBytes.slice(0, 32)));
const inviteCode = bytesToHex(hexBytes.slice(32));
return { npub, invite: inviteCode };
}