Merge branch 'Sterben/communityrelations-master' into master

merge-requests/17/head
Hiroyuki 2021-02-28 12:48:56 -04:00
commit ab841a792c
No known key found for this signature in database
GPG Key ID: C15AC26538975A24
4 changed files with 472 additions and 166 deletions

View File

@ -1,5 +1,6 @@
import { TextChannel } from 'eris'; import { TextChannel } from 'eris';
import { v4 as genUUID } from 'uuid'; import { v4 as genUUID } from 'uuid';
import { Request, Response } from 'express';
import { RichEmbed, Route, Server } from '../../../class'; import { RichEmbed, Route, Server } from '../../../class';
export default class Root extends Route { 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 = <TextChannel>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) => { this.router.post('/resolution', async (req, res) => {
if (!req.body.pin) { if (!req.body.pin) {
return res.status(401).json({ 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.` }); 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) => { this.router.get('/eo/:id', async (req, res) => {
if (!req.params.id) { if (!req.params.id) {
return res.status(400).json({ 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) => { this.router.patch('/eo/:id', async (req, res) => {
if (!req.body.pin) { if (!req.body.pin) {
return res.status(401).json({ 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}.` }); 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) => { this.router.patch('/resolution/:id', async (req, res) => {
if (!req.body.pin) { if (!req.body.pin) {
return res.status(401).json({ return res.status(401).json({
@ -522,5 +790,11 @@ export default class Root extends Route {
res.status(200).json({ resolutions }); 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 });
});
} }
} }

View File

@ -1,147 +1,149 @@
import Stripe from 'stripe'; import Stripe from 'stripe';
import eris from 'eris'; import eris from 'eris';
import pluris from 'pluris'; import pluris from 'pluris';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { Collection, Command, LocalStorage, Queue, Util, ServerManagement, Event } from '.'; import { Collection, Command, LocalStorage, Queue, Util, ServerManagement, Event } from '.';
import { import {
Customer, CustomerInterface, Customer, CustomerInterface,
CustomerPortal, CustomerPortalInterface, CustomerPortal, CustomerPortalInterface,
ExecutiveOrder, ExecutiveOrderInterface, ExecutiveOrder, ExecutiveOrderInterface,
File, FileInterface, File, FileInterface,
Member, MemberInterface, Member, MemberInterface,
Merchant, MerchantInterface, Merchant, MerchantInterface,
Moderation, ModerationInterface, Moderation, ModerationInterface,
Motion, MotionInterface, Motion, MotionInterface,
NNTrainingData, NNTrainingDataInterface, NNTrainingData, NNTrainingDataInterface,
Note, NoteInterface, Note, NoteInterface,
PagerNumber, PagerNumberInterface, PagerNumber, PagerNumberInterface,
Promo, PromoInterface, Proclamation, ProclamationInterface,
Rank, RankInterface, Promo, PromoInterface,
Redirect, RedirectInterface, Rank, RankInterface,
Resolution, ResolutionInterface, Redirect, RedirectInterface,
Score, ScoreInterface, Resolution, ResolutionInterface,
ScoreHistorical, ScoreHistoricalInterface, Score, ScoreInterface,
Staff, StaffInterface, ScoreHistorical, ScoreHistoricalInterface,
Stat, StatInterface, Staff, StaffInterface,
} from '../models'; Stat, StatInterface,
import { Config } from '../../types'; // eslint-disable-line } from '../models';
import { Config } from '../../types'; // eslint-disable-line
pluris(eris);
pluris(eris);
export default class Client extends eris.Client {
public config: Config; export default class Client extends eris.Client {
public config: Config;
public commands: Collection<Command>;
public commands: Collection<Command>;
public events: Collection<Event>;
public events: Collection<Event>;
public intervals: Collection<NodeJS.Timeout>;
public intervals: Collection<NodeJS.Timeout>;
public util: Util;
public util: Util;
public serverManagement: ServerManagement;
public serverManagement: ServerManagement;
public queue: Queue;
public queue: Queue;
public stripe: Stripe;
public stripe: Stripe;
public db: {
Customer: mongoose.Model<CustomerInterface>, public db: {
CustomerPortal: mongoose.Model<CustomerPortalInterface>, Customer: mongoose.Model<CustomerInterface>,
ExecutiveOrder: mongoose.Model<ExecutiveOrderInterface>, CustomerPortal: mongoose.Model<CustomerPortalInterface>,
File: mongoose.Model<FileInterface>, ExecutiveOrder: mongoose.Model<ExecutiveOrderInterface>,
Member: mongoose.Model<MemberInterface>, File: mongoose.Model<FileInterface>,
Merchant: mongoose.Model<MerchantInterface>, Member: mongoose.Model<MemberInterface>,
Moderation: mongoose.Model<ModerationInterface>, Merchant: mongoose.Model<MerchantInterface>,
Motion: mongoose.Model<MotionInterface>, Moderation: mongoose.Model<ModerationInterface>,
NNTrainingData: mongoose.Model<NNTrainingDataInterface>, Motion: mongoose.Model<MotionInterface>,
Note: mongoose.Model<NoteInterface>, NNTrainingData: mongoose.Model<NNTrainingDataInterface>,
PagerNumber: mongoose.Model<PagerNumberInterface>, Note: mongoose.Model<NoteInterface>,
Promo: mongoose.Model<PromoInterface>, PagerNumber: mongoose.Model<PagerNumberInterface>,
Rank: mongoose.Model<RankInterface>, Proclamation: mongoose.Model<ProclamationInterface>,
Redirect: mongoose.Model<RedirectInterface>, Promo: mongoose.Model<PromoInterface>,
Resolution: mongoose.Model<ResolutionInterface>, Rank: mongoose.Model<RankInterface>,
Score: mongoose.Model<ScoreInterface>, Redirect: mongoose.Model<RedirectInterface>,
ScoreHistorical: mongoose.Model<ScoreHistoricalInterface>, Resolution: mongoose.Model<ResolutionInterface>,
Staff: mongoose.Model<StaffInterface>, Score: mongoose.Model<ScoreInterface>,
Stat: mongoose.Model<StatInterface>, ScoreHistorical: mongoose.Model<ScoreHistoricalInterface>,
local: { muted: LocalStorage } Staff: mongoose.Model<StaffInterface>,
}; Stat: mongoose.Model<StatInterface>,
local: { muted: LocalStorage }
constructor(token: string, options?: eris.ClientOptions) { };
super(token, options);
this.commands = new Collection<Command>(); constructor(token: string, options?: eris.ClientOptions) {
this.events = new Collection<Event>(); super(token, options);
this.intervals = new Collection<NodeJS.Timeout>(); this.commands = new Collection<Command>();
this.queue = new Queue(this); this.events = new Collection<Event>();
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') } }; this.intervals = new Collection<NodeJS.Timeout>();
} 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 });
public async loadDatabase() {
const statMessages = await this.db.Stat.findOne({ name: 'messages' }); await mongoose.connect(this.config.mongoDB, { useNewUrlParser: true, useUnifiedTopology: true, poolSize: 50 });
const statCommands = await this.db.Stat.findOne({ name: 'commands' });
const statPages = await this.db.Stat.findOne({ name: 'pages' }); const statMessages = await this.db.Stat.findOne({ name: 'messages' });
const statRequests = await this.db.Stat.findOne({ name: 'requests' }); const statCommands = await this.db.Stat.findOne({ name: 'commands' });
const statPages = await this.db.Stat.findOne({ name: 'pages' });
if (!statMessages) { const statRequests = await this.db.Stat.findOne({ name: 'requests' });
await (new this.db.Stat({ name: 'messages', value: 0 }).save());
} if (!statMessages) {
if (!statCommands) { await (new this.db.Stat({ name: 'messages', value: 0 }).save());
await (new this.db.Stat({ name: 'commands', value: 0 }).save()); }
} if (!statCommands) {
if (!statPages) { await (new this.db.Stat({ name: 'commands', value: 0 }).save());
await (new this.db.Stat({ name: 'pages', value: 0 }).save()); }
} if (!statPages) {
if (!statRequests) { await (new this.db.Stat({ name: 'pages', value: 0 }).save());
await (new this.db.Stat({ name: 'requests', 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); public loadPlugins() {
this.stripe = new Stripe(this.config.stripeKey, { apiVersion: null, typescript: true }); 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) => { public async loadIntervals() {
const intervalName = file.split('.')[0]; const intervalFiles = await fs.readdir(`${__dirname}/../intervals`);
if (file === 'index.js') return; intervalFiles.forEach((file) => {
const interval: NodeJS.Timeout = (require(`${__dirname}/../intervals/${file}`).default)(this); const intervalName = file.split('.')[0];
this.intervals.add(intervalName, interval); if (file === 'index.js') return;
this.util.signale.success(`Successfully loaded interval: ${intervalName}`); 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<typeof Event>) { }
const evtFiles = Object.entries<typeof Event>(eventFiles);
for (const [name, Ev] of evtFiles) { public async loadEvents(eventFiles: { [s: string]: typeof Event; } | ArrayLike<typeof Event>) {
const event = new Ev(this); const evtFiles = Object.entries<typeof Event>(eventFiles);
this.events.add(event.event, event); for (const [name, Ev] of evtFiles) {
this.on(event.event, event.run); const event = new Ev(this);
this.util.signale.success(`Successfully loaded event: ${name}`); this.events.add(event.event, event);
delete require.cache[require.resolve(`${__dirname}/../events/${name}`)]; 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<typeof Command>) { }
const cmdFiles = Object.values<typeof Command>(commandFiles);
for (const Cmd of cmdFiles) { public async loadCommands(commandFiles: { [s: string]: typeof Command; } | ArrayLike<typeof Command>) {
const command = new Cmd(this); const cmdFiles = Object.values<typeof Command>(commandFiles);
if (command.subcmds.length) { for (const Cmd of cmdFiles) {
command.subcmds.forEach((C) => { const command = new Cmd(this);
const cmd: Command = new C(this); if (command.subcmds.length) {
command.subcommands.add(cmd.name, cmd); command.subcmds.forEach((C) => {
this.util.signale.success(`Successfully loaded subcommand ${cmd.name} under ${command.name}`); 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}`); delete command.subcmds;
} this.commands.add(command.name, command);
} this.util.signale.success(`Successfully loaded command: ${command.name}`);
} }
}
}

View File

@ -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<ProclamationInterface>('Proclamations', Proclamation);

View File

@ -1,19 +1,20 @@
export { default as Customer, CustomerInterface } from './Customer'; export { default as Customer, CustomerInterface } from './Customer';
export { default as CustomerPortal, CustomerPortalInterface } from './CustomerPortal'; export { default as CustomerPortal, CustomerPortalInterface } from './CustomerPortal';
export { default as ExecutiveOrder, ExecutiveOrderInterface } from './ExecutiveOrder'; export { default as ExecutiveOrder, ExecutiveOrderInterface } from './ExecutiveOrder';
export { default as File, FileInterface } from './File'; export { default as File, FileInterface } from './File';
export { default as Member, MemberInterface } from './Member'; export { default as Member, MemberInterface } from './Member';
export { default as Merchant, MerchantInterface } from './Merchant'; export { default as Merchant, MerchantInterface } from './Merchant';
export { default as Moderation, ModerationInterface } from './Moderation'; export { default as Moderation, ModerationInterface } from './Moderation';
export { default as Motion, MotionInterface } from './Motion'; export { default as Motion, MotionInterface } from './Motion';
export { default as NNTrainingData, NNTrainingDataInterface } from './NNTrainingData'; export { default as NNTrainingData, NNTrainingDataInterface } from './NNTrainingData';
export { default as Note, NoteInterface } from './Note'; export { default as Note, NoteInterface } from './Note';
export { default as PagerNumber, PagerNumberInterface, PagerNumberRaw } from './PagerNumber'; export { default as PagerNumber, PagerNumberInterface, PagerNumberRaw } from './PagerNumber';
export { default as Promo, PromoInterface } from './Promo'; export { default as Proclamation, ProclamationInterface } from './Proclamation'
export { default as Rank, RankInterface } from './Rank'; export { default as Promo, PromoInterface } from './Promo';
export { default as Redirect, RedirectInterface, RedirectRaw } from './Redirect'; export { default as Rank, RankInterface } from './Rank';
export { default as Resolution, ResolutionInterface } from './Resolution'; export { default as Redirect, RedirectInterface, RedirectRaw } from './Redirect';
export { default as Score, ScoreInterface, ScoreInterfaceRaw } from './Score'; export { default as Resolution, ResolutionInterface } from './Resolution';
export { default as ScoreHistorical, ScoreHistoricalInterface } from './ScoreHistorical'; export { default as Score, ScoreInterface, ScoreInterfaceRaw } from './Score';
export { default as Staff, StaffInterface } from './Staff'; export { default as ScoreHistorical, ScoreHistoricalInterface } from './ScoreHistorical';
export { default as Stat, StatInterface } from './Stat'; export { default as Staff, StaffInterface } from './Staff';
export { default as Stat, StatInterface } from './Stat';