Restructuring as TypeScript
This commit is contained in:
parent
888688a019
commit
a2cb68b421
@ -1,10 +1,11 @@
|
|||||||
const fetch = require('node-fetch');
|
// @ts-nocheck
|
||||||
const WebSocket = require('ws');
|
import fetch from 'node-fetch';
|
||||||
const fs = require('fs').promises;
|
import WebSocket from 'ws';
|
||||||
const calculateLongestRoad = require('./longest-road.js');
|
import fs from 'fs';
|
||||||
|
import calculateLongestRoad from './longest-road.js';
|
||||||
|
|
||||||
const { getValidRoads, getValidCorners } = require('../util/validLocations.js');
|
import { getValidRoads, getValidCorners } from '../util/validLocations.js';
|
||||||
const { layout, staticData } = require('../util/layout.js');
|
import { layout, staticData } from '../util/layout.js';
|
||||||
|
|
||||||
const version = '0.0.1';
|
const version = '0.0.1';
|
||||||
|
|
||||||
@ -24,14 +25,14 @@ const server = process.argv[2];
|
|||||||
const gameId = process.argv[3];
|
const gameId = process.argv[3];
|
||||||
const name = process.argv[4];
|
const name = process.argv[4];
|
||||||
|
|
||||||
const game = {};
|
const game: any = {};
|
||||||
const anyValue = undefined;
|
const anyValue = undefined;
|
||||||
|
|
||||||
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
|
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
|
||||||
|
|
||||||
/* Do not use arrow function as this is rebound to have
|
/* Do not use arrow function as this is rebound to have
|
||||||
* this as the WebSocket */
|
* this as the WebSocket */
|
||||||
let send = function (data) {
|
let send = function (this: WebSocket, data: any) {
|
||||||
if (data.type === 'get') {
|
if (data.type === 'get') {
|
||||||
console.log(`ws - send: get`, data.fields);
|
console.log(`ws - send: get`, data.fields);
|
||||||
} else {
|
} else {
|
||||||
@ -40,7 +41,7 @@ let send = function (data) {
|
|||||||
this.send(JSON.stringify(data));
|
this.send(JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const error = (e) => {
|
const error = (e: any) => {
|
||||||
console.log(`ws - error`, e);
|
console.log(`ws - error`, e);
|
||||||
};
|
};
|
||||||
|
|
@ -1,6 +1,7 @@
|
|||||||
const { layout } = require('../util/layout.js');
|
// @ts-nocheck
|
||||||
|
import { layout } from '../util/layout.js';
|
||||||
|
|
||||||
const processCorner = (game, color, cornerIndex, placedCorner) => {
|
const processCorner = (game: any, color: string, cornerIndex: number, placedCorner: any): number => {
|
||||||
/* If this corner is allocated and isn't assigned to the walking color, skip it */
|
/* If this corner is allocated and isn't assigned to the walking color, skip it */
|
||||||
if (placedCorner.color && placedCorner.color !== color) {
|
if (placedCorner.color && placedCorner.color !== color) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -13,7 +14,7 @@ const processCorner = (game, color, cornerIndex, placedCorner) => {
|
|||||||
placedCorner.walking = true;
|
placedCorner.walking = true;
|
||||||
/* Calculate the longest road branching from both corners */
|
/* Calculate the longest road branching from both corners */
|
||||||
let longest = 0;
|
let longest = 0;
|
||||||
layout.corners[cornerIndex].roads.forEach(roadIndex => {
|
layout.corners[cornerIndex].roads.forEach((roadIndex: number) => {
|
||||||
const placedRoad = game.placements.roads[roadIndex];
|
const placedRoad = game.placements.roads[roadIndex];
|
||||||
if (placedRoad.walking) {
|
if (placedRoad.walking) {
|
||||||
return;
|
return;
|
||||||
@ -32,7 +33,7 @@ const processCorner = (game, color, cornerIndex, placedCorner) => {
|
|||||||
return longest;
|
return longest;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildCornerGraph = (game, color, cornerIndex, placedCorner, set) => {
|
const buildCornerGraph = (game: any, color: string, cornerIndex: number, placedCorner: any, set: any) => {
|
||||||
/* If this corner is allocated and isn't assigned to the walking color, skip it */
|
/* If this corner is allocated and isn't assigned to the walking color, skip it */
|
||||||
if (placedCorner.color && placedCorner.color !== color) {
|
if (placedCorner.color && placedCorner.color !== color) {
|
||||||
return;
|
return;
|
||||||
@ -44,13 +45,13 @@ const buildCornerGraph = (game, color, cornerIndex, placedCorner, set) => {
|
|||||||
|
|
||||||
placedCorner.walking = true;
|
placedCorner.walking = true;
|
||||||
/* Calculate the longest road branching from both corners */
|
/* Calculate the longest road branching from both corners */
|
||||||
layout.corners[cornerIndex].roads.forEach(roadIndex => {
|
layout.corners[cornerIndex].roads.forEach((roadIndex: number) => {
|
||||||
const placedRoad = game.placements.roads[roadIndex];
|
const placedRoad = game.placements.roads[roadIndex];
|
||||||
buildRoadGraph(game, color, roadIndex, placedRoad, set);
|
buildRoadGraph(game, color, roadIndex, placedRoad, set);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const processRoad = (game, color, roadIndex, placedRoad) => {
|
const processRoad = (game: any, color: string, roadIndex: number, placedRoad: any): number => {
|
||||||
/* If this road isn't assigned to the walking color, skip it */
|
/* If this road isn't assigned to the walking color, skip it */
|
||||||
if (placedRoad.color !== color) {
|
if (placedRoad.color !== color) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -75,7 +76,7 @@ const processRoad = (game, color, roadIndex, placedRoad) => {
|
|||||||
return roadLength;
|
return roadLength;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildRoadGraph = (game, color, roadIndex, placedRoad, set) => {
|
const buildRoadGraph = (game: any, color: string, roadIndex: number, placedRoad: any, set: any) => {
|
||||||
/* If this road isn't assigned to the walking color, skip it */
|
/* If this road isn't assigned to the walking color, skip it */
|
||||||
if (placedRoad.color !== color) {
|
if (placedRoad.color !== color) {
|
||||||
return;
|
return;
|
||||||
@ -94,7 +95,7 @@ const buildRoadGraph = (game, color, roadIndex, placedRoad, set) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearRoadWalking = (game) => {
|
const clearRoadWalking = (game: any) => {
|
||||||
/* Clear out walk markers on roads */
|
/* Clear out walk markers on roads */
|
||||||
layout.roads.forEach((item, itemIndex) => {
|
layout.roads.forEach((item, itemIndex) => {
|
||||||
delete game.placements.roads[itemIndex].walking;
|
delete game.placements.roads[itemIndex].walking;
|
||||||
@ -106,7 +107,7 @@ const clearRoadWalking = (game) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculateRoadLengths = (game) => {
|
const calculateRoadLengths = (game: any) => {
|
||||||
const color = game.color;
|
const color = game.color;
|
||||||
clearRoadWalking(game);
|
clearRoadWalking(game);
|
||||||
|
|
||||||
@ -158,4 +159,4 @@ const calculateRoadLengths = (game) => {
|
|||||||
return final;
|
return final;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = calculateRoadLengths;
|
export default calculateRoadLengths;
|
@ -1,19 +1,20 @@
|
|||||||
"use strict";
|
// @ts-nocheck
|
||||||
|
|
||||||
process.env.TZ = "Etc/GMT";
|
process.env.TZ = "Etc/GMT";
|
||||||
|
|
||||||
console.log("Loading ketr.ketran");
|
console.log("Loading ketr.ketran");
|
||||||
|
|
||||||
const express = require("express"),
|
import express from "express";
|
||||||
bodyParser = require("body-parser"),
|
import bodyParser from "body-parser";
|
||||||
config = require("config"),
|
import config from "config";
|
||||||
session = require('express-session'),
|
import session from 'express-session';
|
||||||
basePath = require("./basepath"),
|
import basePath from "./basepath";
|
||||||
cookieParser = require("cookie-parser"),
|
import cookieParser from "cookie-parser";
|
||||||
app = express(),
|
import fs from 'fs';
|
||||||
fs = require('fs');
|
import http from "http";
|
||||||
|
|
||||||
const server = require("http").createServer(app);
|
const app = express();
|
||||||
|
|
||||||
|
const server = http.createServer(app);
|
||||||
|
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ app.use(cookieParser());
|
|||||||
// and URL for requests under the configured basePath so we can trace which
|
// and URL for requests under the configured basePath so we can trace which
|
||||||
// service (server or dev proxy) is handling requests and their returned
|
// service (server or dev proxy) is handling requests and their returned
|
||||||
// status during debugging. Keep this lightweight.
|
// status during debugging. Keep this lightweight.
|
||||||
app.use((req, res, next) => {
|
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const bp = app.get("basePath") || '/';
|
const bp = app.get("basePath") || '/';
|
||||||
if (req.url && req.url.indexOf(bp) === 0) {
|
if (req.url && req.url.indexOf(bp) === 0) {
|
||||||
@ -37,16 +38,17 @@ app.use((req, res, next) => {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
const ws = require('express-ws')(app, server);
|
import expressWs from 'express-ws';
|
||||||
|
expressWs(app, server);
|
||||||
|
|
||||||
require("./console-line.js"); /* Monkey-patch console.log with line numbers */
|
require("./console-line.js"); /* Monkey-patch console.log with line numbers */
|
||||||
|
|
||||||
const frontendPath = config.get("frontendPath").replace(/\/$/, "") + "/",
|
const frontendPath = (config.get("frontendPath") as string).replace(/\/$/, "") + "/",
|
||||||
serverConfig = config.get("server");
|
serverConfig = config.get("server") as any;
|
||||||
|
|
||||||
console.log("Hosting server from: " + basePath);
|
console.log("Hosting server from: " + basePath);
|
||||||
|
|
||||||
let userDB, gameDB;
|
let userDB: any, gameDB: any;
|
||||||
|
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
@ -86,7 +88,7 @@ const users = require("./routes/users");
|
|||||||
app.use(basePath + "api/v1/users", users.router);
|
app.use(basePath + "api/v1/users", users.router);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
app.use(function(err, req, res, next) {
|
app.use(function(err: any, req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
res.status(err.status || 500).json({
|
res.status(err.status || 500).json({
|
||||||
message: err.message,
|
message: err.message,
|
||||||
@ -126,7 +128,7 @@ require("./db/games").then(function(db) {
|
|||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on("error", function(error) {
|
server.on("error", function(error: any) {
|
||||||
if (error.syscall !== "listen") {
|
if (error.syscall !== "listen") {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -145,3 +147,5 @@ server.on("error", function(error) {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export { app, server };
|
@ -1,5 +1,6 @@
|
|||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
let basePathRaw = process.env.VITE_BASEPATH || '';
|
|
||||||
|
let basePathRaw = process.env['VITE_BASEPATH'] || '';
|
||||||
|
|
||||||
// If env not provided, try to detect a <base href="..."> in the
|
// If env not provided, try to detect a <base href="..."> in the
|
||||||
// built client's index.html (if present). This helps when the
|
// built client's index.html (if present). This helps when the
|
||||||
@ -29,4 +30,4 @@ if (basePath === '//') basePath = '/';
|
|||||||
|
|
||||||
console.log(`Using basepath ${basePath}`);
|
console.log(`Using basepath ${basePath}`);
|
||||||
|
|
||||||
module.exports = basePath;
|
export default basePath;
|
@ -1,30 +0,0 @@
|
|||||||
/* monkey-patch console.log to prefix with file/line-number */
|
|
||||||
if (process.env.LOG_LINE) {
|
|
||||||
let cwd = process.cwd(),
|
|
||||||
cwdRe = new RegExp("^[^/]*" + cwd.replace("/", "\\/") + "\/([^:]*:[0-9]*).*$");
|
|
||||||
[ "log", "warn", "error" ].forEach(function(method) {
|
|
||||||
console[method] = (function () {
|
|
||||||
let orig = console[method];
|
|
||||||
return function () {
|
|
||||||
function getErrorObject() {
|
|
||||||
try {
|
|
||||||
throw Error('');
|
|
||||||
} catch (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let err = getErrorObject(),
|
|
||||||
caller_line = err.stack.split("\n")[3],
|
|
||||||
args = [caller_line.replace(cwdRe, "$1 -")];
|
|
||||||
|
|
||||||
/* arguments.unshift() doesn't exist... */
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
args.push(arguments[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
orig.apply(this, args);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
}
|
|
28
server/console-line.ts
Executable file
28
server/console-line.ts
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
/* monkey-patch console.log to prefix with file/line-number */
|
||||||
|
if (process.env['LOG_LINE']) {
|
||||||
|
let cwd = process.cwd(),
|
||||||
|
cwdRe = new RegExp("^[^/]*" + cwd.replace("/", "\\/") + "\/([^:]*:[0-9]*).*$");
|
||||||
|
[ "log", "warn", "error" ].forEach(function(method: string) {
|
||||||
|
(console as any)[method] = (function () {
|
||||||
|
let orig = (console as any)[method];
|
||||||
|
return function (this: any, ...args: any[]) {
|
||||||
|
function getErrorObject(): Error {
|
||||||
|
try {
|
||||||
|
throw Error('');
|
||||||
|
} catch (err) {
|
||||||
|
return err as Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = getErrorObject(),
|
||||||
|
caller_line = err.stack?.split("\n")[3] || '',
|
||||||
|
prefixedArgs = [caller_line.replace(cwdRe, "$1 -")];
|
||||||
|
|
||||||
|
/* arguments.unshift() doesn't exist... */
|
||||||
|
prefixedArgs.push(...args);
|
||||||
|
|
||||||
|
orig.apply(this, prefixedArgs);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
}
|
@ -1,44 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const fs = require('fs'),
|
|
||||||
path = require('path'),
|
|
||||||
Sequelize = require('sequelize'),
|
|
||||||
config = require('config');
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
const db = {
|
|
||||||
sequelize: new Sequelize(config.get("db.games")),
|
|
||||||
Sequelize: Sequelize
|
|
||||||
};
|
|
||||||
|
|
||||||
return db.sequelize.authenticate().then(function () {
|
|
||||||
const Game = db.sequelize.define('game', {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
primaryKey: true,
|
|
||||||
autoIncrement: true
|
|
||||||
},
|
|
||||||
path: Sequelize.STRING,
|
|
||||||
name: Sequelize.STRING,
|
|
||||||
}, {
|
|
||||||
timestamps: false,
|
|
||||||
classMethods: {
|
|
||||||
associate: function() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return db.sequelize.sync({
|
|
||||||
force: false
|
|
||||||
}).then(function () {
|
|
||||||
return db;
|
|
||||||
});
|
|
||||||
}).catch(function (error) {
|
|
||||||
console.log("ERROR: Failed to authenticate with GAMES DB");
|
|
||||||
console.log("ERROR: " + JSON.stringify(config.get("db"), null, 2));
|
|
||||||
console.log(error);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = init();
|
|
@ -1,70 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const Sequelize = require('sequelize'),
|
|
||||||
config = require('config');
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
const db = {
|
|
||||||
sequelize: new Sequelize(config.get("db.users")),
|
|
||||||
Sequelize: Sequelize
|
|
||||||
};
|
|
||||||
|
|
||||||
return db.sequelize.authenticate().then(function () {
|
|
||||||
const User = db.sequelize.define('users', {
|
|
||||||
id: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
primaryKey: true,
|
|
||||||
autoIncrement: true
|
|
||||||
},
|
|
||||||
displayName: Sequelize.STRING,
|
|
||||||
notes: Sequelize.STRING,
|
|
||||||
uid: Sequelize.STRING,
|
|
||||||
authToken: Sequelize.STRING,
|
|
||||||
authDate: Sequelize.DATE,
|
|
||||||
authenticated: Sequelize.BOOLEAN,
|
|
||||||
mailVerified: Sequelize.BOOLEAN,
|
|
||||||
mail: Sequelize.STRING,
|
|
||||||
memberSince: Sequelize.DATE,
|
|
||||||
password: Sequelize.STRING, /* SHA hash of user supplied password */
|
|
||||||
passwordExpires: Sequelize.DATE
|
|
||||||
}, {
|
|
||||||
timestamps: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const Authentication = db.sequelize.define('authentication', {
|
|
||||||
key: {
|
|
||||||
type: Sequelize.STRING,
|
|
||||||
primaryKey: true,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
issued: Sequelize.DATE,
|
|
||||||
type: {
|
|
||||||
type: Sequelize.ENUM,
|
|
||||||
values: [ 'account-setup', 'password-reset' ]
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
allowNull: false,
|
|
||||||
references: {
|
|
||||||
model: User,
|
|
||||||
key: 'id',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
timestamps: false
|
|
||||||
});
|
|
||||||
|
|
||||||
return db.sequelize.sync({
|
|
||||||
force: false
|
|
||||||
}).then(function () {
|
|
||||||
return db;
|
|
||||||
});
|
|
||||||
}).catch(function (error) {
|
|
||||||
console.log("ERROR: Failed to authenticate with USER DB");
|
|
||||||
console.log("ERROR: " + JSON.stringify(config.get("db"), null, 2));
|
|
||||||
console.log(error);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = init();
|
|
@ -1,8 +1,8 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const config = require("config"),
|
import config from "config";
|
||||||
crypto = require("crypto"),
|
import crypto from "crypto";
|
||||||
hb = require("handlebars");
|
import hb from "handlebars";
|
||||||
|
|
||||||
const templates = {
|
const templates = {
|
||||||
"verify": {
|
"verify": {
|
||||||
@ -52,13 +52,13 @@ const templates = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendVerifyMail = function(userDB, req, user) {
|
const sendVerifyMail = function(userDB: any, req: any, user: any): any {
|
||||||
return userDB.sequelize.query("DELETE FROM authentications WHERE userId=:id AND type='account-setup'", {
|
return userDB.sequelize.query("DELETE FROM authentications WHERE userId=:id AND type='account-setup'", {
|
||||||
replacements: {
|
replacements: {
|
||||||
id: user.id
|
id: user.id
|
||||||
}
|
}
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise<string>(function(resolve, reject) {
|
||||||
crypto.randomBytes(16, function(error, buffer) {
|
crypto.randomBytes(16, function(error, buffer) {
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
@ -66,7 +66,7 @@ const sendVerifyMail = function(userDB, req, user) {
|
|||||||
return resolve(buffer.toString('hex'));
|
return resolve(buffer.toString('hex'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).then(function(secret) {
|
}).then(function(secret: string) {
|
||||||
return userDB.sequelize.query(
|
return userDB.sequelize.query(
|
||||||
"INSERT INTO authentications " +
|
"INSERT INTO authentications " +
|
||||||
"(userId,issued,key,type) " +
|
"(userId,issued,key,type) " +
|
||||||
@ -77,11 +77,11 @@ const sendVerifyMail = function(userDB, req, user) {
|
|||||||
}
|
}
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return secret;
|
return secret;
|
||||||
}).catch(function(error) {
|
}).catch(function(error: any) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}).then(function(secret) {
|
}).then(function(secret: string) {
|
||||||
const transporter = req.app.get("transporter");
|
const transporter = req.app.get("transporter");
|
||||||
if (!transporter) {
|
if (!transporter) {
|
||||||
console.log("Not sending VERIFY email; SMTP not configured.");
|
console.log("Not sending VERIFY email; SMTP not configured.");
|
||||||
@ -102,15 +102,16 @@ const sendVerifyMail = function(userDB, req, user) {
|
|||||||
text: hb.compile(templates.verify.text)(data),
|
text: hb.compile(templates.verify.text)(data),
|
||||||
html: hb.compile(templates.verify.html)(data)
|
html: hb.compile(templates.verify.html)(data)
|
||||||
};
|
};
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise<void>(function (resolve, reject) {
|
||||||
let attempts = 10;
|
let attempts = 10;
|
||||||
|
|
||||||
function send(envelope) {
|
function send(envelope: any) {
|
||||||
/* Rate limit to ten per second */
|
/* Rate limit to ten per second */
|
||||||
transporter.sendMail(envelope, function (error, info) {
|
transporter.sendMail(envelope, function (error: any, info: any) {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
console.log('Message sent: ' + info.response);
|
console.log('Message sent: ' + (info && info.response));
|
||||||
return resolve();
|
resolve();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attempts == 0) {
|
if (attempts == 0) {
|
||||||
@ -126,12 +127,12 @@ const sendVerifyMail = function(userDB, req, user) {
|
|||||||
|
|
||||||
send(envelope);
|
send(envelope);
|
||||||
});
|
});
|
||||||
}).catch(function(error) {
|
}).catch(function(error: any) {
|
||||||
console.log("Error creating account: ", error);
|
console.log("Error creating account: ", error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendPasswordChangedMail = function(userDB, req, user) {
|
const sendPasswordChangedMail = function(_userDB: any, req: any, user: any): any {
|
||||||
const transporter = req.app.get("transporter");
|
const transporter = req.app.get("transporter");
|
||||||
if (!transporter) {
|
if (!transporter) {
|
||||||
console.log("Not sending VERIFY email; SMTP not configured.");
|
console.log("Not sending VERIFY email; SMTP not configured.");
|
||||||
@ -151,14 +152,14 @@ const sendPasswordChangedMail = function(userDB, req, user) {
|
|||||||
text: hb.compile(templates.password.text)(data),
|
text: hb.compile(templates.password.text)(data),
|
||||||
html: hb.compile(templates.password.html)(data)
|
html: hb.compile(templates.password.html)(data)
|
||||||
};
|
};
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise<void>(function (resolve, reject) {
|
||||||
let attempts = 10;
|
let attempts = 10;
|
||||||
|
|
||||||
function send(envelope) {
|
function send(envelope: any) {
|
||||||
/* Rate limit to ten per second */
|
/* Rate limit to ten per second */
|
||||||
transporter.sendMail(envelope, function (error, info) {
|
transporter.sendMail(envelope, function (error: any, info: any) {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
console.log('Message sent: ' + info.response);
|
console.log('Message sent: ' + (info && info.response));
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +178,7 @@ const sendPasswordChangedMail = function(userDB, req, user) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
export {
|
||||||
sendVerifyMail,
|
sendVerifyMail,
|
||||||
sendPasswordChangedMail
|
sendPasswordChangedMail
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const createTransport = require('nodemailer').createTransport,
|
import { createTransport } from 'nodemailer';
|
||||||
{ timestamp } = require("./timestamp");
|
import { timestamp } from "./timestamp";
|
||||||
|
|
||||||
const transporter = createTransport({
|
const transporter = createTransport({
|
||||||
host: 'email.ketrenos.com',
|
host: 'email.ketrenos.com',
|
||||||
@ -9,8 +9,8 @@ const transporter = createTransport({
|
|||||||
port: 25
|
port: 25
|
||||||
});
|
});
|
||||||
|
|
||||||
function sendMail(to, subject, message, cc) {
|
function sendMail(to: string, subject: string, message: string, cc?: string): Promise<boolean> {
|
||||||
let envelope = {
|
let envelope: any = {
|
||||||
subject: subject,
|
subject: subject,
|
||||||
from: 'Ketr.Ketran <james_ketran@ketrenos.com>',
|
from: 'Ketr.Ketran <james_ketran@ketrenos.com>',
|
||||||
to: to || '',
|
to: to || '',
|
||||||
@ -29,33 +29,29 @@ function sendMail(to, subject, message, cc) {
|
|||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
let attempts = 10;
|
let attempts = 10;
|
||||||
|
|
||||||
function attemptSend(envelope) {
|
function attemptSend(envelope: any) {
|
||||||
/* Rate limit to ten per second */
|
/* Rate limit to ten per second */
|
||||||
transporter.sendMail(envelope, function (error, info) {
|
transporter.sendMail(envelope, function (error, _info) {
|
||||||
if (error) {
|
if (error) {
|
||||||
if (attempts) {
|
if (attempts) {
|
||||||
attempts--;
|
attempts--;
|
||||||
console.warn(timestamp() + " Unable to send mail. Trying again in 100ms (" + attempts + " attempts remain): ", error);
|
console.warn(timestamp() + " Unable to send mail. Trying again in 100ms (" + attempts + " attempts remain): ", error);
|
||||||
setTimeout(send.bind(undefined, envelope), 100);
|
setTimeout(() => attemptSend(envelope), 100);
|
||||||
} else {
|
} else {
|
||||||
console.error(timestamp() + " Error sending email: ", error)
|
console.error(timestamp() + " Error sending email: ", error)
|
||||||
return reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
console.log(timestamp() + " Mail sent to: " + envelope.to);
|
console.log(timestamp() + " Mail sent to: " + envelope.to);
|
||||||
return resolve(true);
|
resolve(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
attemptSend(envelope);
|
attemptSend(envelope);
|
||||||
}).then(function(success) {
|
|
||||||
if (!success) {
|
|
||||||
console.error(timestamp() + " Mail not sent to: " + envelope.to);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export {
|
||||||
sendMail: sendMail
|
sendMail
|
||||||
};
|
};
|
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "peddlers-of-ketran-server",
|
"name": "peddlers-of-ketran-server",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "app.js",
|
"main": "dist/src/app.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "export $(cat ../.env | xargs) && node dist/app.js",
|
"start": "export $(cat ../.env | xargs) && node dist/src/app.js",
|
||||||
"start:legacy": "export $(cat ../.env | xargs) && node app.js",
|
"start:legacy": "export $(cat ../.env | xargs) && node app.js",
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json",
|
||||||
"start:dev": "ts-node-dev --respawn --transpile-only src/app.ts",
|
"start:dev": "ts-node-dev --respawn --transpile-only src/app.ts",
|
||||||
@ -36,9 +36,25 @@
|
|||||||
"ws": "^8.5.0"
|
"ws": "^8.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/bluebird": "^3.5.38",
|
||||||
|
"@types/config": "^3.3.1",
|
||||||
|
"@types/connect-sqlite3": "^0.9.3",
|
||||||
|
"@types/cookie-parser": "^1.4.4",
|
||||||
|
"@types/express": "^4.17.17",
|
||||||
|
"@types/express-session": "^1.17.7",
|
||||||
|
"@types/express-ws": "^3.0.1",
|
||||||
|
"@types/handlebars": "^4.1.0",
|
||||||
"@types/jest": "^29.5.0",
|
"@types/jest": "^29.5.0",
|
||||||
|
"@types/moment": "^2.13.0",
|
||||||
|
"@types/morgan": "^1.9.5",
|
||||||
"@types/node": "^20.0.0",
|
"@types/node": "^20.0.0",
|
||||||
|
"@types/node-fetch": "^2.6.4",
|
||||||
|
"@types/node-gzip": "^1.1.0",
|
||||||
|
"@types/nodemailer": "^6.4.8",
|
||||||
|
"@types/random-words": "^1.1.0",
|
||||||
|
"@types/sequelize": "^4.28.15",
|
||||||
"@types/supertest": "^2.0.12",
|
"@types/supertest": "^2.0.12",
|
||||||
|
"@types/ws": "^8.5.5",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.3",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
"use strict";
|
import express from 'express';
|
||||||
|
import fs from 'fs';
|
||||||
const express = require("express"),
|
import url from 'url';
|
||||||
fs = require("fs"),
|
|
||||||
url = require("url");
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@ -10,9 +8,9 @@ const router = express.Router();
|
|||||||
* to replace BASEPATH */
|
* to replace BASEPATH */
|
||||||
router.get("/*", (req, res, next) => {
|
router.get("/*", (req, res, next) => {
|
||||||
const parts = url.parse(req.url),
|
const parts = url.parse(req.url),
|
||||||
basePath = req.app.get("basePath");
|
basePath = req.app.get("basePath") as string;
|
||||||
|
|
||||||
if (!/^\/[^/]+\.html$/.exec(parts.pathname)) {
|
if (!/^\/[^/]+\.html$/.exec(parts.pathname || '')) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,14 +18,13 @@ router.get("/*", (req, res, next) => {
|
|||||||
|
|
||||||
/* Replace <script>'<base href="/BASEPATH/">';</script> in index.html with
|
/* Replace <script>'<base href="/BASEPATH/">';</script> in index.html with
|
||||||
* the basePath */
|
* the basePath */
|
||||||
fs.readFile("frontend" + parts.pathname, "utf8", function(error, content) {
|
fs.readFile("frontend" + (parts.pathname || ''), "utf8", function(error, content) {
|
||||||
if (error) {
|
if (error) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
res.send(content.replace(
|
res.send((content as string).replace(
|
||||||
/<script>'<base href="BASEPATH">';<\/script>/,
|
/<script>'<base href="BASEPATH">';<\/script>/,
|
||||||
"<base href='" + basePath + "'>"));
|
"<base href='" + basePath + "'>"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
export default router;
|
||||||
module.exports = router;
|
|
@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const express = require("express");
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -9,7 +9,7 @@ const router = express.Router();
|
|||||||
* by the server. It is mounted under the application's basePath so you can
|
* by the server. It is mounted under the application's basePath so you can
|
||||||
* hit: /<basePath>/__debug/request
|
* hit: /<basePath>/__debug/request
|
||||||
*/
|
*/
|
||||||
router.get('/__debug/request', (req, res) => {
|
router.get('/__debug/request', (req: express.Request, res: express.Response) => {
|
||||||
try {
|
try {
|
||||||
console.log('[debug] __debug/request hit:', req.method, req.originalUrl);
|
console.log('[debug] __debug/request hit:', req.method, req.originalUrl);
|
||||||
// Echo back a compact JSON summary so curl or browsers can inspect it.
|
// Echo back a compact JSON summary so curl or browsers can inspect it.
|
||||||
@ -21,10 +21,10 @@ router.get('/__debug/request', (req, res) => {
|
|||||||
hostname: req.hostname,
|
hostname: req.hostname,
|
||||||
basePath: req.app && req.app.get && req.app.get('basePath')
|
basePath: req.app && req.app.get && req.app.get('basePath')
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
console.error('[debug] error in __debug/request', e && e.stack || e);
|
console.error('[debug] error in __debug/request', e && e.stack || e);
|
||||||
res.status(500).json({ error: 'debug endpoint error' });
|
res.status(500).json({ error: 'debug endpoint error' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
export default router;
|
@ -1,24 +1,31 @@
|
|||||||
"use strict";
|
// @ts-nocheck
|
||||||
|
import express from 'express';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { readFile, writeFile, mkdir } from 'fs/promises';
|
||||||
|
import fs from 'fs';
|
||||||
|
import randomWords from 'random-words';
|
||||||
|
import equal from 'fast-deep-equal';
|
||||||
|
import { layout, staticData } from '../util/layout.js';
|
||||||
|
import basePath from '../basepath';
|
||||||
|
|
||||||
const express = require("express"),
|
import { getValidRoads, getValidCorners, isRuleEnabled } from '../util/validLocations.js';
|
||||||
router = express.Router(),
|
|
||||||
crypto = require("crypto")
|
|
||||||
const { readFile, writeFile, mkdir } = require("fs").promises,
|
|
||||||
fs = require("fs"),
|
|
||||||
accessSync = fs.accessSync,
|
|
||||||
randomWords = require("random-words"),
|
|
||||||
equal = require("fast-deep-equal");
|
|
||||||
const { layout, staticData } = require('../util/layout.js');
|
|
||||||
const basePath = require('../basepath');
|
|
||||||
|
|
||||||
const { getValidRoads, getValidCorners, isRuleEnabled } = require('../util/validLocations.js');
|
interface Player {
|
||||||
|
order: number;
|
||||||
|
orderRoll?: number;
|
||||||
|
position?: string;
|
||||||
|
orderStatus?: string;
|
||||||
|
tied?: boolean;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
const MAX_SETTLEMENTS = 5;
|
const MAX_SETTLEMENTS = 5;
|
||||||
const MAX_CITIES = 4;
|
const MAX_CITIES = 4;
|
||||||
const MAX_ROADS = 15;
|
const MAX_ROADS = 15;
|
||||||
|
|
||||||
const types = [ 'wheat', 'stone', 'sheep', 'wood', 'brick' ];
|
const types: string[] = [ 'wheat', 'stone', 'sheep', 'wood', 'brick' ];
|
||||||
|
|
||||||
const debug = {
|
const debug = {
|
||||||
audio: false,
|
audio: false,
|
||||||
@ -32,9 +39,9 @@ const debug = {
|
|||||||
// others used a flatter shape. This helper accepts either a string or an
|
// others used a flatter shape. This helper accepts either a string or an
|
||||||
// already-parsed object and returns a stable object so handlers don't need
|
// already-parsed object and returns a stable object so handlers don't need
|
||||||
// to defensively check multiple nested locations.
|
// to defensively check multiple nested locations.
|
||||||
function normalizeIncoming(msg) {
|
function normalizeIncoming(msg: unknown): { type: string | null, data: unknown } {
|
||||||
if (!msg) return { type: null, data: null };
|
if (!msg) return { type: null, data: null };
|
||||||
let parsed = null;
|
let parsed: unknown = null;
|
||||||
try {
|
try {
|
||||||
if (typeof msg === 'string') {
|
if (typeof msg === 'string') {
|
||||||
parsed = JSON.parse(msg);
|
parsed = JSON.parse(msg);
|
||||||
@ -46,7 +53,7 @@ function normalizeIncoming(msg) {
|
|||||||
return { type: null, data: null };
|
return { type: null, data: null };
|
||||||
}
|
}
|
||||||
if (!parsed) return { type: null, data: null };
|
if (!parsed) return { type: null, data: null };
|
||||||
const type = parsed.type || parsed.action || null;
|
const type = (parsed as any).type || (parsed as any).action || null;
|
||||||
// Prefer parsed.data when present, but allow flattened payloads where
|
// Prefer parsed.data when present, but allow flattened payloads where
|
||||||
// properties like `name` live at the root.
|
// properties like `name` live at the root.
|
||||||
const data = parsed.data || (Object.keys(parsed).length ? Object.assign({}, parsed) : null);
|
const data = parsed.data || (Object.keys(parsed).length ? Object.assign({}, parsed) : null);
|
||||||
@ -82,14 +89,14 @@ function shuffleArray(array) {
|
|||||||
const games = {};
|
const games = {};
|
||||||
const audio = {};
|
const audio = {};
|
||||||
|
|
||||||
const processTies = (players) => {
|
const processTies = (players: Player[]) => {
|
||||||
|
|
||||||
/* Sort the players into buckets based on their
|
/* Sort the players into buckets based on their
|
||||||
* order, and their current roll. If a resulting
|
* order, and their current roll. If a resulting
|
||||||
* roll array has more than one element, then there
|
* roll array has more than one element, then there
|
||||||
* is a tie that must be resolved */
|
* is a tie that must be resolved */
|
||||||
let slots = [];
|
let slots: Player[][] = [];
|
||||||
players.forEach(player => {
|
players.forEach((player: Player) => {
|
||||||
if (!slots[player.order]) {
|
if (!slots[player.order]) {
|
||||||
slots[player.order] = [];
|
slots[player.order] = [];
|
||||||
}
|
}
|
||||||
@ -98,13 +105,13 @@ const processTies = (players) => {
|
|||||||
|
|
||||||
let ties = false, position = 1;
|
let ties = false, position = 1;
|
||||||
|
|
||||||
const irstify = (position) => {
|
const irstify = (position: number): string => {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 1: return `1st`;
|
case 1: return `1st`;
|
||||||
case 2: return `2nd`;
|
case 2: return `2nd`;
|
||||||
case 3: return `3rd`;
|
case 3: return `3rd`;
|
||||||
case 4: return `4th`;
|
case 4: return `4th`;
|
||||||
default: return position;
|
default: return position.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +119,7 @@ const processTies = (players) => {
|
|||||||
slots.reverse().forEach((slot) => {
|
slots.reverse().forEach((slot) => {
|
||||||
if (slot.length !== 1) {
|
if (slot.length !== 1) {
|
||||||
ties = true;
|
ties = true;
|
||||||
slot.forEach(player => {
|
slot.forEach((player: Player) => {
|
||||||
player.orderRoll = 0; /* Ties have to be re-rolled */
|
player.orderRoll = 0; /* Ties have to be re-rolled */
|
||||||
player.position = irstify(position);
|
player.position = irstify(position);
|
||||||
player.orderStatus = `Tied for ${irstify(position)}`;
|
player.orderStatus = `Tied for ${irstify(position)}`;
|
||||||
@ -5216,4 +5223,4 @@ router.post("/:id?", async (req, res/*, next*/) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
export default router;
|
@ -1,10 +1,10 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const express = require("express"),
|
import express from "express";
|
||||||
fs = require("fs"),
|
import fs from "fs";
|
||||||
url = require("url"),
|
import url from "url";
|
||||||
config = require("config"),
|
import config from "config";
|
||||||
basePath = require("../basepath");
|
import basePath from "../basepath";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@ -31,24 +31,24 @@ const extensionMatch = new RegExp("^.*?(" + extensions.join("|") + ")$", "i");
|
|||||||
* If so, 404 because the asset isn't there. otherwise assume it is a
|
* If so, 404 because the asset isn't there. otherwise assume it is a
|
||||||
* dynamic client side route and *then* return index.html.
|
* dynamic client side route and *then* return index.html.
|
||||||
*/
|
*/
|
||||||
router.get("/*", function(req, res, next) {
|
router.get("/*", function(req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const parts = url.parse(req.url);
|
const parts = url.parse(req.url);
|
||||||
|
|
||||||
/* If req.user isn't set yet (authentication hasn't happened) then
|
/* If req.user isn't set yet (authentication hasn't happened) then
|
||||||
* only allow / to be loaded--everything else chains to the next
|
* only allow / to be loaded--everything else chains to the next
|
||||||
* handler */
|
* handler */
|
||||||
if (!req.user &&
|
if (!(req as any).user &&
|
||||||
req.url != "/" &&
|
req.url != "/" &&
|
||||||
req.url.indexOf("/games") != 0) {
|
req.url.indexOf("/games") != 0) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.url == "/" || req.url.indexOf("/games") == 0 || !extensionMatch.exec(parts.pathname)) {
|
if (req.url == "/" || req.url.indexOf("/games") == 0 || !extensionMatch.exec(parts.pathname || '')) {
|
||||||
console.log("Returning index for " + req.url);
|
console.log("Returning index for " + req.url);
|
||||||
|
|
||||||
/* Replace <script>'<base href="BASEPATH">';</script> in index.html with
|
/* Replace <script>'<base href="BASEPATH">';</script> in index.html with
|
||||||
* the basePath */
|
* the basePath */
|
||||||
const frontendPath = config.get("frontendPath").replace(/\/$/, "") + "/",
|
const frontendPath = (config.get("frontendPath") as string).replace(/\/$/, "") + "/",
|
||||||
index = fs.readFileSync(frontendPath + "index.html", "utf8");
|
index = fs.readFileSync(frontendPath + "index.html", "utf8");
|
||||||
res.send(index.replace(
|
res.send(index.replace(
|
||||||
/<script>'<base href="BASEPATH">';<\/script>/,
|
/<script>'<base href="BASEPATH">';<\/script>/,
|
||||||
@ -63,4 +63,4 @@ router.get("/*", function(req, res, next) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
export default router;
|
@ -1,19 +1,19 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const express = require("express"),
|
import express from "express";
|
||||||
config = require("config"),
|
import config from "config";
|
||||||
{ sendVerifyMail, sendPasswordChangedMail } = require("../lib/mail"),
|
import { sendVerifyMail, sendPasswordChangedMail } from "../lib/mail";
|
||||||
crypto = require("crypto");
|
import crypto from "crypto";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
let userDB;
|
let userDB: any;
|
||||||
|
|
||||||
require("../db/users.js").then(function(db) {
|
import("../db/users.js").then(function(db: any) {
|
||||||
userDB = db;
|
userDB = db;
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/", function(req, res/*, next*/) {
|
router.get("/", function(req: express.Request, res: express.Response/*, next*/) {
|
||||||
console.log("/users/");
|
console.log("/users/");
|
||||||
return getSessionUser(req).then((user) => {
|
return getSessionUser(req).then((user) => {
|
||||||
return res.status(200).send(user);
|
return res.status(200).send(user);
|
||||||
@ -23,12 +23,12 @@ router.get("/", function(req, res/*, next*/) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.put("/password", function(req, res) {
|
router.put("/password", function(req: express.Request, res: express.Response) {
|
||||||
console.log("/users/password");
|
console.log("/users/password");
|
||||||
|
const q = req.query as any;
|
||||||
const changes = {
|
const changes = {
|
||||||
currentPassword: req.query.c || req.body.c,
|
currentPassword: q.c || req.body.c,
|
||||||
newPassword: req.query.n || req.body.n
|
newPassword: q.n || req.body.n
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!changes.currentPassword || !changes.newPassword) {
|
if (!changes.currentPassword || !changes.newPassword) {
|
||||||
@ -39,7 +39,7 @@ router.put("/password", function(req, res) {
|
|||||||
return res.status(400).send("Attempt to set new password to current password.");
|
return res.status(400).send("Attempt to set new password to current password.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSessionUser(req).then(function(user) {
|
return getSessionUser(req).then(function(user: any) {
|
||||||
return userDB.sequelize.query("SELECT id FROM users " +
|
return userDB.sequelize.query("SELECT id FROM users " +
|
||||||
"WHERE uid=:username AND password=:password", {
|
"WHERE uid=:username AND password=:password", {
|
||||||
replacements: {
|
replacements: {
|
||||||
@ -48,13 +48,13 @@ router.put("/password", function(req, res) {
|
|||||||
},
|
},
|
||||||
type: userDB.Sequelize.QueryTypes.SELECT,
|
type: userDB.Sequelize.QueryTypes.SELECT,
|
||||||
raw: true
|
raw: true
|
||||||
}).then(function(users) {
|
}).then(function(users: any) {
|
||||||
if (users.length != 1) {
|
if (users.length != 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
}).then(function(user) {
|
}).then(function(user: any) {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.log("Invalid password");
|
console.log("Invalid password");
|
||||||
/* Invalid password */
|
/* Invalid password */
|
||||||
@ -77,15 +77,15 @@ router.put("/password", function(req, res) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/create", function(req, res) {
|
router.post("/create", function(req: express.Request, res: express.Response) {
|
||||||
console.log("/users/create");
|
console.log("/users/create");
|
||||||
|
const q = req.query as any;
|
||||||
const user = {
|
const user = {
|
||||||
uid: req.query.m || req.body.m,
|
uid: q.m || req.body.m,
|
||||||
displayName: req.query.n || req.body.n || "",
|
displayName: q.n || req.body.n || "",
|
||||||
password: req.query.p || req.body.p || "",
|
password: q.p || req.body.p || "",
|
||||||
mail: req.query.m || req.body.m,
|
mail: q.m || req.body.m,
|
||||||
notes: req.query.w || req.body.w || ""
|
notes: q.w || req.body.w || ""
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!user.uid || !user.password || !user.displayName || !user.notes) {
|
if (!user.uid || !user.password || !user.displayName || !user.notes) {
|
||||||
@ -98,37 +98,38 @@ router.post("/create", function(req, res) {
|
|||||||
replacements: user,
|
replacements: user,
|
||||||
type: userDB.Sequelize.QueryTypes.SELECT,
|
type: userDB.Sequelize.QueryTypes.SELECT,
|
||||||
raw: true
|
raw: true
|
||||||
}).then(function(results) {
|
}).then(function(results: any) {
|
||||||
if (results.length != 0) {
|
if (results.length != 0) {
|
||||||
return res.status(400).send("Email address already used.");
|
return res.status(400).send("Email address already used.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
let re = /^(([^<>()\[\]\\.,;:\s@\"]+(\.[^<>()\[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
if (!re.exec(user.mail)) {
|
if (!re.exec(user.mail)) {
|
||||||
console.log("Invalid email address: " + user.mail);
|
console.log("Invalid email address: " + user.mail);
|
||||||
throw "Invalid email address.";
|
throw "Invalid email address.";
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return userDB.sequelize.query("INSERT INTO users " +
|
return userDB.sequelize.query("INSERT INTO users " +
|
||||||
"(uid,displayName,password,mail,memberSince,authenticated,notes) " +
|
"(uid,displayName,password,mail,memberSince,authenticated,notes) " +
|
||||||
"VALUES(:uid,:displayName,:password,:mail,CURRENT_TIMESTAMP,0,:notes)", {
|
"VALUES(:uid,:displayName,:password,:mail,CURRENT_TIMESTAMP,0,:notes)", {
|
||||||
replacements: user
|
replacements: user
|
||||||
}).spread(function(results, metadata) {
|
}).spread(function(_results: any, metadata: any) {
|
||||||
req.session.userId = metadata.lastID;
|
req.session.userId = metadata.lastID;
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return getSessionUser(req).then(function(user) {
|
return getSessionUser(req).then(function(user: any) {
|
||||||
res.status(200).send(user);
|
res.status(200).send(user);
|
||||||
user.id = req.session.userId;
|
user.id = req.session.userId;
|
||||||
return sendVerifyMail(userDB, req, user);
|
return sendVerifyMail(userDB, req, user);
|
||||||
});
|
});
|
||||||
}).catch(function(error) {
|
}).catch(function(error: any) {
|
||||||
console.log("Error creating account: ", error);
|
console.log("Error creating account: ", error);
|
||||||
return res.status(401).send(error);
|
return res.status(401).send(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSessionUser = function(req) {
|
const getSessionUser = function(req: express.Request): Promise<any> {
|
||||||
return Promise.resolve().then(function() {
|
return Promise.resolve().then(function() {
|
||||||
if (!req.session || !req.session.userId) {
|
if (!req.session || !req.session.userId) {
|
||||||
throw "Unauthorized. You must be logged in.";
|
throw "Unauthorized. You must be logged in.";
|
||||||
@ -143,7 +144,7 @@ const getSessionUser = function(req) {
|
|||||||
},
|
},
|
||||||
type: userDB.Sequelize.QueryTypes.SELECT,
|
type: userDB.Sequelize.QueryTypes.SELECT,
|
||||||
raw: true
|
raw: true
|
||||||
}).then(function(results) {
|
}).then(function(results: any) {
|
||||||
if (results.length != 1) {
|
if (results.length != 1) {
|
||||||
throw "Invalid account.";
|
throw "Invalid account.";
|
||||||
}
|
}
|
||||||
@ -162,7 +163,7 @@ const getSessionUser = function(req) {
|
|||||||
|
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
}).then(function(user) {
|
}).then(function(user: any) {
|
||||||
req.user = user;
|
req.user = user;
|
||||||
|
|
||||||
/* If the user already has a restriction, or there are no album user restrictions,
|
/* If the user already has a restriction, or there are no album user restrictions,
|
||||||
@ -171,7 +172,7 @@ const getSessionUser = function(req) {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
let allowed = config.get("restrictions");
|
let allowed: any = config.get("restrictions");
|
||||||
if (!Array.isArray(allowed)) {
|
if (!Array.isArray(allowed)) {
|
||||||
allowed = [ allowed ];
|
allowed = [ allowed ];
|
||||||
}
|
}
|
||||||
@ -187,8 +188,8 @@ const getSessionUser = function(req) {
|
|||||||
}).then(function(user) {
|
}).then(function(user) {
|
||||||
/* If there are maintainers on this album, check if this user is a maintainer */
|
/* If there are maintainers on this album, check if this user is a maintainer */
|
||||||
if (config.has("maintainers")) {
|
if (config.has("maintainers")) {
|
||||||
let maintainers = config.get("maintainers");
|
let maintainers: any = config.get("maintainers");
|
||||||
if (maintainers.indexOf(user.username) != -1) {
|
if (Array.isArray(maintainers) && maintainers.indexOf(user.username) != -1) {
|
||||||
user.maintainer = true;
|
user.maintainer = true;
|
||||||
if (user.restriction) {
|
if (user.restriction) {
|
||||||
console.warn("User " + user.username + " is a maintainer AND has a restriction which will be ignored: " + user.restriction);
|
console.warn("User " + user.username + " is a maintainer AND has a restriction which will be ignored: " + user.restriction);
|
||||||
@ -212,11 +213,12 @@ const getSessionUser = function(req) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
router.post("/login", function(req, res) {
|
router.post("/login", function(req: express.Request, res: express.Response) {
|
||||||
console.log("/users/login");
|
console.log("/users/login");
|
||||||
|
const q = req.query as any;
|
||||||
|
|
||||||
let username = req.query.u || req.body.u || "",
|
let username = q.u || req.body.u || "",
|
||||||
password = req.query.p || req.body.p || "";
|
password = q.p || req.body.p || "";
|
||||||
|
|
||||||
console.log("Login attempt");
|
console.log("Login attempt");
|
||||||
|
|
||||||
@ -224,7 +226,7 @@ router.post("/login", function(req, res) {
|
|||||||
return res.status(400).send("Missing username and/or password");
|
return res.status(400).send("Missing username and/or password");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((reject, resolve) => {
|
return new Promise((resolve, _reject) => {
|
||||||
console.log("Looking up user in DB.");
|
console.log("Looking up user in DB.");
|
||||||
let query = "SELECT " +
|
let query = "SELECT " +
|
||||||
"id,mailVerified,authenticated,uid AS username,displayName AS name,mail " +
|
"id,mailVerified,authenticated,uid AS username,displayName AS name,mail " +
|
||||||
@ -235,7 +237,7 @@ router.post("/login", function(req, res) {
|
|||||||
password: crypto.createHash('sha256').update(password).digest('base64')
|
password: crypto.createHash('sha256').update(password).digest('base64')
|
||||||
},
|
},
|
||||||
type: userDB.Sequelize.QueryTypes.SELECT
|
type: userDB.Sequelize.QueryTypes.SELECT
|
||||||
}).then(function(users) {
|
}).then(function(users: any) {
|
||||||
if (users.length != 1) {
|
if (users.length != 1) {
|
||||||
return resolve(null);
|
return resolve(null);
|
||||||
}
|
}
|
||||||
@ -243,7 +245,7 @@ router.post("/login", function(req, res) {
|
|||||||
req.session.userId = user.id;
|
req.session.userId = user.id;
|
||||||
return resolve(user);
|
return resolve(user);
|
||||||
});
|
});
|
||||||
}).then(function(user) {
|
}).then(function(user: any) {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.log(username + " not found (or invalid password.)");
|
console.log(username + " not found (or invalid password.)");
|
||||||
req.session.userId = null;
|
req.session.userId = null;
|
||||||
@ -259,7 +261,7 @@ router.post("/login", function(req, res) {
|
|||||||
console.log(message);
|
console.log(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSessionUser(req).then(function(user) {
|
return getSessionUser(req).then(function(user: any) {
|
||||||
return res.status(200).send(user);
|
return res.status(200).send(user);
|
||||||
});
|
});
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
@ -277,7 +279,7 @@ router.get("/logout", function(req, res) {
|
|||||||
res.status(200).send({});
|
res.status(200).send({});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
export {
|
||||||
router,
|
router,
|
||||||
getSessionUser
|
getSessionUser
|
||||||
};
|
};
|
@ -1,42 +1,45 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
||||||
import type { Request, Response, NextFunction } from 'express';
|
import type { Request, Response, NextFunction } from 'express';
|
||||||
|
import express from 'express';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import config from 'config';
|
||||||
|
import basePath from '../basepath';
|
||||||
|
import cookieParser from 'cookie-parser';
|
||||||
|
import http from 'http';
|
||||||
|
import expressWs from 'express-ws';
|
||||||
|
|
||||||
process.env.TZ = "Etc/GMT";
|
process.env.TZ = "Etc/GMT";
|
||||||
|
|
||||||
console.log("Loading ketr.ketran");
|
console.log("Loading ketr.ketran");
|
||||||
|
|
||||||
const express = require("express");
|
|
||||||
const bodyParser = require("body-parser");
|
|
||||||
const config = require("config");
|
|
||||||
const session = require('express-session');
|
|
||||||
const basePath = require("../basepath");
|
|
||||||
const cookieParser = require("cookie-parser");
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = require("http").createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
|
|
||||||
const ws = require('express-ws')(app, server);
|
expressWs(app, server);
|
||||||
|
|
||||||
require("../console-line.js"); /* Monkey-patch console.log with line numbers */
|
require("../console-line.js"); /* Monkey-patch console.log with line numbers */
|
||||||
|
|
||||||
// Temporary debug routes (dev-only). Mount before static so we can
|
// Temporary debug routes (dev-only). Mount before static so we can
|
||||||
// inspect what the server receives for base-prefixed requests.
|
// inspect what the server receives for base-prefixed requests.
|
||||||
try {
|
try {
|
||||||
app.use(basePath, require("../routes/debug.js"));
|
// import the debug router using ESM style; fallback to require at runtime if needed
|
||||||
} catch (e) {
|
// (some dev environments may still emit JS commonjs files)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const debugRouter = require("../routes/debug.js").default || require("../routes/debug.js");
|
||||||
|
app.use(basePath, debugRouter);
|
||||||
|
} catch (e: any) {
|
||||||
console.error('Failed to mount debug routes (src):', e && e.stack || e);
|
console.error('Failed to mount debug routes (src):', e && e.stack || e);
|
||||||
}
|
}
|
||||||
|
|
||||||
const frontendPath = config.get("frontendPath").replace(/\/$/, "") + "/",
|
const frontendPath = (config.get("frontendPath") as string).replace(/\/$/, "") + "/",
|
||||||
serverConfig = config.get("server");
|
serverConfig = config.get("server") as { port: number };
|
||||||
|
|
||||||
console.log("Hosting server from: " + basePath);
|
console.log("Hosting server from: " + basePath);
|
||||||
|
|
||||||
let userDB: any, gameDB: any;
|
// DB handles are initialized by the modules below; we don't need file-scoped vars here.
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
app.use((req: Request, _res: Response, next: NextFunction) => {
|
||||||
console.log(`${req.method} ${req.url}`);
|
console.log(`${req.method} ${req.url}`);
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
@ -48,19 +51,27 @@ app.use(bodyParser.json());
|
|||||||
app.set("trust proxy", true);
|
app.set("trust proxy", true);
|
||||||
|
|
||||||
app.set("basePath", basePath);
|
app.set("basePath", basePath);
|
||||||
app.use(basePath, require("../routes/basepath.js"));
|
// basepath is a simple exported string
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const basepathRouter = require("../routes/basepath.js").default || require("../routes/basepath.js");
|
||||||
|
app.use(basePath, basepathRouter);
|
||||||
|
|
||||||
/* Handle static files first so excessive logging doesn't occur */
|
/* Handle static files first so excessive logging doesn't occur */
|
||||||
app.use(basePath, express.static(frontendPath, { index: false }));
|
app.use(basePath, express.static(frontendPath, { index: false }));
|
||||||
|
|
||||||
const index = require("../routes/index");
|
// index route (may be ESM default export)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const index = require("../routes/index.js").default || require("../routes/index.js");
|
||||||
|
|
||||||
if (config.has("admin")) {
|
if (config.has("admin")) {
|
||||||
const admin = config.get("admin");
|
const admin = config.get("admin");
|
||||||
app.set("admin", admin);
|
app.set("admin", admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(`${basePath}api/v1/games`, require("../routes/games"));
|
// games router
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const gamesRouter = require("../routes/games.js").default || require("../routes/games.js");
|
||||||
|
app.use(`${basePath}api/v1/games`, gamesRouter);
|
||||||
|
|
||||||
/* Allow loading of the app w/out being logged in */
|
/* Allow loading of the app w/out being logged in */
|
||||||
app.use(basePath, index);
|
app.use(basePath, index);
|
||||||
@ -82,11 +93,14 @@ process.on('SIGINT', () => {
|
|||||||
server.close(() => process.exit(1));
|
server.close(() => process.exit(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
require("../db/games").then(function(db: any) {
|
// database initializers
|
||||||
gameDB = db;
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
require("../db/games.js").then(function(_db: any) {
|
||||||
|
// games DB initialized
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return require("../db/users").then(function(db: any) {
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
userDB = db;
|
return require("../db/users.js").then(function(_db: any) {
|
||||||
|
// users DB initialized
|
||||||
});
|
});
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
console.log("DB connected. Opening server.");
|
console.log("DB connected. Opening server.");
|
||||||
@ -118,4 +132,4 @@ server.on("error", function(error: any) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { app, server };
|
export { app, server };
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
"use strict";
|
function twoDigit(number: number): string {
|
||||||
|
|
||||||
function twoDigit(number) {
|
|
||||||
return ("0" + number).slice(-2);
|
return ("0" + number).slice(-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function timestamp(date) {
|
function timestamp(date?: Date): string {
|
||||||
date = date || new Date();
|
date = date || new Date();
|
||||||
return [ date.getFullYear(), twoDigit(date.getMonth() + 1), twoDigit(date.getDate()) ].join("-") +
|
return [ date.getFullYear(), twoDigit(date.getMonth() + 1), twoDigit(date.getDate()) ].join("-") +
|
||||||
" " +
|
" " +
|
||||||
[ twoDigit(date.getHours()), twoDigit(date.getMinutes()), twoDigit(date.getSeconds()) ].join(":");
|
[ twoDigit(date.getHours()), twoDigit(date.getMinutes()), twoDigit(date.getSeconds()) ].join(":");
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export {
|
||||||
timestamp
|
timestamp
|
||||||
};
|
};
|
@ -1,19 +1,32 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2022",
|
||||||
"module": "CommonJS",
|
"module": "CommonJS",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src",
|
"rootDir": ".",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"strict": false,
|
"strict": true,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": true,
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"sourceMap": true
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["**/*.ts", "**/*.js"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist", "test-output"]
|
||||||
}
|
}
|
||||||
|
19
server/types/express-session.d.ts
vendored
Normal file
19
server/types/express-session.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'express';
|
||||||
|
import 'express-session';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Express {
|
||||||
|
interface Request {
|
||||||
|
// populated by getSessionUser
|
||||||
|
user?: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'express-session' {
|
||||||
|
interface SessionData {
|
||||||
|
userId?: number | null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
@ -62,7 +62,7 @@
|
|||||||
* |
|
* |
|
||||||
* 12 | 11 10
|
* 12 | 11 10
|
||||||
*/
|
*/
|
||||||
const Tile = (corners, roads) => {
|
const Tile = (corners: number[], roads: number[]) => {
|
||||||
return {
|
return {
|
||||||
corners: corners, /* 6 */
|
corners: corners, /* 6 */
|
||||||
pip: -1,
|
pip: -1,
|
||||||
@ -74,7 +74,7 @@ const Tile = (corners, roads) => {
|
|||||||
/* Borders have three sections each, so they are numbered
|
/* Borders have three sections each, so they are numbered
|
||||||
* 0-17 clockwise. Some corners share two borders. */
|
* 0-17 clockwise. Some corners share two borders. */
|
||||||
|
|
||||||
const Corner = (roads, banks) => {
|
const Corner = (roads: number[], banks: number[]) => {
|
||||||
return {
|
return {
|
||||||
roads: roads, /* max of 3 */
|
roads: roads, /* max of 3 */
|
||||||
banks: banks, /* max of 2 */
|
banks: banks, /* max of 2 */
|
||||||
@ -82,7 +82,7 @@ const Corner = (roads, banks) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const Road = (corners) => {
|
const Road = (corners: number[]) => {
|
||||||
return {
|
return {
|
||||||
corners: corners, /* 2 */
|
corners: corners, /* 2 */
|
||||||
data: undefined
|
data: undefined
|
||||||
@ -314,7 +314,7 @@ const staticData = {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
export {
|
||||||
layout,
|
layout,
|
||||||
staticData
|
staticData
|
||||||
};
|
};
|
@ -1,34 +1,41 @@
|
|||||||
const { layout } = require('./layout.js');
|
import { layout } from './layout.js';
|
||||||
|
|
||||||
const isRuleEnabled = (game, rule) => {
|
const isRuleEnabled = (game: any, rule: string): boolean => {
|
||||||
return rule in game.rules && game.rules[rule].enabled;
|
return rule in game.rules && game.rules[rule].enabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getValidRoads = (game, color) => {
|
const getValidRoads = (game: any, color: string): number[] => {
|
||||||
const limits = [];
|
const limits: number[] = [];
|
||||||
|
|
||||||
/* For each road, if the road is set, skip it.
|
/* For each road, if the road is set, skip it.
|
||||||
* If no color is set, check the two corners. If the corner
|
* If no color is set, check the two corners. If the corner
|
||||||
* has a matching color, add this to the set. Otherwise skip.
|
* has a matching color, add this to the set. Otherwise skip.
|
||||||
*/
|
*/
|
||||||
layout.roads.forEach((road, roadIndex) => {
|
layout.roads.forEach((road, roadIndex) => {
|
||||||
if (game.placements.roads[roadIndex].color) {
|
if (!game.placements || !game.placements.roads || game.placements.roads[roadIndex]?.color) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let valid = false;
|
let valid = false;
|
||||||
for (let c = 0; !valid && c < road.corners.length; c++) {
|
for (let c = 0; !valid && c < road.corners.length; c++) {
|
||||||
const corner = layout.corners[road.corners[c]],
|
const cornerIndex = road.corners[c] as number;
|
||||||
cornerColor = game.placements.corners[road.corners[c]].color;
|
if (cornerIndex == null || (layout as any).corners[cornerIndex] == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const corner = (layout as any).corners[cornerIndex];
|
||||||
|
const cornerColor = (game as any).placements && (game as any).placements.corners && (game as any).placements.corners[cornerIndex] && (game as any).placements.corners[cornerIndex].color;
|
||||||
/* Roads do not pass through other player's settlements */
|
/* Roads do not pass through other player's settlements */
|
||||||
if (cornerColor && cornerColor !== color) {
|
if (cornerColor && cornerColor !== color) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (let r = 0; !valid && r < corner.roads.length; r++) {
|
for (let r = 0; !valid && r < (corner.roads || []).length; r++) {
|
||||||
/* This side of the corner is pointing to the road being validated. Skip it. */
|
/* This side of the corner is pointing to the road being validated. Skip it. */
|
||||||
if (corner.roads[r] === roadIndex) {
|
if (!corner.roads || corner.roads[r] === roadIndex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (game.placements.roads[corner.roads[r]].color === color) {
|
const rr = corner.roads[r];
|
||||||
|
if (rr == null) { continue; }
|
||||||
|
const placementsRoads = (game as any).placements && (game as any).placements.roads;
|
||||||
|
if (placementsRoads && placementsRoads[rr] && placementsRoads[rr].color === color) {
|
||||||
valid = true;
|
valid = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,8 +48,8 @@ const getValidRoads = (game, color) => {
|
|||||||
return limits;
|
return limits;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getValidCorners = (game, color, type) => {
|
const getValidCorners = (game: any, color: string, type?: string): number[] => {
|
||||||
const limits = [];
|
const limits: number[] = [];
|
||||||
|
|
||||||
/* For each corner, if the corner already has a color set, skip it if type
|
/* For each corner, if the corner already has a color set, skip it if type
|
||||||
* isn't set. If type is set, if it is a match, and the color is a match,
|
* isn't set. If type is set, if it is a match, and the color is a match,
|
||||||
@ -77,14 +84,20 @@ const getValidCorners = (game, color, type) => {
|
|||||||
valid = true; /* Not filtering based on current player */
|
valid = true; /* Not filtering based on current player */
|
||||||
} else {
|
} else {
|
||||||
valid = false;
|
valid = false;
|
||||||
for (let r = 0; !valid && r < corner.roads.length; r++) {
|
for (let r = 0; !valid && r < (corner.roads || []).length; r++) {
|
||||||
valid = game.placements.roads[corner.roads[r]].color === color;
|
const rr = corner.roads[r];
|
||||||
|
if (rr == null) { continue; }
|
||||||
|
const placementsRoads = (game as any).placements && (game as any).placements.roads;
|
||||||
|
valid = !!(placementsRoads && placementsRoads[rr] && placementsRoads[rr].color === color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let r = 0; valid && r < corner.roads.length; r++) {
|
for (let r = 0; valid && r < (corner.roads || []).length; r++) {
|
||||||
const road = layout.roads[corner.roads[r]];
|
if (!corner.roads) { break; }
|
||||||
for (let c = 0; valid && c < road.corners.length; c++) {
|
const ridx = corner.roads[r] as number;
|
||||||
|
if (ridx == null || (layout as any).roads[ridx] == null) { continue; }
|
||||||
|
const road = (layout as any).roads[ridx];
|
||||||
|
for (let c = 0; valid && c < (road.corners || []).length; c++) {
|
||||||
/* This side of the road is pointing to the corner being validated.
|
/* This side of the road is pointing to the corner being validated.
|
||||||
* Skip it. */
|
* Skip it. */
|
||||||
if (road.corners[c] === cornerIndex) {
|
if (road.corners[c] === cornerIndex) {
|
||||||
@ -92,7 +105,8 @@ const getValidCorners = (game, color, type) => {
|
|||||||
}
|
}
|
||||||
/* There is a settlement within one segment from this
|
/* There is a settlement within one segment from this
|
||||||
* corner, so it is invalid for settlement placement */
|
* corner, so it is invalid for settlement placement */
|
||||||
if (game.placements.corners[road.corners[c]].color) {
|
const cc = road.corners[c] as number;
|
||||||
|
if ((game as any).placements && (game as any).placements.corners && (game as any).placements.corners[cc] && (game as any).placements.corners[cc].color) {
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,7 +117,7 @@ const getValidCorners = (game, color, type) => {
|
|||||||
* on the volcano) */
|
* on the volcano) */
|
||||||
if (!(game.state === 'initial-placement'
|
if (!(game.state === 'initial-placement'
|
||||||
&& isRuleEnabled(game, 'volcano')
|
&& isRuleEnabled(game, 'volcano')
|
||||||
&& layout.tiles[game.robber].corners.indexOf(cornerIndex) !== -1
|
&& (layout as any).tiles && (layout as any).tiles[(game as any).robber] && Array.isArray((layout as any).tiles[(game as any).robber].corners) && (layout as any).tiles[(game as any).robber].corners.indexOf(cornerIndex as number) !== -1
|
||||||
)) {
|
)) {
|
||||||
limits.push(cornerIndex);
|
limits.push(cornerIndex);
|
||||||
}
|
}
|
||||||
@ -113,7 +127,7 @@ const getValidCorners = (game, color, type) => {
|
|||||||
return limits;
|
return limits;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export {
|
||||||
getValidCorners,
|
getValidCorners,
|
||||||
getValidRoads,
|
getValidRoads,
|
||||||
isRuleEnabled
|
isRuleEnabled
|
Loading…
x
Reference in New Issue
Block a user