feat(federation): POST /api/dm accepts homeUserId+homeInstance for federated DM creation

This commit is contained in:
Jannis Braun
2026-04-01 12:38:50 +02:00
parent 518b3eb786
commit 5c02bff2d2
+21 -14
View File
@@ -344,24 +344,31 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
app.post<{ Body: CreateDmRequest }>('/api/dm', { app.post<{ Body: CreateDmRequest }>('/api/dm', {
preHandler: authenticate, preHandler: authenticate,
}, async (request, reply) => { }, async (request, reply) => {
const { userId } = request.body; const { userId, homeUserId, homeInstance } = request.body;
if (!userId || typeof userId !== 'string') {
return reply.code(400).send({ error: 'userId is required', statusCode: 400 });
}
if (userId === request.userId) {
return reply.code(400).send({ error: 'Cannot create DM with yourself', statusCode: 400 });
}
const db = getDb(); const db = getDb();
let targetUser: typeof schema.users.$inferSelect | undefined;
if (homeUserId && homeInstance) {
// Federated identity: resolve or create a replicated user stub
targetUser = resolveOrCreateReplicatedUser(homeUserId, homeInstance, db);
} else if (userId && typeof userId === 'string') {
// Local ID: direct lookup (existing behavior)
targetUser = db.select().from(schema.users).where(eq(schema.users.id, userId)).get();
} else {
return reply.code(400).send({ error: 'userId or (homeUserId + homeInstance) is required', statusCode: 400 });
}
// Check if target user exists
const targetUser = db.select().from(schema.users).where(eq(schema.users.id, userId)).get();
if (!targetUser) { if (!targetUser) {
return reply.code(404).send({ error: 'User not found', statusCode: 404 }); return reply.code(404).send({ error: 'User not found', statusCode: 404 });
} }
const targetUserId = targetUser.id;
if (targetUserId === request.userId) {
return reply.code(400).send({ error: 'Cannot create DM with yourself', statusCode: 400 });
}
// Check if DM channel already exists between these two users // Check if DM channel already exists between these two users
// (both have membership rows, regardless of closed state) // (both have membership rows, regardless of closed state)
const myDms = db.select() const myDms = db.select()
@@ -374,7 +381,7 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
.from(schema.dmMembers) .from(schema.dmMembers)
.where(and( .where(and(
eq(schema.dmMembers.dmChannelId, myDm.dmChannelId), eq(schema.dmMembers.dmChannelId, myDm.dmChannelId),
eq(schema.dmMembers.userId, userId), eq(schema.dmMembers.userId, targetUserId),
)) ))
.get(); .get();
@@ -477,7 +484,7 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
tx.insert(schema.dmMembers).values({ tx.insert(schema.dmMembers).values({
dmChannelId, dmChannelId,
userId, userId: targetUserId,
}).run(); }).run();
}); });
@@ -495,7 +502,7 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
}; };
// Broadcast dm_channel_created to the other user so their sidebar updates // Broadcast dm_channel_created to the other user so their sidebar updates
connectionManager.sendToUser(userId, { connectionManager.sendToUser(targetUserId, {
type: 'dm_channel_created', type: 'dm_channel_created',
dmChannel: result, dmChannel: result,
}); });