[nextjs]SNS Server-20(myapp30)

๐Ÿ‘‰๐Ÿป myapp30์—์„œ๋Š” ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๊ธ€ ์‚ญ์ œ์‹œ ๋‚ด ๊ธ€์„ ๋ฐ›์€ ์„œ๋ฒ„์˜ ๊ธ€๋„ ์‚ญ์ œํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
In myapp30, I added a feature that deletes the post on the server that received it whenever I delete my own post.

๐Ÿ‘‰๐Ÿป ์ „์ฒด ์ฝ”๋“œ๋Š” ๊นƒํ—ˆ๋ธŒ์—์„œ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
You can find the full code on GitHub.

https://github.com/gideonslife01/flm-nextjs

๐Ÿ“ ์ „์ฒด ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ / Overall Project Structure

myapp project/  
โ”œโ”€โ”€ app/  (Next.js App Router)
โ”‚   โ”œโ”€โ”€ .well-known/webfinger/route.ts  -> webfinger
โ”‚   โ”œโ”€โ”€ api/follow/route.ts    -> Follow API(temporary)
โ”‚   โ”œโ”€โ”€ api/announce/route.ts  -> Boost(Announcement)
โ”‚   โ”œโ”€โ”€ api/like/route.ts.     -> Like API
โ”‚   โ”œโ”€โ”€ api/posts/route.ts     -> Writing API
โ”‚   โ”œโ”€โ”€ api/timeline/route.ts  -> Timeline API
โ”‚   โ”œโ”€โ”€ users/[username]/
โ”‚   โ”‚   โ”œโ”€โ”€ statuses/[id]/route.ts -> Indivisual Post
โ”‚   โ”‚   โ”œโ”€โ”€ route.ts               -> Acotr Information
โ”‚   โ”‚   โ”œโ”€โ”€ followers/route.ts     -> Followers List
โ”‚   โ”‚   โ”œโ”€โ”€ following/route.ts     -> Following List
โ”‚   โ”‚   โ”œโ”€โ”€ inbox/route.ts         -> Inbox
โ”‚   โ”‚   โ””โ”€โ”€ outbox/route.ts        -> outbox
โ”‚   โ”œโ”€โ”€ usersui/[username]/
โ”‚   โ”‚   โ”œโ”€โ”€ page.tsx               -> Timeline UI
โ”‚   โ”‚   โ””โ”€โ”€ _components/themes/
โ”‚   โ”‚       โ”œโ”€โ”€ themeex/ThemeexTheme.tsx   -> Example Theme
โ”‚   โ”‚       โ”œโ”€โ”€ pinafore/PinaforeTheme.tsx -> Theme 1
โ”‚   โ”‚       โ”œโ”€โ”€ mastodon/MastodonTheme.tsx -> Theme 2
โ”‚   โ”‚       โ””โ”€โ”€ minimal/MinimaltTheme.tsx  -> Theme 3
โ”‚   โ”œโ”€โ”€ layout.tsx, page.tsx, globals.css
โ”‚   โ””โ”€โ”€ favicon.ico
โ”œโ”€โ”€ lib/
โ”‚   โ”œโ”€โ”€ theme.tsx              -> Theme Provider
โ”‚   โ”œโ”€โ”€ watchThemes.ts         -> Check real-time theme changes
โ”‚   โ”œโ”€โ”€ ap.ts                  -> Follow Accept
โ”‚   โ””โ”€โ”€ db.ts                  -> DB connection
โ”œโ”€โ”€ data/
โ”‚   โ””โ”€โ”€ keys/                  -> private.pem, public.pem
โ”œโ”€โ”€ data.sqlite                -> Database
โ”œโ”€โ”€ Caddyfile                  -> https 
โ”œโ”€โ”€ instrumentation.ts         -> Background Server
โ””โ”€โ”€ package.json

๐Ÿ“ ํ”„๋กœ์ ์Šค ์‹œ์ž‘ / Project Start

— ํ”„๋กœ์ ํŠธ ์„ค์น˜,๋ชจ๋“ˆ์„ค์น˜,๋„๋ฉ”์ธ ์„ค์ •,ํ•จ์ˆ˜,๋ผ์šฐํŠธ,ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ์„ค๋ช…์€ ์•„๋ž˜์˜ ํฌ์ŠคํŠธ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”
Please refer to the post below for information on project and module installation, domain configuration, functions, routes, and the project structure.


๐Ÿ“ ์ฝ”๋“œ์ˆ˜์ • / Code Modification

 โœ”๏ธapp/api/posts/route.ts

