ππ» νλ‘μκ³Ό νλ‘μκ° ν
μ΄λΈμ μ μ₯λμ§ μλ λ¬Έμ λ₯Ό μμ νμμ΅λλ€.
Fixed an issue where following and follower data were not being saved to the table.
ππ» IGNORE INTOλ₯Ό μΆκ°ν΄μ λ°μ΄ν° μ€λ³΅μ
λ ₯ λ°μμ μλ¬λ₯Ό λ°μμν€μ§ μκ³ μ§λκ°λλ€.
By adding IGNORE INTO, the operation proceeds without throwing an error when duplicate data is encountered.
π μ 체 νλ‘μ νΈ κ΅¬μ‘° / Overall Project Structure
myapp22/
βββ 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
π νλ‘μ νΈ μμ(myapp22)
Project Start (myapp22)
npx create-next-app@latest
π SQLiteμ€μΉ / Installing SQLite
cd ~/myapp22
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
βοΈ myapp22/app/users/[username]/inbox/route.ts
// β
myapp22 - insert or ignore
try {
db.prepare('INSERT OR IGNORE followers (id, actor, inbox, username) VALUES (?,?,?,?)')
.run(body.id, actorId, inboxUrl, username);
console.log(`β
[${username}] νλ‘μ° μ μ₯ / follow save : ${actorId}`);
} catch (e: any) {
if (e.code === 'SQLITE_CONSTRAINT_UNIQUE') {
console.log(`βΉοΈ μ€λ³΅ νλ‘μ μ°¨λ¨ / Block Duplicate Followers (UNIQUE): ${actorId}`);
return new Response('', { status: 202 });
}
throw e;
}
βοΈ myapp22/api/follow/route.ts
// myapp22 β
// λ°μ΄ν°λ² μ΄μ€μ μ€λ³΅ μ
λ ₯ λ°©μ§ / Preventing duplicate entries in the database
if (result.ok) {
try {
db.prepare('INSERT OR IGNORE INTO following (id, actor, username) VALUES (?, ?, ?)')
.run(result.followDoc.id, target, username);
console.log(`β
DB μ μ₯ / DB Save: ${username} -> ${target}`);
} catch (e: any) {
if (e.code === 'SQLITE_CONSTRAINT_UNIQUE') {
console.log(`βΉοΈ μ΄λ―Έ μ μ₯λ¨ / Already saved: ${target}`);
} else {
console.error('DB μ μ₯ μ€ν¨ / DB save failed:', e);
}
}
}
βοΈ myapp22/lib/db.ts
— DBμ€ν€λ§ μ¬λ°μ(νλ‘μ νΈ μ½λ μ°Έμ‘°)
Re-apply DB schema (refer to project code)
π ν μ€νΈ / Test
βοΈ ν°λ―Έλμμ μλμ λΌμ°νΈλ‘ ν
μ€νΈ ν μ μμ΅λλ€.
You can test using the route below in the terminal.
# νλ‘μ° (following 1λ‘ μ¦κ°)
# Follow (following increased to 1)
curl -X POST https://aloy-horizon.duckdns.org/api/follow \
-H "Content-Type: application/json" \
-d '{"username":"user1","target":"https://freelifemakers.com/users/user1"}'
# μΈνλ‘μ° (following 0μΌλ‘ κ°μ)
# Unfollow (following count reduced to 0)
curl -X DELETE https://aloy-horizon.duckdns.org/api/follow \
-H "Content-Type: application/json" \
-d '{"username":"user1","target":"https://freelifemakers.com/users/user1"}'
# κΈ μ°κΈ° (outbox 7λ‘ μ¦κ° + νλ‘μλ€μκ² λ°°λ¬)
# Writing a post (added to outbox 7 + delivered to followers)
curl -X POST https://aloy-horizon.duckdns.org/api/posts \
-H "Content-Type: application/json" \
-d '{"username":"user1","content":"Hello Follow! #test"}'
βοΈ https://freelifemakers.com/users/user1 μκ² νλ‘μ°λ₯Ό μμ²νλ©΄ following ν
μ΄λΈμ λ°μ΄ν° μ μ₯λλμ§νμΈν©λλ€.
Check if data is saved to the following table when a follow request is sent to https://freelifemakers.com/users/user1.
# νλ‘μ° μμ² / Follow request
myapp22 % curl -X POST https://aloy-horizon.duckdns.org/api/follow \
-H "Content-Type: application/json" \
-d '{"username":"user1","target":"https://freelifemakers.com/users/user1"}'
# μλ΅ / response
{"ok":true,"inbox":"https://freelifemakers.com/users/user1/inbox","target":"https://freelifemakers.com/users/user1","username":"user1","result":"{\"status\":\"Accepted\"}","follow":{"@context":"https://www.w3.org/ns/activitystreams","id":"https://aloy-horizon.duckdns.org/users/user1/follows/1787615594327","type":"Follow","actor":"https://aloy-horizon.duckdns.org/users/user1","object":"https://freelifemakers.com/users/user1"}}%
# λ°μ΄ν°λ² μ΄μ€μ λ°μ΄ν° μ μ₯ μν νμΈ / Checking the data storage status in the database
myapp22 % sqlite3 data.sqlite "SELECT * FROM following;"
https://aloy-horizon.duckdns.org/users/user1/follows/1787615594327|https://freelifemakers.com/users/user1|user1|2026-08-24 23:53:14
gimdaegyeong@gimdaegyeong-ui-MacBookAir myapp22 %
βοΈ freelifemakers.com/@user1(pinafore)μ΄ aloy-horizon.ducdns.orgμκ² νλ‘μ°λ₯Ό μμ²νλ©΄ followerν
μ΄λΈμ μ μ₯λ©λλ€.
When freelifemakers.com/@user1(pinafore) requests to follow aloy-horizon.ducdns.org, the request is stored in the follower table.
# sqlite db μ μ / Connect to SQLite database
myapp22 % sqlite3 data.sqlite
SQLite version 3.51.0 2025-06-12 13:14:41
Enter ".help" for usage hints.
sqlite> select * from followers;
https://freelifemakers.com/users/user1/follow/01RR6E0D7QZA98S06VJYC99FNH|https://freelifemakers.com/users/user1|https://freelifemakers.com/users/user1/inbox|user1
sqlite>
βοΈ API νμΈ / Check API
— λ°°μ΄μ μ μ₯λ κ°μ΄ μΆλ ₯λλμ§ νμΈν©λλ€.
Check whether the values ββstored in the array are output.
— following
https://aloy-horizon.duckdns.org/users/user1/following

— followers
https://aloy-horizon.duckdns.org/users/user1/followers

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