[nextjs]SNS Server-35(myapp45)ย 

๐Ÿ‘‰๐Ÿป myapp45์—์„œ๋Š” SNS ํŽ˜์ด์ง€์— ๊ธ€ ์‚ญ์ œ ๋ฒ„ํŠผ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
In myapp45, a delete button for posts is added.

๐Ÿ‘‰๐Ÿป ๊ธ€์‚ญ์ œ์‹œ ์˜ฌ๋ฐ”๋ฅธ ํ‚ค๋กœ ์‚ญ์ œ๊ฐ€๋Šฅํ•˜๋„๋ก ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
Modified the post deletion process to ensure deletion is possible using the correct key.

— ๋กœ๊ทธ์ธ ๊ตฌํ˜„์‹œ ํ‚ค ๊ฒฝ๋กœ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„
Changes to key paths during login implementation

๐Ÿ‘‰๐Ÿป ํ˜„์žฌ ๋กœ์ปฌ์€ ๊ธ€ ์ €์žฅ ์‚ญ์ œ๊ฐ€ ๋˜๋‚˜ ๊ธ€๋ฐฐ๋‹ฌ ๊ธฐ๋Šฅ๊ณผ pinafore์—์„œ ๊ธ€ ์‚ญ์ œ๊ธฐ๋Šฅ์€ ์•„์ง ๊ตฌํ˜„์ค‘์ž…๋‹ˆ๋‹ค.
Currently, saving and deleting posts works in the local environment, but the post delivery function and the post deletion feature in Pinafore are still under development.

— activitypub : app/api/v1/statuses/route.ts

— local : users/[username]/route.ts

๐Ÿ‘‰๐Ÿป ์ „์ฒด ์ฝ”๋“œ๋Š” ๊นƒํ—ˆ๋ธŒ์—์„œ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
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/followinglist/route.ts  -> followinglist API
โ”‚   โ”œโ”€โ”€ api/followerslist/route.ts  -> followerslist 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
โ”‚   โ”œโ”€โ”€ api/auth/signup/route.ts        -> Signup
โ”‚   โ”œโ”€โ”€ api/auth/login/route.ts         -> Login
โ”‚   โ”œโ”€โ”€ api/auth/logout/route.ts        -> Logout
โ”‚   โ”œโ”€โ”€ api/auth/refresh/route.ts       -> Refresh Token
โ”‚   โ”œโ”€โ”€ api/auth/me/route.tsx           -> Login Check
โ”‚   โ”œโ”€โ”€ auth/signup/page.tsx            -> Signup UI
โ”‚   โ”œโ”€โ”€ auth/signup/page.tsx            -> Login UI
โ”‚   โ”œโ”€โ”€ 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(Server, 404)
โ”‚   โ”‚   โ”œโ”€โ”€ ClientPage.tsx.        -> Timeline UI(Client,Call Active Themes)
โ”‚   โ”‚   โ””โ”€โ”€ _components/themes/
โ”‚   โ”‚       โ”œโ”€โ”€ themeex/ThemeexTheme.tsx   -> Example Theme
โ”‚   โ”‚       โ”œโ”€โ”€ pinafore/PinaforeTheme.tsx -> Theme 1
โ”‚   โ”‚       โ”œโ”€โ”€ pinafore/FollowersList.tsx -> Theme 1, FollowersList
โ”‚   โ”‚       โ”œโ”€โ”€ pinafore/FollowingList.tsx -> Theme 1, FollowingList
โ”‚   โ”‚       โ”œโ”€โ”€ pinafore/NotificationsList.tsx -> Theme 1, NotificationsList
โ”‚   โ”‚       โ”œโ”€โ”€ 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


๐Ÿ“ ๋ ˆ๊ฑฐ์‹œ ํ‚ค๋ฅผ user1์œผ๋กœ ๋ณ€๊ฒฝ
Change the legacy key to user1.

— ์—ฐ๋ฐฉ์„œ๋ฒ„์— ์ž‘์„ฑ๋œ ๊ธ€์€ ๋ ˆ๊ฑฐ์‹œํ‚ค๋กœ ์ž‘์„ฑ์ด ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
Posts created on the federation server are written using legacy keys.

— ํ˜„์žฌ user1์˜ ํ‚ค๋Š” ์ƒˆ๋กœ ๋ฐœ๊ธ‰๋œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.
The key for user1 has currently been newly issued.

