[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