{"id":9902,"date":"2026-09-09T06:50:58","date_gmt":"2026-09-08T21:50:58","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=9902"},"modified":"2026-09-09T10:51:31","modified_gmt":"2026-09-09T01:51:31","slug":"nextjs-sns-server-26","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/09\/nextjs-sns-server-26\/","title":{"rendered":"[nextjs]SNS Server-26(myapp36)\u00a0"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb \ud68c\uc6d0\uac00\uc785 \ubc0f \ub85c\uadf8\uc778\uacfc \uad00\ub828\ub41c \uae30\ub2a5\uc785\ub2c8\ub2e4.<br>These are features related to sign-up and login.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb pinafore\ud654\uba74\uc5d0\uc11c \ud45c\uc2dc \uac83\ub4e4\uc744 \uad6c\ud604 \uc911\uc785\ub2c8\ub2e4.<br>I am currently implementing the elements displayed on the pinafore screen.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \ud648\ud0c0\uc784\ub77c\uc778\uc5d0\uc11c \uc88b\uc544\uc694\uc640 \ubd80\uc2a4\ud2b8\ub97c \ud45c\uc2dc\ud569\ub2c8\ub2e4.<br>Displays likes and boosts on the home timeline.<\/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\"><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 <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 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\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 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   \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=\"nJMeDuX8Og\"><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=pjGOC7PzOM#?secret=nJMeDuX8Og\" data-secret=\"nJMeDuX8Og\" 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>&nbsp;\ud83d\udcc1 \ucf54\ub4dc \uc218\uc815 \/ Code Modification<\/p>\n\n\n\n<p>\u2714\ufe0f pinafore \ud648 \ud0c0\uc784\ub77c\uc778\uc5d0 \ub0b4\uac00 \ud314\ub85c\uc6b0 \ud558\ub294 \uc0ac\ub78c \ubaa8\ub4e0 \uae00 \ub728\uac8c \ud558\uae30<br>Make posts from everyone I follow appear on my Pinafore home timeline.<\/p>\n\n\n\n<p>&#8212; app\/api\/v1\/timelines\/home\/route.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { NextResponse } from 'next\/server';\nimport db from '@\/lib\/db';\n\nconst DOMAIN = process.env.DOMAIN || 'aloy-horizon.duckdns.org';\nfunction parseDate(v: any): number {\n  if (!v) return 0;\n  if (\/^\\d{13}$\/.test(String(v))) return parseInt(v);\n  const d = new Date(v).getTime();\n  return isNaN(d)? 0 : d;\n}\n\nexport async function GET(req: Request) { ...\n\n    \/\/ \u2705 myapp36-\uc804\uccb4 \uac80\uc0c9 \/ Full search\n    const reblogs_count = announcesByObject.get(longId) || announcesByObject.get(originalId) || announcesByObject.get(shortId) || announcesByObject.get(short8) || 0;\n    const favourites_count = likesByObject.get(longId) || likesByObject.get(originalId) || likesByObject.get(shortId) || likesByObject.get(short8) || 0;\n    \n    const favourited = likedSet.has(longId) || likedSet.has(originalId) || likedSet.has(shortId) || likedSet.has(short8);\n    const reblogged = reblogs_count &gt; 0;\n\n    let content = row.content || '';\n    try { if (content.startsWith('{')) { const o = JSON.parse(content); content = o.object?.content || o.content || content; } } catch {}\n\n    const baseAccount = isMy\n     ? { id: '1', username, acct: `${username}@${DOMAIN}`, display_name: username, avatar: `https:\/\/${DOMAIN}\/icon.png` }\n      : (() =&gt; {\n          let uname = 'user1', dom = 'freelifemakers.com', display = 'user1';\n          if (row.actor?.includes('mastodon.social')) { uname = 'freelifemakers'; dom = 'mastodon.social'; display = 'freelifemakers'; }\n          return { id: `remote_${dom.replace(\/\\.\/g,'_')}_${uname}`, username: uname, acct: `${uname}@${dom}`, display_name: display, avatar: `https:\/\/${DOMAIN}\/icon.png` };\n        })();\n\n    return {\n      id: String(row.id),\n      uri: longId,\n      url: longId,\n      account: baseAccount,\n      content: content.startsWith('&lt;')? content : `&lt;p&gt;${content}&lt;\/p&gt;`,\n      created_at: new Date(parseDate(row.created_at)).toISOString(),\n      visibility: 'public',\n      reblogs_count,      \/\/ \uc804\uccb4 \ubd80\uc2a4\ud2b8 \uce74\uc6b4\ud2b8 \/ Full boost count\n      favourites_count,   \/\/ \uc804\uccb4 \uc88b\uc544\uc694 \uce74\uc6b4\ud2b8 \/ Full like count \n      replies_count: 0,\n      favourited: !!favourited, \/\/ \uc804\uccb4 \uc88b\uc544\uc694 \/ Full like\n      reblogged: !!reblogged,   \/\/ \uc804\uccb4 \ubd80\uc2a4\ud2b8 \/ Full Boost\n      muted: false, bookmarked: false, pinned: false,\n    };\n\n... ...\n }<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f announce \ub85c\uc9c1 \uc218\uc815 &#8211; DB\uc5d0\uc11c announce(boost) \uc800\uc7a5\ud558\uae30 \uc54a\uae30<br>Modify &#8216;announce&#8217; logic \u2013 do not save &#8216;announce&#8217; (boost) to the database.<\/p>\n\n\n\n<p>&#8212; announce\uc5d0 \uc800\uc7a5\ub41c \uae00\uacfc \uc77c\uce58\ud558\ub294 inbox_posts \uae00\uc5d0 \ubd80\uc2a4\ud2b8 \ud45c\uc2dc<br>Display a boost indicator on <code>inbox_posts<\/code> entries that match posts stored in <code>announce<\/code>.<\/p>\n\n\n\n<p>&#8211;app\/users\/[username]\/inbox\/route.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    \/\/ Announce \uc218\uc2e0 - actor \ud3ec\ud568! \/ Receive Announce - include actor!\n    if (body.type === 'Announce') {\n      try {\n        const announceId = body.id;\n        let objectId = typeof body.object === 'string' ? body.object : body.object?.id;\n        if (!objectId) return new Response('', { status: 202 });\n        objectId = objectId.replace('\/posts\/', '\/statuses\/');\n        \n        console.log(`\ud83d\udd01 &#91;${username}] Announce \ub3c4\ucc29: ${actorId} -&gt; ${objectId}`);\n\n        \/\/ actor \uceec\ub7fc \ud3ec\ud568\n        db.prepare('INSERT OR IGNORE INTO announces (id, actor, object, username) VALUES (?,?,?,?)')\n          .run(announceId, actorId, objectId, username);\n\n        \/\/ \u2705 myapp36 \n        \/\/ - announce(boost)\ub294 inbox_posts\uc5d0 \uc800\uc7a5 \ucde8\uc18c,announce\ud14c\uc774\ube14 \uac12\uacfc \uac19\uc740\uac83 \uac80\uc0c9\ud558\uae30\n        \/\/ For `announce(boost)`, remove the entry from `inbox_posts` and search for records matching the values \u200b\u200bin the `announce` table.\n        \/\/ try {\n        \/\/   const noteRes = await fetch(objectId, { headers: { Accept: 'application\/activity+json' } });\n        \/\/   if (noteRes.ok) {\n        \/\/     const note = await noteRes.json();\n        \/\/     const longId = note.id || objectId;\n        \/\/     const shortId = `${longId.split('\/').pop()}_boost_${Date.now()}`;\n        \/\/     const content = note.content || '';\n        \/\/     db.prepare(`INSERT OR IGNORE INTO inbox_posts (id, actor, content, username, original_id, created_at) VALUES (?,?,?,?,?,?)`)\n        \/\/       .run(shortId, actorId, content, username, longId, body.published || new Date().toISOString());\n        \/\/   }\n        \/\/ } catch {}\n        console.log(`\u2705 announces \uc800\uc7a5 \uc644\ub8cc \/ announces saved: ${announceId}`);\n      } catch (e) {\n        console.error(`\u274c Announce \uc800\uc7a5 \uc2e4\ud328 \/ Failed to save announce`, 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.social<\/p>\n\n\n\n<p>&#8212; inbox_posts + posts\uc758 \uae00\uc744 \ud655\uc778\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"457\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp36-pinafore-jpg.jpg\" alt=\"\" class=\"wp-image-9917\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp36-pinafore-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp36-pinafore-jpg-300x171.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp36-pinafore-jpg-768x439.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp36-pinafore-jpg-400x229.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Pinafore<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f aloy-horizon.duckdns.org\/@user1<\/p>\n\n\n\n<p>&#8212; \ubaa8\ub4e0 \ub370\uc774\ud130\uc640 \uce74\uc6b4\ud2b8\ub294 \uc5ec\uae30\uc11c \ud655\uc778 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<br>You can check all the data and counts here.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"283\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp36-ui-jpg.jpg\" alt=\"\" class=\"wp-image-9918\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp36-ui-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp36-ui-jpg-300x106.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp36-ui-jpg-768x272.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp36-ui-jpg-400x142.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Local Server<\/figcaption><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\uc694\ud55c\ubcf5\uc74c 8\uc7a5 32\uc808 \/ John 8:32<\/p>\n\n\n\n<p>&#8220;\uadf8\ub9ac\uace0 \ub108\ud76c\ub294 \uc9c4\ub9ac\ub97c \uc54c\uac8c \ub420 \uac83\uc774\uba70, \uc9c4\ub9ac\uac00 \ub108\ud76c\ub97c \uc790\uc720\ub86d\uac8c \ud560 \uac83\uc774\ub2e4.&#8221;<\/p>\n\n\n\n<p>&#8220;Then you will know the truth ,and the truth will set you free&#8221;<\/p>\n<\/blockquote>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udc49\ud83c\udffb \ud68c\uc6d0\uac00\uc785 \ubc0f \ub85c\uadf8\uc778\uacfc \uad00\ub828\ub41c \uae30\ub2a5\uc785\ub2c8\ub2e4.These are features related to sign-up and login. \ud83d\udc49\ud83c\udffb pinafore\ud654\uba74\uc5d0\uc11c \ud45c\uc2dc \uac83\ub4e4\uc744 \uad6c\ud604 \uc911\uc785\ub2c8\ub2e4.I am currently implementing the elements displayed on the pinafore screen. \ud83d\udc49\ud83c\udffb \ud648\ud0c0\uc784\ub77c\uc778\uc5d0\uc11c \uc88b\uc544\uc694\uc640 \ubd80\uc2a4\ud2b8\ub97c \ud45c\uc2dc\ud569\ub2c8\ub2e4.Displays likes and boosts on the home timeline. \ud83d\udc49\ud83c\udffb \uc804\uccb4 \ucf54\ub4dc\ub294 \uae43\ud5c8\ube0c\uc5d0\uc11c \ud655\uc778 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.You can find the full code on [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":9918,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28,27,1,7],"tags":[],"class_list":["post-9902","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-news","category-nextjs","category-uncategorized","category-website","has-thumbnail"],"_links":{"self":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/9902","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=9902"}],"version-history":[{"count":18,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/9902\/revisions"}],"predecessor-version":[{"id":9923,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/9902\/revisions\/9923"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media\/9918"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=9902"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=9902"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=9902"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}