-- posts๋งŒ ์‚ญ์ œํ•˜๊ณ  ์—ฐ๋ฐฉ์—ย Deleteย ์•ˆ ๋ณด๋‚ด๋Š” ๋ฌธ์ œ ์ˆ˜์ •
Fixed an issue where only 'posts' were deleted without sending a 'Delete' request to the federation.

1) inbox_posts๋Š” ์•ˆ ์ง€์›€ +ย likes,ย announcesย ์นด์šดํŠธ๋„ ์•ˆ ์ง€์›€
`inbox_posts` are not deleted, and `likes` and `announces` counts are not cleared either.

2) import { sendDelete }ย ๋งจ ์œ„์— ์ถ”๊ฐ€ - ์„œ๋ช… ํฌํ•จ๋œ ํ•จ์ˆ˜ ์‚ฌ์šฉ
Add `import { sendDelete }` at the top โ€“ use the function that includes the signature.

3) DELETE์—์„œย fetchย ์ง์ ‘ ํ˜ธ์ถœ ->ย sendDelete(f.inbox, id, username)๋กœ ๊ต์ฒด
Replaced direct `fetch` call in `DELETE` with `sendDelete(f.inbox, id, username)`.

4) POST๋„ย for...of awaitย ->ย Promise.allSettledย ๋ณ‘๋ ฌ๋กœ ๋ณ€๊ฒฝย (์†๋„ ํ–ฅ์ƒ)
Changed POST requests from `for...of await` to `Promise.allSettled` for parallel execution (improved speed).

โœ”๏ธapp/lib/ap.ts

— ์ „์ฒด ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง , sendDeleteํ•จ์ˆ˜ ์ถ”๊ฐ€
Refactored the entire codebase and added the sendDelete function.

export async function sendDelete(toInbox: string, postId: string, username: string) {
  const actorId = `https://${DOMAIN}/users/${username}`;

  // postId๊ฐ€ ์ด๋ฏธ ์ „์ฒด URL์ด๋ฉด ๊ทธ๋Œ€๋กœ, UUID๋ฉด ๋งŒ๋“ค์–ด์ฃผ๊ธฐ 
  // If postId is already a full URL, use it; if it's a UUID, construct it! 
  const objectId = postId.startsWith('http') 
    ? postId 
    : `${actorId}/statuses/${postId}`; 

  const doc = {
    '@context': 'https://www.w3.org/ns/activitystreams',
    id: `${actorId}#delete/${crypto.randomUUID()}`,
    type: 'Delete',
    actor: actorId,
    object: objectId,
    published: new Date().toISOString(),
    to: ['https://www.w3.org/ns/activitystreams#Public']
  };
  console.log(`๐Ÿ—‘ [${username}] Delete -> ${toInbox} (${postId})`);
  const { res, text } = await signAndSend(toInbox, doc, username);
  console.log(`๐Ÿ“ฌ Delete: ${res.status}`, text);
  return { ok: res.ok, status: res.status, text, deleteDoc: doc };
}

๐Ÿ“ ํ…Œ์ŠคํŠธ / Test

โœ”๏ธ ๋งˆ์Šคํ† ๋ˆ์—์„œ ๊ธ€์“ฐ๊ณ  ๋‚ด์„œ๋ฒ„๋กœ ๊ธ€ ๋ฐฐ๋‹ฌ๋˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ
Writing a post on Mastodon and checking if it gets delivered to my server.

mastodon

— inbox_posts์— ๊ธ€๋ฐฐ๋‹ฌ ํ™•์ธ
Check for post deliveries in inbox_posts

sqlite> select id,content from inbox_posts;
https://freelifemakers.com/users/user1/statuses/01M0EJPP57WC47FAK0N395S7MB|<p>inbox_posts table test</p>
https://freelifemakers.com/users/user1/statuses/01M0KMS8K1XRFW703S32MX6V25|<p>actor test</p>
01M0XT1VBF23F8Y5EXAPK1VY56|<p>likes test</p>
01M12X859S7WNSJJD42NWZS8T7|<p>boost delivery test!!</p>
01M1AWST9S00BRBDC0AAEEN6VA|<p>Boots Button Test</p>
01M1DCBVDYTGB7W4CK3RVHN121|<p>Favorite TEst</p>
117193471582999448|<p>Hello Mastodon!!!</p>
117194002682623887|<p>mastodon boost test</p>
117194002682623887_boost_1788238635654|<p>mastodon boost test</p>
117194002682623887_boost_1788239235922|<p>mastodon boost test</p>
9b2ead85-4b15-4df9-9de4-04c4a7fbcab4_boost_1788239544485|Hello! Mastodon!!
117204066261760455|<p>mastodon ,ally,user1 posts delete test</p>
sqlite> 

