[nextjs]SNS Server-26(myapp36) 

👉🏻 회원가입 및 로그인과 관련된 기능입니다.
These are features related to sign-up and login.

👉🏻 pinafore화면에서 표시 것들을 구현 중입니다.
I am currently implementing the elements displayed on the pinafore screen.

👉🏻 홈타임라인에서 좋아요와 부스트를 표시합니다.
Displays likes and boosts on the home timeline.

👉🏻 전체 코드는 깃허브에서 확인 할 수 있습니다.
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
│   ├── api/v1/instance/route.ts -> auth
│   ├── api/v1/apps/route.ts.    -> auth
│   ├── api/v1/accounts/verify_credentials/route.ts -> auth
│   ├── api/v1/statuses/route.ts -> Write Post
│   ├── api/v1/timelines/home/route.ts -> timeline
│   ├── oauth/authorize/route.ts -> auth
│   ├── oauth/token/route.ts -> auth
│   ├── api/v1/search/home/route.ts -> search
│   ├── api/v2/search/home/route.ts -> search
│   ├── 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


 📁 코드 수정 / Code Modification

✔️ pinafore 홈 타임라인에 내가 팔로우 하는 사람 모든 글 뜨게 하기
Make posts from everyone I follow appear on my Pinafore home timeline.

— app/api/v1/timelines/home/route.ts

import { NextResponse } from 'next/server';
import db from '@/lib/db';

const DOMAIN = process.env.DOMAIN || 'aloy-horizon.duckdns.org';
function parseDate(v: any): number {
  if (!v) return 0;
  if (/^\d{13}$/.test(String(v))) return parseInt(v);
  const d = new Date(v).getTime();
  return isNaN(d)? 0 : d;
}

export async function GET(req: Request) { ...

    // ✅ myapp36-전체 검색 / Full search
    const reblogs_count = announcesByObject.get(longId) || announcesByObject.get(originalId) || announcesByObject.get(shortId) || announcesByObject.get(short8) || 0;
    const favourites_count = likesByObject.get(longId) || likesByObject.get(originalId) || likesByObject.get(shortId) || likesByObject.get(short8) || 0;
    
    const favourited = likedSet.has(longId) || likedSet.has(originalId) || likedSet.has(shortId) || likedSet.has(short8);
    const reblogged = reblogs_count > 0;

    let content = row.content || '';
    try { if (content.startsWith('{')) { const o = JSON.parse(content); content = o.object?.content || o.content || content; } } catch {}

    const baseAccount = isMy
     ? { id: '1', username, acct: `${username}@${DOMAIN}`, display_name: username, avatar: `https://${DOMAIN}/icon.png` }
      : (() => {
          let uname = 'user1', dom = 'freelifemakers.com', display = 'user1';
          if (row.actor?.includes('mastodon.social')) { uname = 'freelifemakers'; dom = 'mastodon.social'; display = 'freelifemakers'; }
          return { id: `remote_${dom.replace(/\./g,'_')}_${uname}`, username: uname, acct: `${uname}@${dom}`, display_name: display, avatar: `https://${DOMAIN}/icon.png` };
        })();

    return {
      id: String(row.id),
      uri: longId,
      url: longId,
      account: baseAccount,
      content: content.startsWith('<')? content : `<p>${content}</p>`,
      created_at: new Date(parseDate(row.created_at)).toISOString(),
      visibility: 'public',
      reblogs_count,      // 전체 부스트 카운트 / Full boost count
      favourites_count,   // 전체 좋아요 카운트 / Full like count 
      replies_count: 0,
      favourited: !!favourited, // 전체 좋아요 / Full like
      reblogged: !!reblogged,   // 전체 부스트 / Full Boost
      muted: false, bookmarked: false, pinned: false,
    };

... ...
 }

✔️ announce 로직 수정 – DB에서 announce(boost) 저장하기 않기
Modify ‘announce’ logic – do not save ‘announce’ (boost) to the database.

— announce에 저장된 글과 일치하는 inbox_posts 글에 부스트 표시
Display a boost indicator on inbox_posts entries that match posts stored in announce.

–app/users/[username]/inbox/route.ts

    // Announce 수신 - actor 포함! / Receive Announce - include actor!
    if (body.type === 'Announce') {
      try {
        const announceId = body.id;
        let objectId = typeof body.object === 'string' ? body.object : body.object?.id;
        if (!objectId) return new Response('', { status: 202 });
        objectId = objectId.replace('/posts/', '/statuses/');
        
        console.log(`🔁 [${username}] Announce 도착: ${actorId} -> ${objectId}`);

        // actor 컬럼 포함
        db.prepare('INSERT OR IGNORE INTO announces (id, actor, object, username) VALUES (?,?,?,?)')
          .run(announceId, actorId, objectId, username);

        // ✅ myapp36 
        // - announce(boost)는 inbox_posts에 저장 취소,announce테이블 값과 같은것 검색하기
        // For `announce(boost)`, remove the entry from `inbox_posts` and search for records matching the values ​​in the `announce` table.
        // try {
        //   const noteRes = await fetch(objectId, { headers: { Accept: 'application/activity+json' } });
        //   if (noteRes.ok) {
        //     const note = await noteRes.json();
        //     const longId = note.id || objectId;
        //     const shortId = `${longId.split('/').pop()}_boost_${Date.now()}`;
        //     const content = note.content || '';
        //     db.prepare(`INSERT OR IGNORE INTO inbox_posts (id, actor, content, username, original_id, created_at) VALUES (?,?,?,?,?,?)`)
        //       .run(shortId, actorId, content, username, longId, body.published || new Date().toISOString());
        //   }
        // } catch {}
        console.log(`✅ announces 저장 완료 / announces saved: ${announceId}`);
      } catch (e) {
        console.error(`❌ Announce 저장 실패 / Failed to save announce`, e);
      }
      return new Response('', { status: 202 });
    }

📁 테스트 / Test

✔️ pinafore.social

— inbox_posts + posts의 글을 확인합니다.

Pinafore

✔️ aloy-horizon.duckdns.org/@user1

— 모든 데이터와 카운트는 여기서 확인 할 수 있습니다.
You can check all the data and counts here.

Local Server

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

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

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

Leave a Reply