Typing all the types

This commit is contained in:
Râu Cao 2024-10-23 00:27:20 +02:00
parent edaf5f5c71
commit 46ad9813eb
Signed by: raucao
GPG Key ID: 37036C356E56CC51
15 changed files with 97 additions and 105 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.env .env
users.yaml users.yaml
substr

View File

@ -5,24 +5,24 @@ import { log } from "./log.ts";
const dirname = new URL(".", import.meta.url).pathname; const dirname = new URL(".", import.meta.url).pathname;
await load({ envPath: `${dirname}/.env`, export: true }); await load({ envPath: `${dirname}/.env`, export: true });
let staticUsers; let staticUsers: { [key: string]: string } = {};
try { try {
const yamlContent = await Deno.readTextFile(`${dirname}/users.yaml`); const yamlContent = await Deno.readTextFile(`${dirname}/users.yaml`);
staticUsers = parse(yamlContent); const parsedContent = parse(yamlContent);
log(`Serving content for ${Object.keys(staticUsers).length} pubkeys from users.yaml`, "blue"); if (parsedContent !== null && typeof parsedContent === "object") {
staticUsers = parsedContent as { [key: string]: string };
log(`Serving content for pubkeys in users.yaml`, "blue");
}
} catch { } catch {
staticUsers = {};
log(`Could not find or parse a users.yaml config`, "yellow"); log(`Could not find or parse a users.yaml config`, "yellow");
} }
const relay_urls = Deno.env.get("RELAY_URLS")?.split(",");
const config = { const config = {
port: Deno.env.get("PORT") || 8000, port: parseInt(Deno.env.get("PORT") || "8000"),
base_url: Deno.env.get("BASE_URL") || `http://localhost:8000`, base_url: Deno.env.get("BASE_URL") || `http://localhost:8000`,
relay_urls, relay_urls: Deno.env.get("RELAY_URLS")?.split(",") || [],
staticUsers: staticUsers, staticUsers,
ldapEnabled: !!Deno.env.get("LDAP_URL"), ldapEnabled: !!Deno.env.get("LDAP_URL"),
ldap: { ldap: {
url: Deno.env.get("LDAP_URL"), url: Deno.env.get("LDAP_URL"),
@ -32,8 +32,21 @@ const config = {
}, },
}; };
if (config.ldapEnabled && config.ldap.url) { if (config.relay_urls.length === 0) {
log(`No relays configured. Please add at least one relay to RELAY_URLS.`);
Deno.exit(1);
}
if (config.ldapEnabled) {
if (
config.ldap.url && config.ldap.bindDN && config.ldap.password &&
config.ldap.searchDN
) {
log(`Serving content for pubkeys from ${config.ldap.url}`, "blue"); log(`Serving content for pubkeys from ${config.ldap.url}`, "blue");
} else {
log(`The LDAP config is incomplete`);
Deno.exit(1);
}
} else { } else {
log(`LDAP not enabled`, "blue"); log(`LDAP not enabled`, "blue");
} }
@ -43,11 +56,4 @@ if (Object.keys(staticUsers).length === 0 && !config.ldapEnabled) {
Deno.exit(1); Deno.exit(1);
} }
if (config.relay_urls?.length > 0) {
log(`Relays: ${config.relay_urls.join(", ")}`, "green");
} else {
log(`No relays configured. Please add at least one relay to RELAY_URLS.`);
Deno.exit(1);
}
export default config; export default config;

View File

@ -1,23 +1,21 @@
import { Context } from "@oak/oak"; import { Context } from "@oak/oak";
import { nip19 } from "@nostr/tools"; import { nip19 } from "@nostr/tools";
import { log } from "../log.ts";
import { lookupUsernameByPubkey } from "../directory.ts"; import { lookupUsernameByPubkey } from "../directory.ts";
import notFoundHandler from "../handlers/not-found.ts"; import notFoundHandler from "../handlers/not-found.ts";
const naddrHandler = async function (ctx: Context) { const naddrHandler = async function (ctx: Context) {
const naddr = ctx.params.path; const naddr = ctx.state.path;
try { try {
const r = nip19.decode(naddr); const data = nip19.decode(naddr).data as nip19.AddressPointer;
const username = await lookupUsernameByPubkey(r.data.pubkey); const username = await lookupUsernameByPubkey(data.pubkey);
if (username && r.data.identifier) { if (username && data.identifier) {
ctx.response.redirect(`/@${username}/${r.data.identifier}`); ctx.response.redirect(`/@${username}/${data.identifier}`);
} else { } else {
notFoundHandler(ctx); notFoundHandler(ctx);
} }
} catch (e) { } catch (_e) {
log(e, "yellow");
notFoundHandler(ctx); notFoundHandler(ctx);
} }
}; };

