ππ» νμκ°μ
λ° λ‘κ·ΈμΈκ³Ό κ΄λ ¨λ κΈ°λ₯μ
λλ€.
These are features related to sign-up and login.
ππ» myapp38μμλ μλ¦Ό κΈ°λ₯μ ꡬνν©λλ€.
myapp38 implements a notification feature.
ππ» μλ¦Ό κΈ°λ₯μ pinafore.socialμ κΈ°μ€μΌλ‘ ν©λλ€.
The notification feature is based on pinafore.social.
ππ» μ 체 μ½λλ κΉνλΈμμ νμΈ ν μ μμ΅λλ€.
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
β βββ api/v1/accounts/[id]/followers/route.ts -> followers
β βββ api/v1/accounts/[id]/following/route.ts -> following
β βββ 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
π FOLLOWERS,FOLLOWING ν
μ΄λΈ ꡬ쑰 λ³κ²½
Modification of FOLLOWERS and FOLLOWING table structures
βοΈ ν μ΄λΈ / Table
— ν
μ΄λΈμ μλ¦Ό νμΈμ λ μ§κ° μ μ₯λμ§ μμ 무쑰건 nowλ‘ νμλμ΄ followersν
μ΄λΈ νλμ λ μ§μ
λ ₯ μΆκ°
The table always displays ‘now’ because the date is not saved when a notification is checked, so date input was added to the followers table field.
— ν
μ΄λΈ ꡬ쑰λ₯Ό μμ νλ©΄ νλκ° κΈ°μ‘΄ νλλ§μ§λ§μ μΆκ°λλ―λ‘ κ΄λ¦¬μ μΌκ΄μ±λ¬Έμ λ‘ ν
μ΄λΈ μλ‘ μμ±
Since modifying the table structure adds fields to the end of existing fields, a new table is created due to management and consistency issues.
— νμ€μ© μ€νμ μΆμ²ν©λλ€. νλ²μ μ€ννλ©΄ λ°μ΄ν° μμ λ μ μμ΅λλ€.
It is recommended to execute the commands one line at a time. Executing them all at once could result in data loss.
-- 1. λ°±μ
/ backup
CREATE TABLE followers_backup AS SELECT * FROM followers;
CREATE TABLE following_backup AS SELECT * FROM following;
SELECT * FROM followers_backup LIMIT 2;
SELECT * FROM following_backup LIMIT 2;
-- 2. κΈ°μ‘΄ ν
μ΄λΈ μμ / Delete existing table
DROP TABLE followers;
DROP TABLE following;
-- 3. λμΌν μ€ν€λ§λ‘ μλ‘ μμ± (νλμμ λμΌνκ² λ§μΆ€)
-- Create new with the same schema (matching field order)
CREATE TABLE followers (
id TEXT PRIMARY KEY,
actor TEXT NOT NULL,
inbox TEXT NOT NULL,
username TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE following (
id TEXT PRIMARY KEY,
actor TEXT NOT NULL,
inbox TEXT NOT NULL,
username TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 4. μΈλ±μ€λ κΈ°μ‘΄κ³Ό λμΌν¨ / The index is the same as before.
CREATE INDEX idx_followers_username ON followers(username);
CREATE INDEX idx_followers_actor ON followers(actor);
CREATE INDEX idx_followers_created ON followers(created_at DESC);
CREATE UNIQUE INDEX idx_followers_actor_username ON followers(actor, username);
CREATE INDEX idx_following_username ON following(username);
CREATE INDEX idx_following_actor ON following(actor);
CREATE INDEX idx_following_inbox ON following(inbox);
CREATE INDEX idx_following_created ON following(created_at DESC);
CREATE UNIQUE INDEX idx_following_actor_username ON following(actor, username);
-- 5. λ°μ΄ν° 볡ꡬ (inbox μμΌλ©΄! actor + /inboxλ‘! μΆμ !)
-- Data recovery! (If there's no 'inbox', assume it's 'actor' + '/inbox'!)
INSERT INTO followers (id, actor, inbox, username, created_at)
SELECT
id,
actor,
COALESCE(inbox, actor || '/inbox'),
username,
COALESCE(created_at, CURRENT_TIMESTAMP)
FROM followers_backup;
INSERT INTO following (id, actor, inbox, username, created_at)
SELECT
id,
actor,
COALESCE(inbox, actor || '/inbox'),
username,
COALESCE(created_at, CURRENT_TIMESTAMP)
FROM following_backup;
-- 6. νμΈ / check
SELECT * FROM followers;
SELECT * FROM following;
.schema followers
.schema following
-- 7. λ°±μ
ν
μ΄λΈ μμ (νμΈ ν)
-- Delete backup table (after verification)
DROP TABLE followers_backup;
DROP TABLE following_backup;
βοΈ μΈλ±μ€ μΆκ° / Add Index
— ν
μ΄λΈ ꡬ쑰λ³κ²½μΌλ‘ μΈλ±μ€λ λμΌνκ² λ§μΆκΈ°
Adjusting indexes to match table structure changes
CREATE INDEX IF NOT EXISTS idx_followers_inbox ON followers(inbox);
π μ½λμμ / Code Modification
βοΈ ν
μ΄λΈμμ μΌλ‘ μ½λ μμ ν λΆλΆ
Parts of the code modified due to table changes
app/api/v1/accounts/[id]/following/route.ts
app/api/v1/accounts/[id]/followers/route.ts
app/users/[username]/inbox/route.ts
lib/db.ts
βοΈ μλ¦ΌκΈ°λ₯ / Notification feature
— app/api/v1/notifications/route.ts
// β
myapp38 - app/api/v1/notifications/route.ts
import { NextResponse } from 'next/server';
import db from '@/lib/db';
const DOMAIN = process.env.DOMAIN || 'aloy-horizon.duckdns.org';
function parseActor(actor: string, idx: number) { ... }
function makeAccount(parsed: ReturnType<typeof parseActor>, idx: number, createdAt?: string) { ... }
export async function GET(req: Request) { ...
// 1. Follow μλ¦Ό / Follow Notification
try { ... } catch(e){ console.error('follow', e); }
// 2. Like μλ¦Ό / Like Notification
try { ... } catch(e){ console.error('likes notif', e); }
// 3. Reblog μλ¦Ό / Reblog(boost) Notification
try { ... }catch(e){}
}
... ...
π ν μ€νΈ / Test
βοΈ μλ¦Ό λΌμ°νΈ ν°λ―Έλ ν μ€νΈ / Notification Route Terminal Test
curl "https://aloy-horizon.duckdns.org/api/v1/notifications?limit=20"
# ν°λ―Έλ / Terminal
% curl "https://aloy-horizon.duckdns.org/api/v1/notifications?limit=20"
# μλ΅ / Response
[{"id":"follow_0_https://mastodon.social/b7cc4c41-b22a-43b6-9df8-5bb46b2222fd","type":"follow","created_at":"2026-09-10T23:25:07.353Z","account":{"id":"notif_acct_mastodon_social_117193449750714993_0","username":"117193449750714993","acct":"117193449750714993@mastodon.social","display_name":"117193449750714993","avatar":"https://aloy-horizon.duckdns.org/icon.png", ... ... }]
βοΈ νλ‘μ μλ¦Ό / Followers Notification

βοΈ λΆμ€νΈ μλ¦Ό / Boost Notification

βοΈ μ’μμ μλ¦Ό / Likes Notification

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