๐๐ป 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”