/*
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,
ChannelModifySchema,
ChannelReorderSchema,
ChannelUpdateEvent,
Guild,
emitEvent,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get(
"/",
route({
responses: {
201: {
body: "APIChannelArray",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
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: "ChannelModifySchema",
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;
const body = req.body as ChannelModifySchema;
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;
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,
channel_id: channel.id,
guild_id,
} as 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,
channel_id: channel.id,
guild_id,
} as ChannelUpdateEvent);
}
await Guild.update(
{ id: guild_id },
{ channel_ordering: notMentioned },
);
return res.sendStatus(204);
},
);
export default router;