[nextjs]SNS Server-9(myapp19)

πŸ‘‰πŸ» inbox_posts ν…Œμ΄λΈ”μ— actor와 original_idν•„λ“œλ₯Ό μΆ”κ°€ ν•©λ‹ˆλ‹€.
Add the actor and original_id fields to the inbox_posts table.

πŸ‘‰πŸ» original_idν•„λ“œλŠ” λ‚˜μ€‘μ— id(API의 id λΆ€λΆ„)λ₯Ό 짧게 λ³€ν™˜ν•˜κΈ° μœ„ν•΄μ„œ λ§Œλ“  ν•„λ“œμž…λ‹ˆλ‹€.
The original_id field was created to store a shortened version of the id (the ID portion of the API) for later use.

πŸ‘‰πŸ» ActorλŠ” κΈ€ 보낸 μ‚¬λžŒμ˜ 전체 URLμž…λ‹ˆλ‹€.
“Actor” is the full URL of the person who sent the post.

πŸ‘‰πŸ» 기쑴의 νƒ€μž„λΌμΈ UIλΆ€λΆ„μ˜ μ£Όμ†Œμ— @useridλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.
Use @userid in the URL for the existing timeline UI section.

πŸ“ 전체 ν”„λ‘œμ νŠΈ ꡬ쑰 / Overall Project Structure

myapp19/  
β”œβ”€β”€ app/  (Next.js App Router)
β”‚   β”œβ”€β”€ .well-known/webfinger/route.ts  -> webfinger
β”‚   β”œβ”€β”€ api/posts/route.ts     -> Writing API
β”‚   β”œβ”€β”€ api/follow/route.ts    -> Follow API(temporary)
β”‚   β”œβ”€β”€ 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
β”‚   β”œβ”€β”€ layout.tsx, page.tsx, globals.css
β”‚   └── favicon.ico
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ ap.ts                  -> Follow Accept
β”‚   └── db.ts                  -> DB connection
β”œβ”€β”€ data/
β”‚   └── keys/                  -> private.pem, public.pem
β”œβ”€β”€ data.sqlite                -> Database
β”œβ”€β”€ Caddyfile                  -> https 
└── package.json

πŸ“ ν”„λ‘œμ νŠΈ μ‹œμž‘(myapp19)
Project Start (myapp19)

npx create-next-app@latest

πŸ“ SQLiteμ„€μΉ˜ / Installing SQLite

cd ~/myapp19
npm install better-sqlite3
npm install -D @types/better-sqlite3

πŸ“ DDNS,httpsμ„€μ • / DDNS,https settings

πŸ“ λ°μ΄ν„°λ² μ΄μŠ€ ν•„λ“œ μΆ”κ°€
Add database field

βœ”οΈ inbox_postsν…Œμ΄λΈ”μ— actorκ°€ μ—†λ‹€λ©΄ actorν•„λ“œλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
If the actor field does not exist in the inbox_posts table, add it.

ALTER TABLE inbox_posts ADD COLUMN actor TEXT;
ALTER TABLE inbox_posts ADD COLUMN original_id TEXT;

1)inbox_postsν…Œμ΄λΈ”μ— actorν•„λ“œλ₯Ό μΆ”κ°€ν•˜κ³  postsν…Œμ΄λΈ”μ—λŠ” μΆ”κ°€ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
Add the actor field to the inbox_posts table, but do not add it to the posts table.

— 전체 DB μŠ€ν‚€λ§ˆ / Entire DB schema

sqlite> .schema
CREATE TABLE posts (
    id TEXT PRIMARY KEY,
    content TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  , username TEXT DEFAULT 'user1');
CREATE TABLE followers (
    id TEXT PRIMARY KEY,
    actor TEXT NOT NULL,
    inbox TEXT NOT NULL
  , username TEXT);
CREATE TABLE users (
  username TEXT PRIMARY KEY,
  display_name TEXT DEFAULT '',
  summary TEXT DEFAULT '',
  private_key TEXT,
  public_key TEXT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_followers_username ON followers(username);
CREATE INDEX idx_followers_actor ON followers(actor);
CREATE TABLE following (
  id TEXT PRIMARY KEY,
  actor TEXT NOT NULL,
  username TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_following_username ON following(username);
CREATE INDEX idx_following_actor ON following(actor);
CREATE TABLE inbox_posts (
  id TEXT PRIMARY KEY,
  actor TEXT,
  content TEXT,
  username TEXT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
, original_id TEXT);
CREATE INDEX idx_posts_username_created ON posts(username, created_at DESC);
CREATE INDEX idx_inbox_username_created ON inbox_posts(username, created_at DESC);
CREATE INDEX idx_inbox_actor ON inbox_posts(actor);
sqlite> 

βœ”οΈ νƒ€μž„λΌμΈ 라우트 μ½”λ“œ μˆ˜μ •
Modify timeline route code

— myapp19/app/api/timeline/route.ts

// myapp19 βœ…
  const timeline = db.prepare(`
    SELECT id, content, username, username as actor, created_at, 'mine' as source 
    FROM posts WHERE username = ?
    UNION ALL
    SELECT id, content, username, actor, created_at, 'inbox' as source 
    FROM inbox_posts WHERE username = ?
    ORDER BY created_at DESC
    LIMIT 50
`).all(username, username);

1)νƒ€μž„λΌμΈ API둜 λ°μ΄ν„°λ² μ΄μŠ€μ˜ Actorλ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€.
Query the database for the Actor using the Timeline API.

βœ”οΈ νƒ€μž„λΌμΈ UI 라우트 μ½”λ“œ μˆ˜μ •
Modify timeline UI route code.

— myapp19/app/usersui/[username]/page.tsx

... ...

  const timeline = db.prepare(`
    SELECT id, content, username, username as actor, created_at, 'mine' as source 
    FROM posts WHERE username = ?
    UNION ALL
    SELECT id, content, username, actor, created_at, 'inbox' as source 
    FROM inbox_posts WHERE username = ?
    ORDER BY created_at DESC LIMIT 50
  `).all(username, username) as any[];

... ...

  return (
    <div style={{ padding: 20 }}>
      <h1>{username} νƒ€μž„λΌμΈ / Timeline</h1>
      <ul>
        {timeline.map((p:any) => (
          <li key={`${p.source}-${p.id}`}>
            [{p.source}] <b>{p.actor}</b>: {p.content} - {p.created_at}
          </li>
        ))}
      </ul>
    </div>
  );

1)νƒ€μž„λΌμΈUIμ—μ„œ Actorλ₯Ό λ³΄μ—¬μ€λ‹ˆλ‹€.
Displays the Actor in the timeline UI.

βœ”οΈ μΈλ°•μŠ€ 라우트 μ½”λ“œ μˆ˜μ •
Modify inbox route code

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

db.prepare(`
INSERT OR IGNORE INTO inbox_posts (id, actor, content, username, original_id, created_at) VALUES (?, ?, ?, ?, ?, ?)`).run(postId, actorId, content, username, note.id, note.published || new Date().toISOString());

1)DB에 Actor와 orginal_idλ₯Ό μ €μž₯ν•˜λŠ” λΆ€λΆ„μž…λ‹ˆλ‹€.
This is the part that saves the Actor and original_id to the database.

βœ”οΈ λΈŒλΌμš°μ €μ—μ„œ μ•„λž˜μ˜ μ£Όμ†Œλ‘œ ν…ŒμŠ€νŠΈ ν•΄λ΄…λ‹ˆλ‹€.
Try testing it in your browser using the address below.

— Timeline API

https://aloy-horizon.duckdns.org/api/timeline?username=user1
Timeline API

— Timeline UI

https://aloy-horizon.duckdns.org/usersui/user1
TImeline UI

πŸ“ νƒ€μž„λΌμΈ μ£Όμ†Œμ— @userid μ‚¬μš©ν•˜κΈ°
Using @userid in the timeline URL

βœ”οΈ myapp19/next.config.ts μ½”λ“œ μˆ˜μ •
Modify myapp19/next.config.ts code

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* allow domain*/
  allowedDevOrigins: ['aloy-horizon.duckdns.org', '*.duckdns.org'],

  /* host.domain.org/@userid */
  async rewrites() {
    return [
      {
        source: '/@:username',
        destination: '/usersui/:username',
      },
    ]
  }
};


export default nextConfig;

1)rewrites()ν•¨μˆ˜λŠ” nextjsμ˜ˆμ•½ν•¨μˆ˜λΌ μžλ™μ‹€ν–‰λ©λ‹ˆλ‹€.
The rewrites() function is a reserved Next.js function, so it executes automatically.

βœ”οΈ μ„œλ²„λ₯Ό μž¬μ‹œμž‘ν•©λ‹ˆλ‹€.
Restarting the server.

ctrl + c
npm run dev

βœ”οΈ λΈŒλΌμš°μ €μ—μ„œ λ‹€μŒκ³Ό 같이 접속해 λ΄…λ‹ˆλ‹€.
Try accessing the following in your browser.

https://aloy-horizon.duckdns.org/@user1

βœ”οΈ 기쑴의 ν˜•μ‹λ„ μ‚¬μš©κ°€λŠ₯ ν•©λ‹ˆλ‹€.
The existing format can also be used.

https://aloy-horizon.duckdns.org/usersui/user1

πŸ’‘ next.config.tsλ₯Ό μˆ˜μ •ν•˜μ§€ μ•Šκ³  디렉토리이름을 바꿔도 @userid ν˜•μ‹μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
You can use the @userid format even if you change the directory name, without modifying next.config.ts.

app/@[username]/page.tsx
@userid

μš”ν•œλ³΅μŒ 8μž₯ 32절 / John 8:32

“그리고 λ„ˆν¬λŠ” 진리λ₯Ό μ•Œκ²Œ 될 것이며, 진리가 λ„ˆν¬λ₯Ό 자유둭게 ν•  것이닀.”

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

Leave a Reply