diff --git a/src/api/board.ins/routes/root.ts b/src/api/board.ins/routes/root.ts index 846e716..d53b99e 100644 --- a/src/api/board.ins/routes/root.ts +++ b/src/api/board.ins/routes/root.ts @@ -1,5 +1,6 @@ import { TextChannel } from 'eris'; import { v4 as genUUID } from 'uuid'; +import { Request, Response } from 'express'; import { RichEmbed, Route, Server } from '../../../class'; export default class Root extends Route { @@ -125,6 +126,72 @@ export default class Root extends Route { }); }); + this.router.post('/proc', async (req: Request, res: Response) => { + if (!req.body.pin) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const director = await this.server.client.db.Score.findOne({ pin: req.body.pin }); + const staffGuild = this.server.client.guilds.get('446067825673633794') || await this.server.client.getRESTGuild('446067825673633794'); + + if (!director || !staffGuild.members.get(director.userID)?.roles?.includes('662163685439045632')) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + if (!req.body.subject) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + + if (!req.body.body) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + + const proclamationID = genUUID(); + + const staffDiscord = this.server.client.users.get(director.userID) || await this.server.client.getRESTUser(director.userID); + const staffInformation = await this.server.client.db.Staff.findOne({ userID: director.userID }); + + const embed = new RichEmbed(); + embed.setTitle('Proclamation'); + embed.setAuthor(`${staffDiscord.username}#${staffDiscord.discriminator}, ${staffInformation.pn.join(', ')}`, staffDiscord.avatarURL); + embed.setColor('#66e1ff'); + embed.addField('Subject', req.body.subject); + embed.addField('Body', req.body.body); + embed.addField('ID', proclamationID); + embed.setTimestamp(new Date()); + + const channel = this.server.client.getChannel('807444198969835550'); + const motionMessage = await channel.createMessage({ embed }); + await motionMessage.addReaction(this.server.client.util.emojis.SUCCESS); + await motionMessage.addReaction(this.server.client.util.emojis.ERROR); + + const motion = await this.server.client.db.Proclamation.create({ + issuedBy: director.userID, + subject: req.body.subject, + body: req.body.body, + at: new Date(), + oID: proclamationID, + processed: false, + }); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: `Created new Proclamation with ID ${motion.oID} by ${staffDiscord.username}#${staffDiscord.discriminator}, ${staffInformation.pn.join(', ')}.`, + }); + }); + this.router.post('/resolution', async (req, res) => { if (!req.body.pin) { return res.status(401).json({ @@ -288,6 +355,42 @@ export default class Root extends Route { res.status(200).json({ message: `Resolution with ID ${req.params.id} deleted.` }); }); + this.router.delete('/proc/:id', async (req, res) => { + if (!req.body.pin) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const director = await this.server.client.db.Score.findOne({ pin: req.body.pin }); + const staffGuild = this.server.client.guilds.get('446067825673633794') || await this.server.client.getRESTGuild('446067825673633794'); + + if (!director || !staffGuild.members.get(director.userID)?.roles?.includes('662163685439045632')) { + return res.status(403).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + if (!req.params.id) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + if (!(await this.server.client.db.Proclamation.exists({ oID: req.params.id }))) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + await this.server.client.db.Proclamation.deleteOne({ oID: req.params.id }); + + res.status(200).json({ message: `Proclamation with ID ${req.params.id} deleted.` }); + }); + this.router.get('/eo/:id', async (req, res) => { if (!req.params.id) { return res.status(400).json({ @@ -365,6 +468,60 @@ export default class Root extends Route { }); }); + this.router.get('/proc/:id', async (req: Request, res: Response) => { + const proclamation = await this.server.client.db.Proclamation.findOne({ oID: req.params.id }).lean(); + + res.status(200).send({ + proclamation, + }); + }); + + this.router.patch('/proc/:id', async (req, res) => { + if (!req.body.pin) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const director = await this.server.client.db.Score.findOne({ pin: req.body.pin }); + const staffGuild = this.server.client.guilds.get('446067825673633794') || await this.server.client.getRESTGuild('446067825673633794'); + + if (!director || !staffGuild.members.get(director.userID)?.roles?.includes('662163685439045632')) { + return res.status(403).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + if (!req.params.id) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + if (!(await this.server.client.db.Proclamation.exists({ oID: req.params.id }))) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + if (!req.body.subject && !req.body.body) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + + const proclamation = await this.server.client.db.Proclamation.findOne({ oID: req.params.id }); + await proclamation.updateOne({ + subject: req.body.subject || proclamation.subject, + body: req.body.body || proclamation.body, + }); + + res.status(200).json({ message: `Updated Proclamation with ID ${proclamation.oID}.` }); + }); + this.router.patch('/eo/:id', async (req, res) => { if (!req.body.pin) { return res.status(401).json({ @@ -457,6 +614,117 @@ export default class Root extends Route { res.status(200).json({ message: `Updated Motion with ID ${motion.oID}.` }); }); + this.router.patch('/resolution/:id', async (req, res) => { + if (!req.body.pin) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const director = await this.server.client.db.Score.findOne({ pin: req.body.pin }); + const staffGuild = this.server.client.guilds.get('446067825673633794') || await this.server.client.getRESTGuild('446067825673633794'); + + if (!director || !staffGuild.members.get(director.userID)?.roles?.includes('662163685439045632')) { + return res.status(403).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + if (!req.params.id) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + if (!(await this.server.client.db.Resolution.exists({ oID: req.params.id }))) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + if (!req.body.subject && !req.body.body) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + + const resolution = await this.server.client.db.Resolution.findOne({ oID: req.params.id }); + await resolution.updateOne({ + subject: req.body.subject || resolution.subject, + body: req.body.body || resolution.body, + }); + + res.status(200).json({ message: `Updated Resolution with ID ${resolution.oID}.` }); + }); + + this.router.get('/eo', async (_req, res) => { + const executiveOrders = await this.server.client.db.ExecutiveOrder.find().lean(); + + + res.status(200).json({ executiveOrders }); + }); + + this.router.get('/motion', async (_req, res) => { + const motions = await this.server.client.db.Motion.find().lean(); + + res.status(200).json({ motions }); + }); + + this.router.get('/proc', async (_req: Request, res: Response) => { + const proclamations = await this.server.client.db.Proclamation.find().lean(); + + res.status(200).send({ proclamations }); + }); + + this.router.patch('/motion/:id', async (req, res) => { + if (!req.body.pin) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const director = await this.server.client.db.Score.findOne({ pin: req.body.pin }); + const staffGuild = this.server.client.guilds.get('446067825673633794') || await this.server.client.getRESTGuild('446067825673633794'); + + if (!director || !staffGuild.members.get(director.userID)?.roles?.includes('662163685439045632')) { + return res.status(403).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + if (!req.params.id) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + if (!(await this.server.client.db.Motion.exists({ oID: req.params.id }))) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + if (!req.body.subject && !req.body.body) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + + const motion = await this.server.client.db.Motion.findOne({ oID: req.params.id }); + await motion.updateOne({ + subject: req.body.subject || motion.subject, + body: req.body.body || motion.body, + }); + + res.status(200).json({ message: `Updated Motion with ID ${motion.oID}.` }); + }); + this.router.patch('/resolution/:id', async (req, res) => { if (!req.body.pin) { return res.status(401).json({ @@ -522,5 +790,11 @@ export default class Root extends Route { res.status(200).json({ resolutions }); }); + + this.router.get('/resolution', async (_req, res) => { + const resolutions = await this.server.client.db.Resolution.find().lean(); + + res.status(200).json({ resolutions }); + }); } } diff --git a/src/class/Client.ts b/src/class/Client.ts index 18c70de..c2d042a 100644 --- a/src/class/Client.ts +++ b/src/class/Client.ts @@ -1,147 +1,149 @@ -import Stripe from 'stripe'; -import eris from 'eris'; -import pluris from 'pluris'; -import mongoose from 'mongoose'; -import { promises as fs } from 'fs'; -import { Collection, Command, LocalStorage, Queue, Util, ServerManagement, Event } from '.'; -import { - Customer, CustomerInterface, - CustomerPortal, CustomerPortalInterface, - ExecutiveOrder, ExecutiveOrderInterface, - File, FileInterface, - Member, MemberInterface, - Merchant, MerchantInterface, - Moderation, ModerationInterface, - Motion, MotionInterface, - NNTrainingData, NNTrainingDataInterface, - Note, NoteInterface, - PagerNumber, PagerNumberInterface, - Promo, PromoInterface, - Rank, RankInterface, - Redirect, RedirectInterface, - Resolution, ResolutionInterface, - Score, ScoreInterface, - ScoreHistorical, ScoreHistoricalInterface, - Staff, StaffInterface, - Stat, StatInterface, -} from '../models'; -import { Config } from '../../types'; // eslint-disable-line - -pluris(eris); - -export default class Client extends eris.Client { - public config: Config; - - public commands: Collection; - - public events: Collection; - - public intervals: Collection; - - public util: Util; - - public serverManagement: ServerManagement; - - public queue: Queue; - - public stripe: Stripe; - - public db: { - Customer: mongoose.Model, - CustomerPortal: mongoose.Model, - ExecutiveOrder: mongoose.Model, - File: mongoose.Model, - Member: mongoose.Model, - Merchant: mongoose.Model, - Moderation: mongoose.Model, - Motion: mongoose.Model, - NNTrainingData: mongoose.Model, - Note: mongoose.Model, - PagerNumber: mongoose.Model, - Promo: mongoose.Model, - Rank: mongoose.Model, - Redirect: mongoose.Model, - Resolution: mongoose.Model, - Score: mongoose.Model, - ScoreHistorical: mongoose.Model, - Staff: mongoose.Model, - Stat: mongoose.Model, - local: { muted: LocalStorage } - }; - - constructor(token: string, options?: eris.ClientOptions) { - super(token, options); - this.commands = new Collection(); - this.events = new Collection(); - this.intervals = new Collection(); - this.queue = new Queue(this); - this.db = { Customer, CustomerPortal, ExecutiveOrder, File, Member, Merchant, Moderation, Motion, NNTrainingData, Note, PagerNumber, Promo, Rank, Redirect, Resolution, Score, ScoreHistorical, Staff, Stat, local: { muted: new LocalStorage('muted') } }; - } - - public async loadDatabase() { - await mongoose.connect(this.config.mongoDB, { useNewUrlParser: true, useUnifiedTopology: true, poolSize: 50 }); - - const statMessages = await this.db.Stat.findOne({ name: 'messages' }); - const statCommands = await this.db.Stat.findOne({ name: 'commands' }); - const statPages = await this.db.Stat.findOne({ name: 'pages' }); - const statRequests = await this.db.Stat.findOne({ name: 'requests' }); - - if (!statMessages) { - await (new this.db.Stat({ name: 'messages', value: 0 }).save()); - } - if (!statCommands) { - await (new this.db.Stat({ name: 'commands', value: 0 }).save()); - } - if (!statPages) { - await (new this.db.Stat({ name: 'pages', value: 0 }).save()); - } - if (!statRequests) { - await (new this.db.Stat({ name: 'requests', value: 0 }).save()); - } - } - - public loadPlugins() { - this.util = new Util(this); - this.serverManagement = new ServerManagement(this); - this.stripe = new Stripe(this.config.stripeKey, { apiVersion: null, typescript: true }); - } - - public async loadIntervals() { - const intervalFiles = await fs.readdir(`${__dirname}/../intervals`); - intervalFiles.forEach((file) => { - const intervalName = file.split('.')[0]; - if (file === 'index.js') return; - const interval: NodeJS.Timeout = (require(`${__dirname}/../intervals/${file}`).default)(this); - this.intervals.add(intervalName, interval); - this.util.signale.success(`Successfully loaded interval: ${intervalName}`); - }); - } - - public async loadEvents(eventFiles: { [s: string]: typeof Event; } | ArrayLike) { - const evtFiles = Object.entries(eventFiles); - for (const [name, Ev] of evtFiles) { - const event = new Ev(this); - this.events.add(event.event, event); - this.on(event.event, event.run); - this.util.signale.success(`Successfully loaded event: ${name}`); - delete require.cache[require.resolve(`${__dirname}/../events/${name}`)]; - } - } - - public async loadCommands(commandFiles: { [s: string]: typeof Command; } | ArrayLike) { - const cmdFiles = Object.values(commandFiles); - for (const Cmd of cmdFiles) { - const command = new Cmd(this); - if (command.subcmds.length) { - command.subcmds.forEach((C) => { - const cmd: Command = new C(this); - command.subcommands.add(cmd.name, cmd); - this.util.signale.success(`Successfully loaded subcommand ${cmd.name} under ${command.name}`); - }); - } - delete command.subcmds; - this.commands.add(command.name, command); - this.util.signale.success(`Successfully loaded command: ${command.name}`); - } - } -} +import Stripe from 'stripe'; +import eris from 'eris'; +import pluris from 'pluris'; +import mongoose from 'mongoose'; +import { promises as fs } from 'fs'; +import { Collection, Command, LocalStorage, Queue, Util, ServerManagement, Event } from '.'; +import { + Customer, CustomerInterface, + CustomerPortal, CustomerPortalInterface, + ExecutiveOrder, ExecutiveOrderInterface, + File, FileInterface, + Member, MemberInterface, + Merchant, MerchantInterface, + Moderation, ModerationInterface, + Motion, MotionInterface, + NNTrainingData, NNTrainingDataInterface, + Note, NoteInterface, + PagerNumber, PagerNumberInterface, + Proclamation, ProclamationInterface, + Promo, PromoInterface, + Rank, RankInterface, + Redirect, RedirectInterface, + Resolution, ResolutionInterface, + Score, ScoreInterface, + ScoreHistorical, ScoreHistoricalInterface, + Staff, StaffInterface, + Stat, StatInterface, +} from '../models'; +import { Config } from '../../types'; // eslint-disable-line + +pluris(eris); + +export default class Client extends eris.Client { + public config: Config; + + public commands: Collection; + + public events: Collection; + + public intervals: Collection; + + public util: Util; + + public serverManagement: ServerManagement; + + public queue: Queue; + + public stripe: Stripe; + + public db: { + Customer: mongoose.Model, + CustomerPortal: mongoose.Model, + ExecutiveOrder: mongoose.Model, + File: mongoose.Model, + Member: mongoose.Model, + Merchant: mongoose.Model, + Moderation: mongoose.Model, + Motion: mongoose.Model, + NNTrainingData: mongoose.Model, + Note: mongoose.Model, + PagerNumber: mongoose.Model, + Proclamation: mongoose.Model, + Promo: mongoose.Model, + Rank: mongoose.Model, + Redirect: mongoose.Model, + Resolution: mongoose.Model, + Score: mongoose.Model, + ScoreHistorical: mongoose.Model, + Staff: mongoose.Model, + Stat: mongoose.Model, + local: { muted: LocalStorage } + }; + + constructor(token: string, options?: eris.ClientOptions) { + super(token, options); + this.commands = new Collection(); + this.events = new Collection(); + this.intervals = new Collection(); + this.queue = new Queue(this); + this.db = { Customer, CustomerPortal, ExecutiveOrder, File, Member, Merchant, Moderation, Motion, NNTrainingData, Note, PagerNumber, Proclamation, Promo, Rank, Redirect, Resolution, Score, ScoreHistorical, Staff, Stat, local: { muted: new LocalStorage('muted') } }; + } + + public async loadDatabase() { + await mongoose.connect(this.config.mongoDB, { useNewUrlParser: true, useUnifiedTopology: true, poolSize: 50 }); + + const statMessages = await this.db.Stat.findOne({ name: 'messages' }); + const statCommands = await this.db.Stat.findOne({ name: 'commands' }); + const statPages = await this.db.Stat.findOne({ name: 'pages' }); + const statRequests = await this.db.Stat.findOne({ name: 'requests' }); + + if (!statMessages) { + await (new this.db.Stat({ name: 'messages', value: 0 }).save()); + } + if (!statCommands) { + await (new this.db.Stat({ name: 'commands', value: 0 }).save()); + } + if (!statPages) { + await (new this.db.Stat({ name: 'pages', value: 0 }).save()); + } + if (!statRequests) { + await (new this.db.Stat({ name: 'requests', value: 0 }).save()); + } + } + + public loadPlugins() { + this.util = new Util(this); + this.serverManagement = new ServerManagement(this); + this.stripe = new Stripe(this.config.stripeKey, { apiVersion: null, typescript: true }); + } + + public async loadIntervals() { + const intervalFiles = await fs.readdir(`${__dirname}/../intervals`); + intervalFiles.forEach((file) => { + const intervalName = file.split('.')[0]; + if (file === 'index.js') return; + const interval: NodeJS.Timeout = (require(`${__dirname}/../intervals/${file}`).default)(this); + this.intervals.add(intervalName, interval); + this.util.signale.success(`Successfully loaded interval: ${intervalName}`); + }); + } + + public async loadEvents(eventFiles: { [s: string]: typeof Event; } | ArrayLike) { + const evtFiles = Object.entries(eventFiles); + for (const [name, Ev] of evtFiles) { + const event = new Ev(this); + this.events.add(event.event, event); + this.on(event.event, event.run); + this.util.signale.success(`Successfully loaded event: ${name}`); + delete require.cache[require.resolve(`${__dirname}/../events/${name}`)]; + } + } + + public async loadCommands(commandFiles: { [s: string]: typeof Command; } | ArrayLike) { + const cmdFiles = Object.values(commandFiles); + for (const Cmd of cmdFiles) { + const command = new Cmd(this); + if (command.subcmds.length) { + command.subcmds.forEach((C) => { + const cmd: Command = new C(this); + command.subcommands.add(cmd.name, cmd); + this.util.signale.success(`Successfully loaded subcommand ${cmd.name} under ${command.name}`); + }); + } + delete command.subcmds; + this.commands.add(command.name, command); + this.util.signale.success(`Successfully loaded command: ${command.name}`); + } + } +} diff --git a/src/models/Proclamation.ts b/src/models/Proclamation.ts new file mode 100644 index 0000000..df3cc6b --- /dev/null +++ b/src/models/Proclamation.ts @@ -0,0 +1,29 @@ +import { Document, model, Schema } from 'mongoose'; + +export interface ProclamationInterface extends Document { + issuedBy: string; + subject: string; + body: string; + at: Date; + oID: string; + voteResults: { + yea: number; + nay: number; + }; + acceptedAt: number; +} + +const Proclamation = new Schema({ + issuedBy: { type: String, required: true }, + subject: { type: String, required: true }, + body: { type: String, required: true }, + at: { type: Date, required: true }, + oID: { type: String, required: true, unique: true }, + voteResults: { + yea: Number, + nay: Number, + }, + acceptedAt: Number, +}); + +export default model('Proclamations', Proclamation); diff --git a/src/models/index.ts b/src/models/index.ts index 568c71f..0626279 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,19 +1,20 @@ -export { default as Customer, CustomerInterface } from './Customer'; -export { default as CustomerPortal, CustomerPortalInterface } from './CustomerPortal'; -export { default as ExecutiveOrder, ExecutiveOrderInterface } from './ExecutiveOrder'; -export { default as File, FileInterface } from './File'; -export { default as Member, MemberInterface } from './Member'; -export { default as Merchant, MerchantInterface } from './Merchant'; -export { default as Moderation, ModerationInterface } from './Moderation'; -export { default as Motion, MotionInterface } from './Motion'; -export { default as NNTrainingData, NNTrainingDataInterface } from './NNTrainingData'; -export { default as Note, NoteInterface } from './Note'; -export { default as PagerNumber, PagerNumberInterface, PagerNumberRaw } from './PagerNumber'; -export { default as Promo, PromoInterface } from './Promo'; -export { default as Rank, RankInterface } from './Rank'; -export { default as Redirect, RedirectInterface, RedirectRaw } from './Redirect'; -export { default as Resolution, ResolutionInterface } from './Resolution'; -export { default as Score, ScoreInterface, ScoreInterfaceRaw } from './Score'; -export { default as ScoreHistorical, ScoreHistoricalInterface } from './ScoreHistorical'; -export { default as Staff, StaffInterface } from './Staff'; -export { default as Stat, StatInterface } from './Stat'; +export { default as Customer, CustomerInterface } from './Customer'; +export { default as CustomerPortal, CustomerPortalInterface } from './CustomerPortal'; +export { default as ExecutiveOrder, ExecutiveOrderInterface } from './ExecutiveOrder'; +export { default as File, FileInterface } from './File'; +export { default as Member, MemberInterface } from './Member'; +export { default as Merchant, MerchantInterface } from './Merchant'; +export { default as Moderation, ModerationInterface } from './Moderation'; +export { default as Motion, MotionInterface } from './Motion'; +export { default as NNTrainingData, NNTrainingDataInterface } from './NNTrainingData'; +export { default as Note, NoteInterface } from './Note'; +export { default as PagerNumber, PagerNumberInterface, PagerNumberRaw } from './PagerNumber'; +export { default as Proclamation, ProclamationInterface } from './Proclamation' +export { default as Promo, PromoInterface } from './Promo'; +export { default as Rank, RankInterface } from './Rank'; +export { default as Redirect, RedirectInterface, RedirectRaw } from './Redirect'; +export { default as Resolution, ResolutionInterface } from './Resolution'; +export { default as Score, ScoreInterface, ScoreInterfaceRaw } from './Score'; +export { default as ScoreHistorical, ScoreHistoricalInterface } from './ScoreHistorical'; +export { default as Staff, StaffInterface } from './Staff'; +export { default as Stat, StatInterface } from './Stat';