import type { FastifyInstance } from 'fastify'; import { createDirectBodySchema, createGroupBodySchema, createChannelBodySchema, addMemberBodySchema, setAvatarBodySchema, readBodySchema, conversationSchema, conversationListSchema, conversationMemberListSchema, errorSchema, } from '@altricade/core'; import type { CreateDirectBody, CreateGroupBody, CreateChannelBody, AddMemberBody, ReadBody, } from '@altricade/core'; const bearerAuth = [{ bearerAuth: [] }]; const idParamsSchema = { type: 'object', required: ['id'], properties: { id: { type: 'string', format: 'uuid' } }, } as const; const memberParamsSchema = { type: 'object', required: ['id', 'userId'], properties: { id: { type: 'string', format: 'uuid' }, userId: { type: 'string', format: 'uuid' } }, } as const; export const conversationsRoutes = (app: FastifyInstance): Promise => { app.get( '/conversations', { schema: { tags: ['conversations'], summary: 'List my conversations (DMs + groups, newest first)', security: bearerAuth, response: { 200: conversationListSchema, 401: errorSchema }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); return reply.send(await app.conversationsService.list(user.id)); }, ); app.post<{ Body: CreateDirectBody }>( '/conversations/direct', { schema: { tags: ['conversations'], summary: 'Start (or reopen) a direct conversation with a user', security: bearerAuth, body: createDirectBodySchema, response: { 201: conversationSchema, 400: errorSchema, 401: errorSchema, 404: errorSchema }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); const conversation = await app.conversationsService.createDirect(user.id, request.body.username); return reply.code(201).send(conversation); }, ); app.post<{ Body: CreateGroupBody }>( '/conversations', { schema: { tags: ['conversations'], summary: 'Create a group conversation', security: bearerAuth, body: createGroupBodySchema, response: { 201: conversationSchema, 401: errorSchema }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); const conversation = await app.conversationsService.createGroup( user.id, request.body.title, request.body.members ?? [], ); return reply.code(201).send(conversation); }, ); app.post<{ Body: CreateChannelBody }>( '/conversations/channel', { schema: { tags: ['conversations'], summary: 'Create a channel (broadcast: only owner/admins post)', security: bearerAuth, body: createChannelBodySchema, response: { 201: conversationSchema, 401: errorSchema }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); const conversation = await app.conversationsService.createChannel( user.id, request.body.title, request.body.description ?? null, request.body.members ?? [], ); return reply.code(201).send(conversation); }, ); app.get<{ Params: { id: string } }>( '/conversations/:id', { schema: { tags: ['conversations'], summary: 'Get a conversation (member only)', security: bearerAuth, params: idParamsSchema, response: { 200: conversationSchema, 401: errorSchema, 403: errorSchema, 404: errorSchema }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); return reply.send(await app.conversationsService.get(request.params.id, user.id)); }, ); app.get<{ Params: { id: string } }>( '/conversations/:id/members', { schema: { tags: ['conversations'], summary: 'List conversation members (member only)', security: bearerAuth, params: idParamsSchema, response: { 200: conversationMemberListSchema, 401: errorSchema, 403: errorSchema }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); return reply.send(await app.conversationsService.listMembers(request.params.id, user.id)); }, ); app.post<{ Params: { id: string }; Body: AddMemberBody }>( '/conversations/:id/members', { schema: { tags: ['conversations'], summary: 'Add a member to a group (owner only)', security: bearerAuth, params: idParamsSchema, body: addMemberBodySchema, response: { 201: conversationMemberListSchema, 401: errorSchema, 403: errorSchema, 404: errorSchema }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); const members = await app.conversationsService.addMember( request.params.id, user.id, request.body.username, ); return reply.code(201).send(members); }, ); app.delete<{ Params: { id: string; userId: string } }>( '/conversations/:id/members/:userId', { schema: { tags: ['conversations'], summary: 'Remove a member (owner) or leave (self)', security: bearerAuth, params: memberParamsSchema, response: { 401: errorSchema, 403: errorSchema }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); await app.conversationsService.removeMember(request.params.id, user.id, request.params.userId); return reply.code(204).send(); }, ); app.post<{ Params: { id: string }; Body: { objectKey: string } }>( '/conversations/:id/avatar', { schema: { tags: ['conversations'], summary: 'Set a group/channel photo (owner/admin, from an uploaded object key)', security: bearerAuth, params: idParamsSchema, body: setAvatarBodySchema, response: { 200: conversationSchema, 400: errorSchema, 401: errorSchema, 403: errorSchema, 404: errorSchema, }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); const imageUrl = app.mediaService.avatarPublicUrl(request.body.objectKey); return reply.send( await app.conversationsService.setAvatar(request.params.id, user.id, imageUrl), ); }, ); app.post<{ Params: { id: string } }>( '/conversations/:id/clear', { schema: { tags: ['conversations'], summary: 'Clear history for me (chat stays listed)', security: bearerAuth, params: idParamsSchema, response: { 401: errorSchema, 403: errorSchema }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); await app.conversationsService.clear(request.params.id, user.id); return reply.code(204).send(); }, ); app.post<{ Params: { id: string } }>( '/conversations/:id/hide', { schema: { tags: ['conversations'], summary: 'Delete chat for me (returns on new activity)', security: bearerAuth, params: idParamsSchema, response: { 401: errorSchema, 403: errorSchema }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); await app.conversationsService.hide(request.params.id, user.id); return reply.code(204).send(); }, ); app.post<{ Params: { id: string }; Body: ReadBody }>( '/conversations/:id/read', { schema: { tags: ['live'], summary: 'Mark read up to a sequence number', security: bearerAuth, params: idParamsSchema, body: readBodySchema, response: { 401: errorSchema, 403: errorSchema }, }, preHandler: app.authenticate, }, async (request, reply) => { const user = request.authUser; if (user === undefined) return reply.code(401).send({ error: 'unauthorized' }); await app.conversationsService.markRead(request.params.id, user.id, request.body.seq); return reply.code(204).send(); }, ); return Promise.resolve(); };