256 lines
6.8 KiB
TypeScript
256 lines
6.8 KiB
TypeScript
import type { FastifyInstance } from 'fastify'
|
|
import { z } from 'zod'
|
|
import prisma from '../prisma/client.ts'
|
|
import { channelPublicSelect } from '../dto/channel.dto.ts'
|
|
|
|
export default function (fastify: FastifyInstance) {
|
|
// GET /chad/channels - List all channels with client counts
|
|
fastify.get('/channels', async (req, reply) => {
|
|
if (!req.user) {
|
|
return reply.code(401).send({ error: 'Unauthorized' })
|
|
}
|
|
|
|
const channels = await prisma.channel.findMany({
|
|
select: channelPublicSelect,
|
|
orderBy: { name: 'asc' },
|
|
})
|
|
|
|
// Add client count to each channel using Socket.IO rooms
|
|
const channelsWithCounts = await Promise.all(
|
|
channels.map(async channel => {
|
|
const socketsInChannel = await fastify.io.in(channel.id).fetchSockets()
|
|
return {
|
|
...channel,
|
|
clientCount: socketsInChannel.length,
|
|
}
|
|
}),
|
|
)
|
|
|
|
return channelsWithCounts
|
|
})
|
|
|
|
// GET /chad/channels/:id - Get specific channel with client list
|
|
fastify.get('/channels/:id', async (req, reply) => {
|
|
if (!req.user) {
|
|
return reply.code(401).send({ error: 'Unauthorized' })
|
|
}
|
|
|
|
try {
|
|
const paramsSchema = z.object({
|
|
id: z.string(),
|
|
})
|
|
const params = paramsSchema.parse(req.params)
|
|
|
|
const channel = await prisma.channel.findUnique({
|
|
where: { id: params.id },
|
|
select: channelPublicSelect,
|
|
})
|
|
|
|
if (!channel) {
|
|
return reply.code(404).send({ error: 'Channel not found' })
|
|
}
|
|
|
|
// Get clients in this channel using Socket.IO rooms
|
|
const sockets = await fastify.io.in(params.id).fetchSockets()
|
|
const clients = sockets
|
|
.filter(s => s.data.joined)
|
|
.map(s => {
|
|
const channelId = Array.from(s.rooms).find(room => room !== s.id) || 'default'
|
|
return {
|
|
socketId: s.id,
|
|
userId: s.data.userId,
|
|
username: s.data.username,
|
|
displayName: s.data.displayName,
|
|
inputMuted: s.data.inputMuted,
|
|
outputMuted: s.data.outputMuted,
|
|
currentChannelId: channelId,
|
|
}
|
|
})
|
|
|
|
return {
|
|
...channel,
|
|
clients,
|
|
}
|
|
}
|
|
catch (err) {
|
|
fastify.log.error(err)
|
|
reply.code(400)
|
|
if (err instanceof z.ZodError) {
|
|
reply.send({ error: z.prettifyError(err) })
|
|
}
|
|
else {
|
|
reply.send({ error: err.message })
|
|
}
|
|
}
|
|
})
|
|
|
|
// POST /chad/channels - Create channel
|
|
fastify.post('/channels', async (req, reply) => {
|
|
if (!req.user) {
|
|
return reply.code(401).send({ error: 'Unauthorized' })
|
|
}
|
|
|
|
try {
|
|
const schema = z.object({
|
|
name: z.string().min(1).max(50),
|
|
maxClients: z.number().int().positive().optional().nullable(),
|
|
persistent: z.boolean().default(false),
|
|
})
|
|
const input = schema.parse(req.body)
|
|
|
|
const channel = await prisma.channel.create({
|
|
data: {
|
|
name: input.name,
|
|
maxClients: input.maxClients,
|
|
persistent: input.persistent,
|
|
owner_id: req.user.id,
|
|
},
|
|
select: channelPublicSelect,
|
|
})
|
|
|
|
// Notify all connected clients about new channel
|
|
fastify.io.emit('channelCreated', channel)
|
|
|
|
return channel
|
|
}
|
|
catch (err) {
|
|
fastify.log.error(err)
|
|
reply.code(400)
|
|
if (err instanceof z.ZodError) {
|
|
reply.send({ error: z.prettifyError(err) })
|
|
}
|
|
else {
|
|
reply.send({ error: err.message })
|
|
}
|
|
}
|
|
})
|
|
|
|
// PATCH /chad/channels/:id - Update channel
|
|
fastify.patch('/channels/:id', async (req, reply) => {
|
|
if (!req.user) {
|
|
return reply.code(401).send({ error: 'Unauthorized' })
|
|
}
|
|
|
|
try {
|
|
const paramsSchema = z.object({
|
|
id: z.string(),
|
|
})
|
|
const params = paramsSchema.parse(req.params)
|
|
|
|
const schema = z.object({
|
|
name: z.string().min(1).max(50).optional(),
|
|
maxClients: z.number().int().positive().optional().nullable(),
|
|
})
|
|
const input = schema.parse(req.body)
|
|
|
|
// Cannot update default channel
|
|
if (params.id === 'default') {
|
|
return reply.code(403).send({ error: 'Cannot modify default channel' })
|
|
}
|
|
|
|
const existing = await prisma.channel.findUnique({
|
|
where: { id: params.id },
|
|
})
|
|
|
|
if (!existing) {
|
|
return reply.code(404).send({ error: 'Channel not found' })
|
|
}
|
|
|
|
if (existing.owner_id !== req.user.id) {
|
|
return reply.code(403).send({ error: 'Not channel owner' })
|
|
}
|
|
|
|
const channel = await prisma.channel.update({
|
|
where: { id: params.id },
|
|
data: input,
|
|
select: channelPublicSelect,
|
|
})
|
|
|
|
fastify.io.emit('channelUpdated', channel)
|
|
|
|
return channel
|
|
}
|
|
catch (err) {
|
|
fastify.log.error(err)
|
|
reply.code(400)
|
|
if (err instanceof z.ZodError) {
|
|
reply.send({ error: z.prettifyError(err) })
|
|
}
|
|
else {
|
|
reply.send({ error: err.message })
|
|
}
|
|
}
|
|
})
|
|
|
|
// DELETE /chad/channels/:id - Delete channel
|
|
fastify.delete('/channels/:id', async (req, reply) => {
|
|
if (!req.user) {
|
|
return reply.code(401).send({ error: 'Unauthorized' })
|
|
}
|
|
|
|
try {
|
|
const paramsSchema = z.object({
|
|
id: z.string(),
|
|
})
|
|
const params = paramsSchema.parse(req.params)
|
|
|
|
if (params.id === 'default') {
|
|
return reply.code(403).send({ error: 'Cannot delete default channel' })
|
|
}
|
|
|
|
const existing = await prisma.channel.findUnique({
|
|
where: { id: params.id },
|
|
})
|
|
|
|
if (!existing) {
|
|
return reply.code(404).send({ error: 'Channel not found' })
|
|
}
|
|
|
|
if (existing.owner_id !== req.user.id) {
|
|
return reply.code(403).send({ error: 'Not channel owner' })
|
|
}
|
|
|
|
// Move all users in this channel back to default using Socket.IO rooms
|
|
const sockets = await fastify.io.in(params.id).fetchSockets()
|
|
|
|
for (const socket of sockets) {
|
|
// Close all their producers
|
|
for (const producer of socket.data.producers.values()) {
|
|
producer.close()
|
|
}
|
|
socket.data.producers.clear()
|
|
|
|
// Close all their consumers
|
|
for (const consumer of socket.data.consumers.values()) {
|
|
consumer.close()
|
|
}
|
|
socket.data.consumers.clear()
|
|
|
|
// Move to default room
|
|
await socket.leave(params.id)
|
|
await socket.join('default')
|
|
|
|
socket.emit('forcedChannelSwitch', { channelId: 'default' })
|
|
}
|
|
|
|
await prisma.channel.delete({
|
|
where: { id: params.id },
|
|
})
|
|
|
|
fastify.io.emit('channelDeleted', { channelId: params.id })
|
|
|
|
return { success: true }
|
|
}
|
|
catch (err) {
|
|
fastify.log.error(err)
|
|
reply.code(400)
|
|
if (err instanceof z.ZodError) {
|
|
reply.send({ error: z.prettifyError(err) })
|
|
}
|
|
else {
|
|
reply.send({ error: err.message })
|
|
}
|
|
}
|
|
})
|
|
}
|