[nextjs]SNS Server-22(myapp32) 

👉🏻 회원가입 및 로그인 기능 구현 중 입니다.
We are currently implementing the sign-up and login functions.

👉🏻 public key 검증 로직을 계속 진행합니다.
We will proceed with the public key verification logic.

👉🏻 Inbox를 public key로 검증합니다.
Verify the Inbox using the public key.

👉🏻 inbox에 서버의 속도 향상을 위해서 캐시 기능을 추가합니다.
A caching feature is being added to the inbox to improve server speed.

👉🏻 outbox는 캐시기능을 적용하지 않습니다.
The outbox does not utilize caching.

👉🏻 pinafore에서 글 작성이 가능하도록 outbox/route.ts에 POST함수를 추가합니다.
Add a POST function to outbox/route.ts to enable writing posts in Pinafore.

👉🏻 전체 코드는 깃허브에서 확인 할 수 있습니다.
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
│   ├── auth.ts                -> Authentication, User Management
│   ├── ap.ts                  -> Follow,Undo,Create,Likes,Announce
│   └── db.ts                  -> DB connection
├── data/
│   ├── keys/userIDs/          -> private.pem, public.pem(New)
│   └── keys/                  -> private.pem, public.pem(legacy)
├── data.sqlite                -> Database(1/3)
├── data.sqlite-wal            -> Database(2/3)
├── data.sqlite-shm            -> Database(3/3)
├── Caddyfile                  -> https 
├── instrumentation.ts         -> Background Server
└── package.json

📁 프로젝트 시작 / Project Start


📁 Inbox Public Key Cache

✔️ 서버 속도 향상을 위해서 inbox에 캐시기능을 추가합니다.
A caching function is being added to the inbox to improve server speed.

✔️ 다른 서버 글을 빨리 받을 수 있습니다.
You can quickly receive posts from other servers.

— myapp32/lib/ap.ts

... ...

// ✅ myapp32 - 공개키 캐싱 / Public key caching
const publicKeyCache = new Map<string, { key: string, cachedAt: number }>();
const CACHE_TTL = 1000 * 60 * 60 * 1; // 1시간! (빠르면서 최신!) / 1 hour! (fast and up-to-date!)

... ...
export async function fetchActorPublicKeyCached(actorUrl: string, username: string): Promise<string> { ... }

export async function fetchActorPublicKeyWithRetry(
  actorUrl: string, 
  username: string, 
  verifyReq: Request
): Promise<{ publicKey: string, isValid: boolean }> { ... }

✔️ inbox에 캐시 적용
Apply caching to the inbox.

— myapp32/app/users/[username]/inbox/route.ts

import { sendAccept, verifyHttpSignature, fetchActorPublicKey, fetchActorPublicKeyWithRetry } from '@/lib/ap';
export async function POST(req: Request, { params }: { params: Promise<{ username: string }> }) { 
 ... ...
        if (body.type === 'Delete') {
        console.log(`🗑 Delete는 검증 스킵! / Skip validation for Delete! ${actorIdForVerify}`);
      } 
      else if (!actorIdForVerify) {
        console.error('❌ actor 없음! 검증 스킵!/ No actor! Skip verification!', body);
      } else {
... ...
          const verifyReq = new Request(req.url, {
            method: req.method,
            headers: req.headers,
            body: rawBody
          });
... ...          
        
      // ❗️ fetchActorPublicKeyWithRetry에 포함됨 / Included in fetchActorPublicKeyWithRetry
        // const isValid = await verifyHttpSignature(verifyReq, publicKeyPem); 

        // ✅ myapp32 - 캐싱 + 재시도! / Caching + Retry!
        const { isValid } = await fetchActorPublicKeyWithRetry(
            actorIdForVerify, 
            username, 
            verifyReq
        );
... ...        
      }
}

📁 Outbox Public Key 검증
Outbox Public Key Verification

✔️ Outbox는 캐시를 적용하지 않습니다.
The Outbox does not use caching.

💡 inbox는 내 서버에 글이 들어올 때 상대방 서버가 본인의 private key로 서명해서 보내면 내 서버가 글 보내는 서버에 public key를 요청합니다. 두 키가 맞으면 글 받기를 허용합니다.

With the inbox mechanism, when a message arrives at my server—signed by the sender's server using its private key—my server requests the public key from the sending server. If the keys match, the message is accepted.

💡 캐시를 적용하면 내 서버에 상대방 서버의 publick key가 저장되어 있으므로 상대방 서버에 public key 요청 횟수가 줄어듭니다. 그래서 글 받기 속도가 향상됩니다.

