ππ» Userν
μ΄λΈ(νμμ 보)μ λ§λ€κ³ webfingerλ‘ κ²μμ Userν
μ΄λΈμ μ λ³΄λ‘ κ²μλλλ‘ μμ ν©λλ€.
Create a User table (for member information) and modify the system so that searches using WebFinger retrieve data from that table.
π νλ‘μ νΈ μ€μΉ,λΌμ΄λΈλ¬λ¦¬ μ€μΉ,HTTPSμ€μ
Project setup, library installation, HTTPS configuration
βοΈ μλμ μ΄μ ν¬μ€νΈλ₯Ό μ°Έμ‘°νμΈμ
Please refer to the previous post below.
π μ 체 νλ‘μ νΈ κ΅¬μ‘° / Overall Project Structure
myapp16/
βββ app/ (Next.js App Router)
β βββ .well-known/webfinger/route.ts -> webfinger
β βββ api/posts/route.ts -> κΈ μ°κΈ° API / Writing API
β βββ api/follow/route.ts -> νλ‘μ° API(μμ) / Follow API(temporary)
β βββ users/[username]/
β β βββ route.ts -> Actorμ 보 / Acotr Information
β β βββ followers/route.ts -> Followers List
β β βββ inbox/route.ts -> Inbox
β β βββ outbox/route.ts -> outbox
β βββ layout.tsx, page.tsx, globals.css
β βββ favicon.ico
βββ lib/
β βββ ap.ts -> Follow Accept
β βββ db.ts -> DB connection
βββ data/
β βββ keys/ -> private.pem, public.pem
βββ data.sqlite -> Database
βββ Caddyfile -> https
βββ package.json
π νλ‘μ νΈ μμ(myapp16)
Project Start (myapp16)
npx create-next-app@latest
π SQLiteμ€μΉ / Installing SQLite
cd ~/myapp13
npm install better-sqlite3
npm install -D @types/better-sqlite3
π Webfinger μμ / WebFinger modification
βοΈ webfingerλ μ¬μ©μλ₯Ό κ²μν λ μ¬μ©λ©λλ€.
WebFinger is used to search for users.
βοΈ μ΄μ μ ꡬννλ webfingerλ₯Ό μ¬μ©μ κ²μμ μ€μ νμκ°μ
λ μ λ³΄κ° κ²μλλλ‘ μμ ν©λλ€.
I am modifying the previously implemented WebFinger functionality so that actual registered user information is returned when searching for users.
βοΈ webfingerλ Mastodon, Pinafore, GoToSocial, Misskey μμ λͺ¨λ κ²μλλλ‘ ν©λλ€.
WebFinger ensures discoverability across Mastodon, Pinafore, GoToSocial, and Misskey.
βοΈ μ΄μ μ Userν
μ΄λΈμ΄ μκΈ° λλ¬Έμ μλ‘κ² Userν
μ΄λΈμ μμ±ν©λλ€.
Since the User table does not exist, a new User table is created.
— Userν μ΄λΈ μμ± / Create User table
-- users ν
μ΄λΈ μμ± / Create User table
CREATE TABLE IF NOT EXISTS users (
username TEXT PRIMARY KEY,
display_name TEXT DEFAULT '',
summary TEXT DEFAULT '',
private_key TEXT,
public_key TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- κΈ°λ³Έ μ μ λ£κΈ° / Add basic user
INSERT OR IGNORE INTO users (username, display_name, summary)
VALUES ('user1', 'user1', 'My Fediverse account on aloy-horizon');
-- μ μ νμΈ / Check User Information
SELECT * FROM users;
— μ 체 λ°μ΄ν° λ² μ΄μ€ μ€ν€λ§λ μλμ²λΌ μ€μ ν©λλ€.
The entire database schema is configured as shown below.
-- 1. users(μ κ·μΆκ° / Newly Added)
CREATE TABLE IF NOT EXISTS users (
username TEXT PRIMARY KEY,
display_name TEXT,
summary TEXT,
private_key TEXT,
public_key TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 2. followers
CREATE TABLE IF NOT EXISTS followers (
id TEXT PRIMARY KEY,
actor TEXT NOT NULL,
inbox TEXT NOT NULL,
username TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 3. posts
CREATE TABLE IF NOT EXISTS posts (
id TEXT PRIMARY KEY,
content TEXT NOT NULL,
author TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- μΈλ±μ€ / Index
CREATE INDEX IF NOT EXISTS idx_followers_username ON followers(username);
CREATE INDEX IF NOT EXISTS idx_followers_actor ON followers(actor);
βοΈ webfinger μ½λ μμ / Modify WebFinger code
— myapp15/app/.well-known/webfinger/route.ts
import db from '@/lib/db';
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const resource = searchParams.get('resource') || '';
const DOMAIN = process.env.DOMAIN || 'aloy-horizon.duckdns.org';
let username: string | null = null;
if (resource.startsWith('acct:')) {
const m = resource.match(/^acct:([^@]+)@/);
if (m) username = m[1];
} else if (resource.includes('/users/')) {
const m = resource.match(/\/users\/([^\/\?]+)/);
if (m) username = m[1];
}
if (!username) return new Response('not found', { status: 404 });
// DBμμ κ²μ / Search in DB
const user = db.prepare('SELECT username FROM users WHERE username =?').get(username) as any;
if (!user) {
console.log(`β WebFinger: ${username} μμ / Not found`);
return new Response('not found', { status: 404 });
}
console.log(`β
WebFinger: ${username} μ°Ύμ / Found`);
const actorUrl = `https://${DOMAIN}/users/${username}`;
return new Response(JSON.stringify({
subject: `acct:${username}@${DOMAIN}`,
aliases: [actorUrl],
links: [{ rel: 'self', type: 'application/activity+json', href: actorUrl }]
}), {
headers: {
'Content-Type': 'application/jrd+json',
'Access-Control-Allow-Origin': '*'
}
});
}
π ν μ€νΈ / Test
βοΈ ν°λ―Έλμμ μ€ννλ©΄ μλμ κ°μ κ²°κ³Όλ₯Ό λ³Ό μ μμ΅λλ€.
If you run it in the terminal, you will see results like the following.
myapp16 % curl "https://aloy-horizon.duckdns.org/.well-known/webfinger?resource=acct:user1@aloy-horizon.duckdns.org"
{"subject":"acct:user1@aloy-horizon.duckdns.org","aliases":["https://aloy-horizon.duckdns.org/users/user1"],"links":[{"rel":"self","type":"application/activity+json","href":"https://aloy-horizon.duckdns.org/users/user1"}]}% gimdaegyeong@gimdaegyeong-ui-MacBookAir myapp16 %
βοΈ μλ² λ‘κ·Έ / Server Log
GET /users/user1/followers 200 in 870ms (next.js: 842ms, application-code: 28ms)
β
WebFinger: user1 μ°Ύμ!
GET /.well-known/webfinger?resource=acct:user1@aloy-horizon.duckdns.org 200 in 58ms (next.js: 52ms, application-code: 6ms)
π μ§κΈκΉμ§ μλ£ν λ΄μ© / Work completed so far
— WebFinger (users ν
μ΄λΈ μμ± + DB κ²μ)
WebFinger (create ‘users’ table + DB search)
— Follow 보λ΄κΈ° / λ°κΈ°
Follow Send/Receive
— κΈ λ°°λ¬ / Article(Post) Delivery


μν볡μ 8μ₯ 32μ / John 8:32
“κ·Έλ¦¬κ³ λν¬λ μ§λ¦¬λ₯Ό μκ² λ κ²μ΄λ©°, μ§λ¦¬κ° λν¬λ₯Ό μμ λ‘κ² ν κ²μ΄λ€.”
“Then you will know the truth ,and the truth will set you free”