{"id":8109,"date":"2026-08-18T08:40:14","date_gmt":"2026-08-17T23:40:14","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=8109"},"modified":"2026-08-18T13:45:31","modified_gmt":"2026-08-18T04:45:31","slug":"nextjs-sns-server-6","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/08\/18\/nextjs-sns-server-6\/","title":{"rendered":"[nextjs]SNS Server-6(myapp16)"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb User\ud14c\uc774\ube14(\ud68c\uc6d0\uc815\ubcf4)\uc744 \ub9cc\ub4e4\uace0 webfinger\ub85c \uac80\uc0c9\uc2dc User\ud14c\uc774\ube14\uc758 \uc815\ubcf4\ub85c \uac80\uc0c9\ub418\ub3c4\ub85d \uc218\uc815\ud569\ub2c8\ub2e4.<br>Create a User table (for member information) and modify the system so that searches using WebFinger retrieve data from that table.<\/p>\n\n\n\n<p>\ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc124\uce58,\ub77c\uc774\ube0c\ub7ec\ub9ac \uc124\uce58,HTTPS\uc124\uc815<br>Project setup, library installation, HTTPS configuration<\/p>\n\n\n\n<p>\u2714\ufe0f \uc544\ub798\uc758 \uc774\uc804 \ud3ec\uc2a4\ud2b8\ub97c \ucc38\uc870\ud558\uc138\uc694<br>Please refer to the previous post below.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-freelifemakers-org wp-block-embed-freelifemakers-org\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"A4pBZlRTTY\"><a href=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/08\/10\/nextjs-dns-caddy\/\">[nextjs]nextjs+domain(ddns)+https(caddy)<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;[nextjs]nextjs+domain(ddns)+https(caddy)&#8221; &#8212; freelifemakers.org\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/08\/10\/nextjs-dns-caddy\/embed\/#?secret=B3tBHBSHmL#?secret=A4pBZlRTTY\" data-secret=\"A4pBZlRTTY\" width=\"560\" height=\"315\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>\ud83d\udcc1 \uc804\uccb4 \ud504\ub85c\uc81d\ud2b8 \uad6c\uc870 \/ Overall Project Structure<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>myapp16\/  \n\u251c\u2500\u2500 app\/  (Next.js App Router)\n\u2502   \u251c\u2500\u2500 .well-known\/webfinger\/route.ts  -&gt; webfinger\n\u2502   \u251c\u2500\u2500 api\/posts\/route.ts     -&gt; \uae00 \uc4f0\uae30 API \/ Writing API\n\u2502   \u251c\u2500\u2500 api\/follow\/route.ts    -&gt; \ud314\ub85c\uc6b0 API(\uc784\uc2dc) \/ Follow API(temporary)\n\u2502   \u251c\u2500\u2500 users\/&#91;username]\/\n\u2502   \u2502   \u251c\u2500\u2500 route.ts           -&gt; Actor\uc815\ubcf4 \/ Acotr Information\n\u2502   \u2502   \u251c\u2500\u2500 followers\/route.ts -&gt; Followers List\n\u2502   \u2502   \u251c\u2500\u2500 inbox\/route.ts     -&gt; Inbox\n\u2502   \u2502   \u2514\u2500\u2500 outbox\/route.ts    -&gt; outbox\n\u2502   \u251c\u2500\u2500 layout.tsx, page.tsx, globals.css\n\u2502   \u2514\u2500\u2500 favicon.ico\n\u251c\u2500\u2500 lib\/\n\u2502   \u251c\u2500\u2500 ap.ts                  -&gt; Follow Accept\n\u2502   \u2514\u2500\u2500 db.ts                  -&gt; DB connection\n\u251c\u2500\u2500 data\/\n\u2502   \u2514\u2500\u2500 keys\/                  -&gt; private.pem, public.pem\n\u251c\u2500\u2500 data.sqlite                -&gt; Database\n\u251c\u2500\u2500 Caddyfile                  -&gt; https \n\u2514\u2500\u2500 package.json<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc2dc\uc791(myapp13)<br>Project Start (myapp13)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npx create-next-app@latest<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 SQLite\uc124\uce58 \/ Installing SQLite<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd ~\/myapp13\nnpm install better-sqlite3\nnpm install -D @types\/better-sqlite3<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 Webfinger \uc218\uc815 \/ WebFinger modification<\/p>\n\n\n\n<p>\u2714\ufe0f webfinger\ub294 \uc0ac\uc6a9\uc790\ub97c \uac80\uc0c9\ud560\ub54c \uc0ac\uc6a9\ub429\ub2c8\ub2e4.<br>WebFinger is used to search for users.<\/p>\n\n\n\n<p>\u2714\ufe0f \uc774\uc804\uc5d0 \uad6c\ud604\ud588\ub358 webfinger\ub97c \uc0ac\uc6a9\uc790 \uac80\uc0c9\uc2dc \uc2e4\uc81c \ud68c\uc6d0\uac00\uc785\ub41c \uc815\ubcf4\uac00 \uac80\uc0c9\ub418\ub3c4\ub85d \uc218\uc815\ud569\ub2c8\ub2e4.<br>I am modifying the previously implemented WebFinger functionality so that actual registered user information is returned when searching for users.<\/p>\n\n\n\n<p>\u2714\ufe0f webfinger\ub294 Mastodon, Pinafore, GoToSocial, Misskey \uc5d0\uc11c \ubaa8\ub450 \uac80\uc0c9\ub418\ub3c4\ub85d \ud569\ub2c8\ub2e4.<br>WebFinger ensures discoverability across Mastodon, Pinafore, GoToSocial, and Misskey.<\/p>\n\n\n\n<p>\u2714\ufe0f \uc774\uc804\uc5d0 User\ud14c\uc774\ube14\uc774 \uc5c6\uae30 \ub54c\ubb38\uc5d0 \uc0c8\ub86d\uac8c User\ud14c\uc774\ube14\uc744 \uc0dd\uc131\ud569\ub2c8\ub2e4.<br>Since the User table does not exist, a new User table is created.<\/p>\n\n\n\n<p>&#8212; User\ud14c\uc774\ube14 \uc0dd\uc131 \/ Create User table<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-- users \ud14c\uc774\ube14 \uc0dd\uc131 \/ Create User table\nCREATE TABLE IF NOT EXISTS users (\n  username TEXT PRIMARY KEY,\n  display_name TEXT DEFAULT '',\n  summary TEXT DEFAULT '',\n  private_key TEXT,\n  public_key TEXT,\n  created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\n-- \uae30\ubcf8 \uc720\uc800 \ub123\uae30 \/ Add basic user\nINSERT OR IGNORE INTO users (username, display_name, summary) \nVALUES ('user1', 'user1', 'My Fediverse account on aloy-horizon');\n\n-- \uc720\uc800 \ud655\uc778 \/ Check User Information\nSELECT * FROM users;<\/code><\/pre>\n\n\n\n<p>&#8212; \uc804\uccb4 \ub370\uc774\ud130 \ubca0\uc774\uc2a4 \uc2a4\ud0a4\ub9c8\ub294 \uc544\ub798\ucc98\ub7fc \uc124\uc815\ud569\ub2c8\ub2e4.<br>The entire database schema is configured as shown below.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-- 1. users(\uc2e0\uaddc\ucd94\uac00 \/ Newly Added)\nCREATE TABLE IF NOT EXISTS users (\n  username TEXT PRIMARY KEY,\n  display_name TEXT,\n  summary TEXT,\n  private_key TEXT,\n  public_key TEXT,\n  created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\n-- 2. followers\nCREATE TABLE IF NOT EXISTS followers (\n  id TEXT PRIMARY KEY,\n  actor TEXT NOT NULL,\n  inbox TEXT NOT NULL,\n  username TEXT NOT NULL,\n  created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\n-- 3. posts \nCREATE TABLE IF NOT EXISTS posts (\n  id TEXT PRIMARY KEY,\n  content TEXT NOT NULL,\n  author TEXT,\n  created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\n-- \uc778\ub371\uc2a4 \/ Index \nCREATE INDEX IF NOT EXISTS idx_followers_username ON followers(username);\nCREATE INDEX IF NOT EXISTS idx_followers_actor ON followers(actor);<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f webfinger \ucf54\ub4dc \uc218\uc815 \/ Modify WebFinger code<\/p>\n\n\n\n<p>&#8212; myapp15\/app\/.well-known\/webfinger\/route.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import db from '@\/lib\/db';\n\nexport async function GET(req: Request) {\n  const { searchParams } = new URL(req.url);\n  const resource = searchParams.get('resource') || '';\n  const DOMAIN = process.env.DOMAIN || 'aloy-horizon.duckdns.org';\n\n  let username: string | null = null;\n  if (resource.startsWith('acct:')) {\n    const m = resource.match(\/^acct:(&#91;^@]+)@\/);\n    if (m) username = m&#91;1];\n  } else if (resource.includes('\/users\/')) {\n    const m = resource.match(\/\\\/users\\\/(&#91;^\\\/\\?]+)\/);\n    if (m) username = m&#91;1];\n  }\n\n  if (!username) return new Response('not found', { status: 404 });\n\n  \/\/ DB\uc5d0\uc11c \uac80\uc0c9 \/ Search in DB\n  const user = db.prepare('SELECT username FROM users WHERE username =?').get(username) as any;\n  if (!user) {\n    console.log(`\u274c WebFinger: ${username} \uc5c6\uc74c \/ Not found`);\n    return new Response('not found', { status: 404 });\n  }\n\n  console.log(`\u2705 WebFinger: ${username} \ucc3e\uc74c \/ Found`);\n  const actorUrl = `https:\/\/${DOMAIN}\/users\/${username}`;\n\n  return new Response(JSON.stringify({\n    subject: `acct:${username}@${DOMAIN}`,\n    aliases: &#91;actorUrl],\n    links: &#91;{ rel: 'self', type: 'application\/activity+json', href: actorUrl }]\n  }), {\n    headers: {\n      'Content-Type': 'application\/jrd+json',\n      'Access-Control-Allow-Origin': '*'\n    }\n  });\n}<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc2a4\ud2b8 \/ Test<\/p>\n\n\n\n<p>\u2714\ufe0f \ud130\ubbf8\ub110\uc5d0\uc11c \uc2e4\ud589\ud558\uba74 \uc544\ub798\uc640 \uac19\uc740 \uacb0\uacfc\ub97c \ubcfc \uc218 \uc788\uc2b5\ub2c8\ub2e4.<br>If you run it in the terminal, you will see results like the following.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>myapp16 % <strong>curl \"https:\/\/aloy-horizon.duckdns.org\/.well-known\/webfinger?resource=acct:user1@aloy-horizon.duckdns.org\"<\/strong>\n{\"subject\":\"acct:user1@aloy-horizon.duckdns.org\",\"aliases\":&#91;\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\"],\"links\":&#91;{\"rel\":\"self\",\"type\":\"application\/activity+json\",\"href\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\"}]}% gimdaegyeong@gimdaegyeong-ui-MacBookAir myapp16 % <\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f \uc11c\ubc84 \ub85c\uadf8 \/ Server Log<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\n GET \/users\/user1\/followers 200 in 870ms (next.js: 842ms, application-code: 28ms)\n\u2705 WebFinger: user1 \ucc3e\uc74c!\n GET \/.well-known\/webfinger?resource=acct:user1@aloy-horizon.duckdns.org 200 in 58ms (next.js: 52ms, application-code: 6ms)\n<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \uc9c0\uae08\uae4c\uc9c0 \uc644\ub8cc\ud55c \ub0b4\uc6a9 \/ Work completed so far<\/p>\n\n\n\n<p>&#8212;&nbsp;WebFinger&nbsp; (<code>users<\/code>&nbsp;\ud14c\uc774\ube14 \uc0dd\uc131 + DB \uac80\uc0c9)<br>WebFinger (create &#8216;users&#8217; table + DB search)<\/p>\n\n\n\n<p>&#8212;  Follow \ubcf4\ub0b4\uae30 \/ \ubc1b\uae30<br>Follow Send\/Receive<\/p>\n\n\n\n<p>&#8212; \uae00 \ubc30\ub2ec  \/ Article(Post) Delivery<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"472\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp16-1-jpg.jpg\" alt=\"\" class=\"wp-image-8150\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp16-1-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp16-1-jpg-300x177.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp16-1-jpg-768x453.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp16-1-jpg-400x236.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">webfinger pinafore<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"137\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp16-2-jpg.jpg\" alt=\"\" class=\"wp-image-8152\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp16-2-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp16-2-jpg-300x51.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp16-2-jpg-768x132.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp16-2-jpg-400x69.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">webfinger server<\/figcaption><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\uc694\ud55c\ubcf5\uc74c 8\uc7a5 32\uc808 \/ John 8:32<\/p>\n\n\n\n<p>&#8220;\uadf8\ub9ac\uace0 \ub108\ud76c\ub294 \uc9c4\ub9ac\ub97c \uc54c\uac8c \ub420 \uac83\uc774\uba70, \uc9c4\ub9ac\uac00 \ub108\ud76c\ub97c \uc790\uc720\ub86d\uac8c \ud560 \uac83\uc774\ub2e4.&#8221;<\/p>\n\n\n\n<p>&#8220;Then you will know the truth ,and the truth will set you free&#8221;<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udc49\ud83c\udffb User\ud14c\uc774\ube14(\ud68c\uc6d0\uc815\ubcf4)\uc744 \ub9cc\ub4e4\uace0 webfinger\ub85c \uac80\uc0c9\uc2dc User\ud14c\uc774\ube14\uc758 \uc815\ubcf4\ub85c \uac80\uc0c9\ub418\ub3c4\ub85d \uc218\uc815\ud569\ub2c8\ub2e4.Create a User table (for member information) and modify the system so that searches using WebFinger retrieve data from that table. \ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc124\uce58,\ub77c\uc774\ube0c\ub7ec\ub9ac \uc124\uce58,HTTPS\uc124\uc815Project setup, library installation, HTTPS configuration \u2714\ufe0f \uc544\ub798\uc758 \uc774\uc804 \ud3ec\uc2a4\ud2b8\ub97c \ucc38\uc870\ud558\uc138\uc694Please refer to the previous post below. \ud83d\udcc1 \uc804\uccb4 \ud504\ub85c\uc81d\ud2b8 \uad6c\uc870 \/ Overall [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28,27,1,7],"tags":[],"class_list":["post-8109","post","type-post","status-publish","format-standard","hentry","category-news","category-nextjs","category-uncategorized","category-website","missing-thumbnail"],"_links":{"self":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/8109","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/comments?post=8109"}],"version-history":[{"count":47,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/8109\/revisions"}],"predecessor-version":[{"id":8179,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/8109\/revisions\/8179"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=8109"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=8109"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=8109"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}