View File

@ -1,23 +1,21 @@
import { Context } from "@oak/oak"; import { Context } from "@oak/oak";
import { nip19 } from "@nostr/tools"; import { nip19 } from "@nostr/tools";
import { log } from "../log.ts";
import { lookupUsernameByPubkey } from "../directory.ts"; import { lookupUsernameByPubkey } from "../directory.ts";
import notFoundHandler from "../handlers/not-found.ts"; import notFoundHandler from "../handlers/not-found.ts";
const nprofileHandler = async function (ctx: Context) { const nprofileHandler = async function (ctx: Context) {
const nprofile = ctx.params.path; const nprofile = ctx.state.path;
try { try {
const r = nip19.decode(nprofile); const data = nip19.decode(nprofile).data as nip19.ProfilePointer;
const username = await lookupUsernameByPubkey(r.data.pubkey); const username = await lookupUsernameByPubkey(data.pubkey);
if (username) { if (username) {
ctx.response.redirect(`/@${username}`); ctx.response.redirect(`/@${username}`);
} else { } else {
notFoundHandler(ctx); notFoundHandler(ctx);
} }
} catch (e) { } catch (_e) {
log(e, "yellow");
notFoundHandler(ctx); notFoundHandler(ctx);
} }
}; };

View File

@ -1,23 +1,21 @@
import { Context } from "@oak/oak"; import { Context } from "@oak/oak";
import { nip19 } from "@nostr/tools"; import { nip19 } from "@nostr/tools";
import { log } from "../log.ts";
import { lookupUsernameByPubkey } from "../directory.ts"; import { lookupUsernameByPubkey } from "../directory.ts";
import notFoundHandler from "../handlers/not-found.ts"; import notFoundHandler from "../handlers/not-found.ts";
const npubHandler = async function (ctx: Context) { const npubHandler = async function (ctx: Context) {
const npub = ctx.params.path; const npub = ctx.state.path;
try { try {
const r = nip19.decode(npub); const pubkey = nip19.decode(npub).data as string;
const username = await lookupUsernameByPubkey(r.data); const username = await lookupUsernameByPubkey(pubkey);
if (username) { if (username) {
ctx.response.redirect(`/@${username}`); ctx.response.redirect(`/@${username}`);
} else { } else {
notFoundHandler(ctx); notFoundHandler(ctx);
} }
} catch (e) { } catch (_e) {
log(e, "yellow");
notFoundHandler(ctx); notFoundHandler(ctx);
} }
}; };

View File

