refactor(models): use typegoose

refactor/models
Hiroyuki 2021-07-08 20:50:04 -04:00
parent a3d15b231c
commit b9d4a28c4f
No known key found for this signature in database
GPG Key ID: C15AC26538975A24
17 changed files with 217 additions and 173 deletions

View File

@ -39,6 +39,7 @@
"import/prefer-default-export": "off", "import/prefer-default-export": "off",
"no-useless-constructor": "off", "no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": 2, "@typescript-eslint/no-useless-constructor": 2,
"import/extensions": "off" "import/extensions": "off",
"max-classes-per-file": "off"
} }
} }

27
.vscode/settings.json vendored
View File

@ -1,10 +1,21 @@
{ {
"eslint.enable": true, "files.autoSave": "onFocusChange",
"eslint.validate": [ "editor.tabSize": 2,
{ "editor.formatOnPaste": true,
"language": "typescript", "editor.formatOnSave": true,
"autoFix": true "editor.formatOnType": true,
} "cSpell.language": "en-gb",
], "editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.tabSize": 2 "files.enableTrash": false,
"eslint.validate": ["typescript"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"typescript.preferences.importModuleSpecifier": "relative"
} }

View File

@ -26,6 +26,7 @@
"mongoose": "^5.7.4", "mongoose": "^5.7.4",
"nodemailer": "^6.3.1", "nodemailer": "^6.3.1",
"signale": "^1.4.0", "signale": "^1.4.0",
"@typegoose/typegoose": "^7.6.2",
"uuid": "^3.3.3", "uuid": "^3.3.3",
"x509": "bsian03/node-x509" "x509": "bsian03/node-x509"
}, },

View File

@ -1,6 +1,6 @@
import express from 'express'; import express from 'express';
import { AccountInterface } from '../models'; import { Account } from '../models';
export interface Req extends express.Request { export interface Req extends express.Request {
account: AccountInterface account: Account
} }

View File

@ -1,7 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import moment from 'moment'; import moment from 'moment';
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
import { AccountInterface } from '../models';
import { Client } from '..'; import { Client } from '..';
export default class AccountUtil { export default class AccountUtil {
@ -19,7 +18,7 @@ export default class AccountUtil {
* @param data.emailAddress The user's email address. * @param data.emailAddress The user's email address.
* @param moderator The Discord user ID for the Staff member that created the account. * @param moderator The Discord user ID for the Staff member that created the account.
*/ */
public async createAccount(data: { userID: string, username: string, emailAddress: string }, moderator: string): Promise<{ account: AccountInterface, tempPass: string }> { public async createAccount(data: { userID: string, username: string, emailAddress: string }, moderator: string) {
const moderatorMember = this.client.guilds.get('446067825673633794').members.get(moderator); const moderatorMember = this.client.guilds.get('446067825673633794').members.get(moderator);
const tempPass = this.client.util.randomPassword(); const tempPass = this.client.util.randomPassword();
let passHash = await this.client.util.createHash(tempPass); passHash = passHash.replace(/[$]/g, '\\$').replace('\n', ''); let passHash = await this.client.util.createHash(tempPass); passHash = passHash.replace(/[$]/g, '\\$').replace('\n', '');

View File

@ -3,10 +3,11 @@ import Redis from 'ioredis';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import signale from 'signale'; import signale from 'signale';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { getModelForClass } from '@typegoose/typegoose';
import config from '../config.json'; import config from '../config.json';
import { Account, AccountInterface, Moderation, ModerationInterface, Domain, DomainInterface, Tier, TierInterface } from '../models'; import { Account, Moderation, Domain, Tier } from '../models';
import { emojis } from '../stores'; import { emojis } from '../stores';
import { Command, CSCLI, Util, Collection, Server, Event } from '.'; import { Command, Util, Collection, Server, Event } from '.';
export default class Client extends Eris.Client { export default class Client extends Eris.Client {
@ -18,7 +19,12 @@ export default class Client extends Eris.Client {
public events: Collection<Event>; public events: Collection<Event>;
public db: { Account: mongoose.Model<AccountInterface>; Domain: mongoose.Model<DomainInterface>; Moderation: mongoose.Model<ModerationInterface>; Tier: mongoose.Model<TierInterface>; }; public db = {
Account: getModelForClass(Account),
Domain: getModelForClass(Domain),
Moderation: getModelForClass(Moderation),
Tier: getModelForClass(Tier),
}
public redis: Redis.Redis; public redis: Redis.Redis;
@ -43,7 +49,6 @@ export default class Client extends Eris.Client {
this.commands = new Collection<Command>(); this.commands = new Collection<Command>();
this.events = new Collection<Event>(); this.events = new Collection<Event>();
this.functions = new Collection<Function>(); this.functions = new Collection<Function>();
this.db = { Account, Domain, Moderation, Tier };
this.redis = new Redis(); this.redis = new Redis();
this.stores = { emojis }; this.stores = { emojis };
this.signale = signale; this.signale = signale;

View File

@ -2,7 +2,6 @@
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { Request } from 'express'; import { Request } from 'express';
import { Client } from '.'; import { Client } from '.';
import { AccountInterface } from '../models';
export default class Security { export default class Security {
public client: Client; public client: Client;
@ -36,7 +35,7 @@ export default class Security {
* If the bearer token is valid, will return the Account, else will return null. * If the bearer token is valid, will return the Account, else will return null.
* @param bearer The bearer token provided. * @param bearer The bearer token provided.
*/ */
public async checkBearer(bearer: string): Promise<null | AccountInterface> { public async checkBearer(bearer: string) {
try { try {
const res: any = jwt.verify(bearer, this.keys.key, { issuer: 'Library of Code sp-us | CSD' }); const res: any = jwt.verify(bearer, this.keys.key, { issuer: 'Library of Code sp-us | CSD' });
const account = await this.client.db.Account.findOne({ _id: res.id }); const account = await this.client.db.Account.findOne({ _id: res.id });

View File

@ -10,7 +10,7 @@ import moment from 'moment';
import fs from 'fs'; import fs from 'fs';
import { getUserByUid } from '../functions'; import { getUserByUid } from '../functions';
import { AccountUtil, Client, Command, RichEmbed } from '.'; import { AccountUtil, Client, Command, RichEmbed } from '.';
import { ModerationInterface, AccountInterface, Account } from '../models'; import { Account, Moderation } from '../models';
export default class Util { export default class Util {
public client: Client; public client: Client;
@ -198,7 +198,7 @@ export default class Util {
return tempPass; return tempPass;
} }
public async createAccount(hash: string, etcPasswd: string, username: string, userID: string, emailAddress: string, moderatorID: string, code: string): Promise<AccountInterface> { public async createAccount(hash: string, etcPasswd: string, username: string, userID: string, emailAddress: string, moderatorID: string, code: string) {
await this.exec(`useradd -m -p ${hash} -c ${etcPasswd} -s /bin/bash ${username}`); await this.exec(`useradd -m -p ${hash} -c ${etcPasswd} -s /bin/bash ${username}`);
await this.exec(`chage -d0 ${username}`); await this.exec(`chage -d0 ${username}`);
const tier = await this.client.db.Tier.findOne({ id: 1 }); const tier = await this.client.db.Tier.findOne({ id: 1 });
@ -250,7 +250,7 @@ export default class Util {
* *
* `4` - Delete * `4` - Delete
*/ */
public async createModerationLog(user: string, moderator: Member|User, type: number, reason?: string, duration?: number): Promise<ModerationInterface> { public async createModerationLog(user: string, moderator: Member | User, type: number, reason?: string, duration?: number) {
const moderatorID = moderator.id; const moderatorID = moderator.id;
const account = await this.client.db.Account.findOne({ $or: [{ username: user }, { userID: user }] }); const account = await this.client.db.Account.findOne({ $or: [{ username: user }, { userID: user }] });
if (!account) return Promise.reject(new Error(`Account ${user} not found`)); if (!account) return Promise.reject(new Error(`Account ${user} not found`));

View File

@ -2,7 +2,7 @@ import fs, { writeFile, unlink } from 'fs-extra';
import axios from 'axios'; import axios from 'axios';
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
import { Message } from 'eris'; import { Message } from 'eris';
import { AccountInterface } from '../models'; import { Account } from '../models';
import { Client, Command, RichEmbed } from '../class'; import { Client, Command, RichEmbed } from '../class';
import { parseCertificate } from '../functions'; import { parseCertificate } from '../functions';
@ -144,7 +144,7 @@ export default class CWG_Create extends Command {
* @param x509Certificate The contents the certificate and key files. * @param x509Certificate The contents the certificate and key files.
* @example await CWG.createDomain(account, 'mydomain.cloud.libraryofcode.org', 6781); * @example await CWG.createDomain(account, 'mydomain.cloud.libraryofcode.org', 6781);
*/ */
public async createDomain(account: AccountInterface, domain: string, port: number, x509Certificate: { cert?: string, key?: string }) { public async createDomain(account: Account, domain: string, port: number, x509Certificate: { cert?: string, key?: string }) {
try { try {
if (port <= 1024 || port >= 65535) throw new RangeError(`Port range must be between 1024 and 65535, received ${port}.`); if (port <= 1024 || port >= 65535) throw new RangeError(`Port range must be between 1024 and 65535, received ${port}.`);
if (await this.client.db.Domain.exists({ domain })) throw new Error(`Domain ${domain} already exists in the database.`); if (await this.client.db.Domain.exists({ domain })) throw new Error(`Domain ${domain} already exists in the database.`);

View File

@ -1,8 +1,8 @@
import moment from 'moment'; import moment from 'moment';
import { Message, GuildTextableChannel, Member, Role } from 'eris'; import { Message, GuildTextableChannel, Member, Role } from 'eris';
import { Client, Command, Report, RichEmbed } from '../class'; import { Client, Command, RichEmbed } from '../class';
import { dataConversion } from '../functions'; import { dataConversion } from '../functions';
import { AccountInterface } from '../models'; import { Account } from '../models';
export default class Whois extends Command { export default class Whois extends Command {
constructor(client: Client) { constructor(client: Client) {
@ -21,7 +21,7 @@ export default class Whois extends Command {
public async run(message: Message<GuildTextableChannel>, args: string[]) { public async run(message: Message<GuildTextableChannel>, args: string[]) {
try { try {
let full = false; let full = false;
let account: AccountInterface; let account: Account;
if (args[1] === '--full' && this.fullRoles.some((r) => message.member.roles.includes(r) || message.author.id === '554168666938277889')) full = true; if (args[1] === '--full' && this.fullRoles.some((r) => message.member.roles.includes(r) || message.author.id === '554168666938277889')) full = true;
const user = args[0] || message.author.id; const user = args[0] || message.author.id;
@ -68,7 +68,7 @@ export default class Whois extends Command {
} }
} }
public async full(account: AccountInterface, embed: RichEmbed, member: Member) { public async full(account: Account, embed: RichEmbed, member: Member) {
const [cpuUsage, data, fingerInformation, chage, memory] = await Promise.all([ const [cpuUsage, data, fingerInformation, chage, memory] = await Promise.all([
this.client.util.exec(`top -b -n 1 -u ${account.username} | awk 'NR>7 { sum += $9; } END { print sum; }'`), this.client.util.exec(`top -b -n 1 -u ${account.username} | awk 'NR>7 { sum += $9; } END { print sum; }'`),
this.client.redis.get(`storage-${account.username}`), this.client.redis.get(`storage-${account.username}`),
@ -92,7 +92,7 @@ export default class Whois extends Command {
embed.addField('Storage', data ? dataConversion(Number(data)) : 'N/A', true); embed.addField('Storage', data ? dataConversion(Number(data)) : 'N/A', true);
} }
public async default(account: AccountInterface, embed: RichEmbed) { public async default(account: Account, embed: RichEmbed) {
const [cpuUsage, data, memory] = await Promise.all([ const [cpuUsage, data, memory] = await Promise.all([
this.client.util.exec(`top -b -n 1 -u ${account.username} | awk 'NR>7 { sum += $9; } END { print sum; }'`), this.client.util.exec(`top -b -n 1 -u ${account.username} | awk 'NR>7 { sum += $9; } END { print sum; }'`),
this.client.redis.get(`storage-${account.username}`), this.client.redis.get(`storage-${account.username}`),

View File

@ -2,7 +2,6 @@
/* eslint-disable no-continue */ /* eslint-disable no-continue */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import { Client, RichEmbed } from '../class'; import { Client, RichEmbed } from '../class';
import { Tiers } from '../models';
const channelID = '691824484230889546'; const channelID = '691824484230889546';
@ -18,8 +17,7 @@ export default function memory(client: Client) {
// memory in megabytes // memory in megabytes
const memoryConversion = mem / 1024 / 1024; const memoryConversion = mem / 1024 / 1024;
const userLimits: { soft?: number, hard?: number } = {}; const userLimits: { soft?: number, hard?: number } = {};
// @ts-ignore const tier = await client.db.Tier.findOne({ id: acc.tier }).lean().exec();
const tier: Tiers = await client.db.Tier.findOne({ id: acc.tier }).lean().exec();
userLimits.soft = acc.ramLimitNotification; userLimits.soft = acc.ramLimitNotification;
userLimits.hard = tier.resourceLimits.ram; userLimits.hard = tier.resourceLimits.ram;
if ((memoryConversion <= userLimits.soft) && (acc.ramLimitNotification !== 0)) { if ((memoryConversion <= userLimits.soft) && (acc.ramLimitNotification !== 0)) {

View File

@ -1,53 +1,72 @@
import { Document, Schema, model } from 'mongoose'; import { modelOptions, prop } from '@typegoose/typegoose';
import { Base } from '@typegoose/typegoose/lib/defaultClasses';
export interface AccountInterface extends Document { export type Tier = 1 | 2 | 3;
username: string,
userID: string, class Permissions {
homepath: string, @prop()
emailAddress: string, staff?: boolean;
createdBy: string,
createdAt: Date, @prop()
locked: boolean, technician?: boolean;
tier: number,
supportKey: string, @prop()
referralCode: string, director?: boolean;
totalReferrals: number,
permissions: {
staff: boolean,
technician: boolean,
director: boolean,
},
ramLimitNotification: number,
root: boolean,
hash: boolean,
salt: string,
authTag: Buffer
revokedBearers: string[],
} }
const Account = new Schema<AccountInterface>({ @modelOptions({ schemaOptions: { collection: 'Account' } })
username: String, export default class Account extends Base {
userID: String, @prop({ required: true, unique: true })
homepath: String, username: string;
emailAddress: String,
createdBy: String,
createdAt: Date,
locked: Boolean,
tier: Number,
supportKey: String,
referralCode: String,
totalReferrals: Number,
permissions: {
staff: Boolean,
technician: Boolean,
director: Boolean,
},
ramLimitNotification: Number,
root: Boolean,
hash: Boolean,
salt: String,
authTag: Buffer,
revokedBearers: Array,
});
export default model<AccountInterface>('Account', Account); @prop({ required: true, unique: true })
userID: string;
@prop({ required: true, unique: true })
homepath: string;
@prop({ required: true })
emailAddress: string;
@prop({ required: true })
createdBy: string;
@prop({ required: true })
createdAt: Date;
@prop({ required: true, default: false })
locked: boolean;
@prop({ required: true, default: 1 })
tier: Tier;
@prop({ required: true })
supportKey: string;
@prop({ required: true, unique: true })
referralCode: string;
@prop({ required: true, default: 0 })
totalReferrals: number;
@prop()
permissions?: Permissions;
@prop()
ramLimitNotification: number;
@prop()
root: boolean;
@prop()
hash: boolean;
@prop()
salt: string;
@prop()
authTag: Buffer;
@prop({ type: () => String })
revokedBearers: string[];
}

View File

@ -1,24 +1,28 @@
import { Document, Schema, model } from 'mongoose'; import { modelOptions, prop } from '@typegoose/typegoose';
import { AccountInterface } from './Account'; import { Account } from '.';
export interface DomainInterface extends Document { class X509 {
account: AccountInterface, @prop({ required: true })
domain: string, cert: string;
port: number,
// Below is the full absolute path to the location of the x509 certificate and key files. @prop({ required: true })
x509: { key: string;
cert: string,
key: string
},
enabled: true
} }
const Domain = new Schema<DomainInterface>({ @modelOptions({ schemaOptions: { collection: 'Domain' } })
account: Object, export default class Domain {
domain: String, @prop({ type: () => Account, required: true })
port: Number, account: Account;
x509: { cert: String, key: String },
enabled: Boolean,
});
export default model<DomainInterface>('Domain', Domain); @prop({ required: true, unique: true })
domain: string;
@prop({ required: true })
port: number;
@prop({ required: true, type: () => X509 })
x509: X509;
@prop({ required: true })
enabled: boolean;
}

View File

@ -1,38 +1,45 @@
import { Document, Schema, model } from 'mongoose'; import { modelOptions, prop } from '@typegoose/typegoose';
export interface ModerationInterface extends Document {
username: string, class Expiration {
userID: string, @prop({ required: true })
logID: string, date: Date;
moderatorID: string,
reason: string, @prop({ required: true, default: false })
/** processed: boolean;
* @field 0 - Create
* @field 1 - Warn
* @field 2 - Lock
* @field 3 - Unlock
* @field 4 - Delete
*/
type: 0 | 1 | 2 | 3 | 4
date: Date,
expiration: {
date: Date,
processed: boolean
}
} }
const Moderation = new Schema<ModerationInterface>({ enum Type {
username: String, Create,
userID: String, Warn,
logID: String, Lock,
moderatorID: String, Unlock,
reason: String, Delete
type: Number, }
date: Date,
expiration: {
date: Date,
processed: Boolean,
},
});
export default model<ModerationInterface>('Moderation', Moderation); @modelOptions({ schemaOptions: { collection: 'Moderation' } })
export default class Moderation {
@prop({ required: true })
username: string;
@prop({ required: true })
userID: string;
@prop({ required: true, unique: true })
logID: string;
@prop({ required: true })
moderatorID: string;
@prop()
reason?: string;
@prop({ enum: Type, required: true })
type: Type;
@prop({ required: true })
date: Date;
@prop()
expiration?: Expiration;
}

View File

@ -1,23 +1,23 @@
import { Document, Schema, model } from 'mongoose'; import { modelOptions, prop } from '@typegoose/typegoose';
export interface Tiers { class ResourceLimits {
id: number, @prop({ required: true })
resourceLimits: { ram: number;
// In MB
ram: number, storage: number @prop({ required: true })
} storage: number;
} }
export interface TierInterface extends Tiers, Document { @modelOptions({
id: number; schemaOptions: {
} _id: false,
collection: 'Tier',
const Tier = new Schema<TierInterface>({
id: Number,
resourceLimits: {
ram: Number,
storage: Number,
}, },
}, { id: false }); })
export default class Tier {
@prop({ required: true, unique: true })
id: number;
export default model<TierInterface>('Tier', Tier); @prop({ required: true, type: () => ResourceLimits })
resourceLimits: ResourceLimits;
}

View File

@ -1,4 +1,4 @@
export { default as Account, AccountInterface } from './Account'; export { default as Account } from './Account';
export { default as Domain, DomainInterface } from './Domain'; export { default as Domain } from './Domain';
export { default as Moderation, ModerationInterface } from './Moderation'; export { default as Moderation } from './Moderation';
export { default as Tier, TierInterface, Tiers } from './Tier'; export { default as Tier } from './Tier';

View File

@ -47,7 +47,7 @@
// "typeRoots": [], /* List of folders to include type definitions from. */ // "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */ // "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
@ -58,7 +58,7 @@
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */ /* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
} }
} }