Files
Synapsis/src/app/api/account/delete/route.ts
T
cyph3rasi 21258edd95 Replace PostgreSQL and Docker deployment with embedded Turso, Drizzle relational queries v2, native systemd deployment on port 43821, and fresh SQLite migrations.
Hop-State: A_06FP5KEDBTB4A9ZT7QB498G
Hop-Proposal: R_06FP5KDWMCKVVMQZT4MVZ28
Hop-Task: T_06FP5DZ7T0G45FG93PT90B8
Hop-Attempt: AT_06FP5DZ7T0PKQW99V27JCV8
2026-07-14 15:44:42 -07:00

122 lines
4.0 KiB
TypeScript

import { NextResponse } from 'next/server';
import { db, users, posts, sessions, likes, follows, notifications, chatMessages, chatConversations } from '@/db';
import { eq, or, and } from 'drizzle-orm';
import { requireSignedAction, type SignedAction } from '@/lib/auth/verify-signature';
import { verifyPassword } from '@/lib/auth';
import { cookies } from 'next/headers';
export async function POST(request: Request) {
try {
const signedAction: SignedAction = await request.json();
// Verify signature and get user
const user = await requireSignedAction(signedAction);
if (signedAction.action !== 'delete_account') {
return NextResponse.json(
{ error: 'Invalid action type' },
{ status: 400 }
);
}
const { password } = signedAction.data;
// Verify password
if (!user.passwordHash) {
return NextResponse.json(
{ error: 'Account has no password set' },
{ status: 400 }
);
}
const isPasswordValid = await verifyPassword(password, user.passwordHash);
if (!isPasswordValid) {
return NextResponse.json(
{ error: 'Password is incorrect' },
{ status: 403 }
);
}
const userId = user.id;
const userDid = user.did;
// Delete all user data in proper order to respect foreign keys
// 1. Delete chat messages sent by this user
await db.delete(chatMessages)
.where(eq(chatMessages.senderDid, userDid));
// 2. Find and delete conversations where user is a participant
// First get conversation IDs where user is participant1 (local user)
// For participant2, we need to check by handle since it's stored as text (can be remote)
const conversations = await db.query.chatConversations.findMany({
where: { OR: [{ participant1Id: userId }, { participant2Handle: user.handle }] },
});
const conversationIds = conversations.map(c => c.id);
// Delete messages in those conversations
if (conversationIds.length > 0) {
for (const convId of conversationIds) {
await db.delete(chatMessages)
.where(eq(chatMessages.conversationId, convId));
}
}
// 3. Delete the conversations themselves
if (conversationIds.length > 0) {
for (const convId of conversationIds) {
await db.delete(chatConversations)
.where(eq(chatConversations.id, convId));
}
}
// 4. Delete notifications
await db.delete(notifications)
.where(or(
eq(notifications.userId, userId),
eq(notifications.actorId, userId)
));
// 5. Delete likes
await db.delete(likes)
.where(eq(likes.userId, userId));
// 6. Delete follows (both directions)
await db.delete(follows)
.where(or(
eq(follows.followerId, userId),
eq(follows.followingId, userId)
));
// 7. Delete posts (this will cascade delete reposts and post likes via triggers if set up)
await db.delete(posts)
.where(eq(posts.userId, userId));
// 8. Delete sessions
await db.delete(sessions)
.where(eq(sessions.userId, userId));
// 9. Finally, delete the user
await db.delete(users)
.where(eq(users.id, userId));
// Clear session cookie
const cookieStore = await cookies();
cookieStore.delete('synapsis_session');
return NextResponse.json({
success: true,
message: 'Account deleted successfully',
});
} catch (error) {
console.error('Account deletion error:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to delete account' },
{ status: 500 }
);
}
}