{"id":8758,"date":"2026-08-26T08:28:29","date_gmt":"2026-08-25T23:28:29","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=8758"},"modified":"2026-08-26T12:29:27","modified_gmt":"2026-08-26T03:29:27","slug":"nextjs-sns-server-13","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/08\/26\/nextjs-sns-server-13\/","title":{"rendered":"[nextjs]SNS Server-13(myapp23)"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb\uc88b\uc544\uc694\uc640 \uc88b\uc544\uc694 \ucde8\uc18c\uae30\ub2a5\uc744 \uad6c\ud604\ud569\ub2c8\ub2e4.<br>Implement the &#8220;like&#8221; and &#8220;unlike&#8221; functions.<\/p>\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>myapp23\/  \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; Writing API\n\u2502   \u251c\u2500\u2500 api\/follow\/route.ts    -&gt; Follow API(temporary)\n\u2502   \u251c\u2500\u2500 api\/timeline\/route.ts  -&gt; Timeline API\n\u2502   \u251c\u2500\u2500 <strong>api\/like\/route.ts.     -&gt; Like API<\/strong>\n\u2502   \u251c\u2500\u2500 users\/&#91;username]\/\n\u2502   \u2502   \u251c\u2500\u2500 statuses\/&#91;id]\/route.ts -&gt; Indivisual Post\n\u2502   \u2502   \u251c\u2500\u2500 route.ts               -&gt; Acotr Information\n\u2502   \u2502   \u251c\u2500\u2500 followers\/route.ts     -&gt; Followers List\n\u2502   \u2502   \u251c\u2500\u2500 following\/route.ts     -&gt; Following 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 usersui\/&#91;username]\/page.tsx  -&gt; Timeline UI\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(myapp23)<br>Project Start (myapp23)<\/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 ~\/myapp23\nnpm install better-sqlite3\nnpm install -D @types\/better-sqlite3<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 DDNS,https\uc124\uc815 \/ DDNS,https settings<\/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=\"QdGRX45duy\"><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=VCfAOO1anQ#?secret=QdGRX45duy\" data-secret=\"QdGRX45duy\" width=\"560\" height=\"315\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>\ud83d\udcc1 \ub3c4\uba54\uc778\ud5c8\uc6a9, @userid \uc0ac\uc6a9\ud558\uae30<br>Allowing domains, using @userid<\/p>\n\n\n\n<p>&#8212; next.config.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n  \/* allow domain*\/\n  allowedDevOrigins: &#91;'aloy-horizon.duckdns.org', '*.duckdns.org'],\n\n  \/* host.domain.org\/@userid *\/\n<strong>  async rewrites() {\n    return &#91;\n      {\n        source: '\/@:username',\n        destination: '\/usersui\/:username',\n      },\n    ]\n  }<\/strong>\n};\n\n\nexport default nextConfig;\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc774\ube14 \ucd94\uac00 \/ Add table<\/p>\n\n\n\n<p>\u2714\ufe0f \ud14c\uc774\ube14 \ubc0f \uc778\ub371\uc2a4 \uc0dd\uc131(sqlite)<br>Create Table and Index (sqlite)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE TABLE IF NOT EXISTS likes (\n  id TEXT PRIMARY KEY,          \n  actor TEXT NOT NULL,           \n  object TEXT NOT NULL,          \n  username TEXT NOT NULL,       \n  created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE INDEX IF NOT EXISTS idx_likes_object ON likes(object);\nCREATE INDEX IF NOT EXISTS idx_likes_actor ON likes(actor);<\/code><\/pre>\n\n\n\n<p>  1)id TEXT PRIMARY KEY,           <br>&#8212; Like Activity ID: https:\/\/aloy-horizon.duckdns.org\/users\/user1\/likes\/1234<\/p>\n\n\n\n<p>2)actor TEXT NOT NULL, <br>&#8212; \uc88b\uc544\uc694 \ub204\ub978\uc0ac\ub78c \/ People who liked this<\/p>\n\n\n\n<p>&#8212;  https:\/\/freelifemakers.com\/users\/user1<\/p>\n\n\n\n<p>3)object TEXT NOT NULL, <br>&#8212; \uc88b\uc544\uc694 \ub204\ub978 \uae00 \/ Posts I&#8217;ve liked<\/p>\n\n\n\n<p>&#8212; https:\/\/aloy-horizon.duckdns.org\/users\/user1\/statuses\/abc<\/p>\n\n\n\n<p>4) username TEXT NOT NULL, <br>&#8212; \ub0b4 \ub85c\uceec \uc720\uc800\/My local user:: user1<\/p>\n\n\n\n<p>\u2714\ufe0f myapp23\/lib\/db.ts \ucf54\ub4dc \uc218\uc815<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import Database from 'better-sqlite3';\nimport path from 'path';\n\nconst dbPath = path.join(process.cwd(), 'data.sqlite');\nconst db = new Database(dbPath);\ndb.pragma('journal_mode = WAL');\n\ndb.exec(`\n\n... ...\n\n  -- myapp23 \u2705 likes\/Undo likes\n<strong>  CREATE TABLE IF NOT EXISTS likes (\n    id TEXT PRIMARY KEY,           \n    actor TEXT NOT NULL,    \n    object TEXT NOT NULL,      \n    username TEXT NOT NULL,       \n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  );<\/strong>\n`);\n\ndb.exec(`\n  CREATE INDEX IF NOT EXISTS idx_followers_username ON followers(username);\n  CREATE INDEX IF NOT EXISTS idx_followers_actor ON followers(actor);\n  CREATE INDEX IF NOT EXISTS idx_following_username ON following(username);\n  CREATE INDEX IF NOT EXISTS idx_following_actor ON following(actor);\n  CREATE INDEX IF NOT EXISTS idx_posts_username_created ON posts(username, created_at DESC);\n  CREATE INDEX IF NOT EXISTS idx_inbox_username_created ON inbox_posts(username, created_at DESC);\n  CREATE INDEX IF NOT EXISTS idx_inbox_actor ON inbox_posts(actor);\n  CREATE UNIQUE INDEX IF NOT EXISTS idx_following_actor_username ON following(actor, username);\n  CREATE UNIQUE INDEX IF NOT EXISTS idx_followers_actor_username ON followers(actor, username);\n  -- myapp23 \u2705\n<strong>  CREATE INDEX IF NOT EXISTS idx_likes_object ON likes(object);\n  CREATE INDEX IF NOT EXISTS idx_likes_actor ON likes(actor);<\/strong>\n`);\n\nexport default db;<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ucf54\ub4dc \uc218\uc815 \ubc0f \ub77c\uc6b0\ud2b8 \ucd94\uac00<br>Code modifications and route additions<\/p>\n\n\n\n<p>\u2714\ufe0f \ub77c\uc6b0\ud2b8 \ucd94\uac00 \/ Add Route<\/p>\n\n\n\n<p>&#8212; <code>app\/api\/like\/route.ts<\/code>&nbsp;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app\/api\/like\/route.ts\n\/\/ myapp23 \u2705 - Like \/ Undo Like\n\nimport { NextRequest, NextResponse } from 'next\/server';\nimport db from '@\/lib\/db';\nimport { getActorData, sendLike, sendUndoLike, signedFetch } from '@\/lib\/ap';\n\nexport async function POST(req: NextRequest) {\n  try {\n    const { username, target } = await req.json();\n    if (!username || !target) {\n      return NextResponse.json({ ok: false, error: 'username and target required' }, { status: 400 });\n    }\n\n    let inbox: string;\n    let apObjectId: string = target;\n\n    try {\n      const postRes = await signedFetch(target, username);\n      if (postRes.ok) {\n        const postData = await postRes.json();\n        apObjectId = postData.id || target;\n        const attributedTo = postData.attributedTo || postData.actor;\n        const actorUrl = typeof attributedTo === 'string' ? attributedTo : attributedTo?.id;\n        if (!actorUrl) throw new Error('no actor in post');\n        const actorInfo = await getActorData(actorUrl, username);\n        inbox = actorInfo.inbox;\n      } else {\n        const url = new URL(target);\n        const parts = url.pathname.split('\/');\n        const usersIdx = parts.indexOf('users');\n        if (usersIdx === -1) throw new Error('invalid target');\n        const actorUrl = `${url.origin}${parts.slice(0, usersIdx + 2).join('\/')}`;\n        const actorInfo = await getActorData(actorUrl, username);\n        inbox = actorInfo.inbox;\n      }\n    } catch (e) {\n      console.error(e);\n      return NextResponse.json({ ok: false, error: 'invalid target post id' }, { status: 400 });\n    }\n\n    \/\/ \uc911\ubcf5 \uccb4\ud06c\ub294 apObjectId\ub85c \ud574\uc57c \ud568 (Mastodon \ub300\uc751)\n    const existing = db.prepare('SELECT id FROM likes WHERE object = ? AND username = ?').get(apObjectId, username) as any;\n    if (existing) {\n      return NextResponse.json({ ok: true, alreadyLiked: true, id: existing.id });\n    }\n\n    const result = await sendLike(inbox, apObjectId, username);\n\n    db.prepare('INSERT OR IGNORE INTO likes (id, actor, object, username) VALUES (?,?,?,?)')\n      .run(result.likeDoc.id, result.likeDoc.actor, result.likeDoc.object, username);\n\n    return NextResponse.json({ ok: true, result });\n\n  } catch (e: any) {\n    console.error('&#91;Like POST]', e);\n    return NextResponse.json({ ok: false, error: e.message }, { status: 500 });\n  }\n}\n\nexport async function DELETE(req: NextRequest) {\n  try {\n    const { username, target } = await req.json();\n    if (!username || !target) {\n      return NextResponse.json({ ok: false, error: 'username and target required' }, { status: 400 });\n    }\n\n    let apObjectId: string = target;\n    let inbox: string;\n\n    try {\n      const postRes = await signedFetch(target, username);\n      if (postRes.ok) {\n        const postData = await postRes.json();\n        apObjectId = postData.id || target;\n        const attributedTo = postData.attributedTo || postData.actor;\n        const actorUrl = typeof attributedTo === 'string' ? attributedTo : attributedTo?.id;\n        if (!actorUrl) throw new Error('no actor');\n        const actorInfo = await getActorData(actorUrl, username);\n        inbox = actorInfo.inbox;\n      } else {\n        const url = new URL(target);\n        const parts = url.pathname.split('\/');\n        const usersIdx = parts.indexOf('users');\n        if (usersIdx === -1) throw new Error('invalid target');\n        const actorUrl = `${url.origin}${parts.slice(0, usersIdx + 2).join('\/')}`;\n        const actorInfo = await getActorData(actorUrl, username);\n        inbox = actorInfo.inbox;\n      }\n    } catch (e) {\n      return NextResponse.json({ ok: false, error: 'invalid target post id' }, { status: 400 });\n    }\n\n    const likeRow = db.prepare('SELECT id FROM likes WHERE object = ? AND username = ?').get(apObjectId, username) as any;\n    if (!likeRow) {\n      return NextResponse.json({ ok: true, alreadyUnliked: true });\n    }\n\n    const result = await sendUndoLike(inbox, likeRow.id, apObjectId, username);\n\n    db.prepare('DELETE FROM likes WHERE id = ?').run(likeRow.id);\n\n    return NextResponse.json({ ok: true, result });\n\n  } catch (e: any) {\n    console.error('&#91;Like DELETE]', e);\n    return NextResponse.json({ ok: false, error: e.message }, { status: 500 });\n  }\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f \ucf54\ub4dc\uc218\uc815 \/ Code modification<\/p>\n\n\n\n<p>&#8212; lib\/ap.ts\uc5d0 sendLike, sendUndoLike \ucd94\uac00<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\n... ...\n\n\/\/ \u2705 myapp23 - Like \ubcf4\ub0b4\uae30 \/ Send Like\nexport async function sendLike(toInbox: string, targetPostId: string, username: string) {\n  const actorId = `https:\/\/${DOMAIN}\/users\/${username}`;\n  const likeId = `${actorId}\/likes\/${Date.now()}`;\n\n  const likeDoc = {\n    '@context': 'https:\/\/www.w3.org\/ns\/activitystreams',\n    id: likeId,\n    type: 'Like',\n    actor: actorId,\n    object: targetPostId\n  };\n\n  const body = JSON.stringify(likeDoc);\n  const url = new URL(toInbox);\n  const digest = `SHA-256=${crypto.createHash('sha256').update(body).digest('base64')}`;\n  const date = new Date().toUTCString();\n  const signingString = `(request-target): post ${url.pathname}\\nhost: ${url.host}\\ndate: ${date}\\ndigest: ${digest}`;\n  const signer = crypto.createSign('sha256');\n  signer.update(signingString);\n  const signature = signer.sign(PRIVATE_KEY, 'base64');\n  const keyId = `${actorId}#main-key`;\n  const sigHeader = `keyId=\"${keyId}\",headers=\"(request-target) host date digest\",signature=\"${signature}\"`;\n\n  console.log(`\u2764\ufe0f &#91;${username}] Like \uc804\uc1a1 -&gt; ${toInbox} (${targetPostId})`);\n\n  const res = await fetch(toInbox, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application\/activity+json',\n      'Date': date,\n      'Digest': digest,\n      'Signature': sigHeader,\n      'Host': url.host\n    },\n    body\n  });\n\n  const text = await res.text();\n  console.log(`\ud83d\udcec Like \uacb0\uacfc: ${res.status}`, text);\n  return { ok: res.ok, status: res.status, text, likeDoc };\n}\n\n\/\/ \u2705 myapp23 - Undo Like \ubcf4\ub0b4\uae30 \/ Send Undo Like\nexport async function sendUndoLike(toInbox: string, likeId: string, targetPostId: string, username: string) {\n  const actorId = `https:\/\/${DOMAIN}\/users\/${username}`;\n  const undoId = `${actorId}#undo\/${Date.now()}`;\n\n  const undoDoc = {\n    '@context': 'https:\/\/www.w3.org\/ns\/activitystreams',\n    id: undoId,\n    type: 'Undo',\n    actor: actorId,\n    object: {\n      id: likeId,\n      type: 'Like',\n      actor: actorId,\n      object: targetPostId\n    }\n  };\n\n  const body = JSON.stringify(undoDoc);\n  const url = new URL(toInbox);\n  const digest = `SHA-256=${crypto.createHash('sha256').update(body).digest('base64')}`;\n  const date = new Date().toUTCString();\n  const signingString = `(request-target): post ${url.pathname}\\nhost: ${url.host}\\ndate: ${date}\\ndigest: ${digest}`;\n  const signer = crypto.createSign('sha256');\n  signer.update(signingString);\n  const signature = signer.sign(PRIVATE_KEY, 'base64');\n  const keyId = `${actorId}#main-key`;\n  const sigHeader = `keyId=\"${keyId}\",headers=\"(request-target) host date digest\",signature=\"${signature}\"`;\n\n  console.log(`\ud83d\udc94 &#91;${username}] Undo Like \uc804\uc1a1 -&gt; ${toInbox}`);\n\n  const res = await fetch(toInbox, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application\/activity+json',\n      'Date': date,\n      'Digest': digest,\n      'Signature': sigHeader,\n      'Host': url.host\n    },\n    body\n  });\n\n  const text = await res.text();\n  console.log(`\ud83d\udcec Undo Like \uacb0\uacfc: ${res.status}`, text);\n  return { ok: res.ok, status: res.status, text, undoDoc };\n}\n\n\/\/ \u2705 myapp23 - Actor \ub370\uc774\ud130 \uac00\uc838\uc624\uae30 (inbox\/route, like\/route \uacf5\uc6a9)\nexport async function getActorData(actorUrl: string, username: string) {\n  const res = await signedFetch(actorUrl, username);\n  if (!res.ok) throw new Error(`getActorData failed: ${res.status} ${await res.text()}`);\n  const data = await res.json();\n  return {\n    id: data.id,\n    inbox: data.inbox\n  };\n}<\/code><\/pre>\n\n\n\n<p>&#8212; myapp23\/app\/users\/[username]\/inbox\/route.ts\uc5d0 Like \ucc98\ub9ac \ucd94\uac00<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export async function POST(req: Request, { params }: { params: Promise&lt;{ username: string }&gt; }) {\n... ...\n  if (body.type === 'Undo') {\n       \/\/ Follow Undo\uc778 \uacbd\uc6b0 \/ If it's a Follow Undo\n      if (body.object?.type === 'Follow' || typeof body.object === 'string' || body.object?.id?.includes('#follow')) {\n         const unfollowActorId = body.actor; \/\/ \ub204\uac00 \uc5b8\ud314\ud588\ub294\uc9c0 \/ who unfollowed\n         \/\/db.prepare('DELETE FROM followers WHERE actor = ?').run(unfollowActorId);\n\n      \/\/ \u2705 myapp15 - \uc5b8\ud314\ub85c\uc6b0 \uc2dc username\ub3c4 \uc870\uac74\uc5d0 \ucd94\uac00 \/ Add username condition when unfollowing\n         db.prepare('DELETE FROM followers WHERE actor = ? AND username = ?').run(unfollowActorId, username);\n         console.log(`\ud83d\uddd1\ufe0f &#91;${username}] \uc5b8\ud314\ub85c\uc6b0 \/ unfollow : ${unfollowActorId}`);\n      }\n      \/\/ \u2705 myapp23 - Like Undo\n<strong>      else if (objType === 'Like' || obj?.id?.includes('\/likes\/')) {\n        const likeId = typeof obj === 'string'? obj : obj.id;\n        db.prepare('DELETE FROM likes WHERE id =?').run(likeId);\n        console.log(`\ud83d\udc94 &#91;${username}] Unlike \uc800\uc7a5 \/ unlike : ${likeId} by ${actorId}`);\n      }<\/strong>\n\n  }\n}\n\n... ...\n\n    \/\/ \u2705 myapp23 - \uc88b\uc544\uc694 \ubc1b\uae30 \/ Get Likes !\n    if (body.type === 'Like') {\n      try {\n        const likeId = body.id;\n        const objectId = typeof body.object === 'string'? body.object : body.object?.id;\n        console.log(`\u2764\ufe0f &#91;${username}] Like \ub3c4\ucc29: ${actorId} -&gt; ${objectId}`);\n\n        db.prepare('INSERT OR IGNORE INTO likes (id, actor, object, username) VALUES (?,?,?,?)')\n         .run(likeId, actorId, objectId, username);\n\n        console.log(`\u2705 &#91;${username}] likes \uc800\uc7a5 \uc644\ub8cc: ${likeId}`);\n      } catch (e) {\n        console.error(`\u274c Like \uc800\uc7a5 \uc2e4\ud328\/\nFailed to save 'Like'`, e);\n      }\n      return new Response('', { status: 202 });\n    }<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc2a4\ud2b8 \/ Test<\/p>\n\n\n\n<p>\u2714\ufe0f pinafore\uc5d0\uc11c \uc88b\uc544\uc694 \ub204\ub974\uae30 \/ Like on Pinafore<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"785\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-pinafore-favorite-jpg.jpg\" alt=\"\" class=\"wp-image-8822\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-pinafore-favorite-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-pinafore-favorite-jpg-300x294.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-pinafore-favorite-jpg-768x754.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-pinafore-favorite-jpg-400x393.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">pinafore<\/figcaption><\/figure>\n\n\n\n<p>&#8212; \ub0b4 \uc11c\ubc84 \ub85c\uadf8 \/ My server log<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> GET \/users\/user1\/posts\/c0ee1d4b-b09a-4062-b0e9-9b3b40f50bb3 404 in 328ms (next.js: 266ms, application-code: 62ms)\n\ud83d\udce9 &#91;user1] INBOX: Like https:\/\/freelifemakers.com\/users\/user1\n\u26a0\ufe0f &#91;user1] publicKey \uc5c6\uc74c, \uac80\uc99d \uc2a4\ud0b5 \/ no publicKey, skip verify\n\u2764\ufe0f &#91;user1] Like \ub3c4\ucc29: https:\/\/freelifemakers.com\/users\/user1 -&gt; https:\/\/aloy-horizon.duckdns.org\/users\/user1\/posts\/c0ee1d4b-b09a-4062-b0e9-9b3b40f50bb3\n\u2705 &#91;user1] likes \uc800\uc7a5 \uc644\ub8cc: https:\/\/freelifemakers.com\/users\/user1\/liked\/01M0XS4TFGAA6G2YPYE72P0YRA\n POST \/users\/user1\/inbox 202 in 668ms (next.js: 547ms, application-code: 121ms)\n<\/code><\/pre>\n\n\n\n<p>&#8212; \ub370\uc774\ud130\ubca0\uc774\uc2a4 \/ Database(SQLITE)<\/p>\n\n\n\n<p>1)likes\ud14c\uc774\ube14 \/ likes table<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>myapp23 % sqlite3 data.sqlite\nSQLite version 3.51.0 2025-06-12 13:14:41\nEnter \".help\" for usage hints.\nsqlite&gt; select * from likes;\n<strong>https:\/\/freelifemakers.com\/users\/user1\/liked\/01M0XS4TFGAA6G2YPYE72P0YRA|https:\/\/freelifemakers.com\/users\/user1|https:\/\/aloy-horizon.duckdns.org\/users\/user1\/posts\/c0ee1d4b-b09a-4062-b0e9-9b3b40f50bb3|user1|2026-04-10 00:10:26<\/strong>\nsqlite&gt; <\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f freelifemakers.com\uc758 \uae00\uc5d0 \uc88b\uc544\uc694 \ub204\ub974\uae30 \/ Like posts on freelifemakers.com<\/p>\n\n\n\n<p>&#8212; POST \uba54\uc18c\ub4dc\ub85c \ud2b9\uc815 \ud3ec\uc2a4\ud2b8\uc5d0 \uc88b\uc544\uc694 \ub204\ub974\uae30<br>Like a specific post using the POST method.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/like \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/users\/user1\/statuses\/&#91;POST_ID]\"}'<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>myapp23 % curl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/like \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/users\/user1\/statuses\/01M0XT1VBF23F8Y5EXAPK1VY56\"}'\n{\"ok\":true,\"result\":{\"ok\":true,\"status\":202,\"text\":\"{\\\"status\\\":\\\"Accepted\\\"}\",\"likeDoc\":{\"@context\":\"https:\/\/www.w3.org\/ns\/activitystreams\",\"id\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/likes\/1787708354555\",\"type\":\"Like\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"object\":\"https:\/\/freelifemakers.com\/users\/user1\/statuses\/01M0XT1VBF23F8Y5EXAPK1VY56\"}}}%                                                                                                                                     \ngimdaegyeong@gimdaegyeong-ui-MacBookAir myapp23 % <\/code><\/pre>\n\n\n\n<p>1) \uae30\uc874 \uae00 \uac80\uc0c9 \/ Search existing posts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>myapp23 % sqlite3 data.sqlite\nSQLite version 3.51.0 2025-06-12 13:14:41\nEnter \".help\" for usage hints.\nsqlite&gt; <strong>select * from inbox_posts;<\/strong>\nhttps:\/\/freelifemakers.com\/users\/user1\/statuses\/01M0EJPP57WC47FAK0N395S7MB|https:\/\/freelifemakers.com\/users\/user1|&lt;p&gt;inbox_posts table test&lt;\/p&gt;|user1|2026-08-20 03:16:15|\nhttps:\/\/freelifemakers.com\/users\/user1\/statuses\/01M0KMS8K1XRFW703S32MX6V25|https:\/\/freelifemakers.com\/users\/user1|&lt;p&gt;actor test&lt;\/p&gt;|user1|2026-08-22T11:28:48+09:00|https:\/\/freelifemakers.com\/users\/user1\/statuses\/01M0KMS8K1XRFW703S32MX6V25\n<strong>01M0XT1VBF23F8Y5EXAPK1VY56|https:\/\/freelifemakers.com\/users\/user1|&lt;p&gt;likes test&lt;\/p&gt;|user1|2026-08-26T10:13:17+09:00|https:\/\/freelifemakers.com\/users\/user1\/statuses\/01M0XT1VBF23F8Y5EXAPK1VY56<\/strong>\nsqlite&gt; <\/code><\/pre>\n\n\n\n<p>2) \uc88b\uc544\uc694 \uc2e4\ud589\ud558\uae30 \/ like posts on<\/p>\n\n\n\n<p>A. \ud130\ubbf8\ub110\uc5d0\uc11c \uc2e4\ud589\ud558\uae30 \/ Running in the terminal<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/like \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/users\/user1\/statuses\/01M0XT1VBF23F8Y5EXAPK1VY56\"}'<\/code><\/pre>\n\n\n\n<p>B. \uc751\ub2f5\uba54\uc138\uc9c0(\uc131\uacf5\uc778\uacbd\uc6b0) \/ Response message (in case of success)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\"ok\":true,\"result\":{\"ok\":true,\"status\":202,\"text\":\"{\\\"status\\\":\\\"Accepted\\\"}\",\"likeDoc\":{\"@context\":\"https:\/\/www.w3.org\/ns\/activitystreams\",\"id\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/likes\/1787708354555\",\"type\":\"Like\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"object\":\"https:\/\/freelifemakers.com\/users\/user1\/statuses\/01M0XT1VBF23F8Y5EXAPK1VY56\"}}}% <\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"533\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-gotosocial-like-jpg-1-1024x533.jpg\" alt=\"\" class=\"wp-image-8828\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-gotosocial-like-jpg-1-1024x533.jpg 1024w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-gotosocial-like-jpg-1-300x156.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-gotosocial-like-jpg-1-768x400.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-gotosocial-like-jpg-1-1536x800.jpg 1536w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-gotosocial-like-jpg-1-400x208.jpg 400w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-gotosocial-like-jpg-1-800x417.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp23-gotosocial-like-jpg-1.jpg 1690w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Screenshot<\/figcaption><\/figure>\n\n\n\n<p>C.\ub2e4\ub978 \ub9c8\uc2a4\ud1a0\ub3c8(<a href=\"https:\/\/mastodon.social\/users\/Gargron\/statuses\/114559081070832514\">https:\/\/mastodon.social\/users\/Gargron\/statuses\/114559081070832514<\/a>)\uc758 \uac8c\uc2dc\ubb3c\uc5d0 \uc88b\uc544\uc694 \ub204\ub974\uae30(\uc131\uacf5)<br>Liking a post from another Mastodon instance<br>(https:\/\/mastodon.social\/users\/Gargron\/statuses\/114559081070832514) (Success)<\/p>\n\n\n\n<p>&#8211; \ub2e4\ub978 \ub9c8\uc2a4\ud1a0\ub3c8 \uac8c\uc2dc\ubb3c\uc5d0 \uc88b\uc544\uc694 \uc2e4\ud589\ud558\uae30 \/ Like other Mastodon posts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>myapp23 % curl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/like \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/mastodon.social\/@Gargron\/114559081070832514\"}'<\/code><\/pre>\n\n\n\n<p>&#8211; \uc751\ub2f5\uc131\uacf5 \/ Response successful<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\"ok\":true,\"result\":{\"ok\":true,\"status\":202,\"text\":\"\",\"likeDoc\":{\"@context\":\"https:\/\/www.w3.org\/ns\/activitystreams\",\"id\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/likes\/af39dead-cb1a-45ba-bf9d-f3074921c2a0\",\"type\":\"Like\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"object\":\"https:\/\/mastodon.social\/users\/Gargron\/statuses\/114559081070832514\"}}}%<\/code><\/pre>\n\n\n\n<p>3) \uc88b\uc544\uc694 \ucde8\uc18c \uc2e4\ud589 \/ Perform Undo<\/p>\n\n\n\n<p>A.gotosocial &#8211; freelifemakers.com<\/p>\n\n\n\n<p>&#8211; \uc88b\uc544\uc694 \ucde8\uc18c \uc804\uc1a1 \/ Undo Send<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -X DELETE https:\/\/aloy-horizon.duckdns.org\/api\/like \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/users\/user1\/statuses\/01M0XT1VBF23F8Y5EXAPK1VY56\"}'<\/code><\/pre>\n\n\n\n<p>&#8211; \uc751\ub2f5\uc131\uacf5 \/ Response successful<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\"ok\":true,\"result\":{\"ok\":true,\"status\":202,\"text\":\"{\\\"status\\\":\\\"Accepted\\\"}\",\"undoDoc\":{\"@context\":\"https:\/\/www.w3.org\/ns\/activitystreams\",\"id\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1#undo\/75038dda-f43b-4167-b49a-376ce2a0a29f\",\"type\":\"Undo\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"object\":{\"id\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/likes\/09d8e157-6f0e-4320-af76-2bf337faf2f6\",\"type\":\"Like\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"object\":\"https:\/\/freelifemakers.com\/users\/user1\/statuses\/01M0XT1VBF23F8Y5EXAPK1VY56\"}}}}<\/code><\/pre>\n\n\n\n<p>B.mastodon &#8211; <a href=\"https:\/\/mastodon.social\/users\/Gargron\/statuses\/114559081070832514\">https:\/\/mastodon.social\/users\/Gargron\/statuses\/114559081070832514<\/a><\/p>\n\n\n\n<p>&#8211; \uc88b\uc544\uc694 \ucde8\uc18c \uc804\uc1a1 \/ Undo Send<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -X DELETE https:\/\/aloy-horizon.duckdns.org\/api\/like \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/mastodon.social\/@Gargron\/114559081070832514\"}'<\/code><\/pre>\n\n\n\n<p>&#8211; \uc751\ub2f5\uc131\uacf5 \/ Response successful<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> {\"ok\":true,\"result\":{\"ok\":true,\"status\":202,\"text\":\"\",\"undoDoc\":{\"@context\":\"https:\/\/www.w3.org\/ns\/activitystreams\",\"id\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1#undo\/9aabf284-68a7-42a2-9761-1b7ee0b7bcb2\",\"type\":\"Undo\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"object\":{\"id\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/likes\/af39dead-cb1a-45ba-bf9d-f3074921c2a0\",\"type\":\"Like\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"object\":\"https:\/\/mastodon.social\/users\/Gargron\/statuses\/114559081070832514\"}}}}%<\/code><\/pre>\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\uc88b\uc544\uc694\uc640 \uc88b\uc544\uc694 \ucde8\uc18c\uae30\ub2a5\uc744 \uad6c\ud604\ud569\ub2c8\ub2e4.Implement the &#8220;like&#8221; and &#8220;unlike&#8221; functions. \ud83d\udcc1 \uc804\uccb4 \ud504\ub85c\uc81d\ud2b8 \uad6c\uc870 \/ Overall Project Structure \ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc2dc\uc791(myapp23)Project Start (myapp23) \ud83d\udcc1 SQLite\uc124\uce58 \/ Installing SQLite \ud83d\udcc1 DDNS,https\uc124\uc815 \/ DDNS,https settings \ud83d\udcc1 \ub3c4\uba54\uc778\ud5c8\uc6a9, @userid \uc0ac\uc6a9\ud558\uae30Allowing domains, using @userid &#8212; next.config.ts \ud83d\udcc1 \ud14c\uc774\ube14 \ucd94\uac00 \/ Add table \u2714\ufe0f \ud14c\uc774\ube14 \ubc0f \uc778\ub371\uc2a4 \uc0dd\uc131(sqlite)Create Table and Index [&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-8758","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\/8758","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=8758"}],"version-history":[{"count":63,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/8758\/revisions"}],"predecessor-version":[{"id":8829,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/8758\/revisions\/8829"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=8758"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=8758"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=8758"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}