diff --git a/assets/openapi.json b/assets/openapi.json index c3124e1e..09be25e6 100644 --- a/assets/openapi.json +++ b/assets/openapi.json @@ -5171,7 +5171,10 @@ "type": "boolean" }, "parent_id": { - "type": "string" + "type": [ + "null", + "string" + ] } }, "additionalProperties": false, diff --git a/assets/schemas.json b/assets/schemas.json index 5508e2ea..fdf1b9f7 100755 --- a/assets/schemas.json +++ b/assets/schemas.json @@ -60170,7 +60170,10 @@ "type": "boolean" }, "parent_id": { - "type": "string" + "type": [ + "null", + "string" + ] } }, "additionalProperties": false, diff --git a/src/api/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts index 3488b64d..8f8ee3af 100644 --- a/src/api/routes/guilds/#guild_id/channels.ts +++ b/src/api/routes/guilds/#guild_id/channels.ts @@ -108,70 +108,93 @@ router.patch( async (req: Request, res: Response) => { // changes guild channel position const { guild_id } = req.params; - const body = req.body as ChannelReorderSchema; + 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); + 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 }, + }); - await Promise.all( - withPositions.map(async (opt) => { - const channel = await Channel.findOneOrFail({ - where: { id: opt.id }, - }); - - channel.position = opt.position as number; - notMentioned.splice(opt.position as number, 0, channel.id); - - await emitEvent({ - event: "CHANNEL_UPDATE", - data: channel, - channel_id: channel.id, - guild_id, - } as ChannelUpdateEvent); - }), - ); + 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 - await Promise.all( - withParents.map(async (opt) => { - const [channel, parent] = await Promise.all([ - Channel.findOneOrFail({ - where: { id: opt.id }, - }), - Channel.findOneOrFail({ - where: { id: opt.parent_id as string }, - select: { permission_overwrites: true }, - }), - ]); - - if (opt.lock_permissions) - await Channel.update( - { id: channel.id }, - { permission_overwrites: parent.permission_overwrites }, - ); + 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 emitEvent({ + event: "CHANNEL_UPDATE", + data: channel, + channel_id: channel.id, + guild_id, + } as ChannelUpdateEvent); + } await Guild.update( { id: guild_id }, diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts index 2c98783a..d09bbe55 100644 --- a/src/api/util/handlers/route.ts +++ b/src/api/util/handlers/route.ts @@ -28,7 +28,6 @@ import { ajv, getPermission, getRights, - normalizeBody, } from "@spacebar/util"; import { AnyValidateFunction } from "ajv/dist/core"; import { NextFunction, Request, Response } from "express"; @@ -121,7 +120,7 @@ export function route(opts: RouteOptions) { } if (validate) { - const valid = validate(normalizeBody(req.body)); + const valid = validate(req.body); if (!valid) { const fields: Record< string, diff --git a/src/util/schemas/ChannelReorderSchema.ts b/src/util/schemas/ChannelReorderSchema.ts index a026608a..657ed420 100644 --- a/src/util/schemas/ChannelReorderSchema.ts +++ b/src/util/schemas/ChannelReorderSchema.ts @@ -20,5 +20,5 @@ export type ChannelReorderSchema = { id: string; position?: number; lock_permissions?: boolean; - parent_id?: string; + parent_id?: null | string; }[]; diff --git a/src/util/schemas/Validator.ts b/src/util/schemas/Validator.ts index 1de511d3..9a7c1ebb 100644 --- a/src/util/schemas/Validator.ts +++ b/src/util/schemas/Validator.ts @@ -46,44 +46,7 @@ export const ajv = new Ajv({ addFormats(ajv); export function validateSchema(schema: string, data: G): G { - const valid = ajv.validate(schema, normalizeBody(data)); + const valid = ajv.validate(schema, data); if (!valid) throw ajv.errors; return data; } - -// Normalizer is introduced to workaround https://github.com/ajv-validator/ajv/issues/1287 -// this removes null values as ajv doesn't treat them as undefined -// normalizeBody allows to handle circular structures without issues -// taken from https://github.com/serverless/serverless/blob/master/lib/classes/ConfigSchemaHandler/index.js#L30 (MIT license) -export const normalizeBody = (body: object = {}) => { - const normalizedObjectsSet = new WeakSet(); - const normalizeObject = (object: object) => { - if (normalizedObjectsSet.has(object)) return; - normalizedObjectsSet.add(object); - if (Array.isArray(object)) { - for (const [, value] of object.entries()) { - if (typeof value === "object") normalizeObject(value); - } - } else { - for (const [key, value] of Object.entries(object)) { - if (value == null) { - if ( - key === "icon" || - key === "avatar" || - key === "banner" || - key === "splash" || - key === "discovery_splash" - ) - continue; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - delete object[key]; - } else if (typeof value === "object") { - normalizeObject(value); - } - } - } - }; - normalizeObject(body); - return body; -};