{"id":10976,"date":"2026-09-25T08:05:41","date_gmt":"2026-09-24T23:05:41","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=10976"},"modified":"2026-09-25T13:34:58","modified_gmt":"2026-09-25T04:34:58","slug":"nextjs-sns-server-38","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/25\/nextjs-sns-server-38\/","title":{"rendered":"[nextjs]SNS Server-38(myapp48)\u00a0"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb myapp48\uc5d0\uc11c\ub294 \ub85c\uceec SNS\ud398\uc774\uc9c0\uc5d0\uc11c \uae00\uc4f0\uae30 \ud3fc\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4.<br>In myapp48, a post creation form is added to the local social media page.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \uc791\uc131\ud55c \uae00\uc774 public \/ unlisted \/ private\ub85c \uad6c\ubd84\ud574\uc11c \uc800\uc7a5\ub418\ub3c4\ub85d \ub370\uc774\ud130\ubca0\uc774\uc2a4 \ud14c\uc774\ube14\uc744 \uc218\uc815\ud569\ub2c8\ub2e4.<br>Modify the database table so that written posts are saved with a classification of public, unlisted, or private.<\/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  -> webfinger\n\u2502   \u251c\u2500\u2500 api\/follow\/route.ts    -> Follow API(temporary)\n\u2502   \u251c\u2500\u2500 api\/announce\/route.ts  -> Boost(Announcement)\n\u2502   \u251c\u2500\u2500 api\/like\/route.ts.     -> Like API\n\u2502   \u251c\u2500\u2500 api\/posts\/route.ts     -> Writing API\n\u2502   \u251c\u2500\u2500 api\/timeline\/route.ts  -> Timeline API\n\u2502   \u251c\u2500\u2500 api\/followinglist\/route.ts  -> followinglist API\n\u2502   \u251c\u2500\u2500 api\/followerslist\/route.ts  -> 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 -> auth\n\u2502   \u251c\u2500\u2500 api\/v1\/apps\/route.ts.    -> auth\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/verify_credentials\/route.ts -> auth\n\u2502   \u251c\u2500\u2500 api\/v1\/statuses\/route.ts -> 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> -> timeline\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/authorize\/route.ts<\/span> -> auth\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/token\/route.ts<\/span> -> 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> -> 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> -> search\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/&#91;id]\/followers\/route.ts -> followers\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/&#91;id]\/following\/route.ts -> following\n\u2502   \u251c\u2500\u2500 api\/v1\/statuses\/&#91;id]\/route.ts -> -> Post delete(pinafore)\n\u2502   \u251c\u2500\u2500 api\/auth\/signup\/route.ts        -> Signup\n\u2502   \u251c\u2500\u2500 api\/auth\/login\/route.ts         -> Login\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/logout\/route.ts        -> Logout\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/refresh\/route.ts       -> Refresh Token\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/cleanup-orphan\/route.ts     -> post,outbox clean up\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/me\/route.tsx           -> Login Check<strong>\n<\/strong>\u2502   \u251c\u2500\u2500 auth\/signup\/page.tsx            -> Signup UI\n\u2502   \u251c\u2500\u2500 auth\/signup\/page.tsx            -> Login UI\n\u2502   \u251c\u2500\u2500 users\/&#91;username]\/\n\u2502   \u2502   \u251c\u2500\u2500 statuses\/&#91;id]\/route.ts -> Indivisual Post\n\u2502   \u2502   \u251c\u2500\u2500 route.ts               -> Acotr Information\n\u2502   \u2502   \u251c\u2500\u2500 followers\/route.ts     -> Followers List\n\u2502   \u2502   \u251c\u2500\u2500 following\/route.ts     -> Following List\n\u2502   \u2502   \u251c\u2500\u2500 inbox\/route.ts         -> Inbox\n\u2502   \u2502   \u2514\u2500\u2500 outbox\/route.ts        -> outbox\n\u2502   \u251c\u2500\u2500 usersui\/&#91;username]\/\n\u2502   \u2502   \u251c\u2500\u2500 page.tsx               -> Timeline UI(Server, 404)\n\u2502   \u2502   \u251c\u2500\u2500 ClientPage.tsx.        -> Timeline UI(Client,Call Active Themes)\n\u2502   \u2502   \u2514\u2500\u2500 _components\/themes\/\n\u2502   \u2502       \u251c\u2500\u2500 themeex\/ThemeexTheme.tsx   -> Example Theme\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/PinaforeTheme.tsx -> Theme 1\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/FollowersList.tsx -> Theme 1, FollowersList\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/FollowingList.tsx -> Theme 1, FollowingList\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/NotificationsList.tsx -> Theme 1, NotificationsList\n\u2502   \u2502       \u251c\u2500\u2500 mastodon\/MastodonTheme.tsx -> Theme 2\n\u2502   \u2502       \u2514\u2500\u2500 minimal\/MinimaltTheme.tsx  -> 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              -> Theme Provider\n\u2502   \u251c\u2500\u2500 watchThemes.ts         -> Check real-time theme changes\n\u2502   \u251c\u2500\u2500 auth.ts                -> Authentication, User Management\n\u2502   \u251c\u2500\u2500 ap.ts                  -> Follow,Undo,Create,Likes,Announce\n\u2502   \u251c\u2500\u2500 visibility.ts          -> Visibility(Public,Unlisted,Private,Direct)\n\u2502   \u2514\u2500\u2500 db.ts                  -> DB connection\n\u251c\u2500\u2500 data\/\n\u2502   \u251c\u2500\u2500 keys\/userIDs\/          -> private.pem, public.pem(New)\n\u2502   \u2514\u2500\u2500 keys\/                  -> private.pem, public.pem(legacy)\n\u251c\u2500\u2500 data.sqlite                -> Database(1\/3)\n\u251c\u2500\u2500 data.sqlite-wal            -> Database(2\/3)\n\u251c\u2500\u2500 data.sqlite-shm            -> Database(3\/3)\n\u251c\u2500\u2500 Caddyfile                  -> https \n\u251c\u2500\u2500 instrumentation.ts         -> 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=\"zUAP61Ys2B\"><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=mXasnw4Rtx#?secret=zUAP61Ys2B\" data-secret=\"zUAP61Ys2B\" 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 visibility<\/p>\n\n\n\n<figure style=\"font-size:14px\" class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\uae00 \uc0c1\ud0dc<\/th><th>\uac8c\uc2a4\ud2b8<\/th><th>\ub85c\uadf8\uc778(\ub0a8)<\/th><th>\ud314\ub85c\uc6cc<\/th><\/tr><\/thead><tbody><tr><td>public<\/td><td>O<\/td><td>O<\/td><td>O<\/td><\/tr><tr><td>unlisted<\/td><td>X (\ud504\ub85c\ud544)<\/td><td>O (\ud504\ub85c\ud544)<\/td><td>O \/ \ud648\uc740 \uc124\uc815\uc5d0 \ub530\ub77c<\/td><\/tr><tr><td>private<\/td><td>X<\/td><td>X<\/td><td>O (\ubcf8\uc778+\ud314\ub85c\uc6cc)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>\ud83d\udcc1 \ub370\uc774\ud130\ubca0\uc774\uc2a4 \ud14c\uc774\ube14 \uc218\uc815<br>Modify database table<\/p>\n\n\n\n<p>&#8212; \uc544\ub798\uc758 SQL\ubb38\uc744 Sqlite\uc5d0\uc11c \ud55c\uc904\uc529 \uc2e4\ud589\ud569\ub2c8\ub2e4.<br>Execute the SQL statements below in SQLite one line at a time.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>-- 1. posts\uc5d0 visibility + \ud544\uc694\ud55c \uceec\ub7fc \ucd94\uac00\nAdd visibility and necessary columns to the `posts` table.\n\nALTER TABLE posts ADD COLUMN visibility TEXT DEFAULT 'public' CHECK(visibility IN ('public','unlisted','private','direct'));\nALTER TABLE posts ADD COLUMN original_id TEXT;\nALTER TABLE posts ADD COLUMN sensitive INTEGER DEFAULT 0;\n\n-- 2. inbox_posts\uc5d0 visibility + type + sensitive \ucd94\uac00 (\ub9ac\ubaa8\ud2b8 \uae00 \uad6c\ubd84)\nAdded `visibility`, `type`, and `sensitive` to `inbox_posts` (to distinguish remote posts).\n\nALTER TABLE inbox_posts ADD COLUMN visibility TEXT DEFAULT 'public' CHECK(visibility IN ('public','unlisted','private','direct'));\nALTER TABLE inbox_posts ADD COLUMN type TEXT DEFAULT 'Note';\nALTER TABLE inbox_posts ADD COLUMN sensitive INTEGER DEFAULT 0;\n\n-- 3. outbox\uc5d0\ub3c4 visibility \ucd94\uac00 (\ubc30\ub2ec\ud560\ub54c to\/cc \uad6c\ubd84\uc6a9)\nAdded visibility to the outbox as well (to distinguish between 'To' and 'CC' during delivery).\n\nALTER TABLE outbox ADD COLUMN visibility TEXT DEFAULT 'public';\n\n-- 4. \uc778\ub371\uc2a4 \ubcf4\uac15 (\ud0c0\uc784\ub77c\uc778 \uc18d\ub3c4)\nIndex Reinforcement (Timeline Speed)\n\nCREATE INDEX IF NOT EXISTS idx_posts_visibility ON posts(visibility);\nCREATE INDEX IF NOT EXISTS idx_inbox_visibility ON inbox_posts(visibility);\nCREATE INDEX IF NOT EXISTS idx_outbox_visibility ON outbox(visibility);\n\n-- \ud655\uc778\/Check\n.schema posts\n.schema inbox_posts\n.schema outbox<\/code><\/pre>\n\n\n\n<p>&#8212; \ubcc0\uacbd\ub41c \uc2a4\ud0a4\ub9c8 \/ Modified schema<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>sqlite&gt; <strong>.schema posts<\/strong>\nCREATE TABLE posts (\n    id TEXT PRIMARY KEY,\n    content TEXT NOT NULL,\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  , username TEXT DEFAULT 'user1', visibility TEXT DEFAULT 'public' CHECK(visibility IN ('public','unlisted','private','direct')), original_id TEXT, sensitive INTEGER DEFAULT 0);\nCREATE INDEX idx_posts_username_created ON posts(username, created_at DESC);\nCREATE INDEX idx_posts_visibility ON posts(visibility);\n\nsqlite&gt; <strong>.schema inbox_posts<\/strong>\nCREATE TABLE inbox_posts (\n  id TEXT PRIMARY KEY,\n  actor TEXT,\n  content TEXT,\n  username TEXT,\n  created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n, original_id TEXT, visibility TEXT DEFAULT 'public' CHECK(visibility IN ('public','unlisted','private','direct')), type TEXT DEFAULT 'Note', sensitive INTEGER DEFAULT 0);\nCREATE INDEX idx_inbox_username_created ON inbox_posts(username, created_at DESC);\nCREATE INDEX idx_inbox_actor ON inbox_posts(actor);\nCREATE INDEX idx_inbox_visibility ON inbox_posts(visibility);\n\nsqlite&gt; <strong>.schema outbox<\/strong>\nCREATE TABLE outbox (\n          id TEXT PRIMARY KEY,\n          type TEXT,\n          actor TEXT,\n          object TEXT,\n          created_at INTEGER\n        , visibility TEXT DEFAULT 'public');\nCREATE INDEX idx_outbox_visibility ON outbox(visibility);<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud30c\uc77c \ucd94\uac00 \/ Add file &#8211; visibility<\/p>\n\n\n\n<p>\u2714\ufe0f lib\/visibility.ts<\/p>\n\n\n\n<p>&#8212; \ucfe0\ud0a4\uc5d0 \uc788\ub294 \uac12\uc73c\ub85c session\ud14c\uc774\ube14\uc758 \uc720\uc800\ub97c \uac80\uc0c9\ud558\uace0 SNS\ud398\uc774\uc9c0\uc758 \uae00\uc744 public&#8217; | &#8216;unlisted&#8217; | &#8216;private&#8217; \ubcc4\ub85c \ud544\ud130\ub9c1 \ud558\ub294 \uae30\ub2a5\uc785\ub2c8\ub2e4.<br>This feature searches for a user in the <code>session<\/code> table using a value from a cookie and filters posts on the SNS page based on visibility settings: &#8216;public&#8217;, &#8216;unlisted&#8217;, or &#8216;private&#8217;.<\/p>\n\n\n\n<p>&#8212; \uc790\uc138\ud55c \ucf54\ub4dc\ub0b4\uc6a9\uc740 \uae43\ud5c8\ube0c \ucf54\ub4dc\ub97c \ucc38\uc870 \ud558\uc138\uc694<br>Please refer to the GitHub repository for detailed code.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export type Visibility = 'public' | 'unlisted' | 'private' | 'direct';\n\nfunction decodeJwtUsername(jwt: string): string | null { ... }\nexport function getViewerFromRequest(req: Request): string | null { ... }\nexport function isFollower(viewer: string, author: string): boolean { ... }\nexport function canSee(viewer: string | null, post: any): boolean { ... }<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ucf54\ub4dc \uc218\uc815 \/ Code Modification &#8211; visibility \uc800\uc7a5 \ubc0f \uae30\uc874 \uae30\ub2a5 \ud655\uc778<\/p>\n\n\n\n<p>\u2714\ufe0f lib\/db.ts <\/p>\n\n\n\n<p>&#8212; \ubcc0\uacbd\ub41c \uc2a4\ud0a4\ub9c8\uc5d0 \ub9de\uac8c  \ucf54\ub4dc\ub97c \uc218\uc815\ud569\ub2c8\ub2e4. <br>Modify the code to match the updated schema.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>db.exec(`\n... ...\n\n  -- \u2705 myapp48 posts - visibility \ucd94\uac00! (public\/unlisted\/private\/direct)\n  CREATE TABLE IF NOT EXISTS posts (\n    id TEXT PRIMARY KEY,\n    content TEXT NOT NULL,\n    username TEXT DEFAULT 'user1',\n    visibility TEXT DEFAULT 'public' CHECK(visibility IN ('public','unlisted','private','direct')),\n    sensitive INTEGER DEFAULT 0,\n    original_id TEXT,\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  );\n\n... ...\n\n  -- \u2705 myapp48 inbox_posts - visibility + type \ucd94\uac00\/Added\n  CREATE TABLE IF NOT EXISTS inbox_posts (\n    id TEXT PRIMARY KEY,\n    original_id TEXT,\n    actor TEXT,\n    content TEXT,\n    username TEXT,\n    visibility TEXT DEFAULT 'public' CHECK(visibility IN ('public','unlisted','private','direct')),\n    type TEXT DEFAULT 'Note',\n    sensitive INTEGER DEFAULT 0,\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  );\n\n... ...\n\n  -- \u2705 myapp46 outbox - visibility \ucd94\uac00\/added!\n  CREATE TABLE IF NOT EXISTS outbox (\n    id TEXT PRIMARY KEY,\n    type TEXT,\n    actor TEXT,\n    object TEXT,\n    visibility TEXT DEFAULT 'public',\n    created_at INTEGER\n  );\n\n... ...\n`);\n\ndb.exec(`\n... ...\n\n  -- posts\n  CREATE INDEX IF NOT EXISTS idx_posts_username_created ON posts(username, created_at DESC);\n  CREATE INDEX IF NOT EXISTS idx_posts_visibility ON posts(visibility);\n  \n  -- inbox_posts\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 INDEX IF NOT EXISTS idx_inbox_visibility ON inbox_posts(visibility);\n\n... ...\n\n  -- outbox\n  CREATE INDEX IF NOT EXISTS idx_outbox_visibility ON outbox(visibility);\n`);<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0fapp\/api\/v1\/statuses\/route.ts &#8211; remote server<\/p>\n\n\n\n<p>&#8212;  \uc678\ubd80 \uc571\uc5d0\uc11c \ub0b4 \uc11c\ubc84\uc5d0 \uae00 \uc791\uc131\ud560\ub54c visibility \ud544\ub4dc \uc801\uc6a9<br>Apply the visibility field when posting to my server from an external app.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>... ...\nexport async function POST(req: Request) {\n... ...    \n\/*-- \ud14c\uc774\ube14 \uc0dd\uc131 \ubd80\ubd84 \uc0ad\uc81c \/ Delete the table creation section. *\/\n\n    \/\/ posts \uc800\uc7a5 \/ save posts\n    \/\/ \u2705 myapp48 - add visibility\n    try {\n      db.prepare(`INSERT INTO posts (id, content, created_at, username, visibility) VALUES (?,?,?,?,?)`)\n        .run(id, content, now, username, visibility);\n    } catch (e) {\n      console.error('posts \uc800\uc7a5 \uc5d0\ub7ec \/ posts save error', e);\n    }\n\n    \/\/\u2705 myapp48 -  to,cc \uad6c\ubd84 \/ to,CC distinction\n    const followersUrl = `https:\/\/${DOMAIN}\/users\/${username}\/followers`;\n    let to: string&#91;] = &#91;];\n    let cc: string&#91;] = &#91;];\n\n    if (visibility === 'public') {\n      to = &#91;'https:\/\/www.w3.org\/ns\/activitystreams#Public'];\n      cc = &#91;followersUrl];\n    } else if (visibility === 'unlisted') {\n      to = &#91;followersUrl];\n      cc = &#91;'https:\/\/www.w3.org\/ns\/activitystreams#Public'];\n    } else if (visibility === 'private') {\n      to = &#91;followersUrl];\n      cc = &#91;];\n    }\n    \/\/ \u2705 myapp48\n    const note = {\n      id: `https:\/\/${DOMAIN}\/users\/${username}\/statuses\/${id}`,\n      type: 'Note',\n      content: `&lt;p&gt;${content}&lt;\/p&gt;`,\n      attributedTo: `https:\/\/${DOMAIN}\/users\/${username}`,\n      published: new Date(now).toISOString(),\n      to,\n      cc,\n    };\n\n    \/*-- \ud14c\uc774\ube14 \uc0dd\uc131 \ubd80\ubd84 \uc0ad\uc81c \/ Delete the table creation section. *\/\n    \/\/ outbox \uc800\uc7a5\n    \/\/ \u2705 myapp48 - add visibility\n    try {\n      db.prepare(`INSERT INTO outbox (id, type, actor, object, created_at, visibility) VALUES (?,?,?,?,?,?)`)\n        .run(randomUUID(), 'Create', `https:\/\/${DOMAIN}\/users\/${username}`, JSON.stringify(note), now, visibility);\n    } catch (e) {\n      console.error('outbox \uc800\uc7a5 \uc5d0\ub7ec', e);\n    }\n... ...\n}\n... ...<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0fapp\/api\/posts\/route.ts &#8211; local server<\/p>\n\n\n\n<p>&#8212; \ub85c\uceec \uc11c\ubc84 \uae00\uc4f0\uae30 \ud560 \ub54c visibility \ud544\ub4dc \uc801\uc6a9<br>Apply the visibility field when writing on the local server.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>... ...\n\/\/ \u2705 myapp48 \nimport { getViewerFromRequest, canSee } from '@\/lib\/visibility';\n\nexport async function GET(req: Request) {\n  const { searchParams } = new URL(req.url);\n   let targetUsername = searchParams.get('username'); \/\/ \u2705 myapp48\n\n\n  \/\/ \u2705 myapp48 - viewer \/ target \ubd84\ub9ac (\uae30\uc874 \ucf54\ub4dc\ub294 token \uc788\uc73c\uba74 username \ub36e\uc5b4\uc368\uc11c \ud504\ub85c\ud544 \uc870\ud68c\uac00 \ub9dd\uac00\uc9d0)\n  \/\/ targetUsername = \ud504\ub85c\ud544 \uc8fc\uc778, viewer = \uc9c0\uae08 \ub85c\uadf8\uc778\ud55c \uc0ac\ub78c\n  const viewer = getViewerFromRequest(req); \/\/ login user\n  const profileOwner = targetUsername; \/\/ search user\n\n  \/\/ \u2705 myapp48 - target \uccb4\ud06c\n  if (!profileOwner) return NextResponse.json({ error: 'username required' }, { status: 400 });\n\n  const allPosts = db.prepare('SELECT * FROM posts WHERE username =? ORDER BY created_at DESC').all(profileOwner) as any&#91;];\n\n  \/\/ \u2705 myapp48 - visibility \ud544\ud130\ub9c1 \ucd94\uac00\n  \/\/ public: \ub204\uad6c\ub098 \/ unlisted: \ub85c\uadf8\uc778 \ud544\uc694 \/ private: \ubcf8\uc778+\ud314\ub85c\uc6cc\ub9cc\n  const isOwnerView = viewer &amp;&amp; viewer === profileOwner;\n  const filtered = allPosts.filter((p: any) =&gt; {\n    if (isOwnerView) return true; \/\/ \ubcf8\uc778\uc774\uba74 \uc804\ubd80 \ubcf4\uae30\n    return canSee(viewer, p); \n  });\n\n  return NextResponse.json(filtered);\n}\n\nexport async function POST(req: Request) {\n... ...\n  \/\/ \u2705 myapp48 - visibility default value\n  visibility = visibility || 'public';\n... ...\n  \/\/ \u2705 myapp48 - add visibility\n  const id = randomUUID();\n  const now = Date.now();\n  \/\/db.prepare('INSERT INTO posts (id, content, username) VALUES (?, ?, ?)').run(id, content, username);\n  db.prepare('INSERT INTO posts (id, content, username, created_at, visibility) VALUES (?,?,?,?,?)').run(id, content, username, now, visibility);\n\n  \/\/ \u2705 myapp48\n  \/\/ - to\/cc distinction\n  const followersUrl = `https:\/\/${DOMAIN}\/users\/${username}\/followers`;\n  let to: string&#91;] = &#91;];\n  let cc: string&#91;] = &#91;];\n  if (visibility === 'public') {\n    to = &#91;'https:\/\/www.w3.org\/ns\/activitystreams#Public'];\n    cc = &#91;followersUrl];\n  } else if (visibility === 'unlisted') {\n    to = &#91;followersUrl];\n    cc = &#91;'https:\/\/www.w3.org\/ns\/activitystreams#Public'];\n  } else if (visibility === 'private') {\n    to = &#91;followersUrl];\n    cc = &#91;];\n  }\n  \/\/ \u2705 myapp48\n  const noteId = `https:\/\/${DOMAIN}\/users\/${username}\/posts\/${id}`;\n  const note = {\n    id: noteId,\n    type: 'Note',\n    attributedTo: `https:\/\/${DOMAIN}\/users\/${username}`,\n    content: content.startsWith('&lt;p&gt;')? content : `&lt;p&gt;${content}&lt;\/p&gt;`,\n    published: new Date().toISOString(),\n    to,\n    cc\n  };\n\n\/\/ \u2705 myapp48 - add visibility\n  try {\n    db.prepare('INSERT INTO outbox (id, type, actor, object, created_at, visibility) VALUES (?,?,?,?,?,?)')\n     .run(randomUUID(), 'Create', `https:\/\/${DOMAIN}\/users\/${username}`, JSON.stringify(note), now, visibility);\n  } catch (e) {\n    console.error('outbox \uc800\uc7a5 \uc5d0\ub7ec', e);\n  }\n... ...\n\n}\n... ...<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f app\/api\/timeline\/route.ts &#8211; local UI<\/p>\n\n\n\n<p>&#8212; \ub0b4 \uc11c\ubc84 UI\ud398\uc774\uc9c0\uc5d0\uc11c visibility \uc801\uc6a9<br>Apply visibility on my server UI page.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>... ...\n\/\/ \u2705 myapp48 \nimport { getViewerFromRequest, canSee } from '@\/lib\/visibility';\n\nexport async function GET(req: Request) {\n... ...\n  \/\/ \u2705 myapp48\n  const viewer = getViewerFromRequest(req);\n  const isOwnerView = viewer &amp;&amp; viewer === username;\n... ...\n  \/\/ \u2705 myapp48 - visibility \uceec\ub7fc \ucd94\uac00 \uc870\ud68c ( visibility \uac80\uc0c9)\n  const rawTimeline = db.prepare(`\n    SELECT id, content, username, username as actor, created_at, visibility, 'mine' as source, id as original_id\n    FROM posts WHERE username =?\n    UNION ALL\n    SELECT id, content, username, actor, created_at, visibility, 'inbox' as source, original_id\n    FROM inbox_posts WHERE username =?\n    ORDER BY created_at DESC\n    LIMIT 80\n  `).all(username, username) as any&#91;];\n\n  \/\/ \u2705 myapp48 - visibility \ud544\ud130\ub9c1 \ub85c\uc9c1 \ucd94\uac00\n  \/\/ public: \ub204\uad6c\ub098 \/ unlisted: \ub85c\uadf8\uc778 \ud544\uc694 \/ private: \ubcf8\uc778 + \ud314\ub85c\uc6cc\ub9cc\n  const timeline = rawTimeline.filter((p: any) =&gt; {\n    \/\/ \ub0b4 \ud0c0\uc784\ub77c\uc778\uc744 \ub0b4\uac00 \ubcf4\ub294 \uacbd\uc6b0 -&gt; \uc804\ubd80 \ubcf4\uc5ec\uc90c (private \ud3ec\ud568)\n    if (isOwnerView) return true;\n    \/\/ \ub0a8\uc758 \ud0c0\uc784\ub77c\uc778\uc744 \ubcf4\uac70\ub098 \uac8c\uc2a4\ud2b8\uc778 \uacbd\uc6b0 -&gt; canSee\ub85c \ud544\ud130\n    return canSee(viewer, p);\n  });\n\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f app\/api\/v1\/timelines\/home\/route.ts &#8211; External UI(etc. Pinafore)<\/p>\n\n\n\n<p>&#8212; \uc678\ubd80\uc571\uc5d0\uc11c \ub0b4 \uc11c\ubc84 \ud0c0\uc784\ub77c\uc778 \ubcfc\ub54c visibility \uc801\uc6a9<br>Apply visibility settings when viewing my server&#8217;s timeline from an external app.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>\/\/ \u2705 myapp48 - visibility \uacf5\ud1b5 \ud5ec\ud37c import \ucd94\uac00\nimport { getViewerFromRequest, canSee } from '@\/lib\/visibility';\n... ...\nexport async function GET(req: Request) { \n... ...\n  \/\/ \u2705 myapp48 \n  const viewer = getViewerFromRequest(req);\n  const username = viewer || 'user1'; \/\/ \uac8c\uc2a4\ud2b8\uba74 \ube48 \ud0c0\uc784\ub77c\uc778 \ubc18\ud658\ub418\ub3c4\ub85d \uc544\ub798\uc5d0\uc11c \ucc98\ub9ac\n\n  \/\/ \u2705 myapp48 - \ube44\ub85c\uadf8\uc778\uc774\uba74 \ud648 \ud0c0\uc784\ub77c\uc778 \uc811\uadfc \ucc28\ub2e8 (\ub9c8\uc2a4\ud1a0\ub3c8 \uc2a4\ud399)\n  \/\/ Access to the home timeline is blocked for non-logged-in users (Mastodon specification).\n  if (!viewer) {\n    return NextResponse.json(&#91;], { headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application\/json' } });\n  }\n... ...\n  \/\/ \u2705 myapp48 - visibility \ud544\ud130\ub9c1 \uc801\uc6a9\n  \/\/ \uc815\ucc45: public = \ub204\uad6c\ub098, unlisted = \ud648\uc5d0\uc11c\ub294 \ubcf4\uc5ec\uc90c (\ud504\ub85c\ud544\uc5d0\uc11c\ub9cc \uc228\uae30\ub824\uba74 false\ub85c \ubcc0\uacbd), private = \ubcf8\uc778+\ud314\ub85c\uc6cc\ub9cc\n  \/\/ Policy: public = visible to everyone, unlisted = visible on Home (change to false to hide only from profile), private = visible only to self and followers\n  const rawRows = &#91;...myPosts,...inboxPosts];\n  const filteredRows = rawRows.filter((p: any) =&gt; {\n    const vis = p.visibility || 'public';\n    if (vis === 'public') return true;\n    if (vis === 'unlisted') {\n      \/\/ \ub9c8\uc2a4\ud1a0\ub3c8 \uc815\uc11d\uc740 \ud648\uc5d0 unlisted \ud45c\uc2dc\ud568. \n      return true; \/\/ &lt;- false\ub85c \ubc14\uafb8\uba74 \ud648\uc5d0\uc11c unlisted \uc228\uae40\n    }\n    if (vis === 'private') {\n      return canSee(viewer, p);\n    }\n    return true;\n  });\n  filteredRows.sort((a,b) =&gt; parseDate(b.created_at) - parseDate(a.created_at));\n... ...\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0fapp\/users\/[username]\/inbox\/route.ts<\/p>\n\n\n\n<p>&#8212; \uae00 \uc800\uc7a5\uc2dc visibility \ud544\ub4dc \ucd94\uac00 \/ Add a visibility field when saving the post.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>... ...\nexport async function POST(req: Request, { params }: { params: Promise&lt;{ username: string }&gt; }) {\n... ...\nif (body.type === 'Create') {\n... ...\n          \/\/ \u2705 myapp48 - for visibility\n          const to = note.to || &#91;];\n          const cc = note.cc || &#91;];\n          const isPublicInTo = Array.isArray(to)? to.includes('https:\/\/www.w3.org\/ns\/activitystreams#Public') : to === 'https:\/\/www.w3.org\/ns\/activitystreams#Public';\n          const isPublicInCc = Array.isArray(cc)? cc.includes('https:\/\/www.w3.org\/ns\/activitystreams#Public') : cc === 'https:\/\/www.w3.org\/ns\/activitystreams#Public';\n          let visibility = 'public';\n          if (!isPublicInTo &amp;&amp;!isPublicInCc) visibility = 'private';\n          else if (!isPublicInTo &amp;&amp; isPublicInCc) visibility = 'unlisted';\n          else visibility = 'public';\n\n          console.log(` -&gt; visibility: ${visibility} to=${JSON.stringify(to)} cc=${JSON.stringify(cc)}`);\n\n\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, note.published || new Date().toISOString());\n\n          \/\/ \u2705 myapp48 - add visibility\n          db.prepare(`INSERT OR IGNORE INTO inbox_posts (id, actor, content, username, original_id, created_at, visibility, type) VALUES (?,?,?,?,?,?,?,?)`)\n          .run(shortId, actorId, content, username, longId, note.published || new Date().toISOString(), visibility, note.type || 'Note');\n          \n... ...\n}\n... ...\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f app\/usersui\/[username]\/_components\/themes\/pinafore\/PinaforeTheme.tsx<\/p>\n\n\n\n<p>&#8212; \uae00\uc4f0\uae30 \ud3fc \uc801\uc6a9 \/ Apply writing form<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>   \n... ...\nexport function PinaforeTheme({ timeline : initialTimeline, username, initialView, onBoost, onLike }: any) {  \n\n... ...     \n  \/\/ \u2705 myapp48 - writing status\n  const &#91;composeText, setComposeText] = useState('');\n  const &#91;composeVis, setComposeVis] = useState&lt;'public'|'unlisted'|'private'&gt;('public');\n  const &#91;posting, setPosting] = useState(false);\n... ...\n\/\/ \u2705 myapp48 : write post\nconst handlePost = async () =&gt; {\n  if (!composeText.trim() || posting) return;\n  setPosting(true);\n  try {\n    \/\/ \ud1a0\ud070 \uc788\uc73c\uba74 \ud5e4\ub354\uc5d0 \ub123\uace0 \uc5c6\uc73c\uba74 \ucfe0\ud0a4 \uc778\uc99d\uc73c\ub85c!\n    const token = localStorage.getItem('access_token') || localStorage.getItem('token') || '';\n    const res = await fetch('\/api\/v1\/statuses', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application\/json',\n         ...(token? { Authorization: `Bearer ${token}` } : {})\n      },\n      credentials: 'include',\n      body: JSON.stringify({ status: composeText, visibility: composeVis })\n    });\n    if (res.ok) {\n      const newPost = await res.json();\n      setComposeText('');\n      \/\/ \ud0c0\uc784\ub77c\uc778 \ub9e8 \uc704\uc5d0 \uc989\uc2dc \ucd94\uac00!\n      setTimeline((prev: any&#91;]) =&gt; &#91;{\n        id: newPost.id, content: newPost.content, actor: `https:\/\/${DOMAIN}\/users\/${username}`,\n        username: username, created_at: new Date().toISOString(), source: 'local', isMine: true, visibility: composeVis\n      },...prev]);\n      setCounts(c =&gt; ({...c, posts: c.posts + 1}));\n    } else alert('\uac8c\uc2dc \uc2e4\ud328');\n    } catch (e) { console.error(e); alert('\uac8c\uc2dc \uc2e4\ud328'); }\n  setPosting(false);\n};\n... ...\nreturn (\n&lt;&gt;\n&lt;style&gt;{`\n... ...\n       \/* \u2705 myapp48 - composer(write form) *\/\n       .pinafore-composer { margin-top: auto; background: white; border: 1px solid #e6ecf0; border-radius: 12px; padding: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); }\n       .pinafore-composer textarea { width: 100%; height: 80px; resize: none; border: 1px solid #e6ecf0; border-radius: 8px; padding: 8px; font-size: 14px; outline: none; }\n       .pinafore-composer textarea:focus { border-color: #6364ff; }\n       .composer-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; }\n       .composer-foot select { padding: 4px 6px; font-size: 12px; border-radius: 6px; }\n       .composer-foot button { background: #6364ff; color: white; border: none; padding: 6px 16px; border-radius: 20px; font-weight: bold; cursor: pointer; font-size: 13px; }\n       .composer-foot button:disabled { opacity: 0.4; cursor: not-allowed; }\n... ...\n`}&lt;\/style&gt;\n     {\/* \u2705 myapp48 - \uc88c\uce21 \ud558\ub2e8 \uae00\uc4f0\uae30 \ud3fc(\ub85c\uadf8\uc778 \ud6c4) \/ Writing form at the bottom left (after logging in) *\/}\n          {isMe &amp;&amp; (\n            &lt;div className=\"pinafore-composer\"&gt;\n              &lt;textarea\n                value={composeText}\n                onChange={(e) =&gt; setComposeText(e.target.value)}\n                placeholder=\"\ubb34\uc2a8 \uc77c\uc774 \uc77c\uc5b4\ub098\uace0 \uc788\ub098\uc694?\"\n                maxLength={500}\n              \/&gt;\n              &lt;div className=\"composer-foot\"&gt;\n                &lt;select value={composeVis} onChange={(e) =&gt; setComposeVis(e.target.value as any)}&gt;\n                  &lt;option value=\"public\"&gt;\ud83c\udf0d \uacf5\uac1c\/public&lt;\/option&gt;\n                  &lt;option value=\"unlisted\"&gt;\ud83d\udd13 \ubbf8\ub4f1\ub85d\/unlisted&lt;\/option&gt;\n                  &lt;option value=\"private\"&gt;\ud83d\udd12 \ud314\ub85c\uc6cc\ub9cc\/private&lt;\/option&gt;\n                &lt;\/select&gt;\n                &lt;button onClick={handlePost} disabled={posting ||!composeText.trim()}&gt;\n                  {posting? '...' : 'submit'}\n                &lt;\/button&gt;\n              &lt;\/div&gt;\n            &lt;\/div&gt;\n          )}\n... ...\n\n&lt;\/&gt;\n)\n}<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ucf54\ub4dc \uc218\uc815 &#8211; \ud0c0\uc784\ub77c\uc778\uc5d0\uc11c visibility\uc5d0 \ub530\ub77c \ubcf4\uc5ec\uc8fc\uae30<\/p>\n\n\n\n<p>\u2714\ufe0f visibility\uc5d0 \ub530\ub77c \uad6c\ubd84\ud574\uc11c \ubcf4\uc5ec\uc8fc\ub294 \ubd80\ubd84\uc740 \uc544\ub798\uc758 \ud30c\uc77c \ubd80\ubd84\uc785\ub2c8\ub2e4.<br>The section that displays content based on visibility is the file section below.<\/p>\n\n\n\n<p>&#8212; lib\/visibility.ts &#8211; visibility \ud568\uc218 \/ function<\/p>\n\n\n\n<p>&#8212; app\/api\/posts\/route.ts \ud504\ub85c\ud544 \ud0c0\uc784\ub77c\uc778 \/ profile timeline<\/p>\n\n\n\n<p>&#8212; app\/api\/timelines\/home\/route.ts &#8211; \ud0c0\uc784\ub77c\uc778 API \/ Timeline API<\/p>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc2a4\ud2b8 \/ Test<\/p>\n\n\n\n<p>\u2714\ufe0f user1\uc73c\ub85c \ub85c\uadf8\uc778\ud558\uace0 @user1\ud398\uc774\uc9c0 \ubc29\ubb38<br>Log in as user1 and visit the @user1 page.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"419\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-user1login-jpg.jpg\" alt=\"\" class=\"wp-image-11055\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-user1login-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-user1login-jpg-300x157.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-user1login-jpg-768x402.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-user1login-jpg-400x210.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">user1 Login<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f user2\ub85c \ub85c\uadf8\uc778\ud558\uace0 @user1\ud398\uc774\uc9c0 \ubc29\ubb38<br>Log in as user2 and visit @user1&#8217;s page.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"477\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-user2login-jpg.jpg\" alt=\"\" class=\"wp-image-11057\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-user2login-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-user2login-jpg-300x179.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-user2login-jpg-768x458.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-user2login-jpg-400x239.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">User2 Login<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f \ub85c\uadf8\uc544\uc6c3\ud558\uace0 @user1\ud398\uc774\uc9c0 \ubc29\ubb38<br>Log out and visit @user1&#8217;s page.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"570\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-not-logint-jpg.jpg\" alt=\"\" class=\"wp-image-11059\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-not-logint-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-not-logint-jpg-300x214.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-not-logint-jpg-768x547.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-not-logint-jpg-400x285.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Guest<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f pinafore &#8211; user1@freelifemakers.com\uc73c\ub85c \ub85c\uadf8\uc778<br>pinafore &#8211; Log in as user1@freelifemakers.com<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"770\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-pinafore-jpg.jpg\" alt=\"\" class=\"wp-image-11061\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-pinafore-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-pinafore-jpg-300x289.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-pinafore-jpg-768x739.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp48-pinafore-jpg-400x385.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Pinafore<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f DB \ud655\uc778 \/ Check DB<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>myapp48 % sqlite3 data.sqlite\nSQLite version 3.51.0 2025-06-12 13:14:41\nEnter \".help\" for usage hints.\nsqlite&gt; SELECT id,visibility,content FROM posts ORDER BY created_at DESC LIMIT 3\n   ...&gt; ;\n<strong>8ae0380a-6c14-4507-8caf-cf0411264cb6|unlisted|unlisted test\na2435541-db09-4b65-ad08-ae90d84dffdd|private|private test\ncd3f6242-9b9b-4000-b648-0040e9dcbf40|public|form test<\/strong>\nsqlite&gt; <\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f \uae00 \uc9c1\uc811 \uc0ad\uc81c \/ Delete post directly<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -X DELETE \"https:\/\/aloy-horizon.duckdns.org\/api\/cleanup-orphan?id=cd3f6242-9b9b-4000-b648-0040e9dcbf40&amp;username=user1\"\n<\/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 myapp48\uc5d0\uc11c\ub294 \ub85c\uceec SNS\ud398\uc774\uc9c0\uc5d0\uc11c \uae00\uc4f0\uae30 \ud3fc\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4.In myapp48, a post creation form is added to the local social media page. \ud83d\udc49\ud83c\udffb \uc791\uc131\ud55c \uae00\uc774 public \/ unlisted \/ private\ub85c \uad6c\ubd84\ud574\uc11c \uc800\uc7a5\ub418\ub3c4\ub85d \ub370\uc774\ud130\ubca0\uc774\uc2a4 \ud14c\uc774\ube14\uc744 \uc218\uc815\ud569\ub2c8\ub2e4.Modify the database table so that written posts are saved with a classification of public, unlisted, or private. \ud83d\udc49\ud83c\udffb \uc804\uccb4 \ucf54\ub4dc\ub294 \uae43\ud5c8\ube0c\uc5d0\uc11c \ud655\uc778 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":11055,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28,27,1,7],"tags":[],"class_list":["post-10976","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\/10976","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=10976"}],"version-history":[{"count":83,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/10976\/revisions"}],"predecessor-version":[{"id":11065,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/10976\/revisions\/11065"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media\/11055"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=10976"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=10976"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=10976"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}