From 18f3e888a6eda283a655866c6af8b5f00f30ff8c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A2u=20Cao?=
Date: Mon, 21 Oct 2024 00:06:15 +0200
Subject: [PATCH] Show list of linked articles on profile page
---
handlers/naddr.ts | 12 ++++-----
handlers/nprofile.ts | 2 +-
handlers/npub.ts | 2 +-
handlers/username.ts | 7 ++---
html.ts | 64 +++++++++++++++++++++++++++++++++++++-------
ldap.ts | 16 +++++------
main.ts | 7 +++--
nostr.ts | 15 ++++++++++-
8 files changed, 93 insertions(+), 32 deletions(-)
diff --git a/handlers/naddr.ts b/handlers/naddr.ts
index b6716c5..cfd3ce7 100644
--- a/handlers/naddr.ts
+++ b/handlers/naddr.ts
@@ -1,11 +1,8 @@
import { Context } from "@oak/oak";
import { nip19 } from "@nostr/tools";
import { log } from "../log.ts";
-import { articleHtml } from "../html.ts"
-import {
- fetchReplaceableEvent,
- fetchProfileEvent
-} from "../nostr.ts";
+import { articleHtml } from "../html.ts";
+import { fetchProfileEvent, fetchReplaceableEvent } from "../nostr.ts";
const naddrHandler = async function (ctx: Context) {
const { request } = ctx;
@@ -13,7 +10,10 @@ const naddrHandler = async function (ctx: Context) {
try {
const r = nip19.decode(naddr);
- const articleEvent = await fetchReplaceableEvent(r.data.pubkey, r.data.identifier);
+ const articleEvent = await fetchReplaceableEvent(
+ r.data.pubkey,
+ r.data.identifier,
+ );
const profileEvent = await fetchProfileEvent(r.data.pubkey);
let profile;
diff --git a/handlers/nprofile.ts b/handlers/nprofile.ts
index 33703c2..5a889d9 100644
--- a/handlers/nprofile.ts
+++ b/handlers/nprofile.ts
@@ -3,7 +3,7 @@ import { nip19 } from "@nostr/tools";
import { log } from "../log.ts";
import { lookupUsernameByPubkey } from "../ldap.ts";
import { fetchProfileEvent } from "../nostr.ts";
-import { profilePageHtml } from "../html.ts"
+import { profilePageHtml } from "../html.ts";
const nprofileHandler = async function (ctx: Context) {
const { request } = ctx;
diff --git a/handlers/npub.ts b/handlers/npub.ts
index 19dcd22..9cb32f5 100644
--- a/handlers/npub.ts
+++ b/handlers/npub.ts
@@ -3,7 +3,7 @@ import { nip19 } from "@nostr/tools";
import { log } from "../log.ts";
import { lookupUsernameByPubkey } from "../ldap.ts";
import { fetchProfileEvent } from "../nostr.ts";
-import { profilePageHtml } from "../html.ts"
+import { profilePageHtml } from "../html.ts";
const npubHandler = async function (ctx: Context) {
const { request } = ctx;
diff --git a/handlers/username.ts b/handlers/username.ts
index c2581bb..f4d9f98 100644
--- a/handlers/username.ts
+++ b/handlers/username.ts
@@ -2,8 +2,8 @@ import { Context } from "@oak/oak";
import { nip19 } from "@nostr/tools";
import { log } from "../log.ts";
import { lookupPubkeyByUsername } from "../ldap.ts";
-import { fetchProfileEvent } from "../nostr.ts";
-import { profilePageHtml } from "../html.ts"
+import { fetchArticlesByAuthor, fetchProfileEvent } from "../nostr.ts";
+import { profilePageHtml } from "../html.ts";
const usernameHandler = async function (ctx: Context) {
const { request } = ctx;
@@ -20,7 +20,8 @@ const usernameHandler = async function (ctx: Context) {
const profileEvent = await fetchProfileEvent(pubkey);
if (profileEvent) {
- const html = profilePageHtml(profileEvent);
+ const articleEvents = await fetchArticlesByAuthor(pubkey);
+ const html = profilePageHtml(profileEvent, articleEvents);
ctx.response.body = html;
} else {
diff --git a/html.ts b/html.ts
index 05707a2..00165d1 100644
--- a/html.ts
+++ b/html.ts
@@ -1,4 +1,5 @@
import { render as renderMarkdown } from "@deno/gfm";
+import { nip19 } from "@nostr/tools";
import { log } from "./log.ts";
export function htmlLayout(title: string, body: string) {
@@ -47,6 +48,7 @@ export function htmlLayout(title: string, body: string) {
h2, h3, h4 {
margin-top: 2em;
margin-bottom: 2rem;
+ line-height: 1.6em;
}
h1, h2, h3, h4 {
@@ -110,6 +112,14 @@ export function htmlLayout(title: string, body: string) {
p.meta .date {
color: #888;
}
+
+ .article-list .item {
+ margin-bottom: 3rem;
+ }
+
+ .article-list .item h3 {
+ margin-bottom: 1rem;
+ }
@@ -120,7 +130,7 @@ export function htmlLayout(title: string, body: string) {
}
export function articleHtml(articleEvent: object, profile: object) {
- const titleTag = articleEvent.tags.find(t => t[0] === "title");
+ const titleTag = articleEvent.tags.find((t) => t[0] === "title");
const title = titleTag ? titleTag[1] : "Untitled";
const content = renderMarkdown(articleEvent.content);
const date = new Date(articleEvent.created_at * 1000);
@@ -149,16 +159,50 @@ export function articleHtml(articleEvent: object, profile: object) {
return htmlLayout(title, body);
}
-export function profilePageHtml(profileEvent: object) {
+function articleListItemHtml(articleEvent: object) {
+ const identifier = articleEvent.tags.find((t) => t[0] === "d")[1];
+ const naddr = nip19.naddrEncode({
+ identifier: identifier,
+ pubkey: articleEvent.pubkey,
+ kind: articleEvent.kind
+ });
+ const titleTag = articleEvent.tags.find((t) => t[0] === "title");
+ const title = titleTag ? titleTag[1] : "Untitled";
+ const date = new Date(articleEvent.created_at * 1000);
+ const formattedDate = date.toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ });
+
+ return `
+
+ `;
+}
+
+export function articleListHtml(articleEvents: object[]) {
+ if (articleEvents.length === 0) return "";
+ let html = "";
+
+ for (const articleEvent of articleEvents) {
+ html += articleListItemHtml(articleEvent);
+ }
+
+ return `
+ Articles
+
+ ${html}
+
+ `;
+}
+
+export function profilePageHtml(profileEvent: object, articleEvents: object[]) {
const profile = JSON.parse(profileEvent.content);
const name = profile.name || "Anonymous";
- const title = `${name} on Nostr`
- // const date = new Date(articleEvent.created_at * 1000);
- // const formattedDate = date.toLocaleDateString("en-US", {
- // year: "numeric",
- // month: "long",
- // day: "numeric",
- // });
+ const title = `${name} on Nostr`;
const body = `
@@ -171,7 +215,7 @@ export function profilePageHtml(profileEvent: object) {
- Articles
+ ${articleListHtml(articleEvents)}
`;
diff --git a/ldap.ts b/ldap.ts
index 6131339..37cb118 100644
--- a/ldap.ts
+++ b/ldap.ts
@@ -1,20 +1,20 @@
import { load } from "@std/dotenv";
-import { Client } from 'ldapts';
+import { Client } from "ldapts";
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 });
const config = {
url: Deno.env.get("LDAP_URL"),
bindDN: Deno.env.get("LDAP_BIND_DN"),
password: Deno.env.get("LDAP_PASSWORD"),
- searchDN: Deno.env.get("LDAP_SEARCH_DN")
-}
+ searchDN: Deno.env.get("LDAP_SEARCH_DN"),
+};
const client = new Client({ url: config.url });
-export async function lookupPubkeyByUsername (username: string) {
+export async function lookupPubkeyByUsername(username: string) {
let pubkey;
try {
@@ -22,7 +22,7 @@ export async function lookupPubkeyByUsername (username: string) {
const { searchEntries } = await client.search(config.searchDN, {
filter: `(cn=${username})`,
- attributes: ['nostrKey']
+ attributes: ["nostrKey"],
});
pubkey = searchEntries[0]?.nostrKey;
@@ -34,7 +34,7 @@ export async function lookupPubkeyByUsername (username: string) {
}
}
-export async function lookupUsernameByPubkey (pubkey: string) {
+export async function lookupUsernameByPubkey(pubkey: string) {
let username;
try {
@@ -42,7 +42,7 @@ export async function lookupUsernameByPubkey (pubkey: string) {
const { searchEntries } = await client.search(config.searchDN, {
filter: `(nostrKey=${pubkey})`,
- attributes: ['cn']
+ attributes: ["cn"],
});
username = searchEntries[0]?.cn;
diff --git a/main.ts b/main.ts
index 8f322b8..2d374b8 100644
--- a/main.ts
+++ b/main.ts
@@ -23,7 +23,10 @@ router.get("/:path", async (ctx: ctx) => {
ctx.response.body = "Not Found";
}
- log(`${ctx.request.method} ${ctx.request.url} - ${ctx.response.status}`, "gray");
+ log(
+ `${ctx.request.method} ${ctx.request.url} - ${ctx.response.status}`,
+ "gray",
+ );
});
const app = new Application();
@@ -33,4 +36,4 @@ app.use(router.allowedMethods());
const PORT = 8000;
app.listen({ port: PORT });
-console.log(`App listening on http://localhost:${PORT}`)
+console.log(`App listening on http://localhost:${PORT}`);
diff --git a/nostr.ts b/nostr.ts
index 3480237..5d7c330 100644
--- a/nostr.ts
+++ b/nostr.ts
@@ -2,7 +2,10 @@ import { NRelay1 } from "@nostrify/nostrify";
export const relay = new NRelay1("wss://nostr.kosmos.org");
-export async function fetchReplaceableEvent(pubkey: string, identifier: string) {
+export async function fetchReplaceableEvent(
+ pubkey: string,
+ identifier: string,
+) {
const events = await relay.query([{
authors: [pubkey],
kinds: [30023],
@@ -13,6 +16,16 @@ export async function fetchReplaceableEvent(pubkey: string, identifier: string)
return events.length > 0 ? events[0] : null;
}
+export async function fetchArticlesByAuthor(pubkey: string) {
+ const events = await relay.query([{
+ authors: [pubkey],
+ kinds: [30023],
+ limit: 10,
+ }]);
+
+ return events;
+}
+
export async function fetchProfileEvent(pubkey: string) {
const events = await relay.query([{
authors: [pubkey],