Compare commits

..

No commits in common. "a37fd81052f296f010e64d1a66f3e045d4a0e0b4" and "4f6b573100317ee796226c25f72edbc432e7bcc6" have entirely different histories.

11 changed files with 19 additions and 376 deletions

View File

@ -1,7 +1,6 @@
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
@ -26,6 +25,5 @@ export type ControllerOptions = {
discord: DiscordOptions,
databases: DatabaseOptions,
env: Env,
srcDir: string,
rabbitConfig: BrokerOptions
srcDir: string
}

View File

@ -1,11 +0,0 @@
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
}

View File

@ -1,6 +1,6 @@
import { AbstractUser } from "../src/server/interfaces/index.js";
import { LoggerClientOptions } from '@navy.gif/logger';
import { BrokerOptions, MariaOptions, MongoOptions, ObjectId } from '@navy.gif/wrappers';
import { 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,8 +51,7 @@ export type ServerOptions = {
discord: {
scope?: string[],
version?: number
},
rabbitConfig: BrokerOptions
}
}
export type Permissions = {

View File

@ -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.14",
"@navy.gif/wrappers": "^1.3.13",
"@types/cors": "^2.8.13",
"@types/express-fileupload": "^1.4.1",
"@types/express-session": "^1.17.7",

View File

@ -18,7 +18,6 @@ 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 {
@ -98,8 +97,7 @@ class Controller extends EventEmitter {
serverOptions = {} as ServerOptions,
logger = {},
discord = {},
databases = {},
rabbitConfig = {} as BrokerOptions
databases = {}
} = this.#_options;
this.#_logger.info(`Spawning ${shardCount} shards`);
@ -131,7 +129,7 @@ class Controller extends EventEmitter {
for (let i = 0; i < shardCount; i++) {
const shard = new Shard(this, i, {
serverOptions: {
...serverOptions, logger, discord, databases, rabbitConfig
...serverOptions, logger, discord, databases
},
...shardOptions,
env: this.#_options.env,

View File

@ -2,7 +2,6 @@ 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 {
@ -20,62 +19,20 @@ 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, type: 'reqregcode' };
const msg: IPCMessage = { amount };
if (subcommand === 'registration-code')
msg.type = 'reqregcode';
const response = await shard.send(msg, true);
if (!response)
@ -87,6 +44,7 @@ class CreateCommand extends BaseCommand {
out += `${invite.code}\n`;
return out;
}
}

View File

@ -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, MessageBroker, MongoDB } from '@navy.gif/wrappers';
import { MariaDB, MongoDB } from '@navy.gif/wrappers';
// Local
import { Util } from '../util/index.js';
@ -37,7 +37,6 @@ class Server extends EventEmitter {
#_name: string;
#_userDatabase: UserDatabaseInterface;
#_messageBroker: MessageBroker;
#_options: ServerOptions;
#_shardId: number;
#_ready: boolean;
@ -69,10 +68,8 @@ 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,
RABBIT_HOST, RABBIT_USER, RABBIT_PASS, RABBIT_VHOST, RABBIT_PORT
} = process.env as { [key: string]: string };
const { http: httpOpts, databases, name, rabbitConfig } = options;
MONGO_MEMORY_PASS, MONGO_MEMORY_DB, MONGO_MEMORY_AUTH_DB } = process.env as {[key: string]: string};
const { http: httpOpts, databases, name } = 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);
@ -143,15 +140,6 @@ 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
@ -231,8 +219,6 @@ 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);
@ -289,7 +275,6 @@ 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.');
@ -317,17 +302,6 @@ 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 });
}
}
}
@ -412,10 +386,6 @@ class Server extends EventEmitter {
return this.#_mariadb;
}
get messageBroker () {
return this.#_messageBroker;
}
get users () {
return this.#_userDatabase;
}

View File

@ -1,107 +0,0 @@
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;

View File

@ -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' ], { unique: true });
await this.#db.ensureIndex(coll, [ 'name' ]);
}
async fetchUsers (page: number, amount = 10, query = {}): Promise<User[]> {

View File

@ -1,162 +0,0 @@
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;

View File

@ -1773,15 +1773,15 @@ __metadata:
languageName: node
linkType: hard
"@navy.gif/wrappers@npm:^1.3.14":
version: 1.3.14
resolution: "@navy.gif/wrappers@npm:1.3.14"
"@navy.gif/wrappers@npm:^1.3.13":
version: 1.3.13
resolution: "@navy.gif/wrappers@npm:1.3.13"
dependencies:
amqp-connection-manager: ^4.1.12
amqplib: ^0.10.3
mongodb: ^5.2.0
mysql: ^2.18.1
checksum: 0b9d102b34bdb7f423955f0202bf908aca5b6355621feb00ca2466b60e20f87c5e74953e05a3c5932ff2ee6f508940ec694862b7b93bc2c3ecd8937f25878209
checksum: e3328375a8ca4ce6995dcc777b91dece35d92edad1046e6c728226de99a5e524b992fb6f2cb78dd7a266488f5c87294478088c5c0726304ca6b0f3ce56f8668c
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.14
"@navy.gif/wrappers": ^1.3.13
"@types/cors": ^2.8.13
"@types/express-fileupload": ^1.4.1
"@types/express-session": ^1.17.7