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
This commit is contained in:
@@ -13,7 +13,7 @@ export async function GET(req: NextRequest, context: RouteContext) {
|
||||
const { handle } = await context.params;
|
||||
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, handle),
|
||||
where: { handle: handle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -21,10 +21,7 @@ export async function GET(req: NextRequest, context: RouteContext) {
|
||||
}
|
||||
|
||||
const block = await db.query.blocks.findFirst({
|
||||
where: and(
|
||||
eq(blocks.userId, currentUser.id),
|
||||
eq(blocks.blockedUserId, targetUser.id)
|
||||
),
|
||||
where: { AND: [{ userId: currentUser.id }, { blockedUserId: targetUser.id }] },
|
||||
});
|
||||
|
||||
return NextResponse.json({ blocked: !!block });
|
||||
@@ -44,7 +41,7 @@ export async function POST(req: NextRequest, context: RouteContext) {
|
||||
const { handle } = await context.params;
|
||||
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, handle),
|
||||
where: { handle: handle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -57,10 +54,7 @@ export async function POST(req: NextRequest, context: RouteContext) {
|
||||
|
||||
// Check if already blocked
|
||||
const existing = await db.query.blocks.findFirst({
|
||||
where: and(
|
||||
eq(blocks.userId, currentUser.id),
|
||||
eq(blocks.blockedUserId, targetUser.id)
|
||||
),
|
||||
where: { AND: [{ userId: currentUser.id }, { blockedUserId: targetUser.id }] },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
@@ -104,7 +98,7 @@ export async function DELETE(req: NextRequest, context: RouteContext) {
|
||||
const { handle } = await context.params;
|
||||
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, handle),
|
||||
where: { handle: handle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
|
||||
@@ -37,10 +37,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
const targetHandle = `${remote.handle}@${remote.domain}`;
|
||||
const existingRemoteFollow = await db.query.remoteFollows.findFirst({
|
||||
where: and(
|
||||
eq(remoteFollows.followerId, currentUser.id),
|
||||
eq(remoteFollows.targetHandle, targetHandle)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { targetHandle: targetHandle }] },
|
||||
});
|
||||
return NextResponse.json({ following: !!existingRemoteFollow, remote: true });
|
||||
}
|
||||
@@ -50,7 +47,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -65,10 +62,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
const existingFollow = await db.query.follows.findFirst({
|
||||
where: and(
|
||||
eq(follows.followerId, currentUser.id),
|
||||
eq(follows.followingId, targetUser.id)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { followingId: targetUser.id }] },
|
||||
});
|
||||
|
||||
return NextResponse.json({ following: !!existingFollow });
|
||||
@@ -96,7 +90,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
const { handle } = await context.params;
|
||||
const cleanHandle = handle.toLowerCase().replace(/^@/, '');
|
||||
const remote = parseRemoteHandle(handle);
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
if (currentUser.isSuspended || currentUser.isSilenced) {
|
||||
return NextResponse.json({ error: 'Account restricted' }, { status: 403 });
|
||||
@@ -107,10 +101,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
// Check if already following
|
||||
const existingRemoteFollow = await db.query.remoteFollows.findFirst({
|
||||
where: and(
|
||||
eq(remoteFollows.followerId, currentUser.id),
|
||||
eq(remoteFollows.targetHandle, targetHandle)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { targetHandle: targetHandle }] },
|
||||
});
|
||||
if (existingRemoteFollow) {
|
||||
return NextResponse.json({ error: 'Already following' }, { status: 400 });
|
||||
@@ -179,7 +170,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
// Find target user
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -196,10 +187,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
// Check if already following
|
||||
const existingFollow = await db.query.follows.findFirst({
|
||||
where: and(
|
||||
eq(follows.followerId, currentUser.id),
|
||||
eq(follows.followingId, targetUser.id)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { followingId: targetUser.id }] },
|
||||
});
|
||||
|
||||
if (existingFollow) {
|
||||
@@ -268,7 +256,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
const { handle } = await context.params;
|
||||
const cleanHandle = handle.toLowerCase().replace(/^@/, '');
|
||||
const remote = parseRemoteHandle(handle);
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
if (remote) {
|
||||
if (!db) {
|
||||
@@ -276,10 +264,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
}
|
||||
const targetHandle = `${remote.handle}@${remote.domain}`;
|
||||
const existingRemoteFollow = await db.query.remoteFollows.findFirst({
|
||||
where: and(
|
||||
eq(remoteFollows.followerId, currentUser.id),
|
||||
eq(remoteFollows.targetHandle, targetHandle)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { targetHandle: targetHandle }] },
|
||||
});
|
||||
if (!existingRemoteFollow) {
|
||||
return NextResponse.json({ error: 'Not following' }, { status: 400 });
|
||||
@@ -306,7 +291,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Update the user's following count (atomic decrement, clamped to 0)
|
||||
await db.update(users)
|
||||
.set({ followingCount: sql`GREATEST(0, ${users.followingCount} - 1)` })
|
||||
.set({ followingCount: sql`max(0, ${users.followingCount} - 1)` })
|
||||
.where(eq(users.id, currentUser.id));
|
||||
|
||||
console.log(`[Swarm] Unfollow delivered to ${remote.domain}`);
|
||||
@@ -319,7 +304,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Find target user
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -331,10 +316,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Find existing follow
|
||||
const existingFollow = await db.query.follows.findFirst({
|
||||
where: and(
|
||||
eq(follows.followerId, currentUser.id),
|
||||
eq(follows.followingId, targetUser.id)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { followingId: targetUser.id }] },
|
||||
});
|
||||
|
||||
if (!existingFollow) {
|
||||
@@ -346,11 +328,11 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Update counts (atomic decrements, clamped to 0)
|
||||
await db.update(users)
|
||||
.set({ followingCount: sql`GREATEST(0, ${users.followingCount} - 1)` })
|
||||
.set({ followingCount: sql`max(0, ${users.followingCount} - 1)` })
|
||||
.where(eq(users.id, currentUser.id));
|
||||
|
||||
await db.update(users)
|
||||
.set({ followersCount: sql`GREATEST(0, ${users.followersCount} - 1)` })
|
||||
.set({ followersCount: sql`max(0, ${users.followersCount} - 1)` })
|
||||
.where(eq(users.id, targetUser.id));
|
||||
|
||||
return NextResponse.json({ success: true, following: false });
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Find the user
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@@ -94,7 +94,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Get remote followers
|
||||
const userRemoteFollowers = await db.query.remoteFollowers.findMany({
|
||||
where: eq(remoteFollowers.userId, user.id),
|
||||
where: { userId: user.id },
|
||||
limit,
|
||||
});
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Find the user
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@@ -94,7 +94,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Get remote following
|
||||
const userRemoteFollowing = await db.query.remoteFollows.findMany({
|
||||
where: eq(remoteFollows.followerId, user.id),
|
||||
where: { followerId: user.id },
|
||||
limit,
|
||||
});
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Find the user
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
const isRemotePlaceholder = user && cleanHandle.includes('@');
|
||||
|
||||
@@ -141,13 +141,13 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Get user's liked posts
|
||||
const userLikes = await db.query.likes.findMany({
|
||||
where: eq(likes.userId, user.id),
|
||||
where: { userId: user.id },
|
||||
with: {
|
||||
post: {
|
||||
with: likedPostRelations,
|
||||
},
|
||||
},
|
||||
orderBy: [desc(likes.createdAt)],
|
||||
orderBy: () => [desc(likes.createdAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
@@ -156,8 +156,8 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
.map(like => like.post);
|
||||
|
||||
const swarmLikedRows = await db.query.userSwarmLikes.findMany({
|
||||
where: eq(userSwarmLikes.userId, user.id),
|
||||
orderBy: [desc(userSwarmLikes.likedAt)],
|
||||
where: { userId: user.id },
|
||||
orderBy: () => [desc(userSwarmLikes.likedAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
@@ -225,19 +225,12 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
if (localPostIds.length > 0) {
|
||||
const viewerLikes = await db.query.likes.findMany({
|
||||
where: and(
|
||||
eq(likes.userId, viewer.id),
|
||||
inArray(likes.postId, localPostIds)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { postId: { in: localPostIds } }] },
|
||||
});
|
||||
const likedPostIds = new Set(viewerLikes.map(l => l.postId));
|
||||
|
||||
const viewerReposts = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, viewer.id),
|
||||
inArray(posts.repostOfId, localPostIds),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { repostOfId: { in: localPostIds } }, { isRemoved: false }] },
|
||||
});
|
||||
const repostedPostIds = new Set(viewerReposts.map(r => r.repostOfId));
|
||||
|
||||
|
||||
@@ -151,13 +151,13 @@ async function getMixedProfileCursorDate(cursor: string | null) {
|
||||
|
||||
if (cursor.startsWith('swarm-repost:')) {
|
||||
const repostRow = await db.query.userSwarmReposts.findFirst({
|
||||
where: eq(userSwarmReposts.id, cursor.replace('swarm-repost:', '')),
|
||||
where: { id: cursor.replace('swarm-repost:', '') },
|
||||
});
|
||||
return repostRow?.repostedAt ?? null;
|
||||
}
|
||||
|
||||
const cursorPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, cursor),
|
||||
where: { id: cursor },
|
||||
});
|
||||
return cursorPost?.createdAt ?? null;
|
||||
}
|
||||
@@ -173,7 +173,7 @@ async function populateViewerLikeState(
|
||||
const { getSession } = await import('@/lib/auth');
|
||||
const session = await getSession();
|
||||
const viewer = session?.user;
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
if (!viewer) {
|
||||
return remotePosts;
|
||||
@@ -301,7 +301,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Find the user
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
const isRemotePlaceholder = user && cleanHandle.includes('@');
|
||||
|
||||
@@ -330,39 +330,28 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Get user's posts with cursor-based pagination
|
||||
const cursorDate = await getMixedProfileCursorDate(cursor);
|
||||
let whereConditions = and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.isRemoved, false),
|
||||
isNull(posts.replyToId),
|
||||
isNull(posts.swarmReplyToId)
|
||||
);
|
||||
|
||||
if (cursorDate) {
|
||||
whereConditions = and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.isRemoved, false),
|
||||
isNull(posts.replyToId),
|
||||
isNull(posts.swarmReplyToId),
|
||||
lt(posts.createdAt, cursorDate)
|
||||
);
|
||||
}
|
||||
const whereConditions = {
|
||||
userId: user.id,
|
||||
isRemoved: false,
|
||||
replyToId: { isNull: true as const },
|
||||
swarmReplyToId: { isNull: true as const },
|
||||
...(cursorDate ? { createdAt: { lt: cursorDate } } : {}),
|
||||
};
|
||||
|
||||
const localPosts = await db.query.posts.findMany({
|
||||
where: whereConditions,
|
||||
with: userPostRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit: cursor ? limit : limit * 2,
|
||||
});
|
||||
|
||||
const swarmRepostWhere = cursorDate
|
||||
? and(
|
||||
eq(userSwarmReposts.userId, user.id),
|
||||
lt(userSwarmReposts.repostedAt, cursorDate)
|
||||
)
|
||||
: eq(userSwarmReposts.userId, user.id);
|
||||
const swarmRepostWhere = {
|
||||
userId: user.id,
|
||||
...(cursorDate ? { repostedAt: { lt: cursorDate } } : {}),
|
||||
};
|
||||
const swarmRepostRows = await db.query.userSwarmReposts.findMany({
|
||||
where: swarmRepostWhere,
|
||||
orderBy: [desc(userSwarmReposts.repostedAt)],
|
||||
orderBy: () => [desc(userSwarmReposts.repostedAt)],
|
||||
limit: cursor ? limit : limit * 2,
|
||||
});
|
||||
let userPosts: any[] = [
|
||||
@@ -400,19 +389,12 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
if (localPostIds.length > 0) {
|
||||
const viewerLikes = await db.query.likes.findMany({
|
||||
where: and(
|
||||
eq(likes.userId, viewer.id),
|
||||
inArray(likes.postId, localPostIds)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { postId: { in: localPostIds } }] },
|
||||
});
|
||||
viewerLikes.forEach((like) => likedPostIds.add(like.postId));
|
||||
|
||||
const viewerReposts = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, viewer.id),
|
||||
inArray(posts.repostOfId, localPostIds),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { repostOfId: { in: localPostIds } }, { isRemoved: false }] },
|
||||
});
|
||||
viewerReposts.forEach((repost) => {
|
||||
if (repost.repostOfId) {
|
||||
@@ -422,7 +404,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
if (swarmTargets.length > 0) {
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const likedIds = await getViewerSwarmLikedPostIds(
|
||||
swarmTargets.map((post) => ({
|
||||
id: post.id,
|
||||
|
||||
@@ -95,7 +95,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
const isRemotePlaceholder = user && cleanHandle.includes('@');
|
||||
|
||||
@@ -121,30 +121,29 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
let whereConditions = and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.isRemoved, false),
|
||||
or(isNotNull(posts.replyToId), isNotNull(posts.swarmReplyToId)),
|
||||
);
|
||||
let cursorDate: Date | undefined;
|
||||
|
||||
if (cursor) {
|
||||
const cursorPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, cursor),
|
||||
where: { id: cursor },
|
||||
});
|
||||
if (cursorPost) {
|
||||
whereConditions = and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.isRemoved, false),
|
||||
or(isNotNull(posts.replyToId), isNotNull(posts.swarmReplyToId)),
|
||||
lt(posts.createdAt, cursorPost.createdAt),
|
||||
);
|
||||
cursorDate = cursorPost.createdAt;
|
||||
}
|
||||
}
|
||||
|
||||
let replyPosts: any[] = await db.query.posts.findMany({
|
||||
where: whereConditions,
|
||||
where: {
|
||||
userId: user.id,
|
||||
isRemoved: false,
|
||||
OR: [
|
||||
{ replyToId: { isNotNull: true } },
|
||||
{ swarmReplyToId: { isNotNull: true } },
|
||||
],
|
||||
...(cursorDate ? { createdAt: { lt: cursorDate } } : {}),
|
||||
},
|
||||
with: replyRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
@@ -158,21 +157,14 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
const viewerLikes = postIds.length > 0
|
||||
? await db.query.likes.findMany({
|
||||
where: and(
|
||||
eq(likes.userId, viewer.id),
|
||||
inArray(likes.postId, postIds),
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { postId: { in: postIds } }] },
|
||||
})
|
||||
: [];
|
||||
const likedPostIds = new Set(viewerLikes.map((like) => like.postId));
|
||||
|
||||
const viewerReposts = postIds.length > 0
|
||||
? await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, viewer.id),
|
||||
inArray(posts.repostOfId, postIds),
|
||||
eq(posts.isRemoved, false),
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { repostOfId: { in: postIds } }, { isRemoved: false }] },
|
||||
})
|
||||
: [];
|
||||
const repostedPostIds = new Set(viewerReposts.map((post) => post.repostOfId));
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
// If user exists but is a remote placeholder (handle contains @), fetch fresh data from remote
|
||||
@@ -147,10 +147,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
canReceiveDms = true; // Can DM yourself
|
||||
} else {
|
||||
const isFollowingViewer = await db.query.follows.findFirst({
|
||||
where: and(
|
||||
eq(follows.followerId, user.id),
|
||||
eq(follows.followingId, session.user.id)
|
||||
)
|
||||
where: { AND: [{ followerId: user.id }, { followingId: session.user.id }] }
|
||||
});
|
||||
if (isFollowingViewer) {
|
||||
canReceiveDms = true;
|
||||
@@ -163,7 +160,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
// If this is a bot, include owner info
|
||||
if (user.isBot && user.botOwnerId) {
|
||||
const owner = await db.query.users.findFirst({
|
||||
where: eq(users.id, user.botOwnerId),
|
||||
where: { id: user.botOwnerId },
|
||||
});
|
||||
if (owner) {
|
||||
userResponse.botOwner = {
|
||||
|
||||
Reference in New Issue
Block a user