Implementing caching stores the other server's public key on your server, thereby reducing the number of requests made to the other server for that key. This results in faster content retrieval speeds.

💡 outbox에서 POST는 반대로 내가 작성한 글을 상대방 서버에 보내는 기능을 수행합니다.

In the outbox, the POST method performs the function of sending a message I have written to the recipient's server.

💡 글을 보내는건 내 서버의 속도와 관련 없으니 캐시를 적용하지 않습니다.

Sending the content is unrelated to my server's speed, so caching is not applied.

✔️ POST 함수추가 / Add POST function

— myapp32/app/users/[username]/outbox/route.ts

// ✅ POST 추가! Pinafore가 여기로 글 씀! / Added POST! Pinafore writes here!
export async function POST(req: Request, { params }: { params: Promise<{ username: string }> }) { ... }

✔️ GET함수에 username 필터 추가
Add a username filter to the GET function.

export async function GET(_req: Request, { params }: { params: Promise<{ username: string }> }) {
  const { username } = await params;
    // ✅ myap32 - username 필터 추가 / Added username filter
    const posts = db.prepare('SELECT * FROM posts WHERE username = ? ORDER BY created_at DESC LIMIT 20').all(username) as any[];
    
  try {
} catch (e) { }

📁 테스트 / Test

✔️ /api/posts 라우터는 내 UI전용 글쓰기 라우트
The /api/posts router is the route dedicated to post creation for my UI.

— 글을 작성할때 아래의 라우트로 사용합니다.
Use the route below when writing a post.

curl -X POST https://aloy-horizon.duckdns.org/api/posts \
  -H "Content-Type: application/json" \
  -d '{"username":"user1","content":"Hello local UI #apiposts"}'

✔️ /users/[username]/outbox 라우터는 activityPub 표준라우트로 글 작성 할 때 사용합니다.
The /users/[username]/outbox route handles post creation in accordance with the ActivityPub standard.

— pinafore등에서 글 작성 할때 사용함.
Used when writing posts on platforms like Pinafore.

curl -X POST https://aloy-horizon.duckdns.org/users/user1/outbox \
  -H "Content-Type: application/activity+json" \
  -d '{"type":"Create","object":{"content":"Hello Activity UI #activityposts "}}'

✔️ 내가 작성한 글 목록
List of posts I’ve written

https://aloy-horizon.duckdns.org/users/user1/outbox

✔️ 터미널 테스트 / Terminal test

— 로컬 UI,pinafore에서 글 작성 할 때 사용할 라우트
Route to be used when composing a post in the local UI, Pinafore.

# api/posts POST 터미널에서 실행 (로컬 UI 전용)
# Execute api/posts POST request in the terminal (local UI only)

% curl -X POST https://aloy-horizon.duckdns.org/api/posts \
  -H "Content-Type: application/json" \
  -d '{"username":"user1","content":"Hello local UI #apiposts"}'

# 응답 / response

{"id":"aefc7ff8-5b38-4fb7-829d-9b14343cec6a","content":"Hello local UI #apiposts","created_at":"2026-09-05 03:39:37","username":"user1"}%

% 
% 

# users/[username]/outbox POST 터미널에서 실행(ActivityPub 전용)
# Execute POST to users/[username]/outbox in the terminal (ActivityPub only)

% curl -X POST https://aloy-horizon.duckdns.org/users/user1/outbox \
  -H "Content-Type: application/activity+json" \
  -d '{"type":"Create","object":{"content":"Hello Activity UI #activityposts"}}'

# 응답 / response

{"@context":"https://www.w3.org/ns/activitystreams","id":"https://aloy-horizon.duckdns.org/users/user1/posts/4ace7a75-3a40-494f-8dea-bf3f688b6cfb#activity","type":"Create","actor":"https://aloy-horizon.duckdns.org/users/user1","object":{"id":"https://aloy-horizon.duckdns.org/users/user1/posts/4ace7a75-3a40-494f-8dea-bf3f688b6cfb","type":"Note","attributedTo":"https://aloy-horizon.duckdns.org/users/user1","content":"Hello Activity UI #activityposts","published":"2026-09-05T03:39:48.980Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://aloy-horizon.duckdns.org/users/user1/followers"]}}

%    

✔️ 마스트돈에 글 도착 확인
Confirmed receipt of post on Mastodon.

mastodon.social

✔️ gotosocial에 글 도착 확인
Confirming receipt of post on GoToSocial

gotosocial

✔️ 서버 로그 / Server Log

Server Log

요한복음 8장 32절 / John 8:32

“그리고 너희는 진리를 알게 될 것이며, 진리가 너희를 자유롭게 할 것이다.”

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

Leave a Reply