Migrate user/profile URLs to /u/[handle] and remove legacy scripts

All user and post profile URLs have been updated from /[handle] to /u/[handle] for consistency. Added a redirect page for /posts/[id] to the canonical /u/[handle]/posts/[id] route. Deprecated and removed legacy scripts and documentation related to chat and federation endpoints. Push-based federation endpoints are now disabled in favor of real-time pull-based federation.
This commit is contained in:
Christopher
2026-01-27 01:26:41 -08:00
parent 44610157a1
commit a68aee8758
20 changed files with 85 additions and 573 deletions
+1
View File
@@ -102,6 +102,7 @@ export async function GET(request: Request) {
post: row.postId ? {
id: row.postId,
content: row.postContent,
authorHandle: row.actorHandle, // The actor is the post author for likes/reposts
} : null,
};
});
+5 -1
View File
@@ -115,7 +115,10 @@ export async function POST(request: Request) {
}
}
// If this is a reply to a swarm post, deliver it to the origin node
// DEPRECATED: Push-based federation disabled
// Swarm now uses real-time pull-based federation
// Replies are fetched in real-time from the origin node
/*
if (data.swarmReplyTo) {
(async () => {
try {
@@ -153,6 +156,7 @@ export async function POST(request: Request) {
}
})();
}
*/
// Handle local mentions (create notifications for users on this node)
(async () => {
+6 -108
View File
@@ -43,114 +43,12 @@ const swarmPostSchema = z.object({
/**
* POST /api/swarm/inbox
*
* Receives a post from another swarm node.
* Stores it for local users who follow the author.
* DEPRECATED: This endpoint is disabled.
* We now use real-time pull-based federation via /api/swarm/timeline
* instead of push-based caching.
*/
export async function POST(request: NextRequest) {
try {
if (!db) {
return NextResponse.json({ error: 'Database not available' }, { status: 503 });
}
const body = await request.json();
const data = swarmPostSchema.parse(body);
// Construct the swarm post ID
const swarmPostId = `swarm:${data.nodeDomain}:${data.post.id}`;
// Check if we already have this post
const existingPost = await db.query.posts.findFirst({
where: eq(posts.apId, swarmPostId),
});
if (existingPost) {
return NextResponse.json({
success: true,
message: 'Post already exists',
});
}
// Check if anyone on this node follows the author
const authorActorUrl = `swarm://${data.nodeDomain}/${data.author.handle}`;
const hasFollowers = await db.query.remoteFollowers.findFirst({
where: eq(remoteFollowers.actorUrl, authorActorUrl),
});
// Even if no one follows, we might want to cache for timeline
// For now, only store if someone follows
if (!hasFollowers) {
return NextResponse.json({
success: true,
message: 'No local followers',
});
}
// Get or create placeholder user for the remote author
const remoteHandle = `${data.author.handle}@${data.nodeDomain}`;
let remoteUser = await db.query.users.findFirst({
where: eq(users.handle, remoteHandle),
});
if (!remoteUser) {
const [newUser] = await db.insert(users).values({
did: `did:swarm:${data.nodeDomain}:${data.author.handle}`,
handle: remoteHandle,
displayName: data.author.displayName,
avatarUrl: data.author.avatarUrl || null,
isNsfw: data.author.isNsfw,
publicKey: 'swarm-remote-user',
}).returning();
remoteUser = newUser;
} else {
// Update profile info if changed
await db.update(users)
.set({
displayName: data.author.displayName,
avatarUrl: data.author.avatarUrl || remoteUser.avatarUrl,
isNsfw: data.author.isNsfw,
})
.where(eq(users.id, remoteUser.id));
}
// Create the post
const [newPost] = await db.insert(posts).values({
userId: remoteUser.id,
content: data.post.content,
isNsfw: data.post.isNsfw || data.author.isNsfw,
apId: swarmPostId,
apUrl: `https://${data.nodeDomain}/${data.author.handle}/posts/${data.post.id}`,
createdAt: new Date(data.post.createdAt),
linkPreviewUrl: data.post.linkPreviewUrl || null,
linkPreviewTitle: data.post.linkPreviewTitle || null,
linkPreviewDescription: data.post.linkPreviewDescription || null,
linkPreviewImage: data.post.linkPreviewImage || null,
}).returning();
// Store media attachments
if (data.post.media && data.post.media.length > 0) {
for (const m of data.post.media) {
await db.insert(media).values({
userId: remoteUser.id,
postId: newPost.id,
url: m.url,
mimeType: m.mimeType || null,
altText: m.altText || null,
});
}
}
console.log(`[Swarm] Received post from ${remoteHandle}: ${newPost.id}`);
return NextResponse.json({
success: true,
message: 'Post received',
postId: newPost.id,
});
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: 'Invalid request', details: error.issues }, { status: 400 });
}
console.error('[Swarm] Inbox error:', error);
return NextResponse.json({ error: 'Failed to process post' }, { status: 500 });
}
return NextResponse.json({
error: 'This endpoint is deprecated. Swarm uses real-time pull-based federation.',
}, { status: 410 }); // 410 Gone
}
+5 -78
View File
@@ -30,86 +30,13 @@ const swarmReplySchema = z.object({
/**
* POST /api/swarm/replies
*
* Receives a reply from another node in the swarm.
* The reply is stored as a remote reply linked to the local post.
* DEPRECATED: This endpoint is disabled.
* We now use real-time pull-based federation instead of push-based caching.
*/
export async function POST(request: NextRequest) {
try {
if (!db) {
return NextResponse.json({ error: 'Database not available' }, { status: 503 });
}
const body = await request.json();
const data = swarmReplySchema.parse(body);
// Verify the target post exists on this node
const targetPost = await db.query.posts.findFirst({
where: eq(posts.id, data.postId),
});
if (!targetPost) {
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
}
// Check if we already have this reply (by swarm ID)
const swarmReplyId = `swarm:${data.reply.nodeDomain}:${data.reply.id}`;
const existingReply = await db.query.posts.findFirst({
where: eq(posts.apId, swarmReplyId),
});
if (existingReply) {
return NextResponse.json({ success: true, message: 'Reply already exists' });
}
// We need a system user to attribute swarm replies to
// For now, we'll store them with metadata in the apId/apUrl fields
// and create a virtual representation
// Get or create a placeholder user for this remote author
let remoteUser = await db.query.users.findFirst({
where: eq(users.handle, `${data.reply.author.handle}@${data.reply.nodeDomain}`),
});
if (!remoteUser) {
// Create a placeholder user for the remote author
const [newUser] = await db.insert(users).values({
did: `did:swarm:${data.reply.nodeDomain}:${data.reply.author.handle}`,
handle: `${data.reply.author.handle}@${data.reply.nodeDomain}`,
displayName: data.reply.author.displayName,
avatarUrl: data.reply.author.avatarUrl || null,
publicKey: 'swarm-remote-user', // Placeholder
}).returning();
remoteUser = newUser;
}
// Create the reply post
const [replyPost] = await db.insert(posts).values({
userId: remoteUser.id,
content: data.reply.content,
replyToId: data.postId,
apId: swarmReplyId,
apUrl: `https://${data.reply.nodeDomain}/${data.reply.author.handle}/posts/${data.reply.id}`,
createdAt: new Date(data.reply.createdAt),
}).returning();
// Update the parent post's reply count
await db.update(posts)
.set({ repliesCount: targetPost.repliesCount + 1 })
.where(eq(posts.id, data.postId));
console.log(`[Swarm] Received reply from ${data.reply.nodeDomain} to post ${data.postId}`);
return NextResponse.json({
success: true,
replyId: replyPost.id,
});
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: 'Invalid request', details: error.issues }, { status: 400 });
}
console.error('[Swarm] Reply error:', error);
return NextResponse.json({ error: 'Failed to process reply' }, { status: 500 });
}
return NextResponse.json({
error: 'This endpoint is deprecated. Swarm uses real-time pull-based federation.',
}, { status: 410 }); // 410 Gone
}
/**