@ -1,5 +1,4 @@
import { Context } from "@oak/oak"; import { Context } from "@oak/oak";
import { log } from "../log.ts";
import { lookupPubkeyByUsername } from "../directory.ts"; import { lookupPubkeyByUsername } from "../directory.ts";
import { fetchArticlesByAuthor, fetchProfileEvent } from "../nostr.ts"; import { fetchArticlesByAuthor, fetchProfileEvent } from "../nostr.ts";
import { profileAtomFeed } from "../feeds.ts"; import { profileAtomFeed } from "../feeds.ts";
@ -8,8 +7,8 @@ import Profile from "../models/profile.ts";
import notFoundHandler from "../handlers/not-found.ts"; import notFoundHandler from "../handlers/not-found.ts";
const userAtomFeedHandler = async function (ctx: Context) { const userAtomFeedHandler = async function (ctx: Context) {
const username = ctx.params.user.replace(/^(@|~)/, ""); const username = ctx.state.username;
const pubkey = await lookupPubkeyByUsername(username); const pubkey = await lookupPubkeyByUsername(ctx.state.username);
if (!pubkey) { if (!pubkey) {
notFoundHandler(ctx); notFoundHandler(ctx);
@ -18,21 +17,22 @@ const userAtomFeedHandler = async function (ctx: Context) {
try { try {
const profileEvent = await fetchProfileEvent(pubkey); const profileEvent = await fetchProfileEvent(pubkey);
if (profileEvent) {
const profile = new Profile(profileEvent, username); const profile = new Profile(profileEvent, username);
if (profileEvent && profile.nip05) { if (profile.nip05) {
const articleEvents = await fetchArticlesByAuthor(pubkey); const articleEvents = await fetchArticlesByAuthor(pubkey);
const articles = articleEvents.map((a) => new Article(a)); const articles = articleEvents.map((a) => new Article(a));
const atom = profileAtomFeed(profile, articles); const atom = profileAtomFeed(profile, articles);
ctx.response.headers.set("Content-Type", "application/atom+xml"); ctx.response.headers.set("Content-Type", "application/atom+xml");
ctx.response.body = atom; ctx.response.body = atom;
} else { return;
ctx.response.status = 404;
ctx.response.body = "Not Found";
} }
} catch (e) { }
log(e, "yellow"); notFoundHandler(ctx);
} catch (_e) {
notFoundHandler(ctx); notFoundHandler(ctx);
} }
}; };

View File

@ -1,5 +1,4 @@
import { Context } from "@oak/oak"; import { Context } from "@oak/oak";
import { log } from "../log.ts";
import { lookupPubkeyByUsername } from "../directory.ts"; import { lookupPubkeyByUsername } from "../directory.ts";
import { fetchProfileEvent, fetchReplaceableEvent } from "../nostr.ts"; import { fetchProfileEvent, fetchReplaceableEvent } from "../nostr.ts";
import Article from "../models/article.ts"; import Article from "../models/article.ts";
@ -8,8 +7,8 @@ import { articleHtml } from "../html.ts";
import notFoundHandler from "../handlers/not-found.ts"; import notFoundHandler from "../handlers/not-found.ts";
const userEventHandler = async function (ctx: Context) { const userEventHandler = async function (ctx: Context) {
const username = ctx.params.user.replace(/^(@|~)/, ""); const username = ctx.state.username.replace(/^(@|~)/, "");
const identifier = ctx.params.identifier; const identifier = ctx.state.identifier;
const pubkey = await lookupPubkeyByUsername(username); const pubkey = await lookupPubkeyByUsername(username);
if (!pubkey) { if (!pubkey) {
@ -33,8 +32,7 @@ const userEventHandler = async function (ctx: Context) {
} else { } else {
notFoundHandler(ctx); notFoundHandler(ctx);
} }
} catch (e) { } catch (_e) {
log(e, "yellow");
notFoundHandler(ctx); notFoundHandler(ctx);
} }
}; };

View File

@ -1,5 +1,4 @@
import { Context } from "@oak/oak"; import { Context } from "@oak/oak";
import { log } from "../log.ts";
import { lookupPubkeyByUsername } from "../directory.ts"; import { lookupPubkeyByUsername } from "../directory.ts";
import { fetchArticlesByAuthor, fetchProfileEvent } from "../nostr.ts"; import { fetchArticlesByAuthor, fetchProfileEvent } from "../nostr.ts";
import Article from "../models/article.ts"; import Article from "../models/article.ts";
@ -8,7 +7,7 @@ import { profilePageHtml } from "../html.ts";
import notFoundHandler from "../handlers/not-found.ts"; import notFoundHandler from "../handlers/not-found.ts";
const userProfileHandler = async function (ctx: Context) { const userProfileHandler = async function (ctx: Context) {
const username = ctx.params.path.replace(/^(@|~)/, ""); const username = ctx.state.path.replace(/^(@|~)/, "");
const pubkey = await lookupPubkeyByUsername(username); const pubkey = await lookupPubkeyByUsername(username);
if (!pubkey) { if (!pubkey) {
@ -27,11 +26,9 @@ const userProfileHandler = async function (ctx: Context) {
ctx.response.body = html; ctx.response.body = html;
} else { } else {
log(`No profile event found for @${username}`, "yellow");
notFoundHandler(ctx); notFoundHandler(ctx);
} }
} catch (e) { } catch (_e) {
log(e, "yellow");
notFoundHandler(ctx); notFoundHandler(ctx);
} }
}; };

21
ldap.ts
View File

@ -1,28 +1,27 @@
import { Client } from "ldapts"; import { Client } from "ldapts";
import { log } from "./log.ts";
import config from "./config.ts"; import config from "./config.ts";
const { ldap, ldapEnabled } = config; const { ldap, ldapEnabled } = config;
let client: Client; let client: Client;
if (ldapEnabled) { if (ldapEnabled) {
client = new Client({ url: ldap.url }); client = new Client({ url: ldap.url as string });
} }
export async function lookupPubkeyByUsername(username: string) { export async function lookupPubkeyByUsername(username: string) {
let pubkey; let pubkey;
try { try {
await client.bind(ldap.bindDN, ldap.password); await client.bind(ldap.bindDN as string, ldap.password as string);
const { searchEntries } = await client.search(ldap.searchDN, { const { searchEntries } = await client.search(ldap.searchDN as string, {
filter: `(cn=${username})`, filter: `(cn=${username})`,
attributes: ["nostrKey"], attributes: ["nostrKey"],
}); });
pubkey = searchEntries[0]?.nostrKey; pubkey = searchEntries[0]?.nostrKey as string;
} catch (ex) { } catch (e) {
log(ex, "red"); console.error(e);
} finally { } finally {
await client.unbind(); await client.unbind();
} }
@ -34,16 +33,16 @@ export async function lookupUsernameByPubkey(pubkey: string) {
let username; let username;
try { try {
await client.bind(ldap.bindDN, ldap.password); await client.bind(ldap.bindDN as string, ldap.password as string);
const { searchEntries } = await client.search(ldap.searchDN, { const { searchEntries } = await client.search(ldap.searchDN as string, {
filter: `(nostrKey=${pubkey})`, filter: `(nostrKey=${pubkey})`,
attributes: ["cn"], attributes: ["cn"],
}); });
username = searchEntries[0]?.cn; username = searchEntries[0]?.cn;
} catch (ex) { } catch (e) {
log(ex, "red"); console.error(e);
} finally { } finally {
await client.unbind(); await client.unbind();
} }

View File

@ -1,6 +1,6 @@
import { render as renderMarkdown } from "@deno/gfm"; import { render as renderMarkdown } from "@deno/gfm";
import { nip19 } from "@nostr/tools"; import { nip19 } from "@nostr/tools";
import { NEvent } from "../nostr.ts"; import { NostrEvent as NEvent } from "@nostrify/nostrify";
import config from "../config.ts"; import config from "../config.ts";
export default class Article { export default class Article {
@ -51,7 +51,7 @@ export default class Article {
identifier: this.identifier, identifier: this.identifier,
pubkey: this.event.pubkey, pubkey: this.event.pubkey,
kind: this.event.kind, kind: this.event.kind,
relays: [config.home_relay_url], relays: [config.relay_urls[0]],
}); });
} }
} }

View File

@ -1,5 +1,5 @@
import { nip19 } from "@nostr/tools"; import { nip19, NostrEvent as NEvent } from "@nostr/tools";
import { NEvent } from "../nostr.ts"; // import { NEvent } from "../nostr.ts";
export interface ProfileData { export interface ProfileData {
name?: string; name?: string;
@ -12,8 +12,8 @@ export interface ProfileData {
} }
export default class Profile { export default class Profile {
event: NEvent;
private data: ProfileData; private data: ProfileData;
event: NEvent;
username?: string; username?: string;
constructor(event: NEvent, username?: string) { constructor(event: NEvent, username?: string) {

View File

@ -1,21 +1,15 @@
import { NPool, NRelay1 } from "@nostrify/nostrify"; import { NPool, NRelay1 } from "@nostrify/nostrify";
import config from "./config.ts"; import config from "./config.ts";
export interface NEvent {
content: string;
created_at: number;
id: string;
kind: number;
pubkey: string;
sig: string;
tags: Array<[string, string, string?]>;
}
const relayPool = new NPool({ const relayPool = new NPool({
open: (url) => new NRelay1(url), open: (url) => new NRelay1(url),
reqRouter: async (filters) => new Map( // deno-lint-ignore require-await
config.relay_urls.map(url => [ url, filters ]) reqRouter: async (filters) =>
) new Map(
config.relay_urls.map((url) => [url, filters]),
),
// deno-lint-ignore require-await
eventRouter: async (_event) => [],
}); });
export async function fetchReplaceableEvent( export async function fetchReplaceableEvent(

View File

@ -1,4 +1,4 @@
import { Application, Context, Router, send } from "@oak/oak"; import { Application, Router, send } from "@oak/oak";
import config from "./config.ts"; import config from "./config.ts";
import naddrHandler from "./handlers/naddr.ts"; import naddrHandler from "./handlers/naddr.ts";
import nprofileHandler from "./handlers/nprofile.ts"; import nprofileHandler from "./handlers/nprofile.ts";
@ -10,8 +10,8 @@ import notFoundHandler from "./handlers/not-found.ts";
const router = new Router(); const router = new Router();
router.get("/:path", async (ctx: Context) => { router.get("/:path", async (ctx) => {
const { path } = ctx.params; const path = ctx.state.path = ctx.params.path;
if (path.startsWith("naddr")) { if (path.startsWith("naddr")) {
await naddrHandler(ctx); await naddrHandler(ctx);
@ -26,12 +26,14 @@ router.get("/:path", async (ctx: Context) => {
} }
}); });
router.get("/:user/:kind.atom", async (ctx: Context) => { router.get("/:username/:kind.atom", async (ctx) => {
const { user, kind } = ctx.params; ctx.state.username = ctx.params.username.replace(/^(@|~)/, "");
ctx.state.kind = ctx.params.kind;
if ( if (
kind === "articles" && ctx.state.kind === "articles" &&
(user.startsWith("@") || user.startsWith("~")) (ctx.params.username.startsWith("@") ||
ctx.params.username.startsWith("~"))
) { ) {
await userAtomFeedHandler(ctx); await userAtomFeedHandler(ctx);
} else { } else {
@ -39,10 +41,11 @@ router.get("/:user/:kind.atom", async (ctx: Context) => {
} }
}); });
router.get("/:user/:identifier", async (ctx: Context) => { router.get("/:username/:identifier", async (ctx) => {
const { user } = ctx.params; const username = ctx.state.username = ctx.params.username;
ctx.state.identifier = ctx.params.identifier;
if (user.startsWith("@") || user.startsWith("~")) { if (username.startsWith("@") || username.startsWith("~")) {
await userEventHandler(ctx); await userEventHandler(ctx);
} else { } else {
notFoundHandler(ctx); notFoundHandler(ctx);
@ -51,7 +54,7 @@ router.get("/:user/:identifier", async (ctx: Context) => {
router.get("/assets/:path*", async (ctx) => { router.get("/assets/:path*", async (ctx) => {
try { try {
const filePath = ctx.params.path || ''; const filePath = ctx.params.path || "";
await send(ctx, filePath, { await send(ctx, filePath, {
root: `${Deno.cwd()}/assets`, root: `${Deno.cwd()}/assets`,

View File

@ -1,6 +1,6 @@
import { beforeAll, describe, it } from "@std/testing/bdd"; import { beforeAll, describe, it } from "@std/testing/bdd";
import { expect } from "@std/expect"; import { expect } from "@std/expect";
import { NEvent } from "../../nostr.ts"; import { NostrEvent as NEvent } from "@nostrify/nostrify";
import Article from "../../models/article.ts"; import Article from "../../models/article.ts";
describe("Article", () => { describe("Article", () => {

View File

@ -1,6 +1,6 @@
import { beforeAll, describe, it } from "@std/testing/bdd"; import { beforeAll, describe, it } from "@std/testing/bdd";
import { expect } from "@std/expect"; import { expect } from "@std/expect";
import { NEvent } from "../../nostr.ts"; import { NostrEvent as NEvent } from "@nostrify/nostrify";
import Profile from "../../models/profile.ts"; import Profile from "../../models/profile.ts";
describe("Profile", () => { describe("Profile", () => {