diff --git a/client/index.html b/client/index.html
index a85ecc941..b6cfbe6a3 100644
--- a/client/index.html
+++ b/client/index.html
@@ -8,7 +8,8 @@
-
+
+
diff --git a/client/package-lock.json b/client/package-lock.json
index 137215b14..6a9792914 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -33,7 +33,9 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
+ "@vue/tsconfig": "^0.5.1",
"less": "^3.13.1",
+ "typescript": "^5.6.3",
"vite": "^5.4.8"
}
},
@@ -1220,6 +1222,13 @@
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.31.tgz",
"integrity": "sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA=="
},
+ "node_modules/@vue/tsconfig": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.5.1.tgz",
+ "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
@@ -2171,6 +2180,20 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/url": {
"version": "0.11.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz",
diff --git a/client/package.json b/client/package.json
index 2668a4dfe..e61b0c790 100644
--- a/client/package.json
+++ b/client/package.json
@@ -7,7 +7,8 @@
"dev": "vite",
"dev:network": "vite --host",
"build": "vite build",
- "serve": "vite preview"
+ "serve": "vite preview",
+ "check": "tsc --noEmit"
},
"dependencies": {
"@pixi/graphics-extras": "^7.4.0",
@@ -35,7 +36,9 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
+ "@vue/tsconfig": "^0.5.1",
"less": "^3.13.1",
+ "typescript": "^5.6.3",
"vite": "^5.4.8"
}
}
diff --git a/client/public/favicon_dark.ico b/client/public/favicon_dark.ico
new file mode 100644
index 000000000..0a7beec91
Binary files /dev/null and b/client/public/favicon_dark.ico differ
diff --git a/client/src/App.vue b/client/src/App.vue
index 7a8a4f859..b6bb14292 100644
--- a/client/src/App.vue
+++ b/client/src/App.vue
@@ -1,18 +1,12 @@
-
+
-
diff --git a/client/src/views/components/ViewCollapsePanel.vue b/client/src/views/components/ViewCollapsePanel.vue
index 01ff4f1d0..ff12b1f62 100644
--- a/client/src/views/components/ViewCollapsePanel.vue
+++ b/client/src/views/components/ViewCollapsePanel.vue
@@ -34,6 +34,7 @@ export default {
methods: {
toggle () {
this.isCollapsed = !this.isCollapsed;
+ this.$emit('onToggle', this.isCollapsed);
}
},
computed: {
diff --git a/client/src/views/components/ViewContainer.vue b/client/src/views/components/ViewContainer.vue
index cb0a3e312..9cda6382b 100644
--- a/client/src/views/components/ViewContainer.vue
+++ b/client/src/views/components/ViewContainer.vue
@@ -49,6 +49,11 @@ export default {
components: {
'logo': LogoVue,
'view-container-top-bar': ViewContainerTopBarVue
+ },
+ async mounted() {
+ if (!this.$store.state.userId) {
+ await this.$store.dispatch('verify')
+ }
}
}
diff --git a/client/src/views/components/modal/ConfirmationDialog.vue b/client/src/views/components/modal/ConfirmationDialog.vue
index 2fb3f6125..69da9a654 100644
--- a/client/src/views/components/modal/ConfirmationDialog.vue
+++ b/client/src/views/components/modal/ConfirmationDialog.vue
@@ -1,5 +1,5 @@
-
{{dialogSettings.text}}
-
+
-
diff --git a/client/src/views/game/GamePlayerControl.vue b/client/src/views/game/GamePlayerControl.vue
new file mode 100644
index 000000000..150402529
--- /dev/null
+++ b/client/src/views/game/GamePlayerControl.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+ {{ player.alias }} |
+
+
+ |
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/views/game/components/intel/Intel.vue b/client/src/views/game/components/intel/Intel.vue
index 8dc92c360..695084e45 100644
--- a/client/src/views/game/components/intel/Intel.vue
+++ b/client/src/views/game/components/intel/Intel.vue
@@ -145,7 +145,7 @@ export default {
alias: p.alias,
shape: p.shape,
defeated: p.defeated,
- colour: isCurrentPlayer ? '#FFFFFF' : GameHelper.getFriendlyColour(p.colour.value)
+ colour: isCurrentPlayer ? '#FFFFFF' : this.$store.getters.getColourForPlayer(p._id).value
}
})
diff --git a/client/src/views/game/components/menu/HamburgerMenu.vue b/client/src/views/game/components/menu/HamburgerMenu.vue
index ac98f8107..a0f2f4bce 100644
--- a/client/src/views/game/components/menu/HamburgerMenu.vue
+++ b/client/src/views/game/components/menu/HamburgerMenu.vue
@@ -38,7 +38,7 @@
Notes
Spectators
- Intel
+ Intel
Options
How to Play
My Games
diff --git a/client/src/views/game/components/star/StarIcon.vue b/client/src/views/game/components/star/StarIcon.vue
index b29bbbb4c..2c0913afb 100644
--- a/client/src/views/game/components/star/StarIcon.vue
+++ b/client/src/views/game/components/star/StarIcon.vue
@@ -39,7 +39,7 @@ export default {
return new URL(`../../../../assets/map-objects/128x128_star_scannable_binary.svg`, import.meta.url).href;
}
else if (this.isNebula) {
- return `mask-image: url(${new URL(`../../../../assets/nebula/neb0-starless-bright.png`, import.meta.url)});`;
+ return `mask-image: url(${new URL(`../../../../assets/nebula/neb0-starless-bright.png`, import.meta.url)}); -webkit-mask-image: url(${new URL(`../../../../assets/nebula/neb0-starless-bright.png`, import.meta.url)});`;
}
else if (this.isBlackHole) {
return new URL(`../../../../assets/map-objects/128x128_star_black_hole.svg`, import.meta.url).href;
@@ -48,7 +48,7 @@ export default {
return new URL(`../../../../assets/stars/128x128_star_pulsar.svg`, import.meta.url).href;
}
else if (this.isWormHole) {
- return `mask-image: url(${new URL(`../../../../assets/stars/vortex.png`, import.meta.url).href});`;
+ return `mask-image: url(${new URL(`../../../../assets/stars/vortex.png`, import.meta.url).href}); -webkit-mask-image: url(${new URL(`../../../../assets/stars/vortex.png`, import.meta.url).href});`;
}
else {
return new URL(`../../../../assets/map-objects/128x128_star_scannable.svg`, import.meta.url).href;
@@ -63,13 +63,13 @@ export default {
width: 15px;
height: 15px;
}
- .star-svg:deep(.star) {
+ .star-svg .star {
fill: currentColor;
}
- .star-svg:deep(.pulsar) {
+ .star-svg .pulsar {
stroke: currentColor;
}
- .star-svg:deep(.black-hole) {
+ .star-svg .black-hole {
fill: transparent;
stroke: currentColor;
}
@@ -80,6 +80,9 @@ export default {
mask-repeat: no-repeat;
mask-position: center;
mask-size: 100%;
+ -webkit-mask-repeat: no-repeat;
+ -webkit-mask-position: center;
+ -webkit-mask-size: 100%;
}
.nebulaIcon, .wormHoleIcon {
diff --git a/client/tsconfig.json b/client/tsconfig.json
new file mode 100644
index 000000000..b47377074
--- /dev/null
+++ b/client/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "@vue/tsconfig/tsconfig.dom.json"
+}
\ No newline at end of file
diff --git a/server/api/controllers/auth.ts b/server/api/controllers/auth.ts
index 208a69a38..aee718ff1 100644
--- a/server/api/controllers/auth.ts
+++ b/server/api/controllers/auth.ts
@@ -1,6 +1,9 @@
import { DependencyContainer } from '../../services/types/DependencyContainer';
+import {logger} from "../../utils/logging";
const axios = require('axios');
+const log = logger("Auth Controller");
+
export default (container: DependencyContainer) => {
return {
login: async (req, res, next) => {
@@ -92,7 +95,7 @@ export default (container: DependencyContainer) => {
} catch (error) {
// NOTE: An unauthorized token will not throw an error;
// it will return a 401 Unauthorized response in the try block above
- console.error(error);
+ log.error(error);
}
}
diff --git a/server/api/controllers/carrier.ts b/server/api/controllers/carrier.ts
index b97fb95ee..d544bf6c3 100644
--- a/server/api/controllers/carrier.ts
+++ b/server/api/controllers/carrier.ts
@@ -1,11 +1,11 @@
import { DependencyContainer } from '../../services/types/DependencyContainer';
-import { mapToCarrierCalculateCombatRequest, mapToCarrierLoopWaypointsRequest, mapToCarrierRenameCarrierRequest, mapToCarrierSaveWaypointsRequest, mapToCarrierTransferShipsRequest } from '../requests/carrier';
+import { mapToCarrierCalculateCombatRequest, parseCarrierLoopWaypointsRequest, mapToCarrierRenameCarrierRequest, parseCarierSaveWaypointsRequest, parseCarrierTransferShipsRequest } from '../requests/carrier';
export default (container: DependencyContainer) => {
return {
saveWaypoints: async (req, res, next) => {
try {
- const reqObj = mapToCarrierSaveWaypointsRequest(req.body);
+ const reqObj = parseCarierSaveWaypointsRequest(req.body);
let report = await container.waypointService.saveWaypoints(
req.game,
@@ -22,7 +22,7 @@ export default (container: DependencyContainer) => {
},
loopWaypoints: async (req, res, next) => {
try {
- const reqObj = mapToCarrierLoopWaypointsRequest(req.body);
+ const reqObj = parseCarrierLoopWaypointsRequest(req.body);
await container.waypointService.loopWaypoints(
req.game,
@@ -38,7 +38,7 @@ export default (container: DependencyContainer) => {
},
transferShips: async (req, res, next) => {
try {
- const reqObj = mapToCarrierTransferShipsRequest(req.body);
+ const reqObj = parseCarrierTransferShipsRequest(req.body);
await container.shipTransferService.transfer(
req.game,
diff --git a/server/api/controllers/game.ts b/server/api/controllers/game.ts
index ddc5abbfd..5b9fd816b 100644
--- a/server/api/controllers/game.ts
+++ b/server/api/controllers/game.ts
@@ -1,6 +1,9 @@
import ValidationError from '../../errors/validation';
import { DependencyContainer } from '../../services/types/DependencyContainer';
-import { mapToGameConcedeDefeatRequest, mapToGameJoinGameRequest, mapToGameSaveNotesRequest } from '../requests/game';
+import { mapToGameConcedeDefeatRequest, mapToGameJoinGameRequest, mapToGameSaveNotesRequest, parseKickPlayerRequest } from '../requests/game';
+import {logger} from "../../utils/logging";
+
+const log = logger("Game Controller");
export default (container: DependencyContainer) => {
return {
@@ -53,7 +56,7 @@ export default (container: DependencyContainer) => {
res.status(201).json(game._id);
return next();
} catch (err) {
- console.error(err);
+ log.error(err);
return next(err);
}
},
@@ -412,6 +415,18 @@ export default (container: DependencyContainer) => {
return next(err);
}
},
+ kickPlayer: async (req, res, next) => {
+ try {
+ const params = parseKickPlayerRequest(req.body);
+
+ await container.gameService.kickPlayer(req.game, req.session.userId, params.playerId);
+
+ res.sendStatus(200);
+ return next();
+ } catch (err) {
+ return next(err);
+ }
+ },
getPlayerUser: async (req, res, next) => {
try {
let user = await container.gameService.getPlayerUser(
diff --git a/server/api/controllers/shop.ts b/server/api/controllers/shop.ts
index d4d018f1d..309917d75 100644
--- a/server/api/controllers/shop.ts
+++ b/server/api/controllers/shop.ts
@@ -1,8 +1,11 @@
import ValidationError from '../../errors/validation';
import { DependencyContainer } from '../../services/types/DependencyContainer';
+import {logger} from "../../utils/logging";
const COST_PER_TOKEN = 1;
+const log = logger("Shop Controller");
+
export default (container: DependencyContainer) => {
return {
purchase: async (req, res, next) => {
@@ -53,7 +56,7 @@ export default (container: DependencyContainer) => {
res.redirect(`${container.config.clientUrl}/#/shop/paymentcomplete?credits=${result.galacticTokens}`);
return next();
} catch (err) {
- console.error(err);
+ log.error(err);
res.redirect(`${container.config.clientUrl}/#/shop/paymentfailed`);
return next();
diff --git a/server/api/controllers/star.ts b/server/api/controllers/star.ts
index b0f6fad1d..19774f374 100644
--- a/server/api/controllers/star.ts
+++ b/server/api/controllers/star.ts
@@ -1,5 +1,5 @@
import { DependencyContainer } from '../../services/types/DependencyContainer';
-import { mapToStarAbandonStarRequest, mapToStarBuildCarrierRequest, mapToStarDestroyInfrastructureRequest, mapToStarSetBulkIgnoreAllStatusRequest, mapToStarToggleBulkIgnoreStatusRequest, mapToStarUpgradeInfrastructureBulkRequest, mapToScheduledStarUpgradeInfrastructureBulkRequest, mapToScheduledStarUpgradeToggleRepeat, mapToScheduledStarUpgradeTrash, mapToStarUpgradeInfrastructureRequest, StarUpgradeInfrastructureRequest } from '../requests/star';
+import { mapToStarAbandonStarRequest, mapToStarBuildCarrierRequest, mapToStarDestroyInfrastructureRequest, mapToStarSetBulkIgnoreAllStatusRequest, mapToStarToggleBulkIgnoreStatusRequest, mapToStarUpgradeInfrastructureBulkRequest, mapToScheduledStarUpgradeInfrastructureBulkRequest, mapToStarUpgradeInfrastructureRequest, StarUpgradeInfrastructureRequest, parseScheduledStarUpgradeToggleRepeat, parseScheduledStarUpgradeTrashRepeat } from '../requests/star';
export default (container: DependencyContainer) => {
return {
@@ -103,12 +103,12 @@ export default (container: DependencyContainer) => {
},
toggleBulkRepeat: async (req, res, next) => {
try {
- const reqObj = mapToScheduledStarUpgradeToggleRepeat(req.body);
+ const reqObj = parseScheduledStarUpgradeToggleRepeat(req.body);
let summary = await container.scheduleBuyService.toggleBulkRepeat(
req.game,
req.player,
- reqObj._id);
+ reqObj.actionId);
res.status(200).json(summary);
return next();
@@ -118,12 +118,12 @@ export default (container: DependencyContainer) => {
},
trashBulk: async (req, res, next) => {
try {
- const reqObj = mapToScheduledStarUpgradeTrash(req.body);
+ const reqObj = parseScheduledStarUpgradeTrashRepeat(req.body);
await container.scheduleBuyService.trashAction(
req.game,
req.player,
- reqObj._id
+ reqObj.actionId
)
res.sendStatus(200);
return next();
diff --git a/server/api/controllers/user.ts b/server/api/controllers/user.ts
index 740588f5b..2cbdd0126 100644
--- a/server/api/controllers/user.ts
+++ b/server/api/controllers/user.ts
@@ -1,6 +1,9 @@
import ValidationError from '../../errors/validation';
import { DependencyContainer } from '../../services/types/DependencyContainer';
import { mapToUserCreateUserRequest, mapToUserRequestPasswordResetRequest, mapToUserRequestUsernameRequest, mapToUserResetPasswordResetRequest, mapToUserUpdateEmailPreferenceRequest, mapToUserUpdateEmailRequest, mapToUserUpdatePasswordRequest, mapToUserUpdateUsernameRequest } from '../requests/user';
+import {logger} from "../../utils/logging";
+
+const log = logger("User Controller");
export default (container: DependencyContainer) => {
return {
@@ -231,7 +234,7 @@ export default (container: DependencyContainer) => {
try {
await container.emailService.sendTemplate(reqObj.email, container.emailService.TEMPLATES.RESET_PASSWORD, [token]);
} catch (emailError) {
- console.error(emailError);
+ log.error(emailError);
res.sendStatus(500);
return next(emailError);
}
@@ -262,7 +265,7 @@ export default (container: DependencyContainer) => {
try {
await container.emailService.sendTemplate(reqObj.email, container.emailService.TEMPLATES.FORGOT_USERNAME, [username]);
} catch (emailError) {
- console.error(emailError);
+ log.error(emailError);
res.sendStatus(500);
return next(emailError);
diff --git a/server/api/express.ts b/server/api/express.ts
index f10ef1257..1bcf9caa2 100644
--- a/server/api/express.ts
+++ b/server/api/express.ts
@@ -10,6 +10,9 @@ import { DependencyContainer } from '../services/types/DependencyContainer';
import registerRoutes from './routes';
import {SingleRouter} from "./singleRoute";
import Middleware from "./middleware";
+import {logger} from "../utils/logging";
+
+const log = logger("express");
export default async (config: Config, app, container: DependencyContainer) => {
const idempotencyKeyCache: Map = new Map();
@@ -27,7 +30,7 @@ export default async (config: Config, app, container: DependencyContainer) => {
// Catch session store errors
sessionStorage.on('error', function(err) {
- console.error(err);
+ log.error(err);
});
// ---------------
@@ -121,7 +124,7 @@ export default async (config: Config, app, container: DependencyContainer) => {
app.use(middleware.core.handleError);
- console.log('Express intialized.');
+ log.info('Express intialized.');
return {
app,
diff --git a/server/api/index.ts b/server/api/index.ts
index c462e9a85..955350584 100644
--- a/server/api/index.ts
+++ b/server/api/index.ts
@@ -1,3 +1,5 @@
+import {logger, onReady, setupLogging} from "../utils/logging";
+
const express = require('express');
const http = require('http');
import config from '../config';
@@ -7,10 +9,13 @@ import socketLoader from './sockets';
import containerLoader from '../services';
let mongo;
+Error.stackTraceLimit = 1000;
-console.log(`Node ${process.version}`);
+setupLogging();
-Error.stackTraceLimit = 1000;
+const log = logger();
+
+log.info(`Node ${process.version}`);
async function startServer() {
mongo = await mongooseLoader(config, {});
@@ -28,11 +33,11 @@ async function startServer() {
server.listen(config.port, (err) => {
if (err) {
- console.error(err);
+ log.error(err);
return;
}
- console.log(`Server is running on port ${config.port}.`);
+ log.info(`Server is running on port ${config.port}.`);
});
await container.discordService.initialize();
@@ -40,15 +45,15 @@ async function startServer() {
}
process.on('SIGINT', async () => {
- console.log('Shutting down...');
+ log.info('Shutting down...');
- console.log('Disconnecting from MongoDB...');
+ log.info('Disconnecting from MongoDB...');
await mongo.disconnect();
- console.log('MongoDB disconnected.');
+ log.info('MongoDB disconnected.');
- console.log('Shutdown complete.');
-
- process.exit();
+ log.info('Shutdown complete.');
+
+ onReady(() => process.exit());
});
startServer();
diff --git a/server/api/middleware/core.ts b/server/api/middleware/core.ts
index 741f4714e..9c4915fac 100644
--- a/server/api/middleware/core.ts
+++ b/server/api/middleware/core.ts
@@ -2,6 +2,9 @@ import { NextFunction, Request, Response } from 'express';
import { ExpressJoiError } from 'express-joi-validation';
import ValidationError from '../../errors/validation';
import { DependencyContainer } from '../../services/types/DependencyContainer';
+import {logger} from "../../utils/logging";
+
+const log = logger("Core Middleware");
export interface CoreMiddleware {
handleError(err: any, req: Request, res: Response, next: NextFunction);
@@ -21,7 +24,10 @@ export const middleware = (container: DependencyContainer): CoreMiddleware => {
errors = [errors];
}
- console.error(errors);
+ log.error({
+ userId: req.session?.userId,
+ errors
+ });
res.status(err.statusCode).json({
errors
});
@@ -40,7 +46,10 @@ export const middleware = (container: DependencyContainer): CoreMiddleware => {
return;
}
- console.error(err.stack);
+ log.error({
+ userId: req.session?.userId,
+ error: err.stack
+ });
res.status(500).json({
errors: ['Something broke. If the problem persists, please contact a developer.']
diff --git a/server/api/middleware/playerMutex.ts b/server/api/middleware/playerMutex.ts
index 88dbbe65e..2161d411e 100644
--- a/server/api/middleware/playerMutex.ts
+++ b/server/api/middleware/playerMutex.ts
@@ -2,6 +2,9 @@ import { NextFunction, Request, Response } from 'express';
import ValidationError from "../../errors/validation";
import { DBObjectId } from "../../services/types/DBObjectId";
import { DependencyContainer } from "../../services/types/DependencyContainer";
+import {logger} from "../../utils/logging";
+
+const log = logger("Player Mutex Middleware");
export interface PlayerMutexMiddleware {
wait: () => (req: Request<{ gameId?: DBObjectId }>, res: Response, next: NextFunction) => Promise;
@@ -119,7 +122,10 @@ export const middleware = (container: DependencyContainer): PlayerMutexMiddlewar
return next();
} catch (err) {
- console.error("PlayerMutex threw: ", err);
+ log.error({
+ error: err,
+ gameId: req.params.gameId,
+ }, "PlayerMutex threw: ", err);
return next(err);
}
}
diff --git a/server/api/requests/carrier.ts b/server/api/requests/carrier.ts
index 0d3da9238..c0f7a77dc 100644
--- a/server/api/requests/carrier.ts
+++ b/server/api/requests/carrier.ts
@@ -1,125 +1,52 @@
import ValidationError from "../../errors/validation";
-import { CarrierWaypointActionType } from "../../services/types/CarrierWaypoint";
+import { CarrierWaypointActionType, CarrierWaypointActionTypes } from "../../services/types/CarrierWaypoint";
import { DBObjectId } from "../../services/types/DBObjectId";
+import { array, boolean, map, number, numberAdv, object, objectId, or, positiveInteger, string, stringEnumeration, Validator } from "../validate";
import { keyHasArrayValue, keyHasBooleanValue, keyHasNumberValue, keyHasObjectValue, keyHasStringValue } from "./helpers";
-export interface CarrierSaveWaypointsRequest {
- waypoints: [
- {
- source: DBObjectId;
- destination: DBObjectId;
- action: CarrierWaypointActionType;
- actionShips: number;
- delayTicks: number;
- }
- ];
- looped: boolean;
+type CarrierSaveWaypoint = {
+ source: DBObjectId;
+ destination: DBObjectId;
+ action: CarrierWaypointActionType;
+ actionShips: number;
+ delayTicks: number;
};
-export const mapToCarrierSaveWaypointsRequest = (body: any): CarrierSaveWaypointsRequest => {
- let errors: string[] = [];
-
- if (!keyHasBooleanValue(body, 'looped')) {
- errors.push('Looped is required.');
- }
-
- if (!keyHasArrayValue(body, 'waypoints')) {
- errors.push('Waypoints is required.');
- }
-
- if (body.waypoints) {
- for (let waypoint of body.waypoints) {
- if (!keyHasStringValue(waypoint, 'source')) {
- errors.push('Source is required.');
- }
-
- if (!keyHasStringValue(waypoint, 'destination')) {
- errors.push('Destination is required.');
- }
-
- if (!keyHasStringValue(waypoint, 'action')) {
- errors.push('Action is required.');
- }
-
-if (waypoint.actionShips == null) waypoint.actionShips = 0;
-if (waypoint.delayTicks == null) waypoint.delayTicks = 0;
-
- if (!keyHasNumberValue(waypoint, 'actionShips')) {
- errors.push('Action Ships is required.');
- }
-
- if (!keyHasNumberValue(waypoint, 'delayTicks')) {
- errors.push('Delay Ticks is required.');
- }
-
- waypoint.actionShips = +waypoint.actionShips;
- waypoint.delayTicks = +waypoint.delayTicks;
- }
- }
-
- if (errors.length) {
- throw new ValidationError(errors);
- }
-
- return {
- waypoints: body.waypoints,
- looped: body.looped
- }
+export type CarrierSaveWaypointsRequest = {
+ waypoints: CarrierSaveWaypoint[];
+ looped: boolean;
};
-export interface CarrierLoopWaypointsRequest {
+export const parseCarierSaveWaypointsRequest: Validator = object({
+ waypoints: array(object({
+ source: objectId,
+ destination: objectId,
+ action: stringEnumeration(CarrierWaypointActionTypes),
+ actionShips: map((a) => a || 0, positiveInteger),
+ delayTicks: map((a) => a || 0, positiveInteger),
+ })),
+ looped: boolean,
+});
+
+export type CarrierLoopWaypointsRequest = {
loop: boolean;
};
-export const mapToCarrierLoopWaypointsRequest = (body: any): CarrierLoopWaypointsRequest => {
- let errors: string[] = [];
+export const parseCarrierLoopWaypointsRequest: Validator = object({
+ loop: boolean,
+});
- if (!keyHasBooleanValue(body, 'loop')) {
- errors.push('Loop is required.');
- }
-
- if (errors.length) {
- throw new ValidationError(errors);
- }
-
- return {
- loop: body.loop
- }
-};
-
-export interface CarrierTransferShipsRequest {
+export type CarrierTransferShipsRequest = {
carrierShips: number;
starShips: number;
starId: DBObjectId;
};
-export const mapToCarrierTransferShipsRequest = (body: any): CarrierTransferShipsRequest => {
- let errors: string[] = [];
-
- if (!keyHasNumberValue(body, 'carrierShips')) {
- errors.push('Carrier Ships is required.');
- }
-
- if (!keyHasNumberValue(body, 'starShips')) {
- errors.push('Star Ships is required.');
- }
-
- if (!keyHasStringValue(body, 'starId')) {
- errors.push('Star ID is required.');
- }
-
- if (errors.length) {
- throw new ValidationError(errors);
- }
-
- body.starShips = +body.starShips;
-
- return {
- carrierShips: body.carrierShips,
- starShips: body.starShips,
- starId: body.starId
- }
-};
+export const parseCarrierTransferShipsRequest = object({
+ carrierShips: positiveInteger,
+ starShips: positiveInteger,
+ starId: objectId,
+});
export interface CarrierRenameCarrierRequest {
name: string;
diff --git a/server/api/requests/game.ts b/server/api/requests/game.ts
index 7e7cbfdc2..6bf32dfdd 100644
--- a/server/api/requests/game.ts
+++ b/server/api/requests/game.ts
@@ -1,5 +1,6 @@
import ValidationError from "../../errors/validation";
import { DBObjectId } from "../../services/types/DBObjectId";
+import { object, Validator, objectId } from "../validate";
import { keyHasBooleanValue, keyHasNumberValue, keyHasStringValue } from "./helpers";
export interface GameCreateGameRequest {
@@ -82,4 +83,12 @@ export const mapToGameConcedeDefeatRequest = (body: any): GameConcedeDefeatReque
return {
openSlot: body.openSlot
}
-}
\ No newline at end of file
+}
+
+export type KickPlayerRequest = {
+ playerId: DBObjectId,
+}
+
+export const parseKickPlayerRequest: Validator = object({
+ playerId: objectId
+});
diff --git a/server/api/requests/star.ts b/server/api/requests/star.ts
index 4b0e4be90..09fbed769 100644
--- a/server/api/requests/star.ts
+++ b/server/api/requests/star.ts
@@ -3,6 +3,7 @@ import ValidationError from "../../errors/validation";
import { DBObjectId } from "../../services/types/DBObjectId";
import { InfrastructureType } from "../../services/types/Star";
import { keyHasBooleanValue, keyHasNumberValue, keyHasStringValue } from "./helpers";
+import { object, objectId, Validator } from "../validate";
export interface StarUpgradeInfrastructureRequest {
starId: DBObjectId;
@@ -38,12 +39,12 @@ export interface ScheduledStarUpgradeInfrastructureBulkRequest {
tick: number;
};
-export interface ScheduledStarUpgradeToggleRepeat {
- _id: DBObjectId;
+export type ScheduledStarUpgradeToggleRepeat = {
+ actionId: DBObjectId;
};
export interface ScheduledStarUpgradeTrash {
- _id: DBObjectId;
+ actionId: DBObjectId;
}
export const mapToStarUpgradeInfrastructureBulkRequest = (body: any): StarUpgradeInfrastructureBulkRequest => {
@@ -121,37 +122,13 @@ export const mapToScheduledStarUpgradeInfrastructureBulkRequest = (body: any): S
}
};
-export const mapToScheduledStarUpgradeToggleRepeat = (body: any): ScheduledStarUpgradeToggleRepeat => {
- let errors: string[] = [];
-
- if (!keyHasStringValue(body, 'actionId')) {
- errors.push('ObjectId is required.');
- }
-
- if (errors.length) {
- throw new ValidationError(errors);
- }
-
- return {
- _id: body.actionId
- }
-};
+export const parseScheduledStarUpgradeToggleRepeat: Validator = object({
+ actionId: objectId
+});
-export const mapToScheduledStarUpgradeTrash = (body: any): ScheduledStarUpgradeTrash => {
- let errors: string[] = [];
-
- if (!keyHasStringValue(body, 'actionId')) {
- errors.push('ObjectId is required.');
- }
-
- if (errors.length) {
- throw new ValidationError(errors);
- }
-
- return {
- _id: body.actionId
- }
-}
+export const parseScheduledStarUpgradeTrashRepeat: Validator = object({
+ actionId: objectId
+});
export interface StarDestroyInfrastructureRequest {
starId: DBObjectId;
diff --git a/server/api/routes/games.ts b/server/api/routes/games.ts
index 5347111ad..6bef3ee19 100644
--- a/server/api/routes/games.ts
+++ b/server/api/routes/games.ts
@@ -348,6 +348,17 @@ export default (router: SingleRouter, mw: MiddlewareContainer, validator: Expres
controller.fastForward
);
+ router.post('/api/game/:gameId/kick',
+ mw.auth.authenticate(),
+ mw.game.loadGame({
+ lean: false,
+ }),
+ mw.game.validateGameState({
+ isUnlocked: true,
+ }),
+ controller.kickPlayer
+ );
+
router.get('/api/game/:gameId/player/:playerId',
mw.game.loadGame({
lean: true,
diff --git a/server/api/sockets.ts b/server/api/sockets.ts
index 7ec561b9d..1e22c17f1 100644
--- a/server/api/sockets.ts
+++ b/server/api/sockets.ts
@@ -1,9 +1,12 @@
import { Config } from "../config/types/Config";
+import {logger} from "../utils/logging";
const socketio = require('socket.io');
const cookieParser = require('cookie-parser');
const cookie = require('cookie');
+const log = logger('sockets');
+
export default (config: Config, server, sessionStore) => {
const io = socketio(server);
@@ -79,7 +82,7 @@ export default (config: Config, server, sessionStore) => {
});
});
- console.log('Sockets initialized.');
+ log.info('Sockets initialized.');
return io;
};
diff --git a/server/api/validate.ts b/server/api/validate.ts
index 86b819f4d..ca15f585f 100644
--- a/server/api/validate.ts
+++ b/server/api/validate.ts
@@ -1,4 +1,5 @@
import ValidationError from "../errors/validation";
+import { DBObjectId, objectIdFromString } from "../services/types/DBObjectId";
export type Validator = (value: any) => T;
@@ -134,4 +135,62 @@ export const object = (objValidator: ObjectValidator): Validator => {
return n;
}
-}
\ No newline at end of file
+}
+
+export const stringEnumeration = (members: readonly [any, ...M]): Validator => {
+ return v => {
+ const s = string(v);
+ if (members.includes(s as A)) {
+ return s as A;
+ } else {
+ throw failed(members.join(", "), v)
+ }
+ }
+}
+
+export const objectId: Validator = map(objectIdFromString, string);
+
+type NumberValidationProps = {
+ sign?: 'positive' | 'negative',
+ integer?: boolean,
+ range?: {
+ from: number,
+ to: number
+ },
+}
+
+export const numberAdv = (props: NumberValidationProps) => v => {
+ const n = number(v);
+
+ if (props.sign) {
+ const sign = Math.sign(n);
+ if (props.sign === 'positive') {
+ if (sign === -1) {
+ throw failed('positive number', v);
+ }
+ } else if (props.sign === 'negative') {
+ if (sign !== -1) {
+ throw failed('negative number', v);
+ }
+ }
+ }
+
+ if (props.integer) {
+ if (!Number.isInteger(n)) {
+ throw failed('integer', v);
+ }
+ }
+
+ if (props.range) {
+ if (n < props.range.from || n > props.range.to) {
+ throw failed(`number between ${props.range.from} and ${props.range.to}`, v);
+ }
+ }
+
+ return n;
+}
+
+export const positiveInteger = numberAdv({
+ integer: true,
+ sign: 'positive'
+});
\ No newline at end of file
diff --git a/server/config/index.ts b/server/config/index.ts
index 7cd0a7419..008cc96ed 100644
--- a/server/config/index.ts
+++ b/server/config/index.ts
@@ -1,4 +1,4 @@
-import { Config } from "./types/Config";
+import {Config, LoggingType} from "./types/Config";
require('dotenv').config({path:__dirname + '/../.env'});
@@ -11,6 +11,7 @@ const config: Config = {
clientUrl: process.env.CLIENT_URL,
corsUrls: process.env.CORS_URLS?.split(",") || [ process.env.CLIENT_URL || "https://solaris.games" ],
cacheEnabled: process.env.CACHE_ENABLED == "true",
+ logging: process.env.LOGGING_TYPE as LoggingType,
smtp: {
enabled: process.env.SMTP_ENABLED == "true",
host: process.env.SMTP_HOST,
diff --git a/server/config/types/Config.ts b/server/config/types/Config.ts
index 787cb3f36..2585c1dfa 100644
--- a/server/config/types/Config.ts
+++ b/server/config/types/Config.ts
@@ -1,3 +1,5 @@
+export type LoggingType = 'pretty' | 'stdout';
+
export interface Config {
port?: string;
sessionSecret?: string;
@@ -7,6 +9,7 @@ export interface Config {
clientUrl?: string;
corsUrls: string[];
cacheEnabled: boolean;
+ logging?: LoggingType;
smtp: {
enabled: boolean;
host?: string;
diff --git a/server/db/index.ts b/server/db/index.ts
index 1d078fdc2..0a8edeed1 100644
--- a/server/db/index.ts
+++ b/server/db/index.ts
@@ -1,3 +1,5 @@
+import {logger} from "../utils/logging";
+
const mongoose = require('mongoose');
import EventModel from './models/Event';
@@ -7,6 +9,8 @@ import HistoryModel from './models/History';
import UserModel from './models/User';
import PaymentModel from './models/Payment';
+const log = logger("Database");
+
export default async (config, options) => {
async function unlockAgendaJobs(db) {
@@ -25,14 +29,14 @@ export default async (config, options) => {
$set: { nextRunAt:new Date() }
});
- console.log(`Unlocked #${numUnlocked.modifiedCount} jobs.`);
+ log.info(`Unlocked #${numUnlocked.modifiedCount} jobs.`);
} catch (e) {
- console.error(e);
+ log.error(e);
}
}
async function syncIndexes() {
- console.log('Syncing indexes...');
+ log.info('Syncing indexes...');
await EventModel.syncIndexes();
await GameModel.syncIndexes();
await GuildModel.syncIndexes();
@@ -40,12 +44,12 @@ export default async (config, options) => {
await UserModel.syncIndexes();
await PaymentModel.syncIndexes();
// TODO ReportModel?
- console.log('Indexes synced.');
+ log.info('Indexes synced.');
}
const dbConnection = mongoose.connection;
- dbConnection.on('error', console.error.bind(console, 'connection error:'));
+ dbConnection.on('error', log.error.bind(log.error, 'connection error:'));
options = options || {};
options.connectionString = options.connectionString || config.connectionString;
@@ -53,7 +57,7 @@ export default async (config, options) => {
options.unlockJobs = options.unlockJobs == null ? false : options.unlockJobs;
options.poolSize = options.poolSize || 5;
- console.log(`Connecting to database: ${options.connectionString}`);
+ log.info(`Connecting to database: ${options.connectionString}`);
const db = await mongoose.connect(options.connectionString, {
useUnifiedTopology: true,
@@ -71,7 +75,7 @@ export default async (config, options) => {
await unlockAgendaJobs(db);
}
- console.log('MongoDB intialized.');
+ log.info('MongoDB intialized.');
return db;
};
diff --git a/server/db/migrate.ts b/server/db/migrate.ts
index dcbe89297..e49237a0e 100644
--- a/server/db/migrate.ts
+++ b/server/db/migrate.ts
@@ -2,16 +2,19 @@ import config from '../config';
import mongooseLoader from '.';
import fs from 'fs';
import path from 'path';
+import {logger} from "../utils/logging";
let mongo;
+const log = logger("Migrations")
+
async function startup() {
mongo = await mongooseLoader(config, {
syncIndexes: true,
poolSize: 1
});
- console.log('Running migrations...');
+ log.info('Running migrations...');
const dirPath: string = path.join(__dirname, 'migrations');
@@ -20,7 +23,7 @@ async function startup() {
.sort((a, b) => a.localeCompare(b));
for (let file of files) {
- console.log(file);
+ log.info(file);
const filePath = path.join(dirPath, file);
const script = require(filePath);
@@ -28,7 +31,7 @@ async function startup() {
try {
await script.migrate(mongo.connection.db);
} catch (e) {
- console.error(e);
+ log.error(e);
return Promise.reject(e);
}
@@ -42,17 +45,17 @@ process.on('SIGINT', async () => {
});
async function shutdown() {
- console.log('Shutting down...');
+ log.info('Shutting down...');
await mongo.disconnect();
- console.log('Shutdown complete.');
+ log.info('Shutdown complete.');
process.exit();
}
startup().then(async () => {
- console.log('Database migrated.');
+ log.info('Database migrated.');
await shutdown();
}).catch(async err => {
diff --git a/server/db/migrations/020-add-scheduled-actions.js b/server/db/migrations/020-add-scheduled-actions.js
index 9147ec595..d22cca1b4 100644
--- a/server/db/migrations/020-add-scheduled-actions.js
+++ b/server/db/migrations/020-add-scheduled-actions.js
@@ -1,5 +1,3 @@
-const { Console } = require("console");
-
module.exports = {
async migrate(db) {
const games = db.collection('games');
diff --git a/server/db/recalculateRankings.ts b/server/db/recalculateRankings.ts
index 5556c2f74..53cf688c5 100644
--- a/server/db/recalculateRankings.ts
+++ b/server/db/recalculateRankings.ts
@@ -4,10 +4,13 @@ import containerLoader from '../services';
import {DependencyContainer} from '../services/types/DependencyContainer';
import {User} from '../services/types/User';
import {GameWinnerKind} from "../services/leaderboard";
+import {logger} from "../utils/logging";
let mongo,
container: DependencyContainer;
+const log = logger("Recalculate Rankings");
+
function binarySearchUsers(users: User[], id: string) {
let start = 0;
let end = users.length - 1;
@@ -41,9 +44,9 @@ async function startup() {
container = containerLoader(config);
- console.log('Recalculating all player ranks...');
+ log.info('Recalculating all player ranks...');
- console.log(`Resetting users...`);
+ log.info(`Resetting users...`);
await container.userService.userRepo.updateMany({}, {
$set: {
'achievements.level': 1,
@@ -72,7 +75,7 @@ async function startup() {
'achievements.badges.special_arcade': 0,
}
});
- console.log(`Done.`);
+ log.info(`Done.`);
let users = await container.userService.userRepo.find({}, {
_id: 1,
@@ -80,7 +83,7 @@ async function startup() {
},
{ _id: 1 });
- console.log(`Total users: ${users.length}`);
+ log.info(`Total users: ${users.length}`);
let dbQuery = {
'state.endDate': { $ne: null },
@@ -89,7 +92,7 @@ async function startup() {
let total = (await container.gameService.gameRepo.count(dbQuery));
- console.log(`Recalculating rank for ${total} games...`);
+ log.info(`Recalculating rank for ${total} games...`);
let page = 0;
let pageSize = 10;
@@ -186,12 +189,12 @@ async function startup() {
await container.gameService.gameRepo.bulkWrite(leaderboardWrites);
- console.log(`Page ${page}/${totalPages}`);
+ log.info(`Page ${page}/${totalPages}`);
page++;
} while (page <= totalPages);
- console.log(`Done.`);
+ log.info(`Done.`);
let dbWrites = users.map(user => {
return {
@@ -229,9 +232,9 @@ async function startup() {
}
});
- console.log(`Updating users...`);
+ log.info(`Updating users...`);
await container.userService.userRepo.bulkWrite(dbWrites);
- console.log(`Users updated.`);
+ log.info(`Users updated.`);
}
process.on('SIGINT', async () => {
@@ -239,21 +242,21 @@ process.on('SIGINT', async () => {
});
async function shutdown() {
- console.log('Shutting down...');
+ log.info('Shutting down...');
await mongo.disconnect();
- console.log('Shutdown complete.');
+ log.info('Shutdown complete.');
process.exit();
}
startup().then(async () => {
- console.log('Done.');
+ log.info('Done.');
await shutdown();
}).catch(async err => {
- console.error(err);
+ log.error(err);
await shutdown();
});
diff --git a/server/db/restoreGame.ts b/server/db/restoreGame.ts
new file mode 100644
index 000000000..39b192637
--- /dev/null
+++ b/server/db/restoreGame.ts
@@ -0,0 +1,192 @@
+import {DependencyContainer} from "../services/types/DependencyContainer";
+import mongooseLoader from "./index";
+import config from "../config";
+import containerLoader from "../services";
+import mongoose, { ObjectId } from "mongoose";
+import {DBObjectId, objectId} from "../services/types/DBObjectId";
+import {GameHistory, GameHistoryCarrier} from "../services/types/GameHistory";
+import {Game} from "../services/types/Game";
+import {Carrier} from "../services/types/Carrier";
+
+let mongo,
+ container: DependencyContainer;
+
+const loadHistory = async (gameId: DBObjectId, tick: number) => {
+ const history = await container.historyService.getHistoryByTick(gameId, tick);
+
+ return history;
+}
+
+const applyGameState = (game: Game, history: GameHistory) => {
+ game.state.tick = history.tick;
+ game.state.productionTick = history.productionTick;
+ game.state.lastTickDate = new Date();
+}
+
+const applyPlayers = (game: Game, history: GameHistory) => {
+ game.galaxy.players.forEach(player => {
+ const histPlayer = history.players.find(p => p.playerId.toString() === player._id.toString());
+
+ if (!histPlayer) {
+ console.log(`Player ${player._id}/${player.alias} not found in history`);
+
+ return;
+ }
+
+ player.userId = histPlayer.userId;
+ player.alias = histPlayer.alias;
+ player.avatar = histPlayer.avatar;
+ player.researchingNow = histPlayer.researchingNow;
+ player.researchingNext = histPlayer.researchingNext;
+ player.credits = histPlayer.credits;
+ player.creditsSpecialists = histPlayer.creditsSpecialists;
+ player.isOpenSlot = histPlayer.isOpenSlot;
+ player.defeated = histPlayer.defeated;
+ player.defeatedDate = histPlayer.defeatedDate;
+ player.afk = histPlayer.afk;
+ player.ready = histPlayer.ready;
+ player.readyToQuit = histPlayer.readyToQuit;
+ player.research = histPlayer.research;
+ });
+}
+
+const applyStars = (game: Game, history: GameHistory) => {
+ game.galaxy.stars.forEach(star => {
+ const histStar = history.stars.find(s => s.starId.toString() === star._id.toString());
+
+ if (!histStar) {
+ console.log(`Star ${star._id}/${star.name} not found in history`);
+
+ return;
+ }
+
+ star.homeStar = histStar.homeStar
+ star.ships = histStar.ships;
+ star.shipsActual = histStar.shipsActual;
+ star.specialistId = histStar.specialistId;
+ star.naturalResources = histStar.naturalResources;
+ star.infrastructure = histStar.infrastructure;
+ star.ignoreBulkUpgrade = histStar.ignoreBulkUpgrade;
+ star.warpGate = histStar.warpGate;
+ star.ownedByPlayerId = histStar.ownedByPlayerId;
+ star.location = histStar.location;
+ });
+}
+
+const applyWaypoints = (carrier: Carrier, histCarrier: GameHistoryCarrier) => {
+ const historyWaypoint = histCarrier.waypoints[0];
+
+ if (!historyWaypoint) {
+ return;
+ }
+
+ carrier.waypoints = [{
+ _id: objectId(),
+ source: historyWaypoint.source,
+ destination: historyWaypoint.destination,
+ action: "nothing",
+ actionShips: 0,
+ delayTicks: 0
+ }];
+}
+
+const applyCarriers = (game: Game, history: GameHistory) => {
+ const removeCarriers = new Array();
+
+ game.galaxy.carriers.forEach(carrier => {
+ const histCarrier = history.carriers.find(c => c.carrierId.toString() === carrier._id.toString());
+
+ if (!histCarrier) {
+ console.log(`Carrier ${carrier._id}/${carrier.name} not found in history`);
+
+ removeCarriers.push(carrier._id);
+ return;
+ }
+
+ carrier.ships = histCarrier.ships;
+ carrier.specialistId = histCarrier.specialistId
+ carrier.ownedByPlayerId = histCarrier.ownedByPlayerId;
+ carrier.name = histCarrier.name;
+ carrier.location = histCarrier.location
+ carrier.isGift = histCarrier.isGift;
+ carrier.orbiting = histCarrier.orbiting;
+ applyWaypoints(carrier, histCarrier);
+ });
+
+ game.galaxy.carriers = game.galaxy.carriers.filter(c => !removeCarriers.includes(c._id));
+}
+
+const applyHistory = (game: Game, history: GameHistory) => {
+ applyGameState(game, history);
+ applyPlayers(game, history);
+ applyStars(game, history);
+ applyCarriers(game, history);
+}
+
+const startup = async () => {
+ mongo = await mongooseLoader(config, {
+ syncIndexes: true,
+ poolSize: 1
+ });
+
+ container = containerLoader(config);
+
+ console.log("Initialised");
+
+ const gameIdS = process.argv[2];
+ const tick = Number.parseInt(process.argv[3]);
+
+ if (!gameIdS || !tick) {
+ throw new Error("Invalid arguments. Usage: npm run restore-game ");
+ }
+
+ const gameId = new mongoose.Types.ObjectId(gameIdS) as DBObjectId;
+
+ const hist = await loadHistory(gameId, tick);
+ if (!hist) {
+ throw new Error("History not found");
+ }
+
+ console.log("Loaded history entry");
+
+ const currentState = await container.gameService.getByIdAll(gameId);
+ if (!currentState) {
+ throw new Error("Game not found");
+ }
+
+ console.log("Loaded current game state");
+
+ applyHistory(currentState, hist);
+
+ console.log("History applied");
+
+ await currentState.save();
+
+ console.log("Game state saved");
+}
+
+process.on('SIGINT', async () => {
+ await shutdown();
+});
+
+const shutdown = async () =>{
+ console.log('Shutting down...');
+
+ await mongo.disconnect();
+
+ console.log('Shutdown complete.');
+
+ process.exit();
+}
+
+startup().then(async () => {
+ console.log('Done.');
+
+ await shutdown();
+}).catch(async err => {
+ console.error(err);
+
+ await shutdown();
+});
+
+export {};
diff --git a/server/jobs/cleanupGamesTimedOut.ts b/server/jobs/cleanupGamesTimedOut.ts
index 72fdb7fec..aaaa5bef8 100644
--- a/server/jobs/cleanupGamesTimedOut.ts
+++ b/server/jobs/cleanupGamesTimedOut.ts
@@ -1,4 +1,7 @@
import { DependencyContainer } from "../services/types/DependencyContainer";
+import {logger} from "../utils/logging";
+
+const log = logger("Cleanup Games Timed Out Job");
export default (container: DependencyContainer) => {
@@ -19,13 +22,13 @@ export default (container: DependencyContainer) => {
await container.emailService.sendGameTimedOutEmail(game._id);
await container.gameService.delete(game);
} catch (e) {
- console.error(e);
+ log.error(e);
}
}
done();
} catch (e) {
- console.error("CleanupGamesTimedOut job threw unhandled: " + e, e);
+ log.error("CleanupGamesTimedOut job threw unhandled: " + e, e);
}
}
};
diff --git a/server/jobs/cleanupOldGameHistory.ts b/server/jobs/cleanupOldGameHistory.ts
index 51fc64982..cbb7ceac4 100644
--- a/server/jobs/cleanupOldGameHistory.ts
+++ b/server/jobs/cleanupOldGameHistory.ts
@@ -1,4 +1,7 @@
import { DependencyContainer } from "../services/types/DependencyContainer";
+import {logger} from "../utils/logging";
+
+const log = logger("Cleanup Old Game History Job");
export default (container: DependencyContainer) => {
@@ -22,22 +25,22 @@ export default (container: DependencyContainer) => {
continue
}
- console.log(`Deleting history for old game: ${game._id}`);
+ log.info(`Deleting history for old game: ${game._id}`);
try {
await container.historyService.deleteByGameId(game._id);
await container.eventService.deleteByGameId(game._id);
await container.gameService.markAsCleaned(game._id);
} catch (e) {
- console.error(e);
+ log.error(e);
}
}
- console.log('Cleanup completed.');
+ log.info('Cleanup completed.');
done();
} catch (e) {
- console.error("CleanupOldGameHistory job threw unhandled: " + e, e);
+ log.error("CleanupOldGameHistory job threw unhandled: " + e, e);
}
}
diff --git a/server/jobs/cleanupOldTutorials.ts b/server/jobs/cleanupOldTutorials.ts
index a113e44de..264c444f0 100644
--- a/server/jobs/cleanupOldTutorials.ts
+++ b/server/jobs/cleanupOldTutorials.ts
@@ -1,4 +1,7 @@
import { DependencyContainer } from "../services/types/DependencyContainer";
+import {logger} from "../utils/logging";
+
+const log = logger("Cleanup Old Tutorials Job");
export default (container: DependencyContainer) => {
@@ -14,13 +17,13 @@ export default (container: DependencyContainer) => {
try {
await container.gameService.delete(game);
} catch (e) {
- console.error(e);
+ log.error(e);
}
}
done();
} catch (e) {
- console.error("CleanupOldTutorials job threw unhandled: " + e, e);
+ log.error("CleanupOldTutorials job threw unhandled: " + e, e);
}
}
};
diff --git a/server/jobs/gameTick.ts b/server/jobs/gameTick.ts
index 8552a97fd..dce3964c6 100644
--- a/server/jobs/gameTick.ts
+++ b/server/jobs/gameTick.ts
@@ -3,6 +3,9 @@ import { DBObjectId } from "../services/types/DBObjectId";
import { DependencyContainer } from "../services/types/DependencyContainer";
import { Game } from "../services/types/Game";
import { GameMutexLock } from "../services/types/GameMutexLock";
+import {logger} from "../utils/logging";
+
+const log = logger("Game Tick Job");
export default (container: DependencyContainer) => {
@@ -31,7 +34,7 @@ export default (container: DependencyContainer) => {
}
}
catch (e) {
- console.error(`Error in game ${game.settings.general.name} (${game._id})`, e);
+ log.error(`Error in game ${game.settings.general.name} (${game._id})`, e);
}
finally {
await container.gameLockService.lock(gameId, false);
@@ -39,7 +42,7 @@ export default (container: DependencyContainer) => {
}
}
catch (e) {
- console.error(e);
+ log.error(e);
}
finally {
//console.log(`tryTickGame() finished!`);
@@ -59,7 +62,7 @@ export default (container: DependencyContainer) => {
done();
} catch (e) {
- console.error("GameTick job threw unhandled: " + e, e);
+ log.error("GameTick job threw unhandled: " + e, e);
}
}
diff --git a/server/jobs/index.ts b/server/jobs/index.ts
index 8897912cc..9c479fc89 100644
--- a/server/jobs/index.ts
+++ b/server/jobs/index.ts
@@ -1,3 +1,5 @@
+import {logger, onReady, setupLogging} from "../utils/logging";
+
const Agenda = require('agenda');
import config from '../config';
import mongooseLoader from '../db';
@@ -11,6 +13,11 @@ import CleanupOldTutorialsJob from './cleanupOldTutorials';
import SendReviewRemindersJob from './sendReviewReminders';
let mongo;
+Error.stackTraceLimit = 1000;
+
+setupLogging();
+
+const log = logger();
async function startup() {
const container = containerLoader(config);
@@ -26,9 +33,9 @@ async function startup() {
// ------------------------------
// Jobs that run every time the server restarts.
- console.log('Unlock all games...');
+ log.info('Unlock all games...');
await container.gameService.lockAll(false);
- console.log('All games unlocked');
+ log.info('All games unlocked');
// ------------------------------
@@ -103,20 +110,20 @@ async function startup() {
agendajs.every('10 seconds', 'send-review-reminders'); // TODO: Every 10 seconds until we've gone through all backlogged users.
process.on('SIGINT', async () => {
- console.log('Shutting down...');
+ log.info('Shutting down...');
await agendajs.stop();
await mongo.disconnect();
- console.log('Shutdown complete.');
+ log.info('Shutdown complete.');
- process.exit(0);
+ onReady(() => process.exit());
});
}
startup().then(() => {
- console.log('Jobs started.');
+ log.info('Jobs started.');
});
export {};
diff --git a/server/jobs/officialGamesCheck.ts b/server/jobs/officialGamesCheck.ts
index 404c2b00b..bc5f4fe4b 100644
--- a/server/jobs/officialGamesCheck.ts
+++ b/server/jobs/officialGamesCheck.ts
@@ -1,6 +1,9 @@
import {DependencyContainer} from "../services/types/DependencyContainer";
import {Game, GameSettings} from "../services/types/Game";
import {OfficialGameCategory, OfficialGameKind} from "../config/officialGames";
+import {logger} from "../utils/logging";
+
+const log = logger("Official Games Check Job");
const chooseSetting = (container: DependencyContainer, category: OfficialGameCategory): GameSettings => {
if (category.kind === OfficialGameKind.Standard) {
@@ -36,7 +39,7 @@ export default (container: DependencyContainer) => {
const existingOpen = findExistingGame(category, openGames);
if (!existingOpen) {
- console.log(`Could not find game [${container.gameTypeService.getOfficialGameCategoryName(category)}], creating it now...`);
+ log.info(`Could not find game [${container.gameTypeService.getOfficialGameCategoryName(category)}], creating it now...`);
const existingRunning = findExistingGame(category, runningGames);
const existingTemplate = existingRunning?.settings.general.createdFromTemplate;
@@ -53,16 +56,16 @@ export default (container: DependencyContainer) => {
try {
const newGame = await container.gameCreateService.create(newSetting);
- console.log(`${newGame.settings.general.type} game created: ${newGame.settings.general.name}`);
+ log.info(`${newGame.settings.general.type} game created: ${newGame.settings.general.name}`);
} catch (e) {
- console.error(e);
+ log.error(e);
}
}
}
done();
} catch (e) {
- console.error("OfficialGamesCheck job threw unhandled: " + e, e);
+ log.error("OfficialGamesCheck job threw unhandled: " + e, e);
}
}
diff --git a/server/jobs/sendReviewReminders.ts b/server/jobs/sendReviewReminders.ts
index 0548edced..fad5cc1e0 100644
--- a/server/jobs/sendReviewReminders.ts
+++ b/server/jobs/sendReviewReminders.ts
@@ -1,4 +1,5 @@
import { DependencyContainer } from "../services/types/DependencyContainer";
+import {logger} from "../utils/logging";
function sleep(ms: number) {
return new Promise((resolve) => {
@@ -6,6 +7,8 @@ function sleep(ms: number) {
});
}
+const log = logger("Send Review Reminders Job");
+
export default (container: DependencyContainer) => {
return {
@@ -23,7 +26,7 @@ export default (container: DependencyContainer) => {
try {
await container.emailService.sendReviewReminderEmail(user);
} catch (e) {
- console.error(e);
+ log.error(e);
} finally {
await container.userService.setReviewReminderEmailSent(user._id, true);
}
@@ -33,7 +36,7 @@ export default (container: DependencyContainer) => {
done();
} catch (e) {
- console.error("SendReviewReminders job threw unhandled: " + e, e);
+ log.error("SendReviewReminders job threw unhandled: " + e, e);
}
}
diff --git a/server/package-lock.json b/server/package-lock.json
index f216f415e..0e53956e2 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -30,6 +30,8 @@
"mongoose": "^5.12.8",
"mongoose-lean-defaults": "^0.4.1",
"nodemailer": "^6.5.0",
+ "pino": "^9.5.0",
+ "pino-pretty": "^11.3.0",
"qheap": "^1.4.0",
"random-seed": "^0.3.0",
"recaptcha-v2": "^0.1.3",
@@ -519,6 +521,14 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
+ "node_modules/atomic-sleep": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
+ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/axios": {
"version": "0.18.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz",
@@ -547,6 +557,25 @@
"node": ">= 0.6.0"
}
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@@ -661,6 +690,29 @@
"node": ">=0.6.19"
}
},
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -730,6 +782,11 @@
"color-support": "bin.js"
}
},
+ "node_modules/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -973,6 +1030,14 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
+ "node_modules/dateformat": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
+ "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -1096,6 +1161,14 @@
"node": ">= 0.8"
}
},
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
"node_modules/engine.io": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.0.tgz",
@@ -1224,6 +1297,14 @@
"node": ">=6"
}
},
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
@@ -1379,6 +1460,24 @@
}
]
},
+ "node_modules/fast-copy": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz",
+ "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="
+ },
+ "node_modules/fast-redact": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
+ "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fast-safe-stringify": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
+ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
+ },
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -1615,6 +1714,11 @@
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
+ "node_modules/help-me": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
+ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="
+ },
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -1658,6 +1762,25 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
@@ -1805,6 +1928,14 @@
"@sideway/pinpoint": "^2.0.0"
}
},
+ "node_modules/joycon": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
+ "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -1941,7 +2072,6 @@
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
- "dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -2289,6 +2419,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/on-exit-leak-free": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
+ "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -2373,6 +2511,117 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pino": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/pino/-/pino-9.5.0.tgz",
+ "integrity": "sha512-xSEmD4pLnV54t0NOUN16yCl7RIB1c5UUOse5HSyEXtBp+FgFQyPeDutc+Q2ZO7/22vImV7VfEjH/1zV2QuqvYw==",
+ "dependencies": {
+ "atomic-sleep": "^1.0.0",
+ "fast-redact": "^3.1.1",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "^2.0.0",
+ "pino-std-serializers": "^7.0.0",
+ "process-warning": "^4.0.0",
+ "quick-format-unescaped": "^4.0.3",
+ "real-require": "^0.2.0",
+ "safe-stable-stringify": "^2.3.1",
+ "sonic-boom": "^4.0.1",
+ "thread-stream": "^3.0.0"
+ },
+ "bin": {
+ "pino": "bin.js"
+ }
+ },
+ "node_modules/pino-abstract-transport": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
+ "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
+ "dependencies": {
+ "split2": "^4.0.0"
+ }
+ },
+ "node_modules/pino-pretty": {
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.3.0.tgz",
+ "integrity": "sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA==",
+ "dependencies": {
+ "colorette": "^2.0.7",
+ "dateformat": "^4.6.3",
+ "fast-copy": "^3.0.2",
+ "fast-safe-stringify": "^2.1.1",
+ "help-me": "^5.0.0",
+ "joycon": "^3.1.1",
+ "minimist": "^1.2.6",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "^2.0.0",
+ "pump": "^3.0.0",
+ "readable-stream": "^4.0.0",
+ "secure-json-parse": "^2.4.0",
+ "sonic-boom": "^4.0.1",
+ "strip-json-comments": "^3.1.1"
+ },
+ "bin": {
+ "pino-pretty": "bin.js"
+ }
+ },
+ "node_modules/pino-pretty/node_modules/readable-stream": {
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
+ "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
+ "dependencies": {
+ "abort-controller": "^3.0.0",
+ "buffer": "^6.0.3",
+ "events": "^3.3.0",
+ "process": "^0.11.10",
+ "string_decoder": "^1.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/pino-pretty/node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/pino-pretty/node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/pino-pretty/node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pino-std-serializers": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
+ "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="
+ },
"node_modules/prism-media": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.4.tgz",
@@ -2398,11 +2647,24 @@
}
}
},
+ "node_modules/process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
+ "node_modules/process-warning": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz",
+ "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw=="
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -2415,6 +2677,15 @@
"node": ">= 0.10"
}
},
+ "node_modules/pump": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
+ "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
"node_modules/qheap": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/qheap/-/qheap-1.4.0.tgz",
@@ -2437,6 +2708,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/quick-format-unescaped": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
+ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
+ },
"node_modules/random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
@@ -2504,6 +2780,14 @@
"node": ">=8.10.0"
}
},
+ "node_modules/real-require": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
+ "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
+ "engines": {
+ "node": ">= 12.13.0"
+ }
+ },
"node_modules/recaptcha-v2": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/recaptcha-v2/-/recaptcha-v2-0.1.3.tgz",
@@ -2573,6 +2857,14 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
+ "node_modules/safe-stable-stringify": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
+ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -2590,6 +2882,11 @@
"node": ">=6"
}
},
+ "node_modules/secure-json-parse": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
+ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
+ },
"node_modules/semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@@ -2780,6 +3077,14 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
},
+ "node_modules/sonic-boom": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
+ "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
+ "dependencies": {
+ "atomic-sleep": "^1.0.0"
+ }
+ },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -2808,6 +3113,14 @@
"memory-pager": "^1.0.2"
}
},
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -2894,6 +3207,14 @@
"node": ">=10"
}
},
+ "node_modules/thread-stream": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
+ "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
+ "dependencies": {
+ "real-require": "^0.2.0"
+ }
+ },
"node_modules/to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
@@ -3624,6 +3945,11 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
+ "atomic-sleep": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
+ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="
+ },
"axios": {
"version": "0.18.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz",
@@ -3648,6 +3974,11 @@
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
"integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg=="
},
+ "base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
+ },
"base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@@ -3744,6 +4075,15 @@
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
"integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg=="
},
+ "buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "requires": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -3790,6 +4130,11 @@
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="
},
+ "colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="
+ },
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -3977,6 +4322,11 @@
}
}
},
+ "dateformat": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
+ "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="
+ },
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -4070,6 +4420,14 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
},
+ "end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
"engine.io": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.0.tgz",
@@ -4162,6 +4520,11 @@
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
+ "events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
+ },
"express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
@@ -4276,6 +4639,21 @@
}
}
},
+ "fast-copy": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz",
+ "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="
+ },
+ "fast-redact": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
+ "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="
+ },
+ "fast-safe-stringify": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
+ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
+ },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -4468,6 +4846,11 @@
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
+ "help-me": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
+ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="
+ },
"http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -4502,6 +4885,11 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
+ "ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
+ },
"indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
@@ -4608,6 +4996,11 @@
"@sideway/pinpoint": "^2.0.0"
}
},
+ "joycon": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
+ "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="
+ },
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -4712,8 +5105,7 @@
"minimist": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
- "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
- "dev": true
+ "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
},
"minipass": {
"version": "5.0.0",
@@ -4947,6 +5339,11 @@
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
},
+ "on-exit-leak-free": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
+ "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="
+ },
"on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -5010,17 +5407,111 @@
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true
},
+ "pino": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/pino/-/pino-9.5.0.tgz",
+ "integrity": "sha512-xSEmD4pLnV54t0NOUN16yCl7RIB1c5UUOse5HSyEXtBp+FgFQyPeDutc+Q2ZO7/22vImV7VfEjH/1zV2QuqvYw==",
+ "requires": {
+ "atomic-sleep": "^1.0.0",
+ "fast-redact": "^3.1.1",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "^2.0.0",
+ "pino-std-serializers": "^7.0.0",
+ "process-warning": "^4.0.0",
+ "quick-format-unescaped": "^4.0.3",
+ "real-require": "^0.2.0",
+ "safe-stable-stringify": "^2.3.1",
+ "sonic-boom": "^4.0.1",
+ "thread-stream": "^3.0.0"
+ }
+ },
+ "pino-abstract-transport": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
+ "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
+ "requires": {
+ "split2": "^4.0.0"
+ }
+ },
+ "pino-pretty": {
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.3.0.tgz",
+ "integrity": "sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA==",
+ "requires": {
+ "colorette": "^2.0.7",
+ "dateformat": "^4.6.3",
+ "fast-copy": "^3.0.2",
+ "fast-safe-stringify": "^2.1.1",
+ "help-me": "^5.0.0",
+ "joycon": "^3.1.1",
+ "minimist": "^1.2.6",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "^2.0.0",
+ "pump": "^3.0.0",
+ "readable-stream": "^4.0.0",
+ "secure-json-parse": "^2.4.0",
+ "sonic-boom": "^4.0.1",
+ "strip-json-comments": "^3.1.1"
+ },
+ "dependencies": {
+ "readable-stream": {
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
+ "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
+ "requires": {
+ "abort-controller": "^3.0.0",
+ "buffer": "^6.0.3",
+ "events": "^3.3.0",
+ "process": "^0.11.10",
+ "string_decoder": "^1.3.0"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+ },
+ "string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
+ }
+ }
+ },
+ "pino-std-serializers": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
+ "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="
+ },
"prism-media": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.4.tgz",
"integrity": "sha512-eW7LXORkTCQznZs+eqe9VjGOrLBxcBPXgNyHXMTSRVhphvd/RrxgIR7WaWt4fkLuhshcdT5KHL88LAfcvS3f5g==",
"requires": {}
},
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="
+ },
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
+ "process-warning": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz",
+ "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw=="
+ },
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -5030,6 +5521,15 @@
"ipaddr.js": "1.9.1"
}
},
+ "pump": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
+ "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
"qheap": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/qheap/-/qheap-1.4.0.tgz",
@@ -5043,6 +5543,11 @@
"side-channel": "^1.0.4"
}
},
+ "quick-format-unescaped": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
+ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
+ },
"random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
@@ -5095,6 +5600,11 @@
"picomatch": "^2.2.1"
}
},
+ "real-require": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
+ "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="
+ },
"recaptcha-v2": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/recaptcha-v2/-/recaptcha-v2-0.1.3.tgz",
@@ -5149,6 +5659,11 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
+ "safe-stable-stringify": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
+ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="
+ },
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -5163,6 +5678,11 @@
"sparse-bitfield": "^3.0.3"
}
},
+ "secure-json-parse": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
+ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
+ },
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@@ -5349,6 +5869,14 @@
}
}
},
+ "sonic-boom": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
+ "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
+ "requires": {
+ "atomic-sleep": "^1.0.0"
+ }
+ },
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -5374,6 +5902,11 @@
"memory-pager": "^1.0.2"
}
},
+ "split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
+ },
"statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -5436,6 +5969,14 @@
"yallist": "^4.0.0"
}
},
+ "thread-stream": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
+ "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
+ "requires": {
+ "real-require": "^0.2.0"
+ }
+ },
"to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
diff --git a/server/package.json b/server/package.json
index e7aa15dd4..9cc3b4513 100644
--- a/server/package.json
+++ b/server/package.json
@@ -12,6 +12,7 @@
"start-db-migrate:prod": "node dist/db/migrate.js",
"start-db-recalc-ranks:dev": "ts-node-dev --transpile-only --respawn --inspect=9234 db/recalculateRankings.ts",
"start-db-recalc-ranks:prod": "node dist/db/recalculateRankings.js",
+ "restore-game": "ts-node-dev --transpile-only db/restoreGame.ts",
"prebuild": "rm -rf ./dist",
"build": "tsc",
"postbuild": "cp -r services/emailTemplates ./dist/services/emailTemplates && cp .env ./dist | true",
@@ -42,6 +43,8 @@
"mongoose": "^5.12.8",
"mongoose-lean-defaults": "^0.4.1",
"nodemailer": "^6.5.0",
+ "pino": "^9.5.0",
+ "pino-pretty": "^11.3.0",
"qheap": "^1.4.0",
"random-seed": "^0.3.0",
"recaptcha-v2": "^0.1.3",
diff --git a/server/services/ai.ts b/server/services/ai.ts
index b1c551c27..7768f6f1b 100644
--- a/server/services/ai.ts
+++ b/server/services/ai.ts
@@ -22,6 +22,7 @@ import BasicAIService from "./basicAi";
import PlayerAfkService from "./playerAfk";
import ShipService from "./ship";
import PathfindingService from "./pathfinding";
+import {logger} from "../utils/logging";
const Heap = require('qheap');
const mongoose = require("mongoose");
@@ -123,6 +124,8 @@ interface Movement {
score: number;
}
+const log = logger("AI Service");
+
// IMPORTANT IMPLEMENTATION NOTES
// During AI tick, care must be taken to NEVER write any changes to the database.
// This is performed automatically by mongoose (when calling game.save()).
@@ -203,7 +206,7 @@ export default class AIService {
await this.basicAIService._doBasicLogic(game, player, isFirstTickOfCycle, isLastTickOfCycle);
}
} catch (e) {
- console.error(e);
+ log.error(e);
}
}
diff --git a/server/services/discord.ts b/server/services/discord.ts
index f1557db1f..33aa7f751 100644
--- a/server/services/discord.ts
+++ b/server/services/discord.ts
@@ -2,9 +2,12 @@ import ValidationError from '../errors/validation';
import Repository from './repository';
import { Config } from '../config/types/Config';
import { User } from './types/User';
+import {logger} from "../utils/logging";
const Discord = require('discord.js');
+const log = logger("Discord Service");
+
export default class DiscordService {
config: Config;
userRepo: Repository;
@@ -24,7 +27,7 @@ export default class DiscordService {
this.client = new Discord.Client()
await this.client.login(this.config.discord.botToken);
- console.log('Discord Initialized');
+ log.info('Discord Initialized');
}
}
@@ -102,7 +105,7 @@ export default class DiscordService {
embed: messageTemplate
});
} catch (err) {
- console.error(err);
+ log.error(err);
}
}
@@ -126,7 +129,7 @@ export default class DiscordService {
embed: messageTemplate
});
} catch (err) {
- console.error(err);
+ log.error(err);
}
}
}
\ No newline at end of file
diff --git a/server/services/email.ts b/server/services/email.ts
index 6689fb5fa..28928d5d4 100644
--- a/server/services/email.ts
+++ b/server/services/email.ts
@@ -14,15 +14,18 @@ import GamePlayerAFKEvent from "./types/events/GamePlayerAFK";
import { BaseGameEvent } from "./types/events/BaseGameEvent";
import GameJoinService, { GameJoinServiceEvents } from "./gameJoin";
import PlayerReadyService, { PlayerReadyServiceEvents } from "./playerReady";
+import {logger} from "../utils/logging";
const nodemailer = require('nodemailer');
const fs = require('fs');
const path = require('path');
+const log = logger("Email Service");
+
function getFakeTransport() {
return {
async sendMail(message) {
- console.log(`SMTP DISABLED`);
+ log.info(`SMTP DISABLED`);
// console.log(message.text);
// console.log(message.html);
}
@@ -162,7 +165,7 @@ export default class EmailService {
text
};
- console.log(`EMAIL: [${message.to}] - ${subject}`);
+ log.info(`EMAIL: [${message.to}] - ${subject}`);
return await transport.sendMail(message);
}
@@ -176,8 +179,8 @@ export default class EmailService {
subject,
html
};
-
- console.log(`EMAIL HTML: [${message.to}] - ${subject}`);
+
+ log.info(`EMAIL HTML: [${message.to}] - ${subject}`);
return await transport.sendMail(message);
}
@@ -214,7 +217,7 @@ export default class EmailService {
try {
await this.sendTemplate(user.email, this.TEMPLATES.WELCOME, [user.username]);
} catch (err) {
- console.error(err);
+ log.error(err);
}
}
@@ -411,7 +414,7 @@ export default class EmailService {
try {
await this.sendTemplate(user.email, template, args);
} catch (err) {
- console.error(err);
+ log.error(err);
}
}
}
diff --git a/server/services/game.ts b/server/services/game.ts
index 3c9b39178..3553b2df5 100644
--- a/server/services/game.ts
+++ b/server/services/game.ts
@@ -22,6 +22,7 @@ import GamePlayerDefeatedEvent from './types/events/GamePlayerDefeated';
import {LeaderboardPlayer} from "./types/Leaderboard";
import GameJoinService from "./gameJoin";
import GameAuthService from "./gameAuth";
+import cluster from "cluster";
export const GameServiceEvents = {
onPlayerQuit: 'onPlayerQuit',
@@ -259,6 +260,29 @@ export default class GameService extends EventEmitter {
});
}
+ async kickPlayer(game: Game, kickingUser: DBObjectId, playerToKick: DBObjectId) {
+ if (!await this.gameAuthService.isGameAdmin(game, kickingUser)) {
+ throw new ValidationError('You do not have permission to force start this game.');
+ }
+
+ console.log({
+ kickingUser,
+ playerToKick
+ })
+
+ const player = game.galaxy.players.find(p => p._id.toString() === playerToKick.toString());
+
+ if (!player) {
+ throw new ValidationError('Player not found');
+ }
+
+ if (game.state.startDate) {
+ await this.concedeDefeat(game, player, true);
+ } else {
+ await this.quit(game, player);
+ }
+ }
+
async forceStart(game: Game, forceStartingUserId: DBObjectId) {
if (!await this.gameAuthService.isGameAdmin(game, forceStartingUserId)) {
throw new ValidationError('You do not have permission to force start this game.');
diff --git a/server/services/gameTick.ts b/server/services/gameTick.ts
index 25e4bef5f..2e0bf562e 100644
--- a/server/services/gameTick.ts
+++ b/server/services/gameTick.ts
@@ -41,10 +41,13 @@ import ShipService from "./ship";
import ScheduleBuyService from "./scheduleBuy";
import {Moment} from "moment";
import GameLockService from "./gameLock";
+import {logger} from "../utils/logging";
const EventEmitter = require('events');
const moment = require('moment');
+const log = logger("Game Tick Service");
+
export const GameTickServiceEvents = {
onPlayerGalacticCycleCompleted: 'onPlayerGalacticCycleCompleted',
onGameCycleEnded: 'onGameCycleEnded',
@@ -159,7 +162,7 @@ export default class GameTickService extends EventEmitter {
const game = (await this.gameService.getByIdAll(gameId));
if (!game) {
- console.error(`Game not found: ${gameId}`);
+ log.error(`Game not found: ${gameId}`);
return;
}
@@ -179,7 +182,10 @@ export default class GameTickService extends EventEmitter {
*/
let startTime = process.hrtime();
- console.log(`[${game.settings.general.name}] - Game tick started at ${new Date().toISOString()}`);
+ log.info({
+ gameId: game._id,
+ gameName: game.settings.general.name
+ }, `[${game.settings.general.name}] - Game tick started at ${new Date().toISOString()}`);
game.state.lastTickDate = moment().utc();
game.state.forceTick = false;
@@ -190,7 +196,11 @@ export default class GameTickService extends EventEmitter {
let logTime = (taskName: string) => {
taskTimeEnd = process.hrtime(taskTime);
taskTime = process.hrtime();
- console.log(`[${game.settings.general.name}] - ${taskName}: %ds %dms'`, taskTimeEnd[0], taskTimeEnd[1] / 1000000);
+ log.info({
+ gameId: game._id,
+ gameName: game.settings.general.name,
+ tick: game.state.tick
+ }, `[${game.settings.general.name}] - ${taskName}: %ds %dms'`, taskTimeEnd[0], taskTimeEnd[1] / 1000000);
};
let gameUsers = await this.userService.getGameUsers(game);
@@ -206,9 +216,20 @@ export default class GameTickService extends EventEmitter {
this.playerService.incrementMissedTurns(game);
}
+ // Check if win condition was reached before the tick (for example due to RTQ)
+ let hasWinnerBeforeTick = this._gameWinCheck(game, gameUsers);
+ if (hasWinnerBeforeTick) {
+ log.info({
+ gameId: game._id,
+ gameName: game.settings.general.name,
+ tick: game.state.tick
+ }, `Game has reached a win condition before the tick. Tick processing will be skipped.`);
+ iterations = 0;
+ }
+
let hasProductionTicked: boolean = false;
- while (iterations--) {
+ while (iterations > 0) {
if (!await this.gameLockService.isLockedInDatabase(game._id)) {
throw new Error(`The game was not locked after game processing, concurrency issue?`);
}
@@ -275,6 +296,8 @@ export default class GameTickService extends EventEmitter {
if (hasWinner) {
break;
}
+
+ iterations--;
}
// TODO: This has been moved out of _moveCarriers, see comment in there.
@@ -301,7 +324,10 @@ export default class GameTickService extends EventEmitter {
let endTime = process.hrtime(startTime);
- console.log(`[${game.settings.general.name}] - Game tick ended: %ds %dms'`, endTime[0], endTime[1] / 1000000);
+ log.info({
+ gameId: game._id,
+ gameName: game.settings.general.name
+ }, `[${game.settings.general.name}] - Game tick ended: %ds %dms'`, endTime[0], endTime[1] / 1000000);
}
canTick(game: Game) {
diff --git a/server/services/history.ts b/server/services/history.ts
index d7d5f51a8..b65473164 100644
--- a/server/services/history.ts
+++ b/server/services/history.ts
@@ -1,3 +1,5 @@
+import GameStateService from "./gameState";
+
const cache = require('memory-cache');
import { DBObjectId } from './types/DBObjectId';
import ValidationError from '../errors/validation';
@@ -13,30 +15,34 @@ export default class HistoryService {
playerService: PlayerService;
gameService: GameService;
playerStatisticsService: PlayerStatisticsService;
+ gameStateService: GameStateService;
constructor(
historyRepo: Repository,
playerService: PlayerService,
gameService: GameService,
- playerStatisticsService: PlayerStatisticsService
+ playerStatisticsService: PlayerStatisticsService,
+ gameStateService: GameStateService,
) {
this.historyRepo = historyRepo;
this.playerService = playerService;
this.gameService = gameService;
this.playerStatisticsService = playerStatisticsService;
+ this.gameStateService = gameStateService;
this.gameService.on('onGameDeleted', (args) => this.deleteByGameId(args.gameId));
}
async listIntel(gameId: DBObjectId, startTick: number, endTick: number) {
- let settings = await this.gameService.getGameSettings(gameId);
+ const game = await this.gameService.getById(gameId);
- if (!settings || settings.specialGalaxy.darkGalaxy === 'extra') {
+ // change here
+ if (!game?.settings || (game?.settings.specialGalaxy.darkGalaxy === 'extra' && !this.gameStateService.isFinished(game))) {
throw new ValidationError('Intel is not available in this game mode.');
}
startTick = startTick || 0;
- endTick = endTick || Number.MAX_VALUE;;
+ endTick = endTick || Number.MAX_VALUE;
let cacheKey = `intel_${gameId}_${startTick}_${endTick}`;
let cached = cache.get(cacheKey);
diff --git a/server/services/index.ts b/server/services/index.ts
index 0b885fe02..9e218ac16 100644
--- a/server/services/index.ts
+++ b/server/services/index.ts
@@ -113,10 +113,13 @@ import PlayerColourService from "./playerColour";
import GameMaskingService from "./gameMaskingService";
import SessionService from "./session";
import starMovementService from "./starMovement";
+import {logger} from "../utils/logging";
const gameNames = require('../config/game/gameNames');
const starNames = require('../config/game/starNames');
+const log = logger("Dependency Container");
+
const gameRepository = new Repository(GameModel);
const userRepository = new Repository(UserModel);
const historyRepository = new Repository(HistoryModel);
@@ -200,7 +203,7 @@ export default (config): DependencyContainer => {
const userLeaderboardService = new UserLeaderboardService(userRepository, guildUserService);
const researchService = new ResearchService(gameRepository, technologyService, randomService, playerStatisticsService, starService, userService, gameTypeService);
const combatService = new CombatService(technologyService, specialistService, playerService, starService, reputationService, diplomacyService, gameTypeService);
- const historyService = new HistoryService(historyRepository, playerService, gameService, playerStatisticsService);
+ const historyService = new HistoryService(historyRepository, playerService, gameService, playerStatisticsService, gameStateService);
const waypointService = new WaypointService(gameRepository, carrierService, starService, distanceService, starDistanceService, technologyService, gameService, playerService, carrierMovementService, gameMaskingService, historyService);
const specialistBanService = new SpecialistBanService(specialistService);
const specialistHireService = new SpecialistHireService(gameRepository, specialistService, achievementService, waypointService, playerCreditsService, starService, gameTypeService, specialistBanService, technologyService);
@@ -231,7 +234,7 @@ export default (config): DependencyContainer => {
const gameMutexService = new GameMutexService();
- console.log('Dependency container initialized.');
+ log.info('Dependency container initialized.');
return {
config,
diff --git a/server/services/notification.ts b/server/services/notification.ts
index ceeb668fa..1e0b23485 100644
--- a/server/services/notification.ts
+++ b/server/services/notification.ts
@@ -16,6 +16,9 @@ import GameEndedEvent from './types/events/GameEnded';
import GameTurnEndedEvent from './types/events/GameTurnEnded';
import ConversationMessageSentEvent from './types/events/ConversationMessageSent';
import GameJoinService, { GameJoinServiceEvents } from './gameJoin';
+import {logger} from "../utils/logging";
+
+const log = logger("Notification Service");
// Note: We only support discord subscriptions at this point, if any new ones are added
// this class will need to be refactored to use something like the strategy pattern.
@@ -81,7 +84,7 @@ export default class NotificationService {
this.tradeService.on(TradeServiceEvents.onPlayerRenownReceived, (args) => this.onPlayerRenownReceived(args.gameId, args.fromPlayer, args.toPlayer, args.amount));
this.tradeService.on(TradeServiceEvents.onPlayerTechnologyReceived, (args) => this.onPlayerTechnologyReceived(args.gameId, args.fromPlayer, args.toPlayer, args.technology));
- console.log('Notifications initialized.')
+ log.info('Notifications initialized.')
}
}
diff --git a/server/services/playerAfk.ts b/server/services/playerAfk.ts
index 70a71d67a..e1d39b701 100644
--- a/server/services/playerAfk.ts
+++ b/server/services/playerAfk.ts
@@ -60,7 +60,7 @@ export default class PlayerAfkService extends EventEmitter {
if (!player.afk) {
// Check if the player has been AFK.
- let isAfk = this.isAfk(game, player);
+ const isAfk = this.isAfk(game, player);
if (isAfk) {
this.setPlayerAsAfk(game, player);
@@ -69,11 +69,11 @@ export default class PlayerAfkService extends EventEmitter {
// Check if the player has been defeated by conquest.
if (!player.defeated) {
- let stars = this.starService.listStarsOwnedByPlayer(game.galaxy.stars, player._id);
+ const stars = this.starService.listStarsOwnedByPlayer(game.galaxy.stars, player._id);
// If there are no stars and there are no carriers then the player is defeated.
if (stars.length === 0) {
- let carriers = this.carrierService.listCarriersOwnedByPlayer(game.galaxy.carriers, player._id); // Note: This logic looks a bit weird, but its more performant.
+ const carriers = this.carrierService.listCarriersOwnedByPlayer(game.galaxy.carriers, player._id); // Note: This logic looks a bit weird, but its more performant.
if (carriers.length === 0) {
this.playerService.setPlayerAsDefeated(game, player, false);
@@ -146,6 +146,11 @@ export default class PlayerAfkService extends EventEmitter {
return false;
}
+ // if the player is ready for turn/cycle in a TB game, they are not afk
+ if (player.ready) {
+ return false;
+ }
+
let lastSeenMoreThanXDaysAgo = moment(player.lastSeen).utc() <= moment().utc().subtract(game.settings.gameTime.afk.lastSeenTimeout, 'days');
if (lastSeenMoreThanXDaysAgo) {
diff --git a/server/services/scheduleBuy.ts b/server/services/scheduleBuy.ts
index 6b6a666a5..5c4615d8c 100644
--- a/server/services/scheduleBuy.ts
+++ b/server/services/scheduleBuy.ts
@@ -1,3 +1,5 @@
+import {logger} from "../utils/logging";
+
const mongoose = require('mongoose');
import ValidationError from '../errors/validation';
@@ -8,6 +10,7 @@ import {Game} from "./types/Game";
import {Player, PlayerScheduledActions} from "./types/Player";
import {InfrastructureType} from './types/Star';
import {ObjectId} from "mongoose";
+import { DBObjectId } from "./types/DBObjectId";
const buyTypeToPriority = {
@@ -19,6 +22,8 @@ const buyTypeToPriority = {
const EventEmitter = require('events');
+const log = logger("Schedule Buy Service");
+
export default class ScheduleBuyService extends EventEmitter {
gameRepo: Repository;
starUpgradeService: StarUpgradeService
@@ -70,7 +75,7 @@ export default class ScheduleBuyService extends EventEmitter {
await this.starUpgradeService.executeBulkUpgradeReport(game, player, report);
} catch (e) {
- console.error(e)
+ log.error(e)
}
}
@@ -80,7 +85,7 @@ export default class ScheduleBuyService extends EventEmitter {
const totalPercentage = percentageActions.reduce((total, cur) => total + cur.amount, 0);
await this._executePercentageAction(game, player, percentageActions, totalPercentage);
} catch (e) {
- console.error(e)
+ log.error(e)
}
// Only keep actions that are repeated or in the future
@@ -139,8 +144,8 @@ export default class ScheduleBuyService extends EventEmitter {
return action;
}
- async toggleBulkRepeat(game: Game, player: Player, actionId: ObjectId) {
- let action = player.scheduledActions.find(a => a._id == actionId);
+ async toggleBulkRepeat(game: Game, player: Player, actionId: DBObjectId) {
+ const action = player.scheduledActions.find(a => a._id.toString() == actionId.toString());
if (!action) {
throw new ValidationError('Action does not exist');
}
@@ -159,8 +164,8 @@ export default class ScheduleBuyService extends EventEmitter {
return action;
}
- async trashAction(game: Game, player: Player, actionId: ObjectId) {
- let action = player.scheduledActions.find(a => a._id == actionId);
+ async trashAction(game: Game, player: Player, actionId: DBObjectId) {
+ const action = player.scheduledActions.find(a => a._id.toString() == actionId.toString());
if (!action) {
throw new ValidationError('Action does not exist');
}
diff --git a/server/services/types/DBObjectId.ts b/server/services/types/DBObjectId.ts
index 1f9b14d63..97d300a9a 100644
--- a/server/services/types/DBObjectId.ts
+++ b/server/services/types/DBObjectId.ts
@@ -1,10 +1,11 @@
import mongoose, { ObjectId } from "mongoose";
-import { ObjectId as MongoObjectId } from "mongodb"
-export interface DBObjectId extends ObjectId {
+export interface DBObjectId extends mongoose.Types.ObjectId {
// equals(id: DBObjectId): boolean; -- Note: We never use this as we cannot ensure that anything that comes through the API layer via params are mongo object IDs unless we explicitly cast them.
getTimestamp(): Date;
toString(): string;
};
-export const objectId = (): DBObjectId => new mongoose.Types.ObjectId() as any;
\ No newline at end of file
+export const objectId = (): DBObjectId => new mongoose.Types.ObjectId() as any;
+
+export const objectIdFromString = (s: string): DBObjectId => new mongoose.Types.ObjectId(s);
\ No newline at end of file
diff --git a/server/services/types/Mutex.ts b/server/services/types/Mutex.ts
index c863ec6ef..1c76c2063 100644
--- a/server/services/types/Mutex.ts
+++ b/server/services/types/Mutex.ts
@@ -1,4 +1,7 @@
import crypto from 'crypto'
+import {logger} from "../../utils/logging";
+
+const log = logger("Mutex");
export class Mutex {
static lastMutexId: number = 0;
@@ -30,7 +33,7 @@ export class Mutex {
return true;
}
else {
- console.warn(`Cannot unlock Mutex wiht id ${this.id} as lockId does not match. Expected: ${lockId}, Actual: ${lockIdInput}`);
+ log.warn(`Cannot unlock Mutex wiht id ${this.id} as lockId does not match. Expected: ${lockId}, Actual: ${lockIdInput}`);
}
return false;
diff --git a/server/utils/logging.ts b/server/utils/logging.ts
new file mode 100644
index 000000000..681880855
--- /dev/null
+++ b/server/utils/logging.ts
@@ -0,0 +1,41 @@
+import {Logger} from "pino";
+import config from '../config';
+
+const pino = require('pino');
+let transport;
+let baseLogger;
+
+export const setupLogging = () => {
+ const loggingT = config.logging || 'stdout';
+
+ if (loggingT === 'pretty') {
+ transport = pino.transport({
+ target: 'pino-pretty'
+ });
+ } else if (loggingT === 'stdout') {
+ transport = pino.transport({
+ target: 'pino/file',
+ options: {destination: 1}
+ });
+ } else {
+ throw new Error(`Invalid logging type: ${loggingT}`);
+ }
+
+ baseLogger = pino(transport);
+}
+
+export const onReady = (callback: () => void) => {
+ transport.on('ready', callback);
+}
+
+export const logger = (name?: string): Logger => {
+ if (!baseLogger) {
+ setupLogging();
+ }
+
+ if (name) {
+ return baseLogger.child({name});
+ } else {
+ return baseLogger;
+ }
+}
\ No newline at end of file