{"id":8830,"date":"2026-08-27T08:07:09","date_gmt":"2026-08-26T23:07:09","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=8830"},"modified":"2026-08-27T11:07:58","modified_gmt":"2026-08-27T02:07:58","slug":"nextjs-sns-server-14","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/08\/27\/nextjs-sns-server-14\/","title":{"rendered":"[nextjs]SNS Server-14(myapp24)"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb myapp24\uc5d0\uc11c\ub294 Announce (Boost)&nbsp;\ub97c \uad6c\ud604\ud569\ub2c8\ub2e4.<br>myapp24 implements Announce (Boost).<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \ubd80\uc2a4\ud2b8\ub294 \ub2e4\ub978 \uc0ac\ub78c\uc774 \uc4f4 \uae00\uc744 \ub0b4 \ud314\ub85c\uc6cc\uc5d0\uac8c \uc54c\ub9ac\ub294 \uae30\ub2a5\uc785\ub2c8\ub2e4.<br>Boost is a feature that shares posts written by others with your followers.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \ub0b4\uac00 \uc4f4 \uae00\uc740 \ubd80\uc2a4\ud2b8\ub97c \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.<br>I cannot boost the posts I have written.<\/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>myapp24\/  \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\/follow\/route.ts    -&gt; Follow API(temporary)\n\u2502   \u251c\u2500\u2500 <strong>api\/announce\/route.ts  -&gt; Boost(Announcement)<\/strong>\n\u2502   \u251c\u2500\u2500 api\/like\/route.ts.     -&gt; Like API\n\u2502   \u251c\u2500\u2500 api\/posts\/route.ts     -&gt; Writing API\n\u2502   \u251c\u2500\u2500 api\/timeline\/route.ts  -&gt; Timeline API\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(myapp24)<br>Project Start (myapp24)<\/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 ~\/myapp24\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=\"yjBziA988q\"><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=nockmlTyBQ#?secret=yjBziA988q\" data-secret=\"yjBziA988q\" 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<pre class=\"wp-block-code\"><code>CREATE TABLE IF NOT EXISTS announces (\n  id TEXT PRIMARY KEY, \n  username TEXT, \n  object TEXT, \n  created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n);\nCREATE INDEX IF NOT EXISTS idx_announces_obj_user ON announces(object, username);\nCREATE INDEX IF NOT EXISTS idx_announces_username ON announces(username);<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ub77c\uc6b0\ud2b8\ucd94\uac00 \/ Add route<\/p>\n\n\n\n<p>\u2714\ufe0f myapp24\/<code>app\/api\/announce\/route.ts<\/code>&nbsp;\uc0dd\uc131<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ myapp24\/app\/api\/announce\/route.ts\n\/\/ myapp24 \u2705\n\nimport { NextRequest, NextResponse } from 'next\/server';\nimport db from '@\/lib\/db';\nimport { signedFetch, getActorData, sendAnnounce, sendUndoAnnounce } from '@\/lib\/ap';\nconst DOMAIN = process.env.DOMAIN || 'aloy-horizon.duckdns.org'; \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    if (target.startsWith(`https:\/\/${DOMAIN}\/users\/${username}`)) {\n      return NextResponse.json({ ok: false, error: 'cannot boost own post' }, { 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 existing = db.prepare('SELECT id FROM announces WHERE object = ? AND username = ?').get(apObjectId, username) as any;\n    if (existing) {\n      return NextResponse.json({ ok: true, alreadyAnnounced: true, id: existing.id });\n    }\n\n    const result = await sendAnnounce(inbox, apObjectId, username);\n\n    if (result.ok) {\n      db.prepare('INSERT INTO announces (id, username, object) VALUES (?, ?, ?)').run(result.announceDoc.id, username, apObjectId);\n    }\n\n    return NextResponse.json({ ok: true, result });\n\n  } catch (e: any) {\n    console.error('&#91;Announce 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 row = db.prepare('SELECT id FROM announces WHERE object = ? AND username = ?').get(apObjectId, username) as any;\n    if (!row) {\n      return NextResponse.json({ ok: true, alreadyUnAnnounced: true });\n    }\n\n    const result = await sendUndoAnnounce(inbox, row.id, apObjectId, username);\n    db.prepare('DELETE FROM announces WHERE id = ?').run(row.id);\n\n    return NextResponse.json({ ok: true, result });\n\n  } catch (e: any) {\n    console.error('&#91;Announce DELETE]', e);\n    return NextResponse.json({ ok: false, error: e.message }, { status: 500 });\n  }\n}<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ucf54\ub4dc \uc218\uc815 \/  Code Modification<\/p>\n\n\n\n<p>\u2714\ufe0fmyapp24\/lib\/db.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>db.exec(`\n  -- myapp24 \u2705  Boost(Announcement)\n  CREATE TABLE IF NOT EXISTS announces (\n    id TEXT PRIMARY KEY, \n    username TEXT, \n    object TEXT, \n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  );\n`);\ndb.exec(`\n... ...\n  -- myapp24 \u2705\n  CREATE INDEX IF NOT EXISTS idx_announces_obj_user ON announces(object, username);\n  CREATE INDEX IF NOT EXISTS idx_announces_username ON announces(username);\n`);<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0fmyapp24\/lib\/ap.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>... ...\n\n\/\/ getActorData\uc544\ub798\uc5d0 \ucf54\ub4dc \ucd94\uac00\ud558\uae30\n\/\/ Add code below getActorData\n\n\/\/ \u2705 myapp24 - Send Announce(Boost) \nexport async function sendAnnounce(toInbox: string, targetPostId: string, username: string) {\n  const actorId = `https:\/\/${DOMAIN}\/users\/${username}`;\n  const announceId = `${actorId}\/announces\/${crypto.randomUUID()}`;\n\n  const doc = {\n    '@context': 'https:\/\/www.w3.org\/ns\/activitystreams',\n    id: announceId,\n    type: 'Announce',\n    actor: actorId,\n    object: targetPostId,\n    published: new Date().toISOString(),\n    to: &#91;'https:\/\/www.w3.org\/ns\/activitystreams#Public'],\n    cc: &#91;`${actorId}\/followers`, `${actorId}`],\n  };\n\n  const body = JSON.stringify(doc);\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\udd01 &#91;${username}] Announce \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 Announce \uacb0\uacfc: ${res.status}`, text);\n  return { ok: res.ok, status: res.status, text, announceDoc: doc };\n}\n\n\/\/ \u2705 myapp24 - Undo Announce\nexport async function sendUndoAnnounce(toInbox: string, announceId: string, targetPostId: string, username: string) {\n  const actorId = `https:\/\/${DOMAIN}\/users\/${username}`;\n  const undoId = `${actorId}#undo\/${crypto.randomUUID()}`;\n\n  const undoDoc = {\n    '@context': 'https:\/\/www.w3.org\/ns\/activitystreams',\n    id: undoId,\n    type: 'Undo',\n    actor: actorId,\n    published: new Date().toISOString(),\n    to: &#91;'https:\/\/www.w3.org\/ns\/activitystreams#Public'],\n    cc: &#91;`${actorId}\/followers`],\n    object: {\n      id: announceId,\n      type: 'Announce',\n      actor: actorId,\n      object: targetPostId,\n      published: new Date().toISOString(),\n      to: &#91;'https:\/\/www.w3.org\/ns\/activitystreams#Public'],\n      cc: &#91;`${actorId}\/followers`],\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(`\u21a9 &#91;${username}] Undo Announce \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 Announce \uacb0\uacfc: ${res.status}`, text);\n  return { ok: res.ok, status: res.status, text, undoDoc };\n}<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc2a4\ud2b8 \/ Test<\/p>\n\n\n\n<p>\u2714\ufe0f POST,DELETE \uba54\uc18c\ub4dc \ub77c\uc6b0\ud2b8 <br>POST and DELETE method routes<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># -- freelifemakers.com(gotosocial) --\n# \ubd80\uc2a4\ud2b8 \/ Boost\n\ncurl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/@user1\/statuses\/01M10AHFHCKGF9BY5G4H50VSHG\"}'\n\n\n# \ubd80\uc2a4\ud2b8 \ucde8\uc18c \/ Cancle Boost\n\ncurl -X DELETE https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/@user1\/statuses\/01M10AHFHCKGF9BY5G4H50VSHG\"}'\n\n# -- mastodon server ---\n# \ubd80\uc2a4\ud2b8 \/ Boost\n\ncurl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/mastodon.social\/@mcnees\/117089028658055432\"}'\n\n# \ubd80\uc2a4\ud2b8 \ucde8\uc18c \/ Cancle Boost\n\ncurl -X DELETE https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/mastodon.social\/@mcnees\/117089028658055432\"}'\n\n# \ub0b4 \uae00 \ubd80\uc2a4\ud2b8 \uc2dc\ub3c4 -&gt; 400 \uc5d0\ub7ec \ub098\uc57c \uc815\uc0c1\n# Attempting to boost my post -&gt; A 400 error is the expected result.\n\ncurl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/statuses\/e6c99652-572f-491a-9e61-05e67b0d58fc\"}'<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f GTS Server<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># GTS Boost\n\nmyapp24 % <strong>curl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/@user1\/statuses\/01M10AHFHCKGF9BY5G4H50VSHG\"}'<\/strong>\n\n{\"ok\":true,\"result\":{\"ok\":true,\"status\":202,\"text\":\"{\\\"status\\\":\\\"Accepted\\\"}\",\"announceDoc\":{\"@context\":\"https:\/\/www.w3.org\/ns\/activitystreams\",\"id\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/announces\/66cb2e85-0bd0-4e6c-856a-d77070cc3807\",\"type\":\"Announce\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"object\":\"https:\/\/freelifemakers.com\/users\/user1\/statuses\/01M10AHFHCKGF9BY5G4H50VSHG\",\"published\":\"2026-08-27T00:50:15.162Z\",\"to\":&#91;\"https:\/\/www.w3.org\/ns\/activitystreams#Public\"],\"cc\":&#91;\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/followers\",\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\"]}}}%                                                              \ngimdaegyeong@gimdaegyeong-ui-MacBookAir myapp24 %   \n\n# GTS Boost Cancle  \n\nmyapp24 % <strong>curl -X DELETE https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/@user1\/statuses\/01M10AHFHCKGF9BY5G4H50VSHG\"}'<\/strong>\n\n{\"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\/34cb45a0-c1f1-4bcb-bb64-2cb9a0aba982\",\"type\":\"Undo\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"published\":\"2026-08-27T00:53:42.406Z\",\"to\":&#91;\"https:\/\/www.w3.org\/ns\/activitystreams#Public\"],\"cc\":&#91;\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/followers\"],\"object\":{\"id\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/announces\/66cb2e85-0bd0-4e6c-856a-d77070cc3807\",\"type\":\"Announce\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"object\":\"https:\/\/freelifemakers.com\/users\/user1\/statuses\/01M10AHFHCKGF9BY5G4H50VSHG\",\"published\":\"2026-08-27T00:53:42.406Z\",\"to\":&#91;\"https:\/\/www.w3.org\/ns\/activitystreams#Public\"],\"cc\":&#91;\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/followers\"]}}}}%   <\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"450\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp24-gts-boost-jpg.jpg\" alt=\"\" class=\"wp-image-8894\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp24-gts-boost-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp24-gts-boost-jpg-300x169.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp24-gts-boost-jpg-768x432.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp24-gts-boost-jpg-400x225.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">GTS Boost<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f Mastodon Server<\/p>\n\n\n\n<p>&#8212; \uac8c\uc2dc\ubb3c \uc8fc\uc18c \/ Post address<\/p>\n\n\n\n<p><a href=\"https:\/\/mastodon.social\/@mcnees\/117089028658055432\">https:\/\/mastodon.social\/@mcnees\/117089028658055432<\/a><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Boost\n\nmyapp24 % <strong>curl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/mastodon.social\/@mcnees\/117089028658055432\"}'<\/strong>\n\n{\"ok\":true,\"result\":{\"ok\":true,\"status\":202,\"text\":\"\",\"announceDoc\":{\"@context\":\"https:\/\/www.w3.org\/ns\/activitystreams\",\"id\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/announces\/827c782f-9422-43d4-bc14-0fed1cf648b6\",\"type\":\"Announce\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"object\":\"https:\/\/mastodon.social\/users\/mcnees\/statuses\/117089028658055432\",\"published\":\"2026-08-27T00:58:19.780Z\",\"to\":&#91;\"https:\/\/www.w3.org\/ns\/activitystreams#Public\"],\"cc\":&#91;\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/followers\",\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\"]}}}%                                                                                                 \ngimdaegyeong@gimdaegyeong-ui-MacBookAir myapp24 % \n\n# Cancle Boost\n\nmyapp24 % <strong>curl -X DELETE https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/mastodon.social\/@mcnees\/117089028658055432\"}'<\/strong>\n{\"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\/3c643e41-dcb6-4e5c-ae6c-21c0ab5820b3\",\"type\":\"Undo\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"published\":\"2026-08-27T00:59:54.564Z\",\"to\":&#91;\"https:\/\/www.w3.org\/ns\/activitystreams#Public\"],\"cc\":&#91;\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/followers\"],\"object\":{\"id\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/announces\/827c782f-9422-43d4-bc14-0fed1cf648b6\",\"type\":\"Announce\",\"actor\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\",\"object\":\"https:\/\/mastodon.social\/users\/mcnees\/statuses\/117089028658055432\",\"published\":\"2026-08-27T00:59:54.564Z\",\"to\":&#91;\"https:\/\/www.w3.org\/ns\/activitystreams#Public\"],\"cc\":&#91;\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/followers\"]}}}}%                                                                                                                                                         \n<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"443\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp24-mastodon-boost-jpg.jpg\" alt=\"\" class=\"wp-image-8896\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp24-mastodon-boost-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp24-mastodon-boost-jpg-300x166.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp24-mastodon-boost-jpg-768x425.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp24-mastodon-boost-jpg-400x222.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Mastodon Boost<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f \ub0b4 \uae00 \ubd80\uc2a4\ud2b8 \ucc28\ub2e8\ud655\uc778 \/ Check if my post is blocked from the Boost feature<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>myapp24 % <strong>curl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/statuses\/e6c99652-572f-491a-9e61-05e67b0d58fc\"}'<\/strong>\n\n{\"ok\":false,\"error\":\"cannot boost own post\"}%                                                                                                                           \n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\ud83d\udcc1 \uc9c0\uae08\uae4c\uc9c0 \uc791\uc5c5\ud55c \ub0b4\uc6a9\uacfc \ub77c\uc6b0\ud2b8 \uc785\ub2c8\ub2e4.<br>Here is the work done so far and the routes.<\/p>\n\n\n\n<p>\u2714\ufe0f \ube0c\ub77c\uc6b0\uc800\uc5d0\uc11c \ud14c\uc2a4\ud2b8 \ud560 \uc218 \uc788\ub294 \ub77c\uc6b0\ud2b8<br>Routes that can be tested in a browser<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># 1. WebFinger\nhttps:&#47;&#47;aloy-horizon.duckdns.org\/.well-known\/webfinger?resource=acct:user1@aloy-horizon.duckdns.org\n\n# 2. Actor (\ud504\ub85c\ud544 \/ Profile)\nhttps:\/\/aloy-horizon.duckdns.org\/users\/user1\n\n# 3. Followers (\ub0a8\uc774 \ub098 \ud314\ub85c\uc6b0 \/ Someone follows me)\nhttps:\/\/aloy-horizon.duckdns.org\/users\/user1\/followers\n\n# 4. Following (\ub0b4\uac00 \ub0a8\uc744 \ud314\ub85c\uc6b0 \/ I follow others)\nhttps:\/\/aloy-horizon.duckdns.org\/users\/user1\/following\n\n# 5. Outbox (\ub0b4 \uae00 \ubaa9\ub85d \/ My post list)\nhttps:\/\/aloy-horizon.duckdns.org\/users\/user1\/outbox\n\n# 6. \ud0c0\uc784\ub77c\uc778 \/ Timeline\n\n# - \ud0c0\uc784\ub77c\uc778UI \/ Timeline UI\nhttps:\/\/aloy-horizon.duckdns.org\/usersui\/user1\n# or\nhttps:\/\/aloy-horizon.duckdns.org\/@user1\n\n# - \ud0c0\uc784\ub77c\uc778API \/ Timeline API\nhttps:\/\/aloy-horizon.duckdns.org\/api\/timeline?username=user1\n\n# 7. \uac1c\ubcc4 \uae00(\uae00 \uc0c1\uc138) \/ indivisual posts(Post Details)&#91;posts, inbox_posts]\nhttps:\/\/aloy-horizon.duckdns.org\/users\/user1\/statuses\/&#91;ID]\n\n# ex)\n# posts table\nhttps:\/\/aloy-horizon.duckdns.org\/users\/user1\/statuses\/e6c99652-572f-491a-9e61-05e67b0d58fc\n\n# inbox_posts table\nhttps:\/\/aloy-horizon.duckdns.org\/users\/user1\/statuses\/01M0P8VYEQTFW28N97W6MG8PY5\n<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f \ud130\ubbf8\ub110\uc5d0\uc11c\ub9cc \ud14c\uc2a4\ud2b8 \uac00\ub2a5\ud55c \ub77c\uc6b0\ud2b8<br>Routes that can only be tested in the terminal<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># \ud314\ub85c\uc6b0 (following 1\ub85c \uc99d\uac00)\n# Follow (following increased to 1)\n\ncurl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/follow \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/users\/user1\"}'\n\n# \uc5b8\ud314\ub85c\uc6b0 (following 0\uc73c\ub85c \uac10\uc18c)\n# Unfollow (following count reduced to 0)\n\ncurl -X DELETE https:\/\/aloy-horizon.duckdns.org\/api\/follow \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/users\/user1\"}'\n\n# \uae00 \uc4f0\uae30 (outbox 7\ub85c \uc99d\uac00 + \ud314\ub85c\uc6cc\ub4e4\uc5d0\uac8c \ubc30\ub2ec)\n# Writing a post (added to outbox 7 + delivered to followers)\n\ncurl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/posts \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"username\":\"user1\",\"content\":\"Hello Fediverse! #test\"}'\n\n# \uc88b\uc544\uc694 \uc2e4\ud589 \/ Likes\ncurl -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\n# \uc88b\uc544\uc694 \ucde8\uc18c\ud558\uae30 \/ Undo Likes\ncurl -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\"}'\n\n# \ubd80\uc2a4\ud2b8 \uc2e4\ud589 \/ Boost\ncurl -X POST https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/@user1\/statuses\/01M10AHFHCKGF9BY5G4H50VSHG\"}'\n\n# \ubd80\uc2a4\ud2b8 \ucde8\uc18c \/ Cancle Boost\ncurl -X DELETE https:\/\/aloy-horizon.duckdns.org\/api\/announce \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"username\":\"user1\",\"target\":\"https:\/\/freelifemakers.com\/@user1\/statuses\/01M10AHFHCKGF9BY5G4H50VSHG\"}'<\/code><\/pre>\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\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udc49\ud83c\udffb myapp24\uc5d0\uc11c\ub294 Announce (Boost)&nbsp;\ub97c \uad6c\ud604\ud569\ub2c8\ub2e4.myapp24 implements Announce (Boost). \ud83d\udc49\ud83c\udffb \ubd80\uc2a4\ud2b8\ub294 \ub2e4\ub978 \uc0ac\ub78c\uc774 \uc4f4 \uae00\uc744 \ub0b4 \ud314\ub85c\uc6cc\uc5d0\uac8c \uc54c\ub9ac\ub294 \uae30\ub2a5\uc785\ub2c8\ub2e4.Boost is a feature that shares posts written by others with your followers. \ud83d\udc49\ud83c\udffb \ub0b4\uac00 \uc4f4 \uae00\uc740 \ubd80\uc2a4\ud2b8\ub97c \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.I cannot boost the posts I have written. \ud83d\udcc1 \uc804\uccb4 \ud504\ub85c\uc81d\ud2b8 \uad6c\uc870 \/ Overall Project Structure \ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 [&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-8830","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\/8830","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=8830"}],"version-history":[{"count":66,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/8830\/revisions"}],"predecessor-version":[{"id":8900,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/8830\/revisions\/8900"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=8830"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=8830"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=8830"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}