This commit is contained in:
Isaac
2022-07-15 23:19:42 +01:00
parent 145514a86b
commit 97623f3203
15 changed files with 193 additions and 57 deletions

View File

@@ -1,6 +1,7 @@
const { Client: FrameworkClient }= require('@eartharoid/dbf');
const { Intents } = require('discord.js');
const { PrismaClient } = require('@prisma/client');
const Keyv = require('keyv');
module.exports = class Client extends FrameworkClient {
constructor() {
@@ -16,6 +17,7 @@ module.exports = class Client extends FrameworkClient {
async login(token) {
this.prisma = new PrismaClient();
// this.prisma.$use((params, next) => {})
this.keyv = new Keyv();
return super.login(token);
}

View File

@@ -3,10 +3,17 @@ const oauth = require('@fastify/oauth2');
// const { randomBytes } = require('crypto');
const { short } = require('leeks.js');
const { join } = require('path');
const { readFiles } = require('node-dir');
const { readFiles } = require('node-dir');
module.exports = client => {
// cors plugins
fastify.register(require('@fastify/cors'), {
credentials: true,
methods: ['DELETE', 'GET', 'PATCH', 'PUT', 'POST'],
origin: true,
});
// oauth2 plugin
fastify.register(oauth, {
callbackUri: `${process.env.HTTP_EXTERNAL}/auth/callback`,
@@ -32,14 +39,22 @@ module.exports = client => {
signed: false,
},
// secret: randomBytes(16).toString('hex'),
secret: process.env.DB_ENCRYPTION_KEY,
secret: process.env.ENCRYPTION_KEY,
});
// auth
fastify.decorate('authenticate', async (req, res) => {
try {
const data = await req.jwtVerify();
if (data.payload.expiresAt < Date.now()) res.redirect('/auth/login');
// if (data.payload.expiresAt < Date.now()) res.redirect('/auth/login');
if (data.payload.expiresAt < Date.now()) {
return res.code(401).send({
error: 'Unauthorised',
message: 'You are not authenticated.',
statusCode: 401,
});
}
} catch (err) {
res.send(err);
}
@@ -50,14 +65,21 @@ module.exports = client => {
const userId = req.user.payload.id;
const guildId = req.params.guild;
const guild = client.guilds.cache.get(guildId);
const guildMember = await guild.members.fetch(userId);
const isAdmin = guildMember.permissions.has('MANAGE_GUILD');
if (!guild) {
return res.code(404).send({
error: 'Not Found',
message: 'The requested resource could not be found.',
statusCode: 404,
if (!isAdmin) {
return res.code(401).send({
error: 'Unauthorised',
message: 'User is not authorised for this action',
statusCode: 401,
});
}
const guildMember = await guild.members.fetch(userId);
const isAdmin = guildMember?.permissions.has('MANAGE_GUILD');
if (!guildMember || !isAdmin) {
return res.code(403).send({
error: 'Forbidden',
message: 'You are not permitted for this action.',
statusCode: 403,
});
}
@@ -119,7 +141,7 @@ module.exports = client => {
}
// start server
fastify.listen(process.env.HTTP_BIND, (err, addr) => {
fastify.listen({ port: process.env.HTTP_BIND }, (err, addr) => {
if (err) client.log.error.http(err);
else client.log.success.http(`Listening at ${addr}`);
});

View File

@@ -41,8 +41,8 @@ if (!semver.satisfies(process.versions.node, pkg.engines.node)) {
process.exit(1);
}
if (process.env.DB_ENCRYPTION_KEY === undefined) {
console.log('\x07' + colours.redBright('Error: The "DB_ENCRYPTION_KEY" environment variable is not set.\nRun "npm run keygen" to generate a key, or set it to "false" to disable encryption (not recommended).'));
if (process.env.ENCRYPTION_KEY === undefined) {
console.log('\x07' + colours.redBright('Error: The "ENCRYPTION_KEY" environment variable is not set.\nRun "npm run keygen" to generate a key, or set it to "false" to disable encryption (not recommended).'));
process.exit(1);
}

View File

@@ -11,6 +11,7 @@ module.exports = class extends Listener {
}
run() {
process.title = this.client.user.tag + ' [Discord Tickets]';
this.client.log.success('Connected to Discord as "%s"', this.client.user.tag);
}
};
};

View File

@@ -1,6 +1,6 @@
module.exports.get = fastify => ({
handler: async (req, res) => {
/** @type {import('../../../../../../client')} */
/** @type {import('client')} */
const client = res.context.config.client;
const categories = await client.prisma.guild.findUnique({ where: { id: req.params.guild } }).categories();
@@ -12,7 +12,7 @@ module.exports.get = fastify => ({
module.exports.post = fastify => ({
handler: async (req, res) => {
/** @type {import('../../../../../../client')} */
/** @type {import('client')} */
const client = res.context.config.client;
const user = await client.users.fetch(req.user.payload.id);

View File

@@ -0,0 +1,13 @@
module.exports.get = fastify => ({
handler: async (req, res) => {
/** @type {import('client')} */
const client = res.context.config.client;
const id = req.params.guild;
const guild = client.guilds.cache.get(id) ?? {};
const { query } = req.query;
if (!query) return {};
const data = query.split(/\./g).reduce((acc, part) => acc && acc[part], guild);
return data;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});

View File

@@ -1,40 +1,53 @@
module.exports.delete = fastify => ({
handler: async (req, res) => {
/** @type {import('../../../../../client')} */
const client = res.context.config.client;
await client.prisma.guild.delete({ where: { id: req.params.guild } });
const settings = await client.prisma.guild.create({ data: { id: req.params.guild } });
res.send(settings);
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});
/* eslint-disable no-underscore-dangle */
const ms = require('ms');
module.exports.get = fastify => ({
handler: async (req, res) => {
/** @type {import('../../../../../client')} */
/** @type {import("client")} */
const client = res.context.config.client;
const id = req.params.guild;
const cacheKey = `cache/stats/guild:${id}`;
let cached = await client.keyv.get(cacheKey);
const settings = await client.prisma.guild.findUnique({ where: { id: req.params.guild } }) ??
await client.prisma.guild.create({ data: { id: req.params.guild } });
if (!cached) {
const guild = client.guilds.cache.get(id);
const settings = await client.prisma.guild.findUnique({ where: { id } }) ??
await client.prisma.guild.create({ data: { id } });
const categories = await client.prisma.category.findMany({
select: {
_count: { select: { tickets: true } },
id: true,
name: true,
},
where: { guildId: id },
});
const tickets = await client.prisma.ticket.findMany({
select: {
createdAt: true,
firstResponseAt: true,
},
where: { guildId: id },
});
cached = {
createdAt: settings.createdAt,
id: guild.id,
logo: guild.iconURL(),
name: guild.name,
stats: {
avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length),
categories: categories.map(c => ({
id: c.id,
name: c.name,
tickets: c._count.tickets,
})),
tags: await client.prisma.tag.count({ where: { guildId: id } }),
tickets: tickets.length,
},
};
await client.keyv.set(cacheKey, cached, ms('5m'));
}
res.send(settings);
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});
module.exports.patch = fastify => ({
handler: async (req, res) => {
/** @type {import('../../../../../client')} */
const client = res.context.config.client;
const settings = await client.prisma.guild.update({
data: req.body,
where: { id: req.params.guild },
});
res.send(settings);
return cached;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});

View File

@@ -0,0 +1,40 @@
module.exports.delete = fastify => ({
handler: async (req, res) => {
/** @type {import('client')} */
const client = res.context.config.client;
const id = req.params.guild;
await client.prisma.guild.delete({ where: { id } });
const settings = await client.prisma.guild.create({ data: { id } });
return settings;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});
module.exports.get = fastify => ({
handler: async (req, res) => {
/** @type {import('client')} */
const client = res.context.config.client;
const id = req.params.guild;
const settings = await client.prisma.guild.findUnique({ where: { id } }) ??
await client.prisma.guild.create({ data: { id } });
return settings;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});
module.exports.patch = fastify => ({
handler: async (req, res) => {
/** @type {import('client')} */
const client = res.context.config.client;
const id = req.params.guild;
const settings = await client.prisma.guild.update({
data: req.body,
where: { id },
});
return settings;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});

View File

@@ -4,6 +4,7 @@ module.exports.get = fastify => ({
const guilds = client.guilds.cache
.filter(async guild => {
const member = await guild.members.fetch(req.user.payload.id);
if (!member) return false;
return member.permissions.has('MANAGE_GUILD');
})
.map(guild => ({

39
src/routes/api/client.js Normal file
View File

@@ -0,0 +1,39 @@
const ms = require('ms');
module.exports.get = () => ({
handler: async (req, res) => {
/** @type {import("client")} */
const client = res.context.config.client;
const cacheKey = 'cache/stats/client';
let cached = await client.keyv.get(cacheKey);
if (!cached) {
const tickets = await client.prisma.ticket.findMany({
select: {
createdAt: true,
firstResponseAt: true,
},
});
const users = await client.prisma.user.findMany({ select: { messageCount: true } });
cached = {
avatar: client.user.avatarURL(),
discriminator: client.user.discriminator,
id: client.user.id,
stats: {
activatedUsers: users.length,
archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they get deleted
avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length),
categories: await client.prisma.category.count(),
guilds: client.guilds.cache.size,
members: client.guilds.cache.reduce((t, g) => t + g.memberCount, 0),
tags: await client.prisma.tag.count(),
tickets: tickets.length,
},
username: client.user.username,
};
await client.keyv.set(cacheKey, cached, ms('5m'));
}
return cached;
},
});