diff --git a/README.md b/README.md index b3f23b4..0017bbb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ CONFIG_PATH=./user/config.yml DISCORD_SECRET= DISCORD_TOKEN= +DB_CONNECTION_URL="mysql://test:password@localhost/tickets0" ENCRYPTION_KEY= -DB_CONNECTION_URL="" -HTTP_BIND=3000 -HTTP_EXTERNAL= \ No newline at end of file +HTTP_BIND=8080 +HTTP_EXTERNAL=http://localhost:8080 +SUPER= \ No newline at end of file diff --git a/package.json b/package.json index 3b2029b..d92d681 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "node": ">=18.0" }, "dependencies": { - "@eartharoid/dbf": "^0.2.0", + "@eartharoid/dbf": "^0.2.2", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.0.4", "@fastify/cookie": "^6.0.0", @@ -48,6 +48,7 @@ "leekslazylogger": "^4.1.7", "ms": "^2.1.3", "node-dir": "^0.1.17", + "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", "prisma": "^4.0.0", "semver": "^7.3.7", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 11909de..757dd7f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -115,7 +115,7 @@ model Guild { blocklist Json @default("[]") categories Category[] createdAt DateTime @default(now()) - errorColour String @default("RED") + errorColour String @default("Red") feedback Feedback[] footer String? @default("Discord Tickets by eartharoid") id String @id @db.VarChar(19) @@ -123,7 +123,7 @@ model Guild { logChannel String? @db.VarChar(19) primaryColour String @default("#009999") staleAfter Int? - successColour String @default("GREEN") + successColour String @default("Green") tags Tag[] tickets Ticket[] workingHours Json @default("[\"UTC\", [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"]]") @@ -141,7 +141,7 @@ model Question { maxLength Int? @default(4000) minLength Int? @default(0) order Int - placeholder String? @db.VarChar(100) + placeholder String? @db.VarChar(512) required Boolean @default(true) style Int @default(2) value String? @db.Text diff --git a/src/client.js b/src/client.js index a1e78ea..3c8b02a 100644 --- a/src/client.js +++ b/src/client.js @@ -31,6 +31,7 @@ module.exports = class Client extends FrameworkClient { this.i18n = new I18n('en-GB', locales); this.config = config; this.log = log; + this.supers = (process.env.SUPER ?? '').split(','); } async login(token) { diff --git a/src/http.js b/src/http.js index 21d578a..f695fad 100644 --- a/src/http.js +++ b/src/http.js @@ -74,8 +74,8 @@ module.exports = client => { }); } const guildMember = await guild.members.fetch(userId); - const isAdmin = guildMember?.permissions.has('MANAGE_GUILD'); - if (!guildMember || !isAdmin) { + const isAdmin = guildMember?.permissions.has('MANAGE_GUILD') || client.supers.includes(userId); + if (!isAdmin) { return res.code(403).send({ error: 'Forbidden', message: 'You are not permitted for this action.', @@ -88,6 +88,18 @@ module.exports = client => { } }); + // body processing + fastify.addHook('preHandler', (req, res, done) => { + if (req.body && typeof req.body === 'object') { + for (const prop in req.body) { + if (typeof req.body[prop] === 'string') { + req.body[prop] = req.body[prop].trim(); + } + } + } + done(); + }); + // logging fastify.addHook('onResponse', (req, res, done) => { done(); diff --git a/src/lib/logging.js b/src/lib/logging.js index 7bae537..a88572a 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -1,4 +1,4 @@ -const { MessageEmbed } = require('discord.js'); +const { EmbedBuilder } = require('discord.js'); const { diff: getDiff } = require('object-diffy'); function makeDiff({ @@ -61,8 +61,8 @@ async function logAdminEvent(client, { const channel = client.channels.cache.get(settings.logChannel); if (!channel) return; const embeds = [ - new MessageEmbed() - .setColor('ORANGE') + new EmbedBuilder() + .setColor('Orange') .setAuthor({ iconURL: user.avatarURL(), name: user.username, @@ -77,13 +77,18 @@ async function logAdminEvent(client, { targetType: getMessage(`log.admin.description.target.${target.type}`), verb: getMessage(`log.admin.verb.${action}`), })) - .addField(getMessage(`log.admin.title.target.${target.type}`), target.name ?? target.id), + .addFields([ + { + name: getMessage(`log.admin.title.target.${target.type}`), + value: target.name ?? target.id, + }, + ]), ]; if (diff && diff.original) { embeds.push( - new MessageEmbed() - .setColor('ORANGE') + new EmbedBuilder() + .setColor('Orange') .setTitle(getMessage('log.admin.changes')) .setFields(makeDiff(diff)), ); diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category].js b/src/routes/api/admin/guilds/[guild]/categories/[category].js index 143605d..20b0d52 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category].js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category].js @@ -63,6 +63,9 @@ module.exports.patch = fastify => ({ const original = req.params.category && await client.prisma.category.findUnique({ where: { id: categoryId } }); if (!original) return res.status(404); + if (data.hasOwnProperty('id')) delete data.id; + if (data.hasOwnProperty('createdAt')) delete data.createdAt; + const category = await client.prisma.category.update({ data: { ...data, diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index 1a8a9df..f7219a1 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -1,4 +1,6 @@ const { logAdminEvent } = require('../../../../../../lib/logging'); +const emoji = require('node-emoji'); +const { ChannelType: { GuildCategory } } = require('discord.js'); module.exports.get = fastify => ({ handler: async (req, res) => { @@ -42,14 +44,17 @@ module.exports.post = fastify => ({ const user = await client.users.fetch(req.user.payload.id); const guild = client.guilds.cache.get(req.params.guild); const data = req.body; - const allow = ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES']; + const allow = ['ViewChannel', 'ReadMessageHistory', 'SendMessages', 'EmbedLinks', 'AttachFiles']; if (!data.discordCategory) { - const channel = await guild.channels.create(data.name, { + let name = data.name; + if (emoji.hasEmoji(data.emoji)) name = `${emoji.get(data.emoji)} ${name}`; + const channel = await guild.channels.create({ + name, permissionOverwrites: [ ...[ { - deny: ['VIEW_CHANNEL'], + deny: ['ViewChannel'], id: guild.roles.everyone, }, { @@ -64,12 +69,12 @@ module.exports.post = fastify => ({ ], position: 1, reason: `Tickets category created by ${user.tag}`, - type: 'GUILD_CATEGORY', + type: GuildCategory, }); data.discordCategory = channel.id; } - data.channelName ??= 'ticket-{num}'; + data.channelName ||= 'ticket-{num}'; // not ??=, expect empty string const category = await client.prisma.category.create({ data: { diff --git a/src/routes/api/admin/guilds/[guild]/settings.js b/src/routes/api/admin/guilds/[guild]/settings.js index e422142..0c6e929 100644 --- a/src/routes/api/admin/guilds/[guild]/settings.js +++ b/src/routes/api/admin/guilds/[guild]/settings.js @@ -1,4 +1,5 @@ const { logAdminEvent } = require('../../../../../lib/logging.js'); +const { Colors } = require('discord.js'); module.exports.delete = fastify => ({ handler: async (req, res) => { @@ -37,16 +38,25 @@ module.exports.get = fastify => ({ module.exports.patch = fastify => ({ handler: async (req, res) => { - if (req.body.hasOwnProperty('id')) delete req.body.id; - if (req.body.hasOwnProperty('createdAt')) delete req.body.createdAt; + const data = req.body; + if (data.hasOwnProperty('id')) delete data.id; + if (data.hasOwnProperty('createdAt')) delete data.createdAt; + const colours = ['errorColour', 'primaryColour', 'successColour']; + for (const c of colours) { + if (data[c] && !data[c].startsWith('#') && !(data[c] in Colors)) { // if not null/empty and not hex + throw new Error(`${data[c]} is not a valid colour. Valid colours are HEX and: ${Object.keys(Colors).join(', ')}`); + } + } + /** @type {import('client')} */ const client = res.context.config.client; const id = req.params.guild; const original = await client.prisma.guild.findUnique({ where: { id } }); const settings = await client.prisma.guild.update({ - data: req.body, + data: data, where: { id }, }); + logAdminEvent(client, { action: 'update', diff: { @@ -56,7 +66,7 @@ module.exports.patch = fastify => ({ guildId: id, target: { id, - name: client.guilds.cache.get(id), + name: client.guilds.cache.get(id).name, type: 'settings', }, userId: req.user.payload.id,