mirror of
https://github.com/Hessenuk/DiscordTickets.git
synced 2024-12-23 00:03:09 +02:00
Settings, encryption, logging
This commit is contained in:
parent
97623f3203
commit
79462e83e6
@ -32,11 +32,14 @@
|
||||
"dependencies": {
|
||||
"@eartharoid/dbf": "^0.0.1",
|
||||
"@eartharoid/dtf": "^2.0.1",
|
||||
"@eartharoid/i18n": "^1.0.4",
|
||||
"@fastify/cookie": "^6.0.0",
|
||||
"@fastify/cors": "^8.0.0",
|
||||
"@fastify/jwt": "^5.0.1",
|
||||
"@fastify/oauth2": "^5.0.0",
|
||||
"@prisma/client": "^3.15.2",
|
||||
"@prisma/client": "^4.0.0",
|
||||
"cryptr": "^6.0.3",
|
||||
"deep-object-diff": "^1.1.7",
|
||||
"discord.js": "^13.8.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"fastify": "^4.2.1",
|
||||
@ -56,6 +59,6 @@
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"nodemon": "^2.0.19",
|
||||
"prisma": "^3.15.2"
|
||||
"prisma": "^4.0.0"
|
||||
}
|
||||
}
|
@ -109,7 +109,8 @@ model Feedback {
|
||||
}
|
||||
|
||||
model Guild {
|
||||
autoTag Json?
|
||||
autoClose Int?
|
||||
autoTag Json @default("[]")
|
||||
archive Boolean @default(true)
|
||||
blocklist Json @default("[]")
|
||||
categories Category[]
|
||||
@ -118,11 +119,14 @@ model Guild {
|
||||
feedback Feedback[]
|
||||
footer String? @default("Discord Tickets by eartharoid")
|
||||
id String @id @db.VarChar(19)
|
||||
locale String @default("en-GB")
|
||||
logChannel String? @db.VarChar(19)
|
||||
primaryColour String @default("#009999")
|
||||
staleAfter Int?
|
||||
successColour String @default("GREEN")
|
||||
tags Tag[]
|
||||
tickets Ticket[]
|
||||
workingHours Json @default("[null, null, null, null, null, null, null]")
|
||||
|
||||
@@map("guilds")
|
||||
}
|
||||
|
@ -2,9 +2,14 @@ const { Client: FrameworkClient }= require('@eartharoid/dbf');
|
||||
const { Intents } = require('discord.js');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const Keyv = require('keyv');
|
||||
const I18n = require('@eartharoid/i18n');
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
const YAML = require('yaml');
|
||||
const middleware = require('./lib/prisma');
|
||||
|
||||
module.exports = class Client extends FrameworkClient {
|
||||
constructor() {
|
||||
constructor(config, log) {
|
||||
super({
|
||||
intents: [
|
||||
Intents.FLAGS.GUILDS,
|
||||
@ -12,11 +17,26 @@ module.exports = class Client extends FrameworkClient {
|
||||
Intents.FLAGS.GUILD_MESSAGES,
|
||||
],
|
||||
});
|
||||
|
||||
const locales = {};
|
||||
fs.readdirSync(join(__dirname, 'i18n'))
|
||||
.filter(file => file.endsWith('.yml'))
|
||||
.forEach(file => {
|
||||
const data = fs.readFileSync(join(__dirname, 'i18n/' + file), { encoding: 'utf8' });
|
||||
const name = file.slice(0, file.length - 4);
|
||||
locales[name] = YAML.parse(data);
|
||||
});
|
||||
|
||||
/** @type {I18n} */
|
||||
this.i18n = new I18n('en-GB', locales);
|
||||
this.config = config;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
async login(token) {
|
||||
/** @type {PrismaClient} */
|
||||
this.prisma = new PrismaClient();
|
||||
// this.prisma.$use((params, next) => {})
|
||||
this.prisma.$use(middleware);
|
||||
this.keyv = new Keyv();
|
||||
return super.login(token);
|
||||
}
|
||||
|
19
src/i18n/en-GB.yml
Normal file
19
src/i18n/en-GB.yml
Normal file
@ -0,0 +1,19 @@
|
||||
command:
|
||||
log:
|
||||
admin:
|
||||
description:
|
||||
joined: '{user} {verb} {targetType}'
|
||||
target:
|
||||
category: 'a category'
|
||||
settings: 'the settings'
|
||||
differences: 'Differences'
|
||||
title:
|
||||
joined: '{targetType} {verb}'
|
||||
target:
|
||||
category: 'Category'
|
||||
settings: 'Settings'
|
||||
verb:
|
||||
create: 'created'
|
||||
delete: 'deleted'
|
||||
update: 'updated'
|
||||
ticket:
|
@ -72,9 +72,7 @@ process.on('unhandledRejection', error => {
|
||||
log.error(error);
|
||||
});
|
||||
|
||||
const client = new Client();
|
||||
client.config = config;
|
||||
client.log = log;
|
||||
const client = new Client(config, log);
|
||||
client.login().then(() => {
|
||||
http(client);
|
||||
});
|
97
src/lib/logging.js
Normal file
97
src/lib/logging.js
Normal file
@ -0,0 +1,97 @@
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
|
||||
/**
|
||||
* @param {import("client")} client
|
||||
* @param {string} guildId
|
||||
* @returns {import("discord.js").TextChannel?}
|
||||
*/
|
||||
async function getLogChannel(client, guildId) {
|
||||
const { logChannel: channelId } = await client.prisma.guild.findUnique({
|
||||
select: { logChannel: true },
|
||||
where: { id: guildId },
|
||||
});
|
||||
return channelId && client.channels.cache.get(channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("client")} client
|
||||
* @param {*} target
|
||||
* @returns {string} target.type
|
||||
* @returns {string} target.id
|
||||
*/
|
||||
async function getTargetName(client, target) {
|
||||
if (target.type === 'settings') {
|
||||
return client.guilds.cache.get(target.id).name;
|
||||
} else {
|
||||
const row = await client.prisma[target.type].findUnique({ where: { id: target.id } });
|
||||
return row.name ?? target.id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("client")} client
|
||||
* @param {object} details
|
||||
* @param {string} details.guildId
|
||||
* @param {string} details.userId
|
||||
* @param {string} details.action
|
||||
*/
|
||||
async function logAdminEvent(client, {
|
||||
guildId, userId, action, target, diff,
|
||||
}) {
|
||||
const user = await client.users.fetch(userId);
|
||||
client.log.info(`${user.tag} ${action}d ${target.type} ${target.id}`);
|
||||
const settings = await client.prisma.guild.findUnique({
|
||||
select: {
|
||||
footer: true,
|
||||
locale: true,
|
||||
logChannel: true,
|
||||
},
|
||||
where: { id: guildId },
|
||||
});
|
||||
if (!settings.logChannel) return;
|
||||
const getMessage = client.i18n.getLocale(settings.locale);
|
||||
const i18nOptions = {
|
||||
user: `<@${user.id}>`,
|
||||
verb: getMessage(`log.admin.verb.${action}`),
|
||||
};
|
||||
const channel = client.channels.cache.get(settings.logChannel);
|
||||
if (!channel) return;
|
||||
const targetName = await getTargetName(client, target);
|
||||
return await channel.send({
|
||||
embeds: [
|
||||
new MessageEmbed()
|
||||
.setColor('ORANGE')
|
||||
.setAuthor({
|
||||
iconURL: user.avatarURL(),
|
||||
name: user.username,
|
||||
})
|
||||
.setTitle(getMessage('log.admin.title.joined', {
|
||||
...i18nOptions,
|
||||
targetType: getMessage(`log.admin.title.target.${target.type}`),
|
||||
verb: getMessage(`log.admin.verb.${action}`),
|
||||
}))
|
||||
.setDescription(getMessage('log.admin.description.joined', {
|
||||
...i18nOptions,
|
||||
targetType: getMessage(`log.admin.description.target.${target.type}`),
|
||||
verb: getMessage(`log.admin.verb.${action}`),
|
||||
}))
|
||||
.addField(getMessage(`log.admin.title.target.${target.type}`), targetName),
|
||||
// .setFooter({
|
||||
// iconURL: client.guilds.cache.get(guildId).iconURL(),
|
||||
// text: settings.footer,
|
||||
// }),
|
||||
...[
|
||||
action === 'update' && diff &&
|
||||
new MessageEmbed()
|
||||
.setColor('ORANGE')
|
||||
.setTitle(getMessage('log.admin.differences'))
|
||||
.setDescription(`\`\`\`json\n${JSON.stringify(diff)}\n\`\`\``),
|
||||
],
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getLogChannel,
|
||||
logAdminEvent,
|
||||
};
|
41
src/lib/prisma.js
Normal file
41
src/lib/prisma.js
Normal file
@ -0,0 +1,41 @@
|
||||
const Cryptr = require('cryptr');
|
||||
const cryptr = new Cryptr(process.env.ENCRYPTION_KEY);
|
||||
const fields = [
|
||||
'name',
|
||||
'content',
|
||||
'username',
|
||||
'displayName',
|
||||
'channelName',
|
||||
'openingMessage',
|
||||
'description',
|
||||
'value',
|
||||
'placeholder',
|
||||
'closedReason',
|
||||
'topic',
|
||||
'comment',
|
||||
'label',
|
||||
'regex',
|
||||
];
|
||||
const shouldEncrypt = ['create', 'createMany', 'update', 'updateMany', 'upsert'];
|
||||
const shouldDecrypt = ['findUnique', 'findFirst', 'findMany'];
|
||||
|
||||
module.exports = async (params, next) => {
|
||||
if (params.args.data && shouldEncrypt.includes(params.action)) {
|
||||
for (const field of fields) {
|
||||
if (field in params.args.data) {
|
||||
params.args.data[field] = cryptr.encrypt(params.args.data[field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await next(params);
|
||||
|
||||
if (result && shouldDecrypt.includes(params.action)) {
|
||||
for (const field of fields) {
|
||||
if (field in result) {
|
||||
result[field] = cryptr.decrypt(params.result[field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
1
src/lib/strings.js
Normal file
1
src/lib/strings.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports.capitalise = string => string.charAt(0).toUpperCase() + string.slice(1);
|
@ -1,3 +1,6 @@
|
||||
const { logAdminEvent } = require('../../../../../lib/logging.js');
|
||||
const { updatedDiff } = require('deep-object-diff');
|
||||
|
||||
module.exports.delete = fastify => ({
|
||||
handler: async (req, res) => {
|
||||
/** @type {import('client')} */
|
||||
@ -5,7 +8,15 @@ module.exports.delete = fastify => ({
|
||||
const id = req.params.guild;
|
||||
await client.prisma.guild.delete({ where: { id } });
|
||||
const settings = await client.prisma.guild.create({ data: { id } });
|
||||
|
||||
logAdminEvent(client, {
|
||||
action: 'delete',
|
||||
guildId: id,
|
||||
target: {
|
||||
id,
|
||||
type: 'settings',
|
||||
},
|
||||
userId: req.user.payload.id,
|
||||
});
|
||||
return settings;
|
||||
},
|
||||
onRequest: [fastify.authenticate, fastify.isAdmin],
|
||||
@ -29,11 +40,21 @@ module.exports.patch = fastify => ({
|
||||
/** @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,
|
||||
where: { id },
|
||||
});
|
||||
|
||||
logAdminEvent(client, {
|
||||
action: 'update',
|
||||
diff: updatedDiff(original, settings),
|
||||
guildId: id,
|
||||
target: {
|
||||
id,
|
||||
type: 'settings',
|
||||
},
|
||||
userId: req.user.payload.id,
|
||||
});
|
||||
return settings;
|
||||
},
|
||||
onRequest: [fastify.authenticate, fastify.isAdmin],
|
||||
|
7
src/routes/api/locales.js
Normal file
7
src/routes/api/locales.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports.get = () => ({
|
||||
handler: async (req, res) => {
|
||||
/** @type {import("client")} */
|
||||
const client = res.context.config.client;
|
||||
return client.i18n.locales;
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue
Block a user