/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@spacebar/api";
import { Channel, ChannelUpdateEvent, Guild, emitEvent } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { ChannelCreateSchema, ChannelReorderSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
"/",
route({
responses: {
201: {
body: "APIChannelArray",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params as { [key: string]: string };
const channels = await Channel.find({ where: { guild_id } });
for await (const channel of channels) {
channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild);
}
channels.sort((a, b) => a.position - b.position);
res.json(channels);
},
);
router.post(
"/",
route({
requestBody: "ChannelCreateSchema",
permission: "MANAGE_CHANNELS",
responses: {
201: {
body: "Channel",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel
const { guild_id } = req.params as { [key: string]: string };
const body = req.body as ChannelCreateSchema;
const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id);
channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild);
res.status(201).json(channel);
},
);
router.patch(
"/",
route({
requestBody: "ChannelReorderSchema",
permission: "MANAGE_CHANNELS",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// changes guild channel position
const { guild_id } = req.params as { [key: string]: string };
let body = req.body as ChannelReorderSchema;
const guild = await Guild.findOneOrFail({
where: { id: guild_id },
select: { channel_ordering: true },
});
body = body.sort((a, b) => {
const apos = a.position || (a.parent_id ? guild.channel_ordering.findIndex((_) => _ === a.parent_id) + 1 : 0);
const bpos = b.position || (b.parent_id ? guild.channel_ordering.findIndex((_) => _ === b.parent_id) + 1 : 0);
return apos - bpos;
});
// The channels not listed for this query
const notMentioned = guild.channel_ordering.filter((x) => !body.find((c) => c.id == x));
const withParents = body.filter((x) => x.parent_id !== undefined);
const withPositions = body.filter((x) => x.position !== undefined);
// You can't do it with Promise.all or the way this is being done is super incorrect
for await (const opt of withPositions) {
const channel = await Channel.findOneOrFail({
where: { id: opt.id },
});
notMentioned.splice(opt.position as number, 0, channel.id);
channel.position = notMentioned.findIndex((_) => _ === channel.id);
await emitEvent({
event: "CHANNEL_UPDATE",
data: channel.toJSON(),
channel_id: channel.id,
guild_id,
} satisfies ChannelUpdateEvent);
}
// Due to this also being able to change the order, this needs to be done in order
// have to do the parents after the positions
for await (const opt of withParents) {
const [channel, parent] = await Promise.all([
Channel.findOneOrFail({
where: { id: opt.id },
}),
opt.parent_id
? Channel.findOneOrFail({
where: { id: opt.parent_id },
select: {
permission_overwrites: true,
id: true,
},
})
: null,
]);
if (opt.lock_permissions && parent) await Channel.update({ id: channel.id }, { permission_overwrites: parent.permission_overwrites });
if (parent && opt.position === undefined) {
const parentPos = notMentioned.indexOf(parent.id);
notMentioned.splice(parentPos + 1, 0, channel.id);
channel.position = (parentPos + 1) as number;
}
channel.parent = parent || undefined;
channel.parent_id = parent?.id || null;
await channel.save();
await emitEvent({
event: "CHANNEL_UPDATE",
data: channel?.toJSON(),
channel_id: channel.id,
guild_id,
} satisfies ChannelUpdateEvent);
}
await Guild.update({ id: guild_id }, { channel_ordering: notMentioned });
return res.sendStatus(204);
},
);
export default router;