ππ» myapp29μλ μ’μμ λ²νΌμ μΆκ°ν©λλ€.
Add a “Like” button to myapp29.
ππ» κΈ°μ‘΄μ λΆμ€νΈμ μ’μμ λΆλΆμ λ²νΌ μ€λ₯λ‘ λ²νΌ μ€νμ 보λ₯Ό DBκ²μμ 보λ₯Ό νμ©νλλ‘ λ°κΏ¨μ΅λλ€.
To resolve button errors in the existing “Boost” and “Like” features, I modified the implementation to utilize database search information for button actions.
ππ» μ 체 μ½λλ κΉνλΈμμ νμΈ ν μ μμ΅λλ€.
You can find the full code on GitHub.
https://github.com/gideonslife01/flm-nextjs
π μ 체 νλ‘μ νΈ κ΅¬μ‘° / Overall Project Structure
myapp29/
βββ 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
β βββ ap.ts -> Follow Accept
β βββ db.ts -> DB connection
βββ data/
β βββ keys/ -> private.pem, public.pem
βββ data.sqlite -> Database
βββ Caddyfile -> https
βββ instrumentation.ts -> Background Server
βββ package.json
π νλ‘μ νΈ μμ(myapp29)
Project Start (myapp29)
npx create-next-app@latest
π SQLiteμ€μΉ / Installing SQLite
cd ~/myapp29
npm install better-sqlite3
npm install -D @types/better-sqlite3
π chokidar μ€μΉ / Installing chokidar
— μ€μκ° νμΌ λ° ν΄λλ³κ²½ κ°μκΈ°λ₯
Real-time file and folder change monitoring function
npm install -D chokidar
π 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;
π μ΄μ νλ‘μ νΈμμ μ κ· νλ‘μ νΈλ‘ 볡μ¬ν νμΌ
Files to copy from the previous project to the new project
myapp project/
βββ app/ (Next.js App Router)
β βββ .well-known/webfinger/route.ts -> webfinger
β βββ api/ -> API
β βββ users/ -> Inbox,Outbox,Post,Actor
β βββ usersui/ -> Timeline UI,Theme
β βββ layout.tsx -> Privoder
βββ lib/
βββ data/ -> Key
βββ data.sqlite -> Database
βββ Caddyfile -> https
βββ next.config.ts -> DDNS,@
βββ instrumentation.ts -> Background Server
π ν μ΄λΈ μμ / Modify Table
βοΈ announces ν
μ΄λΈμ actorνλλ₯Ό μΆκ°ν©λλ€.
Add an ‘actor’ field to the ‘announces’ table.
ALTER TABLE announces ADD COLUMN actor TEXT;
CREATE INDEX IF NOT EXISTS idx_announces_actor ON announces(actor);
CREATE UNIQUE INDEX IF NOT EXISTS idx_announces_actor_object ON announces(actor, object);
π μ½λ μμ / Code Modification
βοΈ lib/db.ts
... ...
db.exec(`
... ...
CREATE TABLE IF NOT EXISTS users (
-- myapp29 β
Boost(Announcement)
CREATE TABLE IF NOT EXISTS announces (
id TEXT PRIMARY KEY,
username TEXT,
object TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
actor TEXT
);
`);
db.exec(`
... ...
-- myapp29 β
CREATE INDEX IF NOT EXISTS idx_announces_actor ON announces(actor);
CREATE UNIQUE INDEX IF NOT EXISTS idx_announces_actor_object ON announces(actor, object);
`);
βοΈ app/api/timeline/route.ts
— boostλκΈ°μ‘΄ μ½λμ μ§, μλμ½λλ μ’μμ λΆλΆ μ½λ μμ
The ‘boost’ option preserves the existing code, while the code below modifies the ‘like’ section.
export async function GET(req: Request) {
... ...
// λ΄ Like - actor μ νν / Accurate actor for my Like
const myActor = `https://${DOMAIN}/users/${username}`;
const myLikes = db.prepare(`SELECT object FROM likes WHERE actor =?`).all(myActor) as any[];
... ...
// β
shortId μμ νκ²! / shortId safely!
const shortId = getShortId(fullId);
if (!shortId) {
return {...p, fullId, isMyBoost: false, boostCount: 0, isMyLike: false, likeCount: 0 };
}
... ...
// β
μ’μμ - shortId κΈ°λ° / Like - based on shortId
const isMyLike = likedSet.has(shortId);
const likeCount = (db.prepare(
`SELECT COUNT(*) as c FROM likes WHERE object LIKE '%' ||? || '%'`
).get(shortId) as any)?.c || 0;
}
βοΈ app/api/like/route.ts
— μ’μμ λ²νΌ λλ₯Όλ λ°μ΄ν°λ² μ΄μ€μ μ μ₯λ κ°μ κΈ°μ‘΄μΌλ‘ μ€μ
Set the initial value based on the data stored in the database when the “Like” button is clicked.
// β
DB 체ν¬: λ΄κ° μ΄λ―Έ λλ μΌλ©΄ 1 λνμ§ μμ! / DB check: If I already liked, do not add 1!
const existing = db.prepare(`SELECT id FROM likes WHERE actor =? AND object LIKE '%' ||? || '%'`).get(myActor, shortId) as any;
if (existing) {
const likeCount = (db.prepare(`SELECT COUNT(*) as c FROM likes WHERE object LIKE '%' ||? || '%'`).get(shortId) as any).c;
return NextResponse.json({ ok: true, alreadyLiked: true, isMyLike: true, likeCount });
}
βοΈ usersui/[username]/page.tsx
— μ 체 DB κΈ°μ€μΌλ‘ μμ
Modified based on the entire database
const handleBoost = async (post: any) => { ... ... }
const handleLike = async (post: any) => { ... ...}
βοΈapp/api/inbox/route.ts
— actνλ λΉ μ§κ² μΆκ°, Undo λκ° μλκ² μμ ,actor + shortIdλ‘ μμ νλλ‘ μμ
Added the missing ‘act’ field, fixed the duplicate ‘Undo’ entry, and modified the deletion logic to use ‘actor’ + ‘shortId’.
export async function POST(req: Request, { params }: { params: Promise<{ username: string }> }) {
... ...
if (body.type === 'Undo') { ... ...}
if (body.type === 'Create') { ... ... }
if (body.type === 'Like') { ... ... }
if (body.type === 'Announce') { ... ...
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());
}
if (body.type === 'Follow') { ... ... }
... ...
}
}
βοΈ usersui/[username]/theme/pinafore/PinaforeTheme.tsx
— λ²νΌ λ° μΉ΄μ΄ν° μΆκ° / Add buttons and counters
<button onClick={() => onBoost(p)} className={p.isMyBoost? 'boosted' : ''}>π {p.boostCount || ''}</button>
<button onClick={() => onLike(p)} className={p.isMyLike? 'liked' : ''}>β {p.likeCount || ''}</button>
π ν μ€νΈ / Test
βοΈ ν
μ€νΈλ λ§μ€ν λ μλ²μμ κ³μ μμ±ν΄μ ν
μ€νΈνμ΅λλ€.
The testing was conducted by creating an account on a Mastodon server.
βοΈ λΌμ°νΈ / route
βοΈ λ§μ€ν λ κ³μ μ£Όμ
https://mastodon.social/@freelifemakers
βοΈ pinafore

βοΈ mastodon

βοΈ aloy-horizon.duckdns.org


μν볡μ 8μ₯ 32μ / John 8:32
“κ·Έλ¦¬κ³ λν¬λ μ§λ¦¬λ₯Ό μκ² λ κ²μ΄λ©°, μ§λ¦¬κ° λν¬λ₯Ό μμ λ‘κ² ν κ²μ΄λ€.”
“Then you will know the truth ,and the truth will set you free”