[nextjs]SNS Server-6(myapp16)

πŸ‘‰πŸ» 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

webfinger pinafore
webfinger server

μš”ν•œλ³΅μŒ 8μž₯ 32절 / John 8:32

“그리고 λ„ˆν¬λŠ” 진리λ₯Ό μ•Œκ²Œ 될 것이며, 진리가 λ„ˆν¬λ₯Ό 자유둭게 ν•  것이닀.”

“Then you will know the truth ,and the truth will set you free”

Leave a Reply