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": {
|
"dependencies": {
|
||||||
"@eartharoid/dbf": "^0.0.1",
|
"@eartharoid/dbf": "^0.0.1",
|
||||||
"@eartharoid/dtf": "^2.0.1",
|
"@eartharoid/dtf": "^2.0.1",
|
||||||
|
"@eartharoid/i18n": "^1.0.4",
|
||||||
"@fastify/cookie": "^6.0.0",
|
"@fastify/cookie": "^6.0.0",
|
||||||
"@fastify/cors": "^8.0.0",
|
"@fastify/cors": "^8.0.0",
|
||||||
"@fastify/jwt": "^5.0.1",
|
"@fastify/jwt": "^5.0.1",
|
||||||
"@fastify/oauth2": "^5.0.0",
|
"@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",
|
"discord.js": "^13.8.1",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"fastify": "^4.2.1",
|
"fastify": "^4.2.1",
|
||||||
@ -56,6 +59,6 @@
|
|||||||
"eslint": "^8.19.0",
|
"eslint": "^8.19.0",
|
||||||
"eslint-plugin-unused-imports": "^2.0.0",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"nodemon": "^2.0.19",
|
"nodemon": "^2.0.19",
|
||||||
"prisma": "^3.15.2"
|
"prisma": "^4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -109,7 +109,8 @@ model Feedback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Guild {
|
model Guild {
|
||||||
autoTag Json?
|
autoClose Int?
|
||||||
|
autoTag Json @default("[]")
|
||||||
archive Boolean @default(true)
|
archive Boolean @default(true)
|
||||||
blocklist Json @default("[]")
|
blocklist Json @default("[]")
|
||||||
categories Category[]
|
categories Category[]
|
||||||
@ -118,11 +119,14 @@ model Guild {
|
|||||||
feedback Feedback[]
|
feedback Feedback[]
|
||||||
footer String? @default("Discord Tickets by eartharoid")
|
footer String? @default("Discord Tickets by eartharoid")
|
||||||
id String @id @db.VarChar(19)
|
id String @id @db.VarChar(19)
|
||||||
|
locale String @default("en-GB")
|
||||||
logChannel String? @db.VarChar(19)
|
logChannel String? @db.VarChar(19)
|
||||||
primaryColour String @default("#009999")
|
primaryColour String @default("#009999")
|
||||||
|
staleAfter Int?
|
||||||
successColour String @default("GREEN")
|
successColour String @default("GREEN")
|
||||||
tags Tag[]
|
tags Tag[]
|
||||||
tickets Ticket[]
|
tickets Ticket[]
|
||||||
|
workingHours Json @default("[null, null, null, null, null, null, null]")
|
||||||
|
|
||||||
@@map("guilds")
|
@@map("guilds")
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,14 @@ const { Client: FrameworkClient }= require('@eartharoid/dbf');
|
|||||||
const { Intents } = require('discord.js');
|
const { Intents } = require('discord.js');
|
||||||
const { PrismaClient } = require('@prisma/client');
|
const { PrismaClient } = require('@prisma/client');
|
||||||
const Keyv = require('keyv');
|
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 {
|
module.exports = class Client extends FrameworkClient {
|
||||||
constructor() {
|
constructor(config, log) {
|
||||||
super({
|
super({
|
||||||
intents: [
|
intents: [
|
||||||
Intents.FLAGS.GUILDS,
|
Intents.FLAGS.GUILDS,
|
||||||
@ -12,11 +17,26 @@ module.exports = class Client extends FrameworkClient {
|
|||||||
Intents.FLAGS.GUILD_MESSAGES,
|
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) {
|
async login(token) {
|
||||||
|
/** @type {PrismaClient} */
|
||||||
this.prisma = new PrismaClient();
|
this.prisma = new PrismaClient();
|
||||||
// this.prisma.$use((params, next) => {})
|
this.prisma.$use(middleware);
|
||||||
this.keyv = new Keyv();
|
this.keyv = new Keyv();
|
||||||
return super.login(token);
|
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);
|
log.error(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const client = new Client();
|
const client = new Client(config, log);
|
||||||
client.config = config;
|
|
||||||
client.log = log;
|
|
||||||
client.login().then(() => {
|
client.login().then(() => {
|
||||||
http(client);
|
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 => ({
|
module.exports.delete = fastify => ({
|
||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
/** @type {import('client')} */
|
/** @type {import('client')} */
|
||||||
@ -5,7 +8,15 @@ module.exports.delete = fastify => ({
|
|||||||
const id = req.params.guild;
|
const id = req.params.guild;
|
||||||
await client.prisma.guild.delete({ where: { id } });
|
await client.prisma.guild.delete({ where: { id } });
|
||||||
const settings = await client.prisma.guild.create({ data: { 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;
|
return settings;
|
||||||
},
|
},
|
||||||
onRequest: [fastify.authenticate, fastify.isAdmin],
|
onRequest: [fastify.authenticate, fastify.isAdmin],
|
||||||
@ -29,11 +40,21 @@ module.exports.patch = fastify => ({
|
|||||||
/** @type {import('client')} */
|
/** @type {import('client')} */
|
||||||
const client = res.context.config.client;
|
const client = res.context.config.client;
|
||||||
const id = req.params.guild;
|
const id = req.params.guild;
|
||||||
|
const original = await client.prisma.guild.findUnique({ where: { id } });
|
||||||
const settings = await client.prisma.guild.update({
|
const settings = await client.prisma.guild.update({
|
||||||
data: req.body,
|
data: req.body,
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
|
logAdminEvent(client, {
|
||||||
|
action: 'update',
|
||||||
|
diff: updatedDiff(original, settings),
|
||||||
|
guildId: id,
|
||||||
|
target: {
|
||||||
|
id,
|
||||||
|
type: 'settings',
|
||||||
|
},
|
||||||
|
userId: req.user.payload.id,
|
||||||
|
});
|
||||||
return settings;
|
return settings;
|
||||||
},
|
},
|
||||||
onRequest: [fastify.authenticate, fastify.isAdmin],
|
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