296 lines
9.3 KiB
TypeScript
296 lines
9.3 KiB
TypeScript
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<void> => {
|
|
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();
|
|
};
|