[nextjs]SNS Server-16(myapp26)

๐Ÿ‘‰๐Ÿป myapp26์—๋Š” ์Šคํ‚จ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
Skin functionality is implemented in myapp26.

๐Ÿ‘‰๐Ÿป ์ฝ”๋“œ ์–‘์ด ๋งŽ์•„์„œ ์ฃผ์š” ๋ถ€๋ถ„๋งŒ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค .
Due to the large volume of code, I will explain only the key parts.

๐Ÿ‘‰๐Ÿป ์ „์ฒด์ฝ”๋“œ๋Š” ๊นƒํ—ˆ๋ธŒ ์ฝ”๋“œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”
Please refer to the GitHub repository for the full code.

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

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

myapp26/  
โ”œโ”€โ”€ 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/
โ”‚   โ”‚       โ”œโ”€โ”€ pinafore/Pinaforetheme.tsx  -> Theme 1
โ”‚   โ”‚       โ”œโ”€โ”€ mastodon/Mastodontheme.tsx  -> Theme 2
โ”‚   โ”‚       โ””โ”€โ”€ minimal/Minimaltheme.tsx    -> Theme 3
โ”‚   โ”œโ”€โ”€ layout.tsx, page.tsx, globals.css
โ”‚   โ””โ”€โ”€ favicon.ico
โ”œโ”€โ”€ lib/
โ”‚   โ”œโ”€โ”€ theme.tsx              -> Theme Provider
โ”‚   โ”œโ”€โ”€ ap.ts                  -> Follow Accept
โ”‚   โ””โ”€โ”€ db.ts                  -> DB connection
โ”œโ”€โ”€ data/
โ”‚   โ””โ”€โ”€ keys/                  -> private.pem, public.pem
โ”œโ”€โ”€ data.sqlite                -> Database
โ”œโ”€โ”€ Caddyfile                  -> https 
โ””โ”€โ”€ package.json

๐Ÿ“ ํ”„๋กœ์ ํŠธ ์‹œ์ž‘(myapp26)
Project Start (myapp26)

npx create-next-app@latest

๐Ÿ“ SQLite์„ค์น˜ / Installing SQLite

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

๐Ÿ“ DDNS,https์„ค์ • / DDNS,https settings

๐Ÿ“ ๋„๋ฉ”์ธํ—ˆ์šฉ, @userid ์‚ฌ์šฉํ•˜๊ธฐ
Allowing domains, using @userid

— next.config.ts

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;

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

โœ”๏ธ myapp26/lib/theme.tsxํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์™€ ์ปจํ…์ŠคํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
Import the myapp26/lib/theme.tsx file and apply the context provider.

๐Ÿ’ก ์ปจํ…์ŠคํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋Š” ํ•˜๋‚˜์˜ ์ด๋ฒคํŠธ๋กœ ์ „์ฒด ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ธฐ๋Šฅ์œผ๋กœ ํ…Œ๋งˆ์ ์šฉ์ด๋‚˜ ์–ธ์–ด ์ ์šฉ ์„ค์ •์— ๋งŽ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
A Context Provider is a feature that allows the entire state to be updated via a single event; it is commonly used for settings such as applying themes or languages.

— myapp26/app/usersui/[username]/Layout.tsx

import { ThemeProvider } from "@/lib/theme"; // โœ… myapp26

... ...

export default function RootLayout({ children }: LayoutProps<"/">) {
  return (
    <html
      lang="en"
      className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
    >
      <body className="min-h-full flex flex-col">
        {/* โœ… myapp26 - Add ThemeProvide */}
        <ThemeProvider>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

โœ”๏ธ ์—ฌ๊ธฐ์„œ ํ…Œ๋งˆํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ํ…Œ๋งˆ(์Šคํ‚จ)์„์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
Here, you load the theme file and apply the theme (skin).

— myapp26/app/paget.tsx

... ...
import { PinaforeTheme } from './_components/themes/pinafore/Pinaforetheme';
import { MastodonTheme } from './_components/themes/mastodon/Mastodontheme';
import { MinimalTheme } from './_components/themes/minimal/Minimaltheme';
... ...

  // โœ… timeline fetch
  useEffect(() => {
    params.then(p => {
      setUsername(p.username);
      fetch(`/api/timeline?username=${p.username}`)
        .then(r => r.json())
        .then(data => {
          setTimeline(data);
          setLoading(false);
        });
    });
  }, [params]);
... ...
  if (loading) return <div style={{ padding: 20 }}>Loading...</div>;

  // โœ… theme ๋ถ„๊ธฐ + props ์ „๋‹ฌ / Theme branching + props passing
  const props = { timeline, username, onBoost: handleBoost, onLike: handleLike };

  // apply theme
  if (theme === 'pinafore') return <PinaforeTheme {...props} />
  if (theme === 'mastodon') return <MastodonTheme {...props} />
  if (theme === 'minimal') return <MinimalTheme {...props} />
  
  return <PinaforeTheme {...props} />;

๐Ÿ“ ๋””๋ ‰ํ† ๋ฆฌ ๋ฐ ํŒŒ์ผ ์ถ”๊ฐ€ / Add directories and files

โœ”๏ธ ํƒ€์ž„๋ผ์ธ ์Šคํ‚จ(ํ…Œ๋งˆ) UI / Timeline Skin (Theme) UI

— ์•„๋ž˜์˜ ํŒŒ์ผ์€ ๋ชจ๋‘ ํ…Œ๋งˆ ๋””์ž์ธ์œผ๋กœ ์ข‹์•„์š”,๋ถ€์ŠคํŠธ๊ธฐ๋Šฅ์€ ๋น ์ ธ์žˆ์Šต๋‹ˆ๋‹ค.
The files below are all theme designs; the boost function is not included.

myapp26/app/usersui/[username]/_components/themes/pinafore/Pinaforetheme.tsx

myapp26/app/usersui/[username]/_components/themes/mastodon/Mastodontheme.tsx

myapp26/app/usersui/[username]/_components/themes/minimal/Minimaltheme.tsx

— ์„ธ ํŒŒ์ผ ๋ชจ๋‘ ๋™์ผํ•˜๋ฉฐ ํ…Œ๋งˆ์˜ ๊ตฌ๋ถ„์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ…Œ๋งˆ ์ด๋ฆ„๋งŒ ํ‘œ์‹œ์คฌ์Šต๋‹ˆ๋‹ค.
All three files are identical; only the theme names are displayed to distinguish between them.

<h2>๐Ÿ˜ {username}/ Pinafore</h2> {/* ํ…Œ๋งˆ์ด๋ฆ„ / Theme Name */}
Pinafore theme
Mastodon theme

— ํ…Œ๋งˆ ๋ณ€์ˆ˜์˜ ๊ฐ’์ด ๋ณ€ํ•˜๋ฉด ์ปจํ…์ŠคํŠธ ํ”„๋กœ๋ฐ”์ด๋”์—์„œ ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
When the value of the theme variable changes, the context provider updates the value in local storage.

... ...
const [theme, setTheme] = useState<ThemeName>('pinafore');
... ...         
<select 
            value={theme} //  ํ˜„์žฌ ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ๊ฐ’ ํ‘œ์‹œ / Display current local storage value
            onChange={(e) => {
            setTheme(e.target.value as any); 
         }}>
            <option value="pinafore">Pinafore</option>
            <option value="mastodon">Mastodon</option>
            <option value="minimal">Minimal</option>
          </select>
Minimal
Browser Local storage

โœ”๏ธ ์ปจํ…์ŠคํŠธ ํ”„๋กœ๋ฐ”์ด๋” / Context Provider

— myapp26/lib/theme.tsx

... ...
  useEffect(() => {
    const saved = localStorage.getItem('theme') as ThemeName;
    if (saved) setTheme(saved);
  }, []);
... ...  

return (
    <ThemeContext.Provider value={{ theme, setTheme: updateTheme }}>
      <div data-theme={theme} style={{ minHeight: '100%', display: 'flex', flexDirection: 'column', flex: 1 }}>
        {children}
      </div>
    </ThemeContext.Provider>
  );

1)์ปจํ…์ŠคํŠธ ํ”„๋กœ๋ฐ”์ด๋”์— theme๋ณ€์ˆ˜๋ฅผ ํŒŒ๋ผ๋ฉ”ํ„ฐ๋กœ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
Apply the theme variable as a parameter to the context provider.

2)theme ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด ๋‹ค๋ฅธ ํ…Œ๋งˆํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
It is used to load a different theme file when the theme value changes.

3)ํ…Œ๋งˆ์ด๋ฆ„์€ ๋ธŒ๋ผ์šฐ์ €์˜ ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
The theme name is stored in the browser’s local storage.

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

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

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

Leave a Reply