76 lines
2 KiB
TypeScript
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 };
|
|
}
|