Compare commits
2 Commits
4f6b573100
...
a37fd81052
Author | SHA1 | Date | |
---|---|---|---|
a37fd81052 | |||
26175ce357 |
@ -1,6 +1,7 @@
|
||||
import { LoggerMasterOptions, LoggerClientOptions } from '@navy.gif/logger';
|
||||
import { ServerOptions } from './Server.js';
|
||||
import { DatabaseOptions, DiscordOptions } from './Other.js';
|
||||
import { BrokerOptions } from '@navy.gif/wrappers';
|
||||
|
||||
type Env = {
|
||||
[key: string]: string
|
||||
@ -25,5 +26,6 @@ export type ControllerOptions = {
|
||||
discord: DiscordOptions,
|
||||
databases: DatabaseOptions,
|
||||
env: Env,
|
||||
srcDir: string
|
||||
srcDir: string,
|
||||
rabbitConfig: BrokerOptions
|
||||
}
|
11
@types/Flags.ts
Normal file
11
@types/Flags.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export type FlagType = string | number | boolean | number[] | null
|
||||
export type FlagEnv = 'test' | 'prod'
|
||||
export type FlagConsumer = 'client' | 'server' | 'api'
|
||||
|
||||
export type FlagData<T = FlagType> = {
|
||||
_id?: string,
|
||||
name: string,
|
||||
env: FlagEnv,
|
||||
consumer: FlagConsumer,
|
||||
value: T
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { AbstractUser } from "../src/server/interfaces/index.js";
|
||||
import { LoggerClientOptions } from '@navy.gif/logger';
|
||||
import { MariaOptions, MongoOptions, ObjectId } from '@navy.gif/wrappers';
|
||||
import { BrokerOptions, MariaOptions, MongoOptions, ObjectId } from '@navy.gif/wrappers';
|
||||
import { Request as ExpressRequest, Response as ExpressResponse, NextFunction } from "express";
|
||||
import http from 'http';
|
||||
import { UploadedFile } from "express-fileupload";
|
||||
@ -51,7 +51,8 @@ export type ServerOptions = {
|
||||
discord: {
|
||||
scope?: string[],
|
||||
version?: number
|
||||
}
|
||||
},
|
||||
rabbitConfig: BrokerOptions
|
||||
}
|
||||
|
||||
export type Permissions = {
|
||||
|
@ -15,7 +15,7 @@
|
||||
"@navy.gif/commandparser": "^1.4.5",
|
||||
"@navy.gif/logger": "^2.3.3",
|
||||
"@navy.gif/passport-discord": "^0.2.2-b",
|
||||
"@navy.gif/wrappers": "^1.3.13",
|
||||
"@navy.gif/wrappers": "^1.3.14",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/express-fileupload": "^1.4.1",
|
||||
"@types/express-session": "^1.17.7",
|
||||
|
@ -18,6 +18,7 @@ import { ControllerOptions } from '../../@types/Controller.js';
|
||||
import BaseCommand from './BaseCommand.js';
|
||||
import { IPCMessage } from '../../@types/Other.js';
|
||||
import { ServerOptions } from '../../@types/Server.js';
|
||||
import { BrokerOptions } from '@navy.gif/wrappers';
|
||||
|
||||
class Controller extends EventEmitter {
|
||||
|
||||
@ -97,7 +98,8 @@ class Controller extends EventEmitter {
|
||||
serverOptions = {} as ServerOptions,
|
||||
logger = {},
|
||||
discord = {},
|
||||
databases = {}
|
||||
databases = {},
|
||||
rabbitConfig = {} as BrokerOptions
|
||||
} = this.#_options;
|
||||
this.#_logger.info(`Spawning ${shardCount} shards`);
|
||||
|
||||
@ -129,7 +131,7 @@ class Controller extends EventEmitter {
|
||||
for (let i = 0; i < shardCount; i++) {
|
||||
const shard = new Shard(this, i, {
|
||||
serverOptions: {
|
||||
...serverOptions, logger, discord, databases
|
||||
...serverOptions, logger, discord, databases, rabbitConfig
|
||||
},
|
||||
...shardOptions,
|
||||
env: this.#_options.env,
|
||||
|
@ -2,6 +2,7 @@ import { OptionType, ArgsResult } from '@navy.gif/commandparser';
|
||||
import BaseCommand from '../BaseCommand.js';
|
||||
import Controller from '../Controller.js';
|
||||
import { IPCMessage, SignupCode } from '../../../@types/Other.js';
|
||||
import Util from '../../util/Util.js';
|
||||
|
||||
class CreateCommand extends BaseCommand {
|
||||
|
||||
@ -19,20 +20,62 @@ class CreateCommand extends BaseCommand {
|
||||
valueOptional: true,
|
||||
defaultValue: 1
|
||||
}]
|
||||
}, {
|
||||
name: 'account',
|
||||
type: OptionType.SUB_COMMAND,
|
||||
options: [{
|
||||
name: 'name',
|
||||
type: OptionType.STRING,
|
||||
}, {
|
||||
name: 'password',
|
||||
type: OptionType.STRING
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async execute ({ subcommand, args }: {subcommand: string, args: ArgsResult}) {
|
||||
|
||||
if (subcommand === 'registration-code')
|
||||
return this.#code(args);
|
||||
else if (subcommand === 'account')
|
||||
return this.#account(args);
|
||||
return 'Unknown subcommand';
|
||||
|
||||
}
|
||||
|
||||
async #account (args: ArgsResult) {
|
||||
const { name, password } = args;
|
||||
let accountName = 'admin';
|
||||
let accountPass = Util.randomString();
|
||||
if (name)
|
||||
accountName = name.value as string;
|
||||
if (password)
|
||||
accountPass = password.value as string;
|
||||
|
||||
const shard = this.controller.shards.random();
|
||||
if (!shard)
|
||||
return 'No available shard';
|
||||
|
||||
const msg = {
|
||||
type: 'account-create',
|
||||
accountName,
|
||||
accountPass
|
||||
};
|
||||
const response = await shard.send(msg, true) as IPCMessage;
|
||||
if (response.success)
|
||||
return `Account ${accountName} created with password ${accountPass}`;
|
||||
return `Failed to create account:\n${response.message}`;
|
||||
}
|
||||
|
||||
async #code (args: ArgsResult) {
|
||||
const amount = args.amount?.value || 1;
|
||||
const shard = this.controller.shards.random();
|
||||
if (!shard)
|
||||
return `No available shard`;
|
||||
|
||||
const msg: IPCMessage = { amount };
|
||||
if (subcommand === 'registration-code')
|
||||
msg.type = 'reqregcode';
|
||||
const msg: IPCMessage = { amount, type: 'reqregcode' };
|
||||
|
||||
const response = await shard.send(msg, true);
|
||||
if (!response)
|
||||
@ -44,7 +87,6 @@ class CreateCommand extends BaseCommand {
|
||||
out += `${invite.code}\n`;
|
||||
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import MongoStore from 'connect-mongo';
|
||||
// Own
|
||||
import { LogFunction, LoggerClient } from '@navy.gif/logger';
|
||||
import DiscordStrategy from '@navy.gif/passport-discord';
|
||||
import { MariaDB, MongoDB } from '@navy.gif/wrappers';
|
||||
import { MariaDB, MessageBroker, MongoDB } from '@navy.gif/wrappers';
|
||||
|
||||
// Local
|
||||
import { Util } from '../util/index.js';
|
||||
@ -37,6 +37,7 @@ class Server extends EventEmitter {
|
||||
|
||||
#_name: string;
|
||||
#_userDatabase: UserDatabaseInterface;
|
||||
#_messageBroker: MessageBroker;
|
||||
#_options: ServerOptions;
|
||||
#_shardId: number;
|
||||
#_ready: boolean;
|
||||
@ -68,8 +69,10 @@ class Server extends EventEmitter {
|
||||
MONGO_PORT, MONGO_PASS, MONGO_DB, MONGO_AUTH_DB,
|
||||
NODE_ENV, SECRET, CRYPTO_SECRET, CRYPTO_SALT,
|
||||
MONGO_MEMORY_URI, MONGO_MEMORY_HOST, MONGO_MEMORY_USER, MONGO_MEMORY_PORT,
|
||||
MONGO_MEMORY_PASS, MONGO_MEMORY_DB, MONGO_MEMORY_AUTH_DB } = process.env as {[key: string]: string};
|
||||
const { http: httpOpts, databases, name } = options;
|
||||
MONGO_MEMORY_PASS, MONGO_MEMORY_DB, MONGO_MEMORY_AUTH_DB,
|
||||
RABBIT_HOST, RABBIT_USER, RABBIT_PASS, RABBIT_VHOST, RABBIT_PORT
|
||||
} = process.env as { [key: string]: string };
|
||||
const { http: httpOpts, databases, name, rabbitConfig } = options;
|
||||
|
||||
// This key never leaves memory and is exclusively used on the server, the salt can stay static
|
||||
const encryption = Util.createEncryptionKey(CRYPTO_SECRET as string, CRYPTO_SALT as string);
|
||||
@ -140,6 +143,15 @@ class Server extends EventEmitter {
|
||||
}
|
||||
});
|
||||
this.#_userDatabase = new UserDatabase(this, this.#_mongodb);
|
||||
|
||||
this.#_messageBroker = new MessageBroker(this, {
|
||||
...rabbitConfig,
|
||||
host: RABBIT_HOST,
|
||||
user: RABBIT_USER,
|
||||
pass: RABBIT_PASS,
|
||||
vhost: RABBIT_VHOST,
|
||||
port: parseInt(RABBIT_PORT)
|
||||
});
|
||||
|
||||
// Provider needs to implement getKey(key) and setKey(key, value)
|
||||
// Distributed memory storage, using mongo in this case, but this could be redis or whatever
|
||||
@ -219,6 +231,8 @@ class Server extends EventEmitter {
|
||||
|
||||
await this.#_userDatabase.init();
|
||||
|
||||
await this.#_messageBroker.init();
|
||||
|
||||
this.#logger.info('Loading endpoints');
|
||||
await this.#_registry.loadEndpoints();
|
||||
this.#logger.debug(this.#_registry.print);
|
||||
@ -275,6 +289,7 @@ class Server extends EventEmitter {
|
||||
await this.#_mongodb.close();
|
||||
await this.#_mariadb.close();
|
||||
await this.#_memoryStoreProvider.close();
|
||||
await this.#_messageBroker.close();
|
||||
this.#logger.status('DB shutdowns complete.');
|
||||
|
||||
this.#logger.status('Server shutdown complete.');
|
||||
@ -302,6 +317,17 @@ class Server extends EventEmitter {
|
||||
codes.push(code);
|
||||
}
|
||||
process.send({ _id: msg._id, codes });
|
||||
} else if (msg.type === 'account-create') {
|
||||
const name = msg.accountName as string;
|
||||
const pass = msg.accountPass as string;
|
||||
this.#logger.info(`Creating account ${name}`);
|
||||
try {
|
||||
await this.users.createUser(name, pass);
|
||||
process.send({ _id: msg._id, success: true });
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
process.send({ _id: msg._id, success: false, message: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,6 +412,10 @@ class Server extends EventEmitter {
|
||||
return this.#_mariadb;
|
||||
}
|
||||
|
||||
get messageBroker () {
|
||||
return this.#_messageBroker;
|
||||
}
|
||||
|
||||
get users () {
|
||||
return this.#_userDatabase;
|
||||
}
|
||||
|
107
src/server/components/FlagManager.ts
Normal file
107
src/server/components/FlagManager.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { MongoDB, ObjectId } from "@navy.gif/wrappers";
|
||||
import Server from "../Server.js";
|
||||
import Flag from "../structures/Flag.js";
|
||||
import { FlagConsumer, FlagData, FlagEnv } from "../../../@types/Flags.js";
|
||||
import { Collection } from "@discordjs/collection";
|
||||
import { LoggerClient } from "@navy.gif/logger";
|
||||
|
||||
type FlagQuery = {
|
||||
id?: string,
|
||||
name?: string,
|
||||
env?: FlagEnv,
|
||||
consumer?: FlagConsumer
|
||||
}
|
||||
|
||||
class FlagManager {
|
||||
|
||||
#server: Server;
|
||||
#mongo: MongoDB;
|
||||
#collectionName = 'flags';
|
||||
#flags: Collection<string, Flag>;
|
||||
#broker;
|
||||
#logger: LoggerClient;
|
||||
|
||||
constructor (server: Server) {
|
||||
this.#server = server;
|
||||
this.#mongo = server.mongodb;
|
||||
this.#logger = server.createLogger(this);
|
||||
this.#flags = new Collection();
|
||||
this.#broker = server.messageBroker;
|
||||
}
|
||||
|
||||
async init () {
|
||||
|
||||
const data = await this.#mongo.find<FlagData>(this.#collectionName, {});
|
||||
for (const flagData of data) {
|
||||
flagData._id = flagData._id.toString();
|
||||
const flag = new Flag(this, flagData);
|
||||
this.#flags.set(flag.id, flag);
|
||||
}
|
||||
|
||||
this.#broker.subscribe('flagUpdates', this.#flagUpdate.bind(this));
|
||||
|
||||
}
|
||||
|
||||
async #flagUpdate (incoming: { origin: number, flag: FlagData }) {
|
||||
const { flag: data, origin } = incoming;
|
||||
if (origin === this.#server.shardId)
|
||||
return;
|
||||
this.#logger.info(`Incoming flag update for ${data.name}`);
|
||||
const flag = this.#flags.get(data._id as string);
|
||||
if (!flag) {
|
||||
if (!data._id)
|
||||
throw new Error(`Missing flag id? ${data.name}`);
|
||||
this.#flags.set(data._id, new Flag(this, data));
|
||||
return;
|
||||
}
|
||||
flag.value = data.value;
|
||||
}
|
||||
|
||||
getFlags ({ id, name, env, consumer }: FlagQuery): Flag[] {
|
||||
|
||||
if (id) {
|
||||
const flag = this.#flags.get(id);
|
||||
if (!flag)
|
||||
return [];
|
||||
return [ flag ];
|
||||
}
|
||||
|
||||
let filtered = this.#flags;
|
||||
if (name)
|
||||
filtered = filtered.filter(flag => flag.name === name);
|
||||
if (env)
|
||||
filtered = filtered.filter(flag => flag.env === env);
|
||||
if (consumer)
|
||||
filtered = filtered.filter(flag => flag.consumer === consumer);
|
||||
|
||||
return [ ...filtered.values() ];
|
||||
|
||||
}
|
||||
|
||||
async createFlag (data: FlagData) {
|
||||
|
||||
const existing = await this.#mongo.findOne(this.#collectionName, { name: data.name, env: data.env, consumer: data.consumer });
|
||||
if (existing)
|
||||
throw new Error(`A flag with the given parameters already exists`);
|
||||
|
||||
data._id = (new ObjectId()).toString();
|
||||
const flag = new Flag(this, data);
|
||||
await this.#mongo.insertOne(this.#collectionName, flag.json);
|
||||
this.#flags.set(flag.id, flag);
|
||||
this.#broker.publish('flagUpdates', { origin: this.#server.shardId, flag: flag.json });
|
||||
return flag;
|
||||
|
||||
}
|
||||
|
||||
async updateFlag (flag: Flag): Promise<void> {
|
||||
|
||||
const json = flag.json as { _id?: string };
|
||||
delete json._id;
|
||||
await this.#mongo.updateOne(this.#collectionName, { _id: flag.id }, json, true);
|
||||
this.#broker.publish('flagUpdates', { origin: this.#server.shardId, flag: flag.json });
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default FlagManager;
|
@ -61,7 +61,7 @@ class UserDatabase implements UserDatabaseInterface {
|
||||
|
||||
async init () {
|
||||
for (const coll of [ this.#_userCollection, this.#_appCollection, this.#_roleCollection ])
|
||||
await this.#db.ensureIndex(coll, [ 'name' ]);
|
||||
await this.#db.ensureIndex(coll, [ 'name' ], { unique: true });
|
||||
}
|
||||
|
||||
async fetchUsers (page: number, amount = 10, query = {}): Promise<User[]> {
|
||||
|
162
src/server/structures/Flag.ts
Normal file
162
src/server/structures/Flag.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import { FlagConsumer, FlagData, FlagEnv, FlagType } from "../../../@types/Flags.js";
|
||||
import FlagManager from "../components/FlagManager.js";
|
||||
|
||||
// const BITS = {
|
||||
// Client: (1 << 0),
|
||||
// Server: (1 << 1),
|
||||
// Test: (1 << 2),
|
||||
// Prod: (1 << 3)
|
||||
// };
|
||||
|
||||
const CONSUMERS = [ 'client', 'server', 'api' ];
|
||||
const ENVS = [ 'test', 'prod' ];
|
||||
|
||||
class Flag {
|
||||
|
||||
#manager: FlagManager;
|
||||
|
||||
#_id: string;
|
||||
#_name: string;
|
||||
|
||||
#_env: FlagEnv;
|
||||
#_consumer: FlagConsumer;
|
||||
|
||||
#_value: FlagType;
|
||||
#_type: string;
|
||||
|
||||
constructor (manager: FlagManager, data: FlagData) {
|
||||
this.#manager = manager;
|
||||
|
||||
if (!data._id)
|
||||
throw new Error('Missing Id');
|
||||
if (!data.name)
|
||||
throw new Error('Missing name');
|
||||
|
||||
if (!data.consumer)
|
||||
throw new Error('Missing consumer');
|
||||
if (!CONSUMERS.includes(data.consumer))
|
||||
throw new Error('Bad consumer');
|
||||
|
||||
if (!data.env)
|
||||
throw new Error('Missing env');
|
||||
if (!ENVS.includes(data.env))
|
||||
throw new Error('Bad env');
|
||||
|
||||
if (!('value' in data))
|
||||
throw new Error('Missing value');
|
||||
|
||||
this.#_id = data._id;
|
||||
this.#_env = data.env;
|
||||
this.#_consumer = data.consumer;
|
||||
this.#_name = data.name;
|
||||
this.#_value = data.value;
|
||||
this.#_type = Flag.resolveType(data.value);
|
||||
}
|
||||
|
||||
save (): Promise<void> {
|
||||
return this.#manager.updateFlag(this);
|
||||
}
|
||||
|
||||
get name () {
|
||||
return this.#_name;
|
||||
}
|
||||
|
||||
get id () {
|
||||
return this.#_id;
|
||||
}
|
||||
|
||||
get type () {
|
||||
return this.#_type;
|
||||
}
|
||||
|
||||
get value () {
|
||||
return this.#_value;
|
||||
}
|
||||
|
||||
set value (val) {
|
||||
this.#_value = val;
|
||||
}
|
||||
|
||||
get env () {
|
||||
return this.#_env;
|
||||
}
|
||||
|
||||
get consumer () {
|
||||
return this.#_consumer;
|
||||
}
|
||||
|
||||
get json () {
|
||||
return {
|
||||
_id: this.id,
|
||||
name: this.name,
|
||||
value: this.value,
|
||||
env: this.env,
|
||||
consumer: this.consumer
|
||||
};
|
||||
}
|
||||
|
||||
// get client () {
|
||||
// return (this.#env & BITS.Client) === BITS.Client;
|
||||
// }
|
||||
|
||||
// set client (val: boolean) {
|
||||
// if (val)
|
||||
// this.#env |= BITS.Client;
|
||||
// else
|
||||
// this.#env &= ~BITS.Client;
|
||||
// }
|
||||
|
||||
// get server () {
|
||||
// return (this.#env & BITS.Server) === BITS.Server;
|
||||
// }
|
||||
|
||||
// set server (val: boolean) {
|
||||
// if (val)
|
||||
// this.#env |= BITS.Server;
|
||||
// else
|
||||
// this.#env &= ~BITS.Server;
|
||||
// }
|
||||
|
||||
// get test () {
|
||||
// return (this.#env & BITS.Test) === BITS.Test;
|
||||
// }
|
||||
|
||||
// set test (val: boolean) {
|
||||
// if (val)
|
||||
// this.#env |= BITS.Test;
|
||||
// else
|
||||
// this.#env &= ~BITS.Test;
|
||||
// }
|
||||
|
||||
// get prod () {
|
||||
// return (this.#env & BITS.Prod) === BITS.Prod;
|
||||
// }
|
||||
|
||||
// set prod (val: boolean) {
|
||||
// if (val)
|
||||
// this.#env |= BITS.Prod;
|
||||
// else
|
||||
// this.#env &= ~BITS.Prod;
|
||||
// }
|
||||
|
||||
static resolveType (value: unknown) {
|
||||
if (!value)
|
||||
throw new Error('Missing value');
|
||||
|
||||
let type = '';
|
||||
const nativeType = typeof value;
|
||||
if (nativeType === 'object') {
|
||||
type = value.constructor.name;
|
||||
if (value instanceof Array) {
|
||||
const compositeType = typeof value[0];
|
||||
type += `<${compositeType}>`;
|
||||
}
|
||||
} else {
|
||||
type = nativeType;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Flag;
|
10
yarn.lock
10
yarn.lock
@ -1773,15 +1773,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@navy.gif/wrappers@npm:^1.3.13":
|
||||
version: 1.3.13
|
||||
resolution: "@navy.gif/wrappers@npm:1.3.13"
|
||||
"@navy.gif/wrappers@npm:^1.3.14":
|
||||
version: 1.3.14
|
||||
resolution: "@navy.gif/wrappers@npm:1.3.14"
|
||||
dependencies:
|
||||
amqp-connection-manager: ^4.1.12
|
||||
amqplib: ^0.10.3
|
||||
mongodb: ^5.2.0
|
||||
mysql: ^2.18.1
|
||||
checksum: e3328375a8ca4ce6995dcc777b91dece35d92edad1046e6c728226de99a5e524b992fb6f2cb78dd7a266488f5c87294478088c5c0726304ca6b0f3ce56f8668c
|
||||
checksum: 0b9d102b34bdb7f423955f0202bf908aca5b6355621feb00ca2466b60e20f87c5e74953e05a3c5932ff2ee6f508940ec694862b7b93bc2c3ecd8937f25878209
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -7038,7 +7038,7 @@ __metadata:
|
||||
"@navy.gif/commandparser": ^1.4.5
|
||||
"@navy.gif/logger": ^2.3.3
|
||||
"@navy.gif/passport-discord": ^0.2.2-b
|
||||
"@navy.gif/wrappers": ^1.3.13
|
||||
"@navy.gif/wrappers": ^1.3.14
|
||||
"@types/cors": ^2.8.13
|
||||
"@types/express-fileupload": ^1.4.1
|
||||
"@types/express-session": ^1.17.7
|
||||
|
Loading…
Reference in New Issue
Block a user