[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