{"id":11067,"date":"2026-09-26T07:51:42","date_gmt":"2026-09-25T22:51:42","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=11067"},"modified":"2026-09-26T14:07:03","modified_gmt":"2026-09-26T05:07:03","slug":"nextjs-sns-server-39","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/26\/nextjs-sns-server-39\/","title":{"rendered":"[nextjs]SNS Server-39(myapp49)\u00a0"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb myapp49\uc5d0\uc11c\ub294 myapp48\uae4c\uc9c0 \uae30\ub2a5 \ucd94\uac00 \ub418\uba74\uc11c \ubc1c\uc0dd\ud55c \uc624\ub958\ub098 \ubc84\uadf8\ub97c \uc218\uc815\ud569\ub2c8\ub2e4.<br>myapp49 fixes errors and bugs that arose during the addition of features up to myapp48.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \ubd80\uc2a4\ud2b8 ,\uc88b\uc544\uc694 \ubc30\ub2ec \ubd80\ubd84 \uc624\ub958\ub97c \uc218\uc815\ud569\ub2c8\ub2e4.<br>Fixed errors related to Boosts, Likes, and delivery.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \uc678\ubd80\uc571\uc5d0\uc11c \uae00\uc4f0\uac70\ub098 \uc0ad\uc81c\ud558\ub294 API\ub97c \ud130\ubbf8\ub110\uc5d0\uc11c \uc0ac\uc6a9\ud560 \uacbd\uc6b0 \ud1a0\ud070\uc744 \uc785\ub825\ud574\uc57c\ud569\ub2c8\ub2e4.<br>You must enter a token when using the API to create or delete posts from an external app via the terminal.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \uc804\uccb4 \ucf54\ub4dc\ub294 \uae43\ud5c8\ube0c\uc5d0\uc11c \ud655\uc778 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<br>You can find the full code on GitHub.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/gideonslife01\/flm-nextjs\">https:\/\/github.com\/gideonslife01\/flm-nextjs<\/a><\/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\" style=\"font-size:14px\"><code>myapp project\/  \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 api\/announce\/route.ts  -&gt; Boost(Announcement)\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 api\/followinglist\/route.ts  -&gt; followinglist API\n\u2502   \u251c\u2500\u2500 api\/followerslist\/route.ts  -&gt; followerslist API\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/instance<\/span>\/route.ts -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/apps\/route.ts.    -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/verify_credentials\/route.ts -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/statuses\/route.ts -&gt; Write Post\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/timelines\/home\/route.ts<\/span> -&gt; timeline\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/authorize\/route.ts<\/span> -&gt; auth\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/token\/route.ts<\/span> -&gt; auth\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/search\/home\/route.ts<\/span> -&gt; search\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v2\/search\/home\/route.ts<\/span> -&gt; search\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/&#91;id]\/followers\/route.ts -&gt; followers\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/&#91;id]\/following\/route.ts -&gt; following\n\u2502   \u251c\u2500\u2500 api\/v1\/statuses\/&#91;id]\/route.ts -&gt; -&gt; Post delete(pinafore)\n\u2502   \u251c\u2500\u2500 api\/auth\/signup\/route.ts        -&gt; Signup\n\u2502   \u251c\u2500\u2500 api\/auth\/login\/route.ts         -&gt; Login\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/logout\/route.ts        -&gt; Logout\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/refresh\/route.ts       -&gt; Refresh Token\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/cleanup-orphan\/route.ts     -&gt; post,outbox clean up\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/me\/route.tsx           -&gt; Login Check<strong>\n<\/strong>\u2502   \u251c\u2500\u2500 auth\/signup\/page.tsx            -&gt; Signup UI\n\u2502   \u251c\u2500\u2500 auth\/signup\/page.tsx            -&gt; Login UI\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]\/\n\u2502   \u2502   \u251c\u2500\u2500 page.tsx               -&gt; Timeline UI(Server, 404)\n\u2502   \u2502   \u251c\u2500\u2500 ClientPage.tsx.        -&gt; Timeline UI(Client,Call Active Themes)\n\u2502   \u2502   \u2514\u2500\u2500 _components\/themes\/\n\u2502   \u2502       \u251c\u2500\u2500 themeex\/ThemeexTheme.tsx   -&gt; Example Theme\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/PinaforeTheme.tsx -&gt; Theme 1\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/FollowersList.tsx -&gt; Theme 1, FollowersList\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/FollowingList.tsx -&gt; Theme 1, FollowingList\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/NotificationsList.tsx -&gt; Theme 1, NotificationsList\n\u2502   \u2502       \u251c\u2500\u2500 mastodon\/MastodonTheme.tsx -&gt; Theme 2\n\u2502   \u2502       \u2514\u2500\u2500 minimal\/MinimaltTheme.tsx  -&gt; Theme 3\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 theme.tsx              -&gt; Theme Provider\n\u2502   \u251c\u2500\u2500 watchThemes.ts         -&gt; Check real-time theme changes\n\u2502   \u251c\u2500\u2500 auth.ts                -&gt; Authentication, User Management\n\u2502   \u251c\u2500\u2500 ap.ts                  -&gt; Follow,Undo,Create,Likes,Announce\n\u2502   \u251c\u2500\u2500 visibility.ts          -&gt; Visibility(Public,Unlisted,Private,Direct)\n\u2502   \u2514\u2500\u2500 db.ts                  -&gt; DB connection\n\u251c\u2500\u2500 data\/\n\u2502   \u251c\u2500\u2500 keys\/userIDs\/          -&gt; private.pem, public.pem(New)\n\u2502   \u2514\u2500\u2500 keys\/                  -&gt; private.pem, public.pem(legacy)\n\u251c\u2500\u2500 data.sqlite                -&gt; Database(1\/3)\n\u251c\u2500\u2500 data.sqlite-wal            -&gt; Database(2\/3)\n\u251c\u2500\u2500 data.sqlite-shm            -&gt; Database(3\/3)\n\u251c\u2500\u2500 Caddyfile                  -&gt; https \n\u251c\u2500\u2500 instrumentation.ts         -&gt; Background Server\n\u2514\u2500\u2500 package.json<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc2dc\uc791 \/ Project Start<\/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=\"8y71qJ0oal\"><a href=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/03\/nextjs-project-summary\/\">[nextjs]\ud504\ub85c\uc81d\ud2b8\uc694\uc57d\/project summary(myapp30+)<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;[nextjs]\ud504\ub85c\uc81d\ud2b8\uc694\uc57d\/project summary(myapp30+)&#8221; &#8212; freelifemakers.org\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/03\/nextjs-project-summary\/embed\/#?secret=nwbyjzdvuc#?secret=8y71qJ0oal\" data-secret=\"8y71qJ0oal\" width=\"560\" height=\"315\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\ud83d\udcc1 \ucf54\ub4dc \uc218\uc815 \/ Code Modification<\/p>\n\n\n\n<p>\u2714\ufe0f \ub0b4 \uc11c\ubc84\uc5d0\uc11c \uc6d0\ubcf8\uae00 \uc0ad\uc81c\uc2dc \ub9ac\ubaa8\ud2b8 \uc11c\ubc84\uc5d0\uc11c \uc0ad\uc81c\ub418\uc9c0 \uc54a\ub294 \ubb38\uc81c \uc218\uc815<br>Fixed an issue where deleting the original post on my server did not delete it from the remote server.<\/p>\n\n\n\n<p>&#8212; app\/api\/posts\/route.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>  \/\/const noteId = `https:\/\/${DOMAIN}\/users\/${username}\/posts\/${id}`; \/\/ \u2705 \uc804\uccb4 URL \ub9cc\ub4e4\uae30 \/ Create full URL\n\n  \/\/ \u2705 myapp49 - bug fix\n  const noteId = `https:\/\/${DOMAIN}\/users\/${username}\/statuses\/${id}`;<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f \ubd80\uc2a4\ud2b8  \ubc0f \uc88b\uc544\uc694 \uc624\ub958 \uc218\uc815<br>Fixed Boost and Like errors.<\/p>\n\n\n\n<p>&#8212; \uace0\ud22c\uc18c\uc15c \uc6d0\ubcf8 \uae00\uc5d0 \uc88b\uc544\uc694,\ubd80\uc2a4\ud2b8\ub97c \ub0b4 \uc11c\ubc84\uc758 \ubc30\ub2ec\ub41c \uae00\uc5d0 \uc2e4\ud589\ud55c\uacbd\uc6b0 \uc6d0\ubcf8 \uae00 \uc0ad\uc81c\uc2dc \ub0b4\uc11c\ubc84\uc758 \ubc30\ub2ec\ub41c\uae00\uacfc \uc88b\uc544\uc694,\ubd80\uc2a4\ud2b8\ub3c4 \uac19\uc774 \uc0ad\uc81c<br>If you &#8220;like&#8221; or &#8220;boost&#8221; an original GoToSocial post and that action is applied to the copy delivered to your server, deleting the original post will also delete the delivered copy along with the associated likes and boosts.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>if (body.type === 'Delete') {\n... ...\nif (objectId) {\n... ...\n              \/\/ \u2705 myapp49 - \ubc30\ub2ec\ub41c \uae00\uc758 \uc6d0\ubcf8 \uc0ad\uc81c\uc2dc \ubd80\uc2a4\ud2b8 \uc88b\uc544\uc694 \uc0ad\uc81c \n              \/\/ Deleting the original delivered post removes the 'Boost Like'.\n\n              \/\/ - \uc88b\uc544\uc694 \uc815\ub9ac \/ delete likes\n              const likeDel = db.prepare(`DELETE FROM likes WHERE object LIKE? OR object LIKE?`).run(`%${shortId}%`, `%${canonicalId}%`);\n              if (likeDel.changes &gt; 0) {\n                console.log(` -&gt; likes \uc815\ub9ac: ${likeDel.changes}\uac1c \uc0ad\uc81c (\uc6d0\ubcf8 ${shortId})`);\n              }\n\n              \/\/ - \ubd80\uc2a4\ud2b8 \uc815\ub9ac \/ delete boost(announce)\n              const announceDel = db.prepare(`DELETE FROM announces WHERE object LIKE? OR object LIKE?`).run(`%${shortId}%`, `%${canonicalId}%`);\n              if (announceDel.changes &gt; 0) {\n                console.log(` -&gt; announces \uc815\ub9ac: ${announceDel.changes}\uac1c \uc0ad\uc81c (\uc6d0\ubcf8 ${shortId})`);\n              }\n... ...\n}\n... ...\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f \ubd80\uc2a4\ud2b8 \ub77c\ubca8 \uc218\uc815 \/ Edit Boost Label<\/p>\n\n\n\n<p>&#8212;  inbox_posts\ub85c \ubc30\ub2ec\ub418\ub294 \ubaa8\ub4e0\uae00\uc5d0 boosted \ub77c\ubca8\uc774 \ubd99\ub294 \ubb38\uc81c \uc218\uc815<br>Fixed an issue where all posts delivered to inbox_posts were labeled as &#8220;boosted.&#8221;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    const boostCount = (db.prepare(\n      `SELECT COUNT(*) as c FROM announces WHERE object =? OR object =? OR object =? OR object LIKE '%' ||? || '%'`\n    ).get(fullId, p.original_id || '', p.id || '', shortId) as any)?.c || 0;\n\n    \/\/ \u2705 myapp49- announces.object\uc5d0 \uc6d0\ubcf8 \uc788\uc73c\uba74 true (\ub124\uac00 \ub9d0\ud55c \ub85c\uc9c1)\n    const isBoostedPost = boostCount &gt; 0 ? true : false;<\/code><\/pre>\n\n\n\n<p>1)announces\ud14c\uc774\ube14\uc5d0\uc11c id\ub294 \ubd80\uc2a4\ud2b8\ud55c\uc0ac\ub78c \uc804\uccb4 URL,object\ub294 \uc6d0\ubcf8\uae00URL, actor\ub294 \ubd80\uc2a4\ud2b8\ud55c \uc0ac\uc6a9\uc790 \uacc4\uc815 \uc8fc\uc18c<br>In the <code>announces<\/code> table, <code>id<\/code> is the full URL of the boost, <code>object<\/code> is the URL of the original post, and <code>actor<\/code> is the account address of the user who performed the boost.<\/p>\n\n\n\n<p>2) announces\ud14c\uc774\ube14\uc758 object\ud544\ub4dc, inbox_posts \ud14c\uc774\ube14\uc758 id, original_id\ub294 \uc6d0\ubcf8 \uae00\uc744 \uac00\ub9ac\ud0b4<br>The <code>object<\/code> field in the <code>announces<\/code> table, as well as the <code>id<\/code> and <code>original_id<\/code> fields in the <code>inbox_posts<\/code> table, refer to the original post.<\/p>\n\n\n\n<p>\u2714\ufe0f \uc88b\uc544\uc694 \uc2e4\ud589  \ubc0f \uc88b\uc544\uc694\ucde8\uc18c<br>Like and Unlike<\/p>\n\n\n\n<p>&#8212; \uc88b\uc544\uc694 \ubd80\ubd84\uc5d0\uc11c \ub0b4 \uae00\uc778\uc9c0 \uccb4\ud06c\ud558\ub294 \ubd80\ubd84 \ub3c4\uba54\uc778\ucd94\uac00<br>Added the domain check to verify if the post belongs to the user in the &#8220;Like&#8221; section.<\/p>\n\n\n\n<p>&#8212; \ubc30\uc1a1\ub41c \uc88b\uc544\uc694 \uc0ad\uc81c \ub85c\uc9c1 \uc218\uc815<br>Fixed logic for deleting delivered &#8216;likes&#8217;<\/p>\n\n\n\n<p>&#8212; app\/api\/like\/route.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>export async function POST(req: NextRequest) {\n... ...\nconst isOwnPost = target.includes(`${DOMAIN}\/users\/${username}\/`); \/\/ \u2705 myapp49 \ub3c4\uba54\uc778\ucd94\uac00 \/ add DOMAIN\n... ...\n}\n\nexport async function DELETE(req: NextRequest) {\n  const { username, target } = await req.json();\n  const myActor = `https:\/\/${DOMAIN}\/users\/${username}`;\n  const shortId = target.replace('\/posts\/', '\/statuses\/').split('\/').pop()!.split('?')&#91;0];\n\n  const likeRow = db.prepare(`SELECT id, object FROM likes WHERE actor =? AND object LIKE '%' ||? || '%'`).get(myActor, shortId) as any;\n\n  if (!likeRow) {\n    const likeCount = (db.prepare(`SELECT COUNT(*) as c FROM likes WHERE object LIKE '%' ||? || '%'`).get(shortId) as any).c;\n    return NextResponse.json({ ok: true, alreadyUnliked: true, isMyLike: false, likeCount });\n  }\n\n  const objectForCheck = likeRow.object || target;\n\n  const isOwnPost = objectForCheck.includes(`${DOMAIN}\/users\/${username}\/`);\n\n \/\/ \u2705 myapp49 - \uc88b\uc544\uc694 \ubc30\ub2ec \/ Send a Like\n  if (!isOwnPost) {\n    try {\n      const u = new URL(objectForCheck);\n      const parts = u.pathname.split('\/').filter(Boolean);\n      const idx = parts.indexOf('users');\n      const inbox = idx!== -1? `${u.origin}\/users\/${parts&#91;idx+1]}\/inbox` : null;\n      if (inbox) {\n        await sendUndoLike(inbox, likeRow.id, objectForCheck, username);\n      }\n    } catch (e) {\n      console.error('Undo Like \ubc30\ub2ec \uc2e4\ud328', e);\n    }\n  }\n\n  db.prepare('DELETE FROM likes WHERE id =?').run(likeRow.id);\n  const likeCount = (db.prepare(`SELECT COUNT(*) as c FROM likes WHERE object LIKE '%' ||? || '%'`).get(shortId) as any).c;\n  return NextResponse.json({ ok: true, isMyLike: false, likeCount });\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f \ubd80\uc2a4\ud2b8 \uc2e4\ud589 \ubc0f \ubd80\uc2a4\ud2b8 \ucde8\uc18c<br>Start Boost and Cancel Boost<\/p>\n\n\n\n<p>&#8212; \uc88b\uc544\uc694 \ubd80\ubd84\uc5d0 \ub0b4 \uae00 \uc778\uc9c0 \uccb4\ud06c\ud558\ub294 \ubd80\ubd84 \ub3c4\uba54\uc778\ucd94\uac00<br>Added a domain check to the &#8220;Like&#8221; section to verify if the post belongs to the user.<\/p>\n\n\n\n<p>&#8212; \ubc30\uc1a1\ub41c \ubd80\uc2a4\ud2b8 \uc0ad\uc81c \ub85c\uc9c1 \uc218\uc815<br>Modified logic for deleting delivered boosts<\/p>\n\n\n\n<p>&#8212; app\/api\/announces\/route.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>export async function POST(req: NextRequest) {\n... ...\nconst isOwnPost = target.includes(`${DOMAIN}\/\/users\/${username}\/`); \/\/ \u2705 myapp48 \ub3c4\uba54\uc778\ucd94\uac00  \/ Add DOMAIN\n... ...\n}\n\nexport async function DELETE(req: NextRequest) {\n  try {\n    const { username, target } = await req.json();\n    const myActor = `https:\/\/${DOMAIN}\/users\/${username}`;\n    const shortId = target.replace('\/posts\/', '\/statuses\/').split('?')&#91;0].split('#')&#91;0].split('\/').filter(Boolean).pop() || target;\n\n    const row = db.prepare(\n      `SELECT id, object, actor FROM announces WHERE username =? AND object LIKE '%' ||? || '%'`\n    ).get(username, shortId) as any;\n\n    if (!row) {\n      const boostCount = (db.prepare(\n        `SELECT COUNT(*) as c FROM announces WHERE object LIKE '%' ||? || '%'`\n      ).get(shortId) as any).c;\n      return NextResponse.json({ ok: true, alreadyUnboosted: true, isMyBoost: false, boostCount });\n    }\n\n    \/\/ \ubd80\uc2a4\ud2b8 \ucde8\uc18c \ubc30\ub2ec \/ Undo Boost Delivery\n    const objectForCheck = row.object || target;\n    const isOwnPost = objectForCheck.includes(`${DOMAIN}\/users\/${username}\/`);\n\n    if (!isOwnPost) {\n      try {\n        const u = new URL(objectForCheck);\n        const parts = u.pathname.split('\/').filter(Boolean);\n        const idx = parts.indexOf('users');\n        const inbox = idx!== -1? `${u.origin}\/users\/${parts&#91;idx+1]}\/inbox` : null;\n        if (inbox) {\n          await sendUndoAnnounce(inbox, row.id, objectForCheck, username);\n        }\n      } catch (e) {\n        console.error('Undo Announce \ubc30\ub2ec \uc2e4\ud328', e);\n      }\n    }\n\n    db.prepare('DELETE FROM announces WHERE id =?').run(row.id);\n\n    const boostCount = (db.prepare(\n      `SELECT COUNT(*) as c FROM announces WHERE object LIKE '%' ||? || '%'`\n    ).get(shortId) as any).c;\n\n    return NextResponse.json({ ok: true, isMyBoost: false, boostCount });\n\n  } catch (e: any) {\n    console.error('&#91;Boost DELETE]', e);\n    return NextResponse.json({ ok: false, error: e.message }, { status: 500 });\n  }\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f \ub77c\uc6b0\ud2b8 \ubcf4\uc548\uac15\ud654 \/ Route Security Enhancement<\/p>\n\n\n\n<p>&#8212; \ud130\ubbf8\ub110\uc5d0\uc11c \ud1a0\ud070\uc774\ub098 \ucfe0\ud0a4 \uc5c6\uc774 \uae00\uc4f0\uac70\ub098 \uc0ad\uc81c\ub97c \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.<br>You cannot write or delete posts from the terminal without a token or cookie.<\/p>\n\n\n\n<p>&#8212; app\/api\/v1\/statuses\/route.ts &#8212; Pinafore<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>export async function POST(req: Request) {\n... ...\n    \/\/ \u2705 myapp49 - pinafore,local server login \n    let username: string | null = null;\n    \/\/ \u2705 myapp49 - 1. Bearer \ud1a0\ud070 \uccb4\ud06c \/ Bearer token check (Pinafore)\n    if (token) {\n      try {\n        const oauthToken = db.prepare('SELECT username FROM oauth_tokens WHERE access_token=?').get(token) as any;\n        if (oauthToken?.username) username = oauthToken.username;\n      } catch {}\n    }\n\n    \/\/ 2. \ucfe0\ud0a4 \uccb4\ud06c (\ub85c\uceec \uc11c\ubc84) \/ Cookie Check (Local Server)\n    if (!username) {\n      try {\n        const { getViewerFromRequest } = await import('@\/lib\/visibility');\n        username = getViewerFromRequest(req);\n      } catch {}\n    }\n\n    \/\/ \u2705 3. \ub458 \ub2e4 \uc5c6\uc73c\uba74 401 - 'user1' \ud3f4\ubc31 \uc808\ub300 \uae08\uc9c0\n    \/\/ If neither exists, return 401\u2014falling back to 'user1' is strictly prohibited.\n    if (!username) {\n      return NextResponse.json({ error: 'unauthorized - Bearer token or login required' }, { status: 401 });\n    }\n... ...\n}<\/code><\/pre>\n\n\n\n<p>&#8212; app\/api\/v1\/statuses\/[id]\/route.ts &#8211; Pinafore <\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>\nexport async function DELETE(\n  req: Request,\n  { params }: { params: Promise&lt;{ id: string }&gt; }\n) {\n... ...  \n\/\/ \u2705 myapp49 - 1. \uc778\uc99d - Bearer + \ucfe0\ud0a4 \ub458 \ub2e4 \uc9c0\uc6d0 \/ Authentication - Supports both Bearer and cookies.\n\n  const auth = req.headers.get('Authorization') || '';\n  const token = auth.replace('Bearer ', '');\n  let username: string | null = null;\n\n\n  if (token) {\n    try {\n      const oauthToken = db.prepare('SELECT username FROM oauth_tokens WHERE access_token=?').get(token) as any;\n      if (oauthToken?.username) username = oauthToken.username;\n    } catch {}\n  }\n\n  if (!username) {\n    try {\n      const { getViewerFromRequest } = await import('@\/lib\/visibility');\n      username = getViewerFromRequest(req as any);\n    } catch {}\n  }\n\n  \/\/ let oauthToken: any;\n  \/\/ try { oauthToken = db.prepare('SELECT * FROM oauth_tokens WHERE access_token=?').get(token) as any; } catch {}\n  \/\/ const username = oauthToken?.username || 'user1';\n\n  \/\/ \u2705 myapp49 - \ud3f4\ubc31 \uae08\uc9c0 - \uc778\uc99d \uc5c6\uc73c\uba74 401 \/ Disable fallback \u2013 401 if unauthenticated\n  if (!username) {\n    return NextResponse.json({ error: 'unauthorized' }, { status: 401, headers: { 'Access-Control-Allow-Origin': '*' } });\n  }\n\n  console.log(`\ud83d\uddd1 Pinafore \uc0ad\uc81c \uc694\uccad \/ Request to delete Pinafore : ${username} -&gt; ${id}`);\n\n  \/\/ \u2705 myapp49 - \uc18c\uc720\uad8c \uccb4\ud06c - \ub0b4 \uae00 \ub9de\ub294\uc9c0 \ud655\uc778 \/ Ownership Check \u2013 Verify if it is my post\n  let post: any;\n  try {\n    post = db.prepare('SELECT id, username, visibility FROM posts WHERE id=?').get(id) as any;\n  } catch {}\n\n  if (!post) {\n    \/\/ \uc774\ubbf8 \uc9c0\uc6cc\uc84c\uc73c\uba74 Pinafore\ub294 404\ubcf4\ub2e4 200\uc744 \uc6d0\ud568 - \uba71\ub4f1\uc131\n    return NextResponse.json({ id, deleted: true, alreadyDeleted: true }, {\n      headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*' }\n    });\n  }\n\n  if (post.username !== username) {\n    console.log(`\ud83d\udeab \uc0ad\uc81c \uac70\ubd80: ${username}\uac00 ${post.username} \uae00 \uc0ad\uc81c \uc2dc\ub3c4`);\n    return NextResponse.json({ error: 'forbidden - not your post' }, { \n      status: 403, \n      headers: { 'Access-Control-Allow-Origin': '*' } \n    });\n  }\n... ...\n}<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc2a4\ud2b8 \/ Test<\/p>\n\n\n\n<p>\u2714\ufe0f \uc88b\uc544\uc694 \/ Likes<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"388\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-like-aloy-jpg.jpg\" alt=\"\" class=\"wp-image-11124\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-like-aloy-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-like-aloy-jpg-300x146.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-like-aloy-jpg-768x372.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-like-aloy-jpg-400x194.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">local<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"362\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-lke-gts-jpg.jpg\" alt=\"\" class=\"wp-image-11125\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-lke-gts-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-lke-gts-jpg-300x136.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-lke-gts-jpg-768x348.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-lke-gts-jpg-400x181.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">GTS<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f \ubd80\uc2a4\ud2b8 \/ Boost<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"403\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-boost-aloy-jpg.jpg\" alt=\"\" class=\"wp-image-11126\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-boost-aloy-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-boost-aloy-jpg-300x151.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-boost-aloy-jpg-768x387.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-boost-aloy-jpg-400x202.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Local<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"316\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-boost-gts-jpg.jpg\" alt=\"\" class=\"wp-image-11127\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-boost-gts-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-boost-gts-jpg-300x119.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-boost-gts-jpg-768x303.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp49-boost-gts-jpg-400x158.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">GTS<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f api\/v1\/statuses \ub77c\uc6b0\ud2b8 \ud130\ubbf8\ub110 \ud14c\uc2a4\ud2b8<br>Terminal test for the api\/v1\/statuses route<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># 1.\uae00\uc4f0\uae30 \ud14c\uc2a4\ud2b8 - \ud1a0\ud070\uc774\ub098 \ucfe0\ud0a4 \uc5c6\ub294 \uacbd\uc6b0\nWriting Test - No tokens or cookies\n\n curl -s -X POST https:\/\/aloy-horizon.duckdns.org\/api\/v1\/statuses \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"status\":\"write post test-1\",\"visibility\":\"public\"}'\n\n# \uc751\ub2f5 \/ Response\n{\"error\":\"unauthorized - Bearer token or login required\"}%    \n\n# 2.\uae00\uc4f0\uae30 \ud14c\uc2a4\ud2b8 \/ Test - refresh token\ncurl -s -X POST https:\/\/aloy-horizon.duckdns.org\/api\/v1\/statuses \\\n -H \"Cookie: <strong>refresh_token=099b61b8616edcf48b ... ...<\/strong>\" \\\n -H \"Content-Type: application\/json\" \\\n -d '{\"status\":\"refresh token\",\"visibility\":\"public\"}'\n\n# \uc751\ub2f5 \/ Response\n\n{\"id\":\"c3b1cefe-853c-476e-b978-3b2e28506a92\",\"uri\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/statuses\/c3b1cefe-853c-476e-b978-3b2e28506a92\",\"url\":\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/statuses\/c3b1cefe-853c-476e-b978-3b2e28506a92\",\"account\":{\"id\":\"1\",\"username\":\"user1\",\"acct\":\"user1@aloy-horizon.duckdns.org\",\"display_name\":\"user1\",\"avatar\":\"https:\/\/aloy-horizon.duckdns.org\/icon.png\"},\"content\":\"&lt;p&gt;refresh token&lt;\/p&gt;\",\"created_at\":\"2026-09-26T04:26:48.469Z\",\"visibility\":\"public\",\"reblogs_count\":0,\"favourites_count\":0,\"replies_count\":0}%   \n\n   \n# 3. \uae00 \uc0ad\uc81c \ud14c\uc2a4\ud2b8 - \ud1a0\ud070\uc774\ub098 \ucfe0\ud0a4\uac00 \uc5c6\ub294\uacbd\uc6b0\nmyapp49 % curl -s -X DELETE https:\/\/aloy-horizon.duckdns.org\/api\/v1\/statuses\/9a9f4c12-265c-46ed-b956-f51a5e61fdb2\n# \uc751\ub2f5 \/ Response\n{\"error\":\"unauthorized\"}%                         \n\n# 4. \uae00 \uc0ad\uc81c \ud14c\uc2a4\ud2b8 - refresh token\ncurl -s -X DELETE https:\/\/aloy-horizon.duckdns.org\/api\/v1\/statuses\/9a9f4c12-265c-46ed-b956-f51a5e61fdb2 \\\n -H \"Authorization: Bearer 099b61b8616edcf48b4cf7260ef818b7a451... ...\"\n\n# \uc751\ub2f5 \/ Response   \n{\"id\":\"9a9f4c12-265c-46ed-b956-f51a5e61fdb2\",\"deleted\":true}%   \n                                                 \n                                                          <\/code><\/pre>\n\n\n\n<p>\ud83d\udca1 \ud130\ubbf8\ub110\uc5d0\uc11c \uc9c1\uc811 \uae00\uc4f0\uae30 \/ Writing directly in the terminal<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  curl -X POST https:\/\/aloy-horizon.duckdns.org\/users\/user1\/outbox \\\n  -H \"Content-Type: application\/activity+json\" \\\n  -d '{\"type\":\"Create\",\"object\":{\"content\":\"Hello Activity UI #activityposts \"}}'<\/code><\/pre>\n\n\n\n<p>\ud83d\udca1 \ud130\ubbf8\ub110\uc5d0\uc11c \uc9c1\uc811 \uae00 \uc0ad\uc81c \/ Delete posts directly from the terminal.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -X DELETE \"https:\/\/aloy-horizon.duckdns.org\/api\/cleanup-orphan?id=c429765e-1ffd-4168-b487-e4b36a7a162a&amp;username=user1\"<\/code><\/pre>\n\n\n\n<p>\ud83d\udca1 \uc88b\uc544\uc694 \ubc0f \uc88b\uc544\uc694 \ucde8\uc18c\uc2e4\ud589 \/ Perform Like and Unlike actions<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># \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:\/\/freelifemakers.com\/users\/user1\/statuses\/01M0XT1VBF23F8Y5EXAPK1VY56\"}'<\/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 myapp49\uc5d0\uc11c\ub294 myapp48\uae4c\uc9c0 \uae30\ub2a5 \ucd94\uac00 \ub418\uba74\uc11c \ubc1c\uc0dd\ud55c \uc624\ub958\ub098 \ubc84\uadf8\ub97c \uc218\uc815\ud569\ub2c8\ub2e4.myapp49 fixes errors and bugs that arose during the addition of features up to myapp48. \ud83d\udc49\ud83c\udffb \ubd80\uc2a4\ud2b8 ,\uc88b\uc544\uc694 \ubc30\ub2ec \ubd80\ubd84 \uc624\ub958\ub97c \uc218\uc815\ud569\ub2c8\ub2e4.Fixed errors related to Boosts, Likes, and delivery. \ud83d\udc49\ud83c\udffb \uc678\ubd80\uc571\uc5d0\uc11c \uae00\uc4f0\uac70\ub098 \uc0ad\uc81c\ud558\ub294 API\ub97c \ud130\ubbf8\ub110\uc5d0\uc11c \uc0ac\uc6a9\ud560 \uacbd\uc6b0 \ud1a0\ud070\uc744 \uc785\ub825\ud574\uc57c\ud569\ub2c8\ub2e4.You must enter a token when using the [&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-11067","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\/11067","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=11067"}],"version-history":[{"count":53,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/11067\/revisions"}],"predecessor-version":[{"id":11128,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/11067\/revisions\/11128"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=11067"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=11067"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=11067"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}