— ๊ทธ๋ž˜์„œ ๋‘ํ‚ค๊ฐ€ ๋งž์ง€์•Š์•„ ์ƒˆ๋กœ ๋ฐœ๊ธ‰๋œ ํ‚ค๋ฅผ ๋ฐฑ์—…ํ•˜๊ณ  ๋ ˆ๊ฑฐ์‹œํ‚ค๋กœ ๋Œ€์ฒด ํ•ฉ๋‹ˆ๋‹ค.
Since the key did not match, I backed up the newly issued key and replaced it with the legacy key.

# ์‹ ๊ทœํ‚ค ๋ฐฑ์—… ๋ฐ ๋ ˆ๊ฑฐ์‹œํ‚ค๋ฅผ user1 ํ‚ค ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๋ณต์‚ฌ
# Back up the new key and copy the legacy key to the user1 key directory.

# ๋ฐฑ์—… / backup
myapp45 % cp data/keys/user1/private.pem data/keys/user1/private.pem.bak
myapp45 % cp data/keys/user1/public.pem data/keys/user1/public.pem.bak

# ๋ ˆ๊ฑฐ์‹œํ‚ค๋ฅผ user1 ํ‚ค ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๋ณต์‚ฌ
# Copy the legacy key to the user1 key directory.
myapp45 % cp data/keys/private.pem data/keys/user1/private.pem
% cp data/keys/public.pem data/keys/user1/public.pem

# ํ™•์ธ / check
myapp45 % diff data/keys/private.pem data/keys/user1/private.pem && echo "โœ… same key" || echo "โŒ diffrent key"
โœ… same key
myapp45 % 

# DB๋„ ์ˆ˜์ • / # DB also updated
sqlite3 db.sqlite "UPDATE users SET public_key = '$(cat data/keys/user1/public.pem)' WHERE username='user1';"

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

๐Ÿ’ก ์•„๋ž˜์˜ ํ…Œ๋งˆํŒŒ์ผ ์ˆ˜์ • ์ด์ „๊นŒ์ง€ ๋‹จ๋ฝ์€ ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๊ธ€์— ์‚ญ์ œ๋ฒ„ํŠผ์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
The section below, prior to the theme file modification, implements a feature that displays a delete button on posts I have written.

โœ”๏ธ API – app/api/posts/route.ts

— ๋ผ์šฐํŠธ ์ˆ˜์ •, ๋ชจ๋“  ํ•จ์ˆ˜๋ฅผ ํ† ํฐ์„ ์ ์šฉํ•˜๋„๋ก ์ˆ˜์ •
Modified routes and updated all functions to implement token-based authentication.

— inbox_posts ํ…Œ์ด๋ธ”๋„ ๋‚ด์šฉ๋„ ์‚ญ์ œํ•˜๋„๋ก ์ˆ˜์ •
Modified to delete both the inbox_posts table and its contents.

export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);

  // โœ… myapp45 - ํ† ํฐ ์ ์šฉ / token apply
  let username = searchParams.get('username');
  const auth = req.headers.get('Authorization') || '';
  const token = auth.replace('Bearer ', '').trim();
  if (token) {
    const oauth = db.prepare('SELECT username FROM oauth_tokens WHERE access_token=?').get(token) as any;
    if (oauth?.username) username = oauth.username; // If you have a token username > tokenuser
  }
  if (!username) return NextResponse.json({ error: 'username required' }, { status: 401 });
... ...
}

export async function POST(req: Request) {
  let { content, username } = await req.json();

  // โœ… myapp45 - apply token
  const auth = req.headers.get('Authorization') || '';
  const token = auth.replace('Bearer ', '').trim();
  if (token) {
    const oauth = db.prepare('SELECT username FROM oauth_tokens WHERE access_token=?').get(token) as any;
    if (oauth?.username) username = oauth.username; // ํ† ํฐ ์žˆ์œผ๋ฉด ํ† ํฐ ์œ ์ €๋กœ ๋ฎ์–ด์“ฐ๊ธฐ!
  }
  if (!username) return NextResponse.json({ error: '๋กœ๊ทธ์ธ ํ•„์š” / username required' }, { status: 400 });
  if (!content) return NextResponse.json({ error: '๋‚ด์šฉ ์—†์Œ / Content required' }, { status: 400 });
...... }