โœ”๏ธ ๊ธ€ ์‚ญ์ œ / Delete Post

— ๋‚ด๊ฐ€ ์“ด๊ธ€๊ณผ ์„œ๋ฒ„๋กœ ๋ฐฐ๋‹ฌ๋œ ๊ธ€๋„ ์‚ญ์ œ(POST,GET ๋ชจ๋‘ ์ง€์›)
Deletes both the posts I wrote and the posts delivered to the server (supports both POST and GET).

# ๊ธฐ์กด POST ๋ฐฉ์‹๊ณผ GET๋ฐฉ์‹์„ ๋ชจ๋‘ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. 
# Supports both existing POST and GET methods.
# POST
curl -X DELETE https://aloy-horizon.duckdns.org/api/posts \
 -H "Content-Type: application/json" \
 -d '{"username":"user1","id":"117204066261760455"}'

# GET
curl -X DELETE "https://aloy-horizon.duckdns.org/api/posts?id=117204066261760455&username=user1"

— ๋‚ด ๋ฐ›์€ ๊ธ€ ์‚ญ์ œํ•ด๋ณด๊ธฐ( ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๊ธ€ ์•„๋‹ˆ๋ฉด ์‚ญ์ œ ๊ธˆ์ง€,์ •์ƒ์ ์œผ๋กœ ์—๋Ÿฌ ๋ฐœ์ƒ ์—ฌ๋ถ€ ํ™•์ธํ•˜๊ธฐ )
Try deleting a received post (ensure posts not written by me cannot be deleted and verify that errors occur as expected).

# SQLITE
sqlite> select id,content from inbox_posts;
https://freelifemakers.com/users/user1/statuses/01M0EJPP57WC47FAK0N395S7MB|<p>inbox_posts table test</p>
https://freelifemakers.com/users/user1/statuses/01M0KMS8K1XRFW703S32MX6V25|<p>actor test</p>
01M0XT1VBF23F8Y5EXAPK1VY56|<p>likes test</p>
01M12X859S7WNSJJD42NWZS8T7|<p>boost delivery test!!</p>
01M1AWST9S00BRBDC0AAEEN6VA|<p>Boots Button Test</p>
01M1DCBVDYTGB7W4CK3RVHN121|<p>Favorite TEst</p>
117193471582999448|<p>Hello Mastodon!!!</p>
117194002682623887|<p>mastodon boost test</p>
117194002682623887_boost_1788238635654|<p>mastodon boost test</p>
117194002682623887_boost_1788239235922|<p>mastodon boost test</p>
9b2ead85-4b15-4df9-9de4-04c4a7fbcab4_boost_1788239544485|Hello! Mastodon!!
117204066261760455|<p>mastodon ,ally,user1 posts delete test</p>
sqlite> 

# Terminal
myapp30 % curl -X DELETE "https://aloy-horizon.duckdns.org/api/posts?id=117204066261760455&username=user1"
{"error":"๋‚ด ๊ธ€ ์•„๋‹˜ / Not your post"}%                                                                                                                   
myapp30 % curl -X DELETE https://aloy-horizon.duckdns.org/api/posts \
 -H "Content-Type: application/json" \
 -d '{"username":"user1","id":"117204066261760455"}'
{"error":"๋‚ด ๊ธ€ ์•„๋‹˜ / Not your post"}%   

— ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๊ธ€ ์‚ญ์ œํ•˜๊ธฐ / Delete my post

1)ํ„ฐ๋ฏธ๋„์—์„œ ๊ธ€์“ฐ๊ธฐ / Writing in the Terminal

# ํ„ฐ๋ฏธ๋„์—์„œ ๊ธ€์“ฐ๊ธฐ
# Writing in the Terminal

myapp30 % curl -X POST https://aloy-horizon.duckdns.org/api/posts \
  -H "Content-Type: application/json" \
  -d '{"username":"user1","content":"<p>์—ฐ๋ฐฉ ์‚ญ์ œ ํ…Œ์ŠคํŠธ / Federated Deletion Test!!!</p>"}'
{"id":"fe9d75bb-8fb4-4ec3-9cff-76e5de8a4261","content":"<p>์—ฐ๋ฐฉ ์‚ญ์ œ ํ…Œ์ŠคํŠธ / Federated Deletion Test!!!</p>","created_at":"2026-09-03 00:33:22","username":"user1"}%  

2) ๋งˆ์Šคํ† ๋ˆ ๋ฐ ๊ณ ํˆฌ์†Œ์…œ ์„œ๋ฒ„์— ๊ธ€ ๋ฐฐ๋‹ฌํ™•์ธ
Verify post delivery to Mastodon and GoToSocial servers.

mastodon(mastodon.social/@freelifemakers)
gotosocial(freelifemakers.com/@user1)

3) ๋‚ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋‹ค๋ฅธ ์„œ๋ฒ„๋กœ ๋ฐฐ๋‹ฌ๋œ ๊ธ€๋„ ์‚ญ์ œ ํ™•์ธ
Check for deletion of posts delivered to servers other than my database as well.

                                                                                                                                             
# SQLITE์— ๊ธ€ ์ €์žฅ ํ™•์ธ
# Verify post storage in SQLite

myapp30 % sqlite3 data.sqlite
SQLite version 3.51.0 2025-06-12 13:14:41
Enter ".help" for usage hints.
sqlite> select id,content from posts;
e6c99652-572f-491a-9e61-05e67b0d58fc|Hello! my SNS!!
dcece9ee-8d4a-4f63-91e1-22fc60513f62|Hello! my SNS!!
cc30330e-7040-4a1b-9e60-731066b8817e|Hello! my SNS3!!
e6d532c8-974b-4a01-89f6-6e1b2c4cff32|Hello Fediverse! #test
aea0ec89-a368-44a9-8493-b2bd82a797a8|post test! #test
9b2ead85-4b15-4df9-9de4-04c4a7fbcab4|Hello! Mastodon!!
fe9d75bb-8fb4-4ec3-9cff-76e5de8a4261|<p>์—ฐ๋ฐฉ ์‚ญ์ œ ํ…Œ์ŠคํŠธ / Federated Deletion Test!!!</p>
sqlite> 

# ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๊ธ€ ์‚ญ์ œ (GET)
# Delete My Post (GET)

curl -X DELETE "https://aloy-horizon.duckdns.org/api/posts?id=fe9d75bb-8fb4-4ec3-9cff-76e5de8a4261&username=user1"

# Terminal

myapp30 % curl -X DELETE "https://aloy-horizon.duckdns.org/api/posts?id=fe9d75bb-8fb4-4ec3-9cff-76e5de8a4261&username=user1"
{"ok":true,"deletedId":"fe9d75bb-8fb4-4ec3-9cff-76e5de8a4261"}%                                                                                           
gimdaegyeong@gimdaegyeong-ui-MacBookAir myapp30 % 

# SQLITE
myapp30 % sqlite3 data.sqlite
SQLite version 3.51.0 2025-06-12 13:14:41
Enter ".help" for usage hints.
sqlite> select id,content from posts;
e6c99652-572f-491a-9e61-05e67b0d58fc|Hello! my SNS!!
dcece9ee-8d4a-4f63-91e1-22fc60513f62|Hello! my SNS!!
cc30330e-7040-4a1b-9e60-731066b8817e|Hello! my SNS3!!
e6d532c8-974b-4a01-89f6-6e1b2c4cff32|Hello Fediverse! #test
aea0ec89-a368-44a9-8493-b2bd82a797a8|post test! #test
9b2ead85-4b15-4df9-9de4-04c4a7fbcab4|Hello! Mastodon!!
sqlite> 

# ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๊ธ€ ์‚ญ์ œ(POST)
# Delete Post I Wrote (POST)

myapp30 % curl -X DELETE https://aloy-horizon.duckdns.org/api/posts \
 -H "Content-Type: application/json" \
 -d '{"username":"user1","id":"73f8e331-fdcd-48a4-bb03-c3c010bceba5"}'
{"ok":true,"deletedId":"73f8e331-fdcd-48a4-bb03-c3c010bceba5"}%   

์š”ํ•œ๋ณต์Œ 8์žฅ 32์ ˆ / John 8:32

“๊ทธ๋ฆฌ๊ณ  ๋„ˆํฌ๋Š” ์ง„๋ฆฌ๋ฅผ ์•Œ๊ฒŒ ๋  ๊ฒƒ์ด๋ฉฐ, ์ง„๋ฆฌ๊ฐ€ ๋„ˆํฌ๋ฅผ ์ž์œ ๋กญ๊ฒŒ ํ•  ๊ฒƒ์ด๋‹ค.”

“Then you will know the truth ,and the truth will set you free”

Leave a Reply