export async function PUT(req: Request) {
    // โœ… myapp45 - apply token
  let { id, content, username } = await req.json();
  const auth = req.headers.get('Authorization') || '';
  const token = auth.replace('Bearer ', '').trim();
  if (token) {
    const oauth = db.prepare('SELECT username FROM oauth_tokens WHERE access_token=?').get(token) as any;
    if (oauth?.username) username = oauth.username; // ํ† ํฐ ์žˆ์œผ๋ฉด ํ† ํฐ ์œ ์ €๋กœ ๋ฎ์–ด์“ฐ๊ธฐ!
  }
... ...
}

export async function DELETE(req: Request) {
  const { searchParams } = new URL(req.url);
  let id = searchParams.get('id'); // defalut method is GET
  let username = searchParams.get('username');

  // โœ… myapp45 - apply token
  const auth = req.headers.get('Authorization') || '';
  const token = auth.replace('Bearer ', '').trim();
  if (token) {
    const oauth = db.prepare('SELECT username FROM oauth_tokens WHERE access_token=?').get(token) as any;
    if (oauth?.username) username = oauth.username; // ํ† ํฐ ์žˆ์œผ๋ฉด ํ† ํฐ ์œ ์ €๋กœ ๋ฎ์–ด์“ฐ๊ธฐ!
  }
... ...
  // โœ… myapp45 - inbox_posts ์‚ญ์ œ / Delete inbox_posts
  db.prepare('DELETE FROM inbox_posts WHERE original_id LIKE ? OR id LIKE ? OR original_id = ?').run(`%${id}%`, `%${id}%`, id);

... ...
}

โœ”๏ธ API – /app/api/timeline/route.ts

— app/usersui/[username]/ClientPage.tsx์—์„œ ํ˜ธ์ถœํ•˜๋Š” API
API called by app/usersui/[username]/ClientPage.tsx

— isMine , isBoostedPost

— isBoostedPost๋Š” ์žฌํ™•์ธ ์˜ˆ์ •
isBoostedPost is subject to re-verification.

... ...
  const enriched = timeline.map((p: any) => {
    
    // โœ… myapp45 - ๋‹ค๋ฅธ ์‚ฌ๋ž†์ด ๋ถ€์ŠคํŠธํ•œ ๊ธ€ ์ฒดํฌ ์ถ”๊ฐ€ / Added a check for posts boosted by others.
    // - inbox_posts ํ…Œ์ด๋ธ” ๊ฒ€์ƒ‰์ธ ๊ฒฝ์šฐ / In the case of a search on the `inbox_posts` table
    const isBoostedPost = p.source === 'inbox' && !!p.original_id && p.original_id !== p.id;

    // - posts ํ…Œ์ด๋ธ” ๊ฒ€์ƒ‰์ธ ๊ฒฝ์šฐ / In the case of a search on the `posts` table
    const isMine = p.source === 'mine';
... ...
    return {
    ...p,
      fullId,
      isMyBoost,
      boostCount,
      isMyLike,
      likeCount,
      isBoostedPost, // inbox_posts ์ค‘ ๋ถ€์ŠคํŠธ์ธ์ง€ ํ™•์ธ / Check if the inbox_posts item is a boost.
      isMine, // ๋‚ด๊ฐ€ ์“ด ๊ธ€๋งŒ ์‚ญ์ œ ๊ฐ€๋Šฅ / You can only delete posts you have written.
    };
  });

โœ”๏ธ API – users/[username]/route.ts

— lib/ap.ts ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์ˆ˜์ •
Modified to use the function in lib/ap.ts

... ...
import  { getPublicKeyPemForServing }  from '@/lib/ap'; 

export async function GET(_req: Request, { params }: { params: Promise<{ username: string }> }) {
  const { username } = await params;
  const domain = 'aloy-horizon.duckdns.org';
  //const pubkey = fs.readFileSync(path.join(process.cwd(), 'data/keys/public.pem'), 'utf8');
  const pubkey = getPublicKeyPemForServing(username); 
... ...
}

โœ”๏ธ UI – /app/usersui/[username]/_components/themes/pinafore/PinaforeTheme.tsx

— ํ…Œ๋งˆํŒŒ์ผ ์ž…๋‹ˆ๋‹ค. / This is a theme file.

... ...
// โœ… myapp45 - timeline == iniitialTimeline(like alias, not type)
export function PinaforeTheme({ timeline : initialTimeline, username, initialView, onBoost, onLike }: any) {
... ....
 // โœ… myapp45 - initialTimeline ๊ฐ’์ด ๋ฐ”๋€Œ๋ฉด UI ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
  // Redraw the UI when the initialTimeline value changes.
  const [timeline, setTimeline] = useState<any[]>(initialTimeline || []);
  useEffect(() => { setTimeline(initialTimeline || []); }, [initialTimeline]);
... ...
// โœ… myapp45: ์‚ญ์ œ ํ•ธ๋“ค๋Ÿฌ  / Delete handler
const handleDelete = async (postId: string) => {
  if (!confirm('์‚ญ์ œํ• ๊นŒ? / Delete?')) return;
  try {
    // ๊ธฐ์กด DELETE ๋ผ์šฐํŠธ ์‚ฌ์šฉ / Use the existing DELETE route.
    const res = await fetch(`/api/posts?id=${postId}&username=${currentUser}`, { 
      method: 'DELETE', 
      credentials: 'include' 
    });
    if (res.ok) {
      setTimeline((prev: any[]) => prev.filter((t: any) => t.id !== postId));
      console.log(`๐Ÿ—‘๏ธ Delete ${postId} ok`);
    } else {
      alert('์‚ญ์ œ ์‹คํŒจ / Deletion failed');
    }
  } catch {
    alert('์‚ญ์ œ ์‹คํŒจ / Deletion failed');
  }
};
... ...
                  <div className="status-header">
                    {/* - Actor */}
                    <b>{getDisplayName(p.actor)}</b>
                    
                    {/* - boosted- */}
                    <span className="status-time">{new Date(p.created_at).toLocaleString()}</span>
                    {p.source === 'inbox' && <span className="boost-label">๐Ÿ” boosted</span>}

                    {/* - โœ… myapp45 - Delete Posts - */}
                    {currentUser && p.isMine && (
                      <button onClick={() => handleDelete(p.id)} style={{ color: '#e0245e', marginLeft: 'auto' }}>
                        ๐Ÿ—‘ Delete
                      </button>
                    )}
                  </div>
}

๐Ÿ’ก ์—ฌ๊ธฐ์„œ ๋ถ€ํ„ฐ๋Š” ์˜ค๋ฅ˜ ์ˆ˜์ •์„ ์œ„ํ•ด์„œ ํŒŒ์ผ ์ˆ˜์ •๋œ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.
The following section contains modifications made to the file to fix errors.

โœ”๏ธ lib/ap.ts

— ์™ธ๋ถ€์—์„œ ํ•จ์ˆ˜ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๋„๋ก exportํ•จ์ˆ˜ ์ถ”๊ฐ€
Added an export function to make the function accessible from the outside.

// โœ… myapp31 - ์‚ฌ์šฉ์ž๋ณ„ ๊ณต๊ฐœํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ / Get user-specific public key
function getPublicKey(username: string): string {
  const userKeyPath = path.join(process.cwd(), `data/keys/${username}/public.pem`);
  const legacyKeyPath = path.join(process.cwd(), 'data/keys/public.pem');
  const directPath = path.join(process.cwd(), `data/keys/${username}.pub.pem`);
  
  try {
    if (fs.existsSync(userKeyPath)) {
      return fs.readFileSync(userKeyPath, 'utf8');
    }
    if (fs.existsSync(legacyKeyPath)) {
      return fs.readFileSync(legacyKeyPath, 'utf8');
    }
    if (fs.existsSync(directPath)) {
      return fs.readFileSync(directPath, 'utf8');
    }
    // DB์—์„œ fallback! / Fallback from DB!
    const user = db.prepare('SELECT public_key FROM users WHERE username = ?').get(username) as any;
    if (user?.public_key) return user.public_key;
  } catch (e) {
    console.error(`๐Ÿ”‘ public ํ‚ค ๋กœ๋“œ ์‹คํŒจ / Public key load failed for ${username}:`, e);
  }
  throw new Error(`Public key not found for ${username}`);
}

// โœ… myapp45 - for mastodon
export function getPublicKeyPemForServing(username: string): string {
  return getPublicKey(username); // ํŒŒ์ผ ์šฐ์„ ! DB fallback
}

// โœ… ๊ณต์šฉ - ์„œ๋ช… + ์ „์†ก / Shared Use - Sign and send
async function signAndSend(inbox: string, doc: any, username: string) {
  const actorId = `https://${DOMAIN}/users/${username}`;
  const privateKey = getPrivateKey(username);
  const body = JSON.stringify(doc);
  const url = new URL(inbox);
  
  const digest = `SHA-256=${crypto.createHash('sha256').update(body).digest('base64')}`;
  const date = new Date().toUTCString();
  // โœ… myapp45 - search ํฌํ•จ + ์†Œ๋ฌธ์ž post!
  const target = `${url.pathname}${url.search}`;
  const signingString = `(request-target): post ${target}\nhost: ${url.host}\ndate: ${date}\ndigest: ${digest}`;
  
  const signer = crypto.createSign('sha256');
  signer.update(signingString);
  const signature = signer.sign(privateKey, 'base64');

  const keyId = `${actorId}#main-key`;
  // โœ… myapp45 - algorithm ์ถ”๊ฐ€ ๋งˆ์Šคํ† ๋ˆ ํ•„์ˆ˜
  const sigHeader = `keyId="${keyId}",headers="(request-target) host date digest",signature="${signature}",algorithm="rsa-sha256"`;

  console.log(`๐Ÿ” SIGN ${username} -> ${inbox}\n${signingString}\n`); // ๋””๋ฒ„๊ทธ์šฉ!

  const res = await fetch(inbox, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/activity+json',
      'Date': date,
      'Digest': digest,
      'Signature': sigHeader,
      'Host': url.host
    },
    body
  });

  const text = await res.text();
  return { res, text, body, doc };
}

โœ”๏ธ users/[username]/route.ts

— lib/ap.tsํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋„๋ก ์ˆ˜์ •ํ•จ.
Modified to use the lib/ap.ts file.

... ...
import  { getPublicKeyPemForServing }  from '@/lib/ap'; 
... ...
export async function GET(_req: Request, { params }: { params: Promise<{ username: string }> }) {
... ...
  //const pubkey = fs.readFileSync(path.join(process.cwd(), 'data/keys/public.pem'), 'utf8');
  const pubkey = getPublicKeyPemForServing(username); 
... ...
}

๐Ÿ“ ํ…Œ์ŠคํŠธ / Test

๐Ÿ’ก ํ˜„์žฌ ๋กœ์ปฌ์„œ๋ฒ„์—์„œ ๊ธ€ ์ €์žฅ ์‚ญ์ œ ๊ธฐ๋Šฅ์€ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
The functions for saving and deleting posts are currently working correctly on the local server.

๐Ÿ’ก ์•„์ง ํŠน์ •์„œ๋ฒ„์— ๊ธ€๋ฐฐ๋‹ฌ์ด ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ ๋‚ด ๊ธ€ ์‚ญ์ œ์‹œ ๋ฐฐ๋‹ฌ๋œ ์—ฐ๋ฐฉ์„œ๋ฒ„์˜ ๊ธ€์ด ์‚ญ์ œ ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
There are currently issues where posts are not being delivered to certain servers, and where posts delivered to federated servers are not deleted when the original post is removed.

โœ”๏ธ ๋กœ๊ทธ์ธ ํ›„ ์‚ญ์ œ ๋ฒ„ํŠผ ์ ์šฉ

local server

โœ”๏ธ ๊ธ€ ๋ฐฐ๋‹ฌํ™•์ธ / Message Delivery Confirmation

— ํ˜„์žฌ๋Š” gotosocial๋งŒ ๋ฐฐ๋‹ฌ๋จ / Currently, only GoToSocial is being deployed.

๊ธ€๋ฐฐ๋‹ฌ / post delivery

โœ”๏ธ ๊ธ€ ์‚ญ์ œ / delte post

— ์—ฐ๋ฐฉ๊ธ€ ์‚ญ์ œ๋Š” ํ˜„์žฌ ์š”์ฒญ๋งŒ ์ „๋‹ฌ๋จ.
The request to delete the syndicated post has been forwarded.

server log

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

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

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

Leave a Reply