);
if (
this.st.user.sid == g.players[0].sid ||
- this.st.user.id == g.players[0].uid
+ this.st.user.id == g.players[0].id
)
return g.players[1].name || "@nonymous";
return g.players[0].name || "@nonymous";
if (
// My game ?
game.players.some(p =>
- p.sid == this.st.user.sid ||
- p.uid == this.st.user.id
+ p.sid == this.st.user.sid || p.id == this.st.user.id
)
) {
const message =
GameStorage.remove(game.id, afterDelete);
else {
const mySide =
- game.players[0].uid == this.st.user.id
+ game.players[0].id == this.st.user.id
? "White"
: "Black";
game["deletedBy" + mySide] = true;
.catch((err) => { this.alertAndQuit("Mispelled variant name", true); });
},
loadGame: function(orientation) {
- // NOTE: no need to set score (~unused)
this.game.vname = this.gameRef.vname;
this.game.fenStart = this.gameRef.fen;
this.game.fen = this.gameRef.fen;
+ this.game.score = "*"; //never change
this.curFen = this.game.fen;
this.adjustFenSize();
this.game.mycolor = orientation || V.ParseFen(this.gameRef.fen).turn;
isConnected: function(index) {
const player = this.game.players[index];
// Is it me ? In this case no need to bother with focus
- if (this.st.user.sid == player.sid || this.st.user.id == player.uid)
+ if (this.st.user.sid == player.sid || this.st.user.id == player.id)
// Still have to check for name (because of potential multi-accounts
// on same browser, although this should be rare...)
return (!this.st.user.name || this.st.user.name == player.name);
)
||
(
- player.uid &&
+ player.id &&
Object.values(this.people).some(p =>
- p.id == player.uid && p.focus)
+ p.id == player.id && p.focus)
)
);
},
{
data: data,
targets: this.game.players.map(p => {
- return { sid: p.sid, uid: p.uid };
+ return { sid: p.sid, id: p.id };
})
}
);
this.addAndGotoLiveGame(gameInfo);
} else if (
gameType == "corr" &&
- gameInfo.players.some(p => p.uid == this.st.user.id)
+ gameInfo.players.some(p => p.id == this.st.user.id)
) {
this.$router.push("/game/" + gameInfo.id);
} else {
const gtype = this.getGameType(game);
const tc = extractTime(game.cadence);
const myIdx = game.players.findIndex(p => {
- return p.sid == this.st.user.sid || p.uid == this.st.user.id;
+ return p.sid == this.st.user.sid || p.id == this.st.user.id;
});
const mycolor = [undefined, "w", "b"][myIdx + 1]; //undefined for observers
if (!game.chats) game.chats = []; //live games don't have chat history
// opponent sid not strictly required (or available), but easier
// at least oppsid or oppid is available anyway:
oppsid: myIdx < 0 ? undefined : game.players[1 - myIdx].sid,
- oppid: myIdx < 0 ? undefined : game.players[1 - myIdx].uid
+ oppid: myIdx < 0 ? undefined : game.players[1 - myIdx].id
},
game,
);
g.moves.forEach(m => {
m.squares = JSON.parse(m.squares);
});
+ g.players = [{ id: g.white }, { id: g.black }];
+ delete g["white"];
+ delete g["black"];
afterRetrieval(g);
}
}
this.game.scoreMsg = scoreMsg;
this.$set(this.game, "scoreMsg", scoreMsg);
const myIdx = this.game.players.findIndex(p => {
- return p.sid == this.st.user.sid || p.uid == this.st.user.id;
+ return p.sid == this.st.user.sid || p.id == this.st.user.id;
});
if (myIdx >= 0) {
// OK, I play in this game
:showBoth="true"
@show-game="showGame"
)
- GameList(
- v-show="gdisplay=='corr'"
- :games="filterGames('corr')"
- :showBoth="true"
- @show-game="showGame"
- )
+ div(v-show="gdisplay=='corr'")
+ GameList(
+ :games="filterGames('corr')"
+ :showBoth="true"
+ @show-game="showGame"
+ )
+ button#loadMoreBtn(
+ v-if="hasMore"
+ @click="loadMore()"
+ )
+ | {{ st.tr["Load more"] }}
</template>
<script>
st: store.state,
cdisplay: "live", //or corr
gdisplay: "live",
+ // timestamp of last showed (oldest) corr game:
+ cursor: Number.MAX_SAFE_INTEGER,
+ // hasMore == TRUE: a priori there could be more games to load
+ hasMore: true,
games: [],
challenges: [],
people: {},
this.setDisplay('g', showGtype);
// Ask server for current corr games (all but mines)
ajax(
- "/games",
+ "/observedgames",
"GET",
{
- data: { uid: this.st.user.id, excluded: true },
+ data: {
+ uid: this.st.user.id,
+ cursor: this.cursor
+ },
success: (response) => {
if (
response.games.length > 0 &&
}
this.games = this.games.concat(
response.games.map(g => {
- const type = this.classifyObject(g);
const vname = this.getVname(g.vid);
return Object.assign(
{},
g,
{
- type: type,
+ type: "corr",
vname: vname
}
);
const game = data.data;
// Ignore games where I play (will go in MyGames page)
if (game.players.every(p =>
- p.sid != this.st.user.sid && p.uid != this.st.user.id))
+ p.sid != this.st.user.sid && p.id != this.st.user.id))
{
let locGame = this.games.find(g => g.id == game.id);
if (!locGame) {
this.conn.addEventListener("message", this.socketMessageListener);
this.conn.addEventListener("close", this.socketCloseListener);
},
+ loadMore: function() {
+ ajax(
+ "/observedgames",
+ "GET",
+ {
+ data: {
+ uid: this.st.user.id,
+ cursor: this.cursor
+ },
+ success: (res) => {
+ if (res.games.length > 0) {
+ const L = res.games.length;
+ this.cursor = res.games[L - 1].created;
+ let moreGames = res.games.map(g => {
+ const vname = this.getVname(g.vid);
+ return Object.assign(
+ {},
+ g,
+ {
+ type: "corr",
+ vname: vname
+ }
+ );
+ });
+ this.games = this.games.concat(moreGames);
+ } else this.hasMore = false;
+ }
+ }
+ );
+ },
// Challenge lifecycle:
addChallenge: function(chall) {
// NOTE about next condition: see "askchallenges" case.
!!c.mycolor
? (c.mycolor == "w" ? [c.seat, c.from] : [c.from, c.seat])
: shuffle([c.from, c.seat]);
- // Convention for players IDs in stored games is 'uid'
- players.forEach(p => {
- let pWithUid = p;
- pWithUid["uid"] = p.id;
- delete pWithUid["id"];
- });
// These game informations will be shared
let gameInfo = {
id: getRandString(),
fen: c.fen || V.GenRandInitFen(c.randomness),
+ randomness: c.randomness, //for rematch
// White player index 0, black player index 1:
players: c.mycolor
? (c.mycolor == "w" ? [c.seat, c.from] : [c.from, c.seat])
{
data: gameInfo,
targets: gameInfo.players.map(p => {
- return { sid: p.sid, uid: p.uid };
+ return { sid: p.sid, id: p.id };
})
}
);
() => {
GameStorage.add(game, (err) => {
// If an error occurred, game is not added: a tab already
- // added the game and (if focused) is redirected toward it.
- // If no error and the tab is hidden: do not show anything.
- if (!err && !document.hidden) {
- if (this.st.settings.sound)
- new Audio("/sounds/newgame.flac").play().catch(() => {});
- this.$router.push("/game/" + gameInfo.id);
- }
+ // added the game. Maybe a focused one, maybe not.
+ // We know for sure that it emitted the gong start sound.
+ // ==> Do not play it again.
+ if (!err && this.st.settings.sound)
+ new Audio("/sounds/newgame.flac").play().catch(() => {});
+ this.$router.push("/game/" + gameInfo.id);
});
},
document.hidden ? 500 + 1000 * Math.random() : 0
h4
margin: 5px 0
+button#loadMoreBtn
+ margin-top: 0
+ margin-bottom: 0
+
td.remove-preset
background-color: lightgrey
text-align: center
@show-game="showGame"
@abortgame="abortGame"
)
- GameList(
- ref="corrgames"
- v-show="display=='corr'"
- :games="corrGames"
- @show-game="showGame"
- @abortgame="abortGame"
- )
+ div(v-show="display=='corr'")
+ GameList(
+ ref="corrgames"
+ :games="corrGames"
+ @show-game="showGame"
+ @abortgame="abortGame"
+ )
+ button#loadMoreBtn(
+ v-if="hasMore"
+ @click="loadMore()"
+ )
+ | {{ st.tr["Load more"] }}
</template>
<script>
display: "live",
liveGames: [],
corrGames: [],
+ // timestamp of last showed (oldest) corr game:
+ cursor: Number.MAX_SAFE_INTEGER,
+ // hasMore == TRUE: a priori there could be more games to load
+ hasMore: true,
conn: null,
connexionString: ""
};
this.decorate(localGames);
this.liveGames = localGames;
if (this.st.user.id > 0) {
+ // Ask running corr games first
ajax(
- "/games",
+ "/runninggames",
"GET",
{
- data: { uid: this.st.user.id },
success: (res) => {
- let serverGames = res.games.filter(g => {
- const mySide =
- g.players[0].uid == this.st.user.id
- ? "White"
- : "Black";
- return !g["deletedBy" + mySide];
- });
- serverGames.forEach(g => g.type = "corr");
- this.decorate(serverGames);
- this.corrGames = serverGames;
- adjustAndSetDisplay();
+ // These games are garanteed to not be deleted
+ this.corrGames = res.games;
+ this.corrGames.forEach(g => g.type = "corr");
+ this.decorate(this.corrGames);
+ // Now ask completed games (partial list)
+ ajax(
+ "/completedgames",
+ "GET",
+ {
+ data: { cursor: this.cursor },
+ success: (res2) => {
+ if (res2.games.length > 0) {
+ const L = res2.games.length;
+ this.cursor = res2.games[L - 1].created;
+ let completedGames = res2.games;
+ completedGames.forEach(g => g.type = "corr");
+ this.decorate(completedGames);
+ this.corrGames = this.corrGames.concat(completedGames);
+ adjustAndSetDisplay();
+ }
+ }
+ }
+ );
}
}
);
decorate: function(games) {
games.forEach(g => {
g.myColor =
- (g.type == "corr" && g.players[0].uid == this.st.user.id) ||
+ (g.type == "corr" && g.players[0].id == this.st.user.id) ||
(g.type == "live" && g.players[0].sid == this.st.user.sid)
? 'w'
: 'b';
gameInfo
);
game.myTurn =
- (type == "corr" && game.players[0].uid == this.st.user.id) ||
+ (type == "corr" && game.players[0].id == this.st.user.id) ||
(type == "live" && game.players[0].sid == this.st.user.sid);
gamesArrays[type].push(game);
if (game.myTurn) this.tryShowNewsIndicator(type);
}
);
}
+ },
+ loadMore: function() {
+ ajax(
+ "/completedgames",
+ "GET",
+ {
+ data: { cursor: this.cursor },
+ success: (res) => {
+ if (res.games.length > 0) {
+ const L = res.games.length;
+ this.cursor = res.games[L - 1].created;
+ let moreGames = res.games;
+ moreGames.forEach(g => g.type = "corr");
+ this.decorate(moreGames);
+ this.corrGames = this.corrGames.concat(moreGames);
+ } else this.hasMore = false;
+ }
+ }
+ );
}
}
};
table.game-list
max-height: 100%
+button#loadMoreBtn
+ margin-top: 0
+ margin-bottom: 0
+
.somethingnew
background-color: #c5fefe !important
</style>
return {
devs: [1], //for now the only dev is me
st: store.state,
- cursor: 0, //ID of last showed news
- hasMore: true, //a priori there could be more news to load
+ // timestamp of oldest showed news:
+ cursor: Number.MAX_SAFE_INTEGER,
+ // hasMore == TRUE: a priori there could be more news to load
+ hasMore: true,
curnews: { id: 0, content: "" },
newsList: [],
infoMsg: ""
{
data: { cursor: this.cursor },
success: (res) => {
- this.newsList = res.newsList.sort((n1, n2) => n2.added - n1.added);
+ // The returned list is sorted from most recent to oldest
+ this.newsList = res.newsList;
const L = res.newsList.length;
- if (L > 0) this.cursor = this.newsList[0].id;
+ if (L > 0) this.cursor = res.newsList[L - 1].added;
}
}
);
if (res.newsList.length > 0) {
this.newsList = this.newsList.concat(res.newsList);
const L = res.newsList.length;
- if (L > 0) this.cursor = res.newsList[L - 1].id;
+ if (L > 0) this.cursor = res.newsList[L - 1].added;
} else this.hasMore = false;
}
}
app.use(favicon(path.join(__dirname, "static", "favicon.ico")));
if (app.get('env') === 'development')
-{
// Full logging in development mode
app.use(logger('dev'));
-}
-else
-{
+else {
// http://dev.rdybarra.com/2016/06/23/Production-Logging-With-Morgan-In-Express/
app.set('trust proxy', true);
// In prod, only log error responses (https://github.com/expressjs/morgan)
app.use(express.static(path.join(__dirname, 'fallback')));
// In development stage the client side has its own server
-if (params.cors.enable)
-{
+if (params.cors.enable) {
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", params.cors.allowedOrigin);
res.header("Access-Control-Allow-Credentials", true); //for cookies
// Error handler
app.use(function(err, req, res, next) {
res.status(err.status || 500);
- if (app.get('env') === 'development')
- console.log(err.stack);
+ if (app.get('env') === 'development') console.log(err.stack);
res.send(
"<h1>" + err.message + "</h1>" +
"<h2>" + err.status + "</h2>"
-module.exports =
-{
+module.exports = {
// For mail sending. NOTE: *no trailing slash*
siteURL: "http://localhost:8080",
vid integer,
fenStart varchar, --initial state
fen varchar, --current state
+ white integer,
+ black integer,
score varchar default '*',
scoreMsg varchar,
cadence varchar,
rematchOffer character default '',
deletedByWhite boolean,
deletedByBlack boolean,
- foreign key (vid) references Variants(id)
+ foreign key (vid) references Variants(id),
+ foreign key (white) references Users(id),
+ foreign key (black) references Users(id)
);
create table Chats (
added datetime
);
--- Store informations about players in a corr game
-create table Players (
- gid integer,
- uid integer,
- color character,
- foreign key (gid) references Games(id),
- foreign key (uid) references Users(id)
-);
-
create table Moves (
gid integer,
squares varchar, --description, appear/vanish/from/to
foreign key (gid) references Games(id)
);
+create index scoreIdx on Games(score);
+
pragma foreign_keys = on;
* cadence: string (3m+2s, 7d ...)
*/
-const ChallengeModel =
-{
- checkChallenge: function(c)
- {
+const ChallengeModel = {
+ checkChallenge: function(c) {
return (
c.vid.toString().match(/^[0-9]+$/) &&
c.cadence.match(/^[0-9dhms +]+$/) &&
c.randomness.toString().match(/^[0-2]$/) &&
c.fen.match(/^[a-zA-Z0-9, /-]*$/) &&
- (!c.to || UserModel.checkNameEmail({name: c.to}))
+ (!c.to || UserModel.checkNameEmail({ name: c.to }))
);
},
- create: function(c, cb)
- {
+ create: function(c, cb) {
db.serialize(function() {
const query =
"INSERT INTO Challenges " +
"(" + Date.now() + "," + c.uid + "," + (c.to ? c.to + "," : "") +
c.vid + "," + c.randomness + ",'" + c.fen + "','" + c.cadence + "')";
db.run(query, function(err) {
- cb(err, {cid: this.lastID});
+ cb(err, { id: this.lastID });
});
});
},
// All challenges related to user with ID uid
- getByUser: function(uid, cb)
- {
+ getByUser: function(uid, cb) {
db.serialize(function() {
const query =
"SELECT * " +
"WHERE target IS NULL" +
" OR uid = " + uid +
" OR target = " + uid;
- db.all(query, (err,challenges) => {
+ db.all(query, (err, challenges) => {
cb(err, challenges);
});
});
},
- remove: function(id)
- {
+ remove: function(id) {
db.serialize(function() {
const query =
"DELETE FROM Challenges " +
});
},
- safeRemove: function(id, uid)
- {
+ safeRemove: function(id, uid) {
db.serialize(function() {
const query =
"SELECT 1 " +
* vid: integer (variant id)
* fenStart: varchar (initial position)
* fen: varchar (current position)
+ * white: integer
+ * black: integer
* cadence: string
* score: varchar (result)
* scoreMsg: varchar ("Time", "Mutual agreement"...)
* created: datetime
* drawOffer: char ('w','b' or '' for none)
- *
- * Structure table Players:
- * gid: ref game id
- * uid: ref user id
- * color: character
+ * rematchOffer: char (similar to drawOffer)
+ * randomness: integer
+ * deletedByWhite: boolean
+ * deletedByBlack: boolean
*
* Structure table Moves:
* gid: ref game id
return (
g.vid.toString().match(/^[0-9]+$/) &&
g.cadence.match(/^[0-9dhms +]+$/) &&
+ g.randomness.match(/^[0-2]$/) &&
g.fen.match(/^[a-zA-Z0-9, /-]*$/) &&
g.players.length == 2 &&
- g.players.every(p => p.uid.toString().match(/^[0-9]+$/))
+ g.players.every(p => p.toString().match(/^[0-9]+$/))
);
},
- create: function(vid, fen, cadence, players, cb)
- {
+ create: function(vid, fen, randomness, cadence, players, cb) {
db.serialize(function() {
let query =
"INSERT INTO Games " +
- "(vid, fenStart, fen, cadence, created) " +
+ "(" +
+ "vid, fenStart, fen, randomness, " +
+ "white, black, " +
+ "cadence, created" +
+ ") " +
"VALUES " +
- "(" + vid + ",'" + fen + "','" + fen + "','" + cadence + "'," + Date.now() + ")";
+ "(" +
+ vid + ",'" + fen + "','" + fen + "'," + randomness + "," +
+ "'" + players[0] + "','" + players[1] + "," +
+ "'" + cadence + "'," + Date.now() +
+ ")";
db.run(query, function(err) {
- if (err)
- cb(err)
- else
- {
- players.forEach((p,idx) => {
- const color = (idx==0 ? "w" : "b");
- query =
- "INSERT INTO Players VALUES " +
- "(" + this.lastID + "," + p.uid + ",'" + color + "')";
- db.run(query);
- });
- cb(null, {gid: this.lastID});
- }
+ cb(err, { id: this.lastId });
});
});
},
// TODO: some queries here could be async
- getOne: function(id, cb)
- {
+ getOne: function(id, cb) {
// NOTE: ignoring errors (shouldn't happen at this stage)
db.serialize(function() {
let query =
- "SELECT g.id, g.vid, g.fen, g.fenStart, g.cadence, g.created, g.score, " +
- "g.scoreMsg, g.drawOffer, g.rematchOffer, v.name AS vname " +
+ "SELECT " +
+ "g.id, g.vid, g.fen, g.fenStart, g.cadence, g.created, " +
+ "g.white, g.black, g.score, g.scoreMsg, " +
+ "g.drawOffer, g.rematchOffer, v.name AS vname " +
"FROM Games g " +
"JOIN Variants v " +
" ON g.vid = v.id " +
"WHERE g.id = " + id;
db.get(query, (err, gameInfo) => {
query =
- "SELECT p.uid, p.color, u.name " +
- "FROM Players p " +
- "JOIN Users u " +
- " ON p.uid = u.id " +
- "WHERE p.gid = " + id;
- db.all(query, (err2, players) => {
+ "SELECT squares, played, idx " +
+ "FROM Moves " +
+ "WHERE gid = " + id;
+ db.all(query, (err3, moves) => {
query =
- "SELECT squares, played, idx " +
- "FROM Moves " +
+ "SELECT msg, name, added " +
+ "FROM Chats " +
"WHERE gid = " + id;
- db.all(query, (err3, moves) => {
- query =
- "SELECT msg, name, added " +
- "FROM Chats " +
- "WHERE gid = " + id;
- db.all(query, (err4, chats) => {
- const game = Object.assign(
- {},
- gameInfo,
- {
- players: players,
- moves: moves,
- chats: chats,
- }
- );
- cb(null, game);
- });
+ db.all(query, (err4, chats) => {
+ const game = Object.assign(
+ {},
+ gameInfo,
+ {
+ moves: moves,
+ chats: chats
+ }
+ );
+ cb(null, game);
});
});
});
});
},
- // For display on MyGames or Hall: no need for moves or chats
- getByUser: function(uid, excluded, cb)
- {
- // Some fields are not required when showing a games list:
- const getOneLight = (id, cb2) => {
+ // For display on Hall: no need for moves or chats
+ getObserved: function(uid, cursor, cb) {
+ db.serialize(function() {
+ let query =
+ "SELECT g.id, g.vid, g.cadence, g.created, " +
+ " g.score, g.white, g.black " +
+ "FROM Games g ";
+ if (uid > 0) query +=
+ "WHERE " +
+ " created < " + cursor + " AND " +
+ " white <> " + uid + " AND " +
+ " black <> " + uid + " ";
+ query +=
+ "ORDER BY created DESC " +
+ "LIMIT 20"; //TODO: 20 hard-coded...
+ db.all(query, (err, games) => {
+ // Query players names
+ let pids = {};
+ games.forEach(g => {
+ if (!pids[g.white]) pids[g.white] = true;
+ if (!pids[g.black]) pids[g.black] = true;
+ });
+ UserModel.getByIds(Object.keys(pids), (err2, users) => {
+ let names = {};
+ users.forEach(u => { names[u.id] = u.name; });
+ cb(
+ games.map(
+ g => {
+ return {
+ id: g.id,
+ cadence: g.cadence,
+ vname: g.vname,
+ created: g.created,
+ score: g.score,
+ white: names[g.white],
+ black: names[g.black]
+ };
+ }
+ )
+ );
+ });
+ });
+ });
+ },
+
+ // For display on MyGames: registered user only
+ getRunning: function(uid, cb) {
+ db.serialize(function() {
let query =
- "SELECT g.id, g.vid, g.fen, g.cadence, g.created, g.score, " +
- "g.scoreMsg, g.deletedByWhite, g.deletedByBlack, v.name AS vname " +
+ "SELECT g.id, g.cadence, g.created, g.score, " +
+ "g.white, g.black, v.name AS vname " +
"FROM Games g " +
"JOIN Variants v " +
" ON g.vid = v.id " +
- "WHERE g.id = " + id;
- db.get(query, (err, gameInfo) => {
+ "WHERE white = " + uid + " OR black = " + uid;
+ db.all(query, (err, games) => {
+ // Get movesCount (could be done in // with next query)
query =
- "SELECT p.uid, p.color, u.name " +
- "FROM Players p " +
- "JOIN Users u " +
- " ON p.uid = u.id " +
- "WHERE p.gid = " + id;
- db.all(query, (err2, players) => {
- query =
- "SELECT COUNT(*) AS nbMoves " +
- "FROM Moves " +
- "WHERE gid = " + id;
- db.get(query, (err,ret) => {
- const game = Object.assign(
- {},
- gameInfo,
- {
- players: players,
- movesCount: ret.nbMoves
- }
+ "SELECT gid, COUNT(*) AS nbMoves " +
+ "FROM Moves " +
+ "WHERE gid IN " + "(" + games.map(g => g.id).join(",") + ") " +
+ "GROUP BY gid";
+ db.all(query, (err, mstats) => {
+ let movesCounts = {};
+ mstats.forEach(ms => { movesCounts[ms.gid] = ms.nbMoves; });
+ // Query player names
+ let pids = {};
+ games.forEach(g => {
+ if (!pids[g.white]) pids[g.white] = true;
+ if (!pids[g.black]) pids[g.black] = true;
+ });
+ UserModel.getByIds(pids, (err2, users) => {
+ let names = {};
+ users.forEach(u => { names[u.id] = u.name; });
+ cb(
+ games.map(
+ g => {
+ return {
+ id: g.id,
+ cadence: g.cadence,
+ vname: g.vname,
+ created: g.created,
+ score: g.score,
+ movesCount: movesCounts[g.id],
+ white: names[g.white],
+ black: names[g.black]
+ };
+ }
+ )
);
- cb2(game);
});
});
});
- };
+ });
+ },
+
+ // These games could be deleted on some side. movesCount not required
+ getCompleted: function(uid, cursor, cb) {
db.serialize(function() {
- let query = "";
- if (uid == 0) {
- // Special case anonymous user: show all games
- query =
- "SELECT id AS gid " +
- "FROM Games";
- }
- else {
- // Registered user:
- query =
- "SELECT gid " +
- "FROM Players " +
- "GROUP BY gid " +
- "HAVING COUNT(uid = " + uid + " OR NULL) " +
- (excluded ? " = 0" : " > 0");
- }
- db.all(query, (err,gameIds) => {
- if (err || gameIds.length == 0) cb(err, []);
- else {
- let gameArray = [];
- let gCounter = 0;
- for (let i=0; i<gameIds.length; i++) {
- getOneLight(gameIds[i]["gid"], (game) => {
- gameArray.push(game);
- gCounter++; //TODO: let's hope this is atomic?!
- // Call callback function only when gameArray is complete:
- if (gCounter == gameIds.length)
- cb(null, gameArray);
- });
- }
- }
+ let query =
+ "SELECT g.id, g.cadence, g.created, g.score, g.scoreMsg, " +
+ "g.white, g.black, g.deletedByWhite, g.deletedByBlack, " +
+ "v.name AS vname " +
+ "FROM Games g " +
+ "JOIN Variants v " +
+ " ON g.vid = v.id " +
+ "WHERE " +
+ " created < " + cursor + " AND " +
+ " (" +
+ " (" + uid + " = white AND NOT deletedByWhite) OR " +
+ " (" + uid + " = black AND NOT deletedByBlack)" +
+ " ) ";
+ query +=
+ "ORDER BY created DESC " +
+ "LIMIT 20";
+ db.all(query, (err, games) => {
+ // Query player names
+ let pids = {};
+ games.forEach(g => {
+ if (!pids[g.white]) pids[g.white] = true;
+ if (!pids[g.black]) pids[g.black] = true;
+ });
+ UserModel.getByIds(pids, (err2, users) => {
+ let names = {};
+ users.forEach(u => { names[u.id] = u.name; });
+ cb(
+ games.map(
+ g => {
+ return {
+ id: g.id,
+ cadence: g.cadence,
+ vname: g.vname,
+ created: g.created,
+ score: g.score,
+ scoreMsg: g.scoreMsg,
+ white: names[g.white],
+ black: names[g.black],
+ deletedByWhite: g.deletedByWhite,
+ deletedByBlack: g.deletedByBlack
+ };
+ }
+ )
+ );
+ });
});
});
},
- getPlayers: function(id, cb)
- {
+ getPlayers: function(id, cb) {
db.serialize(function() {
const query =
- "SELECT uid " +
- "FROM Players " +
+ "SELECT white, black " +
+ "FROM Games " +
"WHERE gid = " + id;
- db.all(query, (err,players) => {
+ db.all(query, (err, players) => {
return cb(err, players);
});
});
},
- checkGameUpdate: function(obj)
- {
+ checkGameUpdate: function(obj) {
// Check all that is possible (required) in obj:
return (
(
},
// obj can have fields move, chat, fen, drawOffer and/or score + message
- update: function(id, obj, cb)
- {
+ update: function(id, obj, cb) {
db.parallelize(function() {
let query =
"UPDATE Games " +
});
},
- remove: function(id)
- {
+ remove: function(id_s) {
+ const suffix =
+ Array.isArray(id_s)
+ ? " IN (" + id_s.join(",") + ")"
+ : " = " + id_s;
db.parallelize(function() {
let query =
"DELETE FROM Games " +
- "WHERE id = " + id;
- db.run(query);
- query =
- "DELETE FROM Players " +
- "WHERE gid = " + id;
+ "WHERE id " + suffix;
db.run(query);
query =
"DELETE FROM Moves " +
- "WHERE gid = " + id;
+ "WHERE gid " + suffix;
db.run(query);
query =
"DELETE FROM Chats " +
- "WHERE gid = " + id;
+ "WHERE gid " + suffix;
db.run(query);
});
},
- cleanGamesDb: function()
- {
+ cleanGamesDb: function() {
const tsNow = Date.now();
// 86400000 = 24 hours in milliseconds
const day = 86400000;
db.serialize(function() {
let query =
"SELECT id, created " +
- "FROM Games ";
- db.all(query, (err,games) => {
- games.forEach(g => {
- query =
- "SELECT count(*) as nbMoves, max(played) AS lastMaj " +
- "FROM Moves " +
- "WHERE gid = " + g.id;
- db.get(query, (err2,mstats) => {
- // Remove games still not really started,
- // with no action in the last 3 months:
- if ((mstats.nbMoves == 0 && tsNow - g.created > 91*day) ||
- (mstats.nbMoves == 1 && tsNow - mstats.lastMaj > 91*day))
- {
- GameModel.remove(g.id);
+ "FROM Games";
+ db.all(query, (err, games) => {
+ query =
+ "SELECT gid, count(*) AS nbMoves, MAX(played) AS lastMaj " +
+ "FROM Moves " +
+ "GROUP BY gid";
+ db.get(query, (err2, mstats) => {
+ // Reorganize moves data to avoid too many array lookups:
+ let movesGroups = {};
+ mstats.forEach(ms => {
+ movesGroups[ms.gid] = {
+ nbMoves: ms.nbMoves,
+ lastMaj: ms.lastMaj
+ };
+ });
+ // Remove games still not really started,
+ // with no action in the last 3 months:
+ let toRemove = [];
+ games.forEach(g => {
+ if (
+ (
+ !movesGroups[g.id] &&
+ tsNow - g.created > 91*day
+ )
+ ||
+ (
+ movesGroups[g.id].nbMoves == 1 &&
+ tsNow - movesGroups[g.id].lastMaj > 91*day
+ )
+ ) {
+ toRemove.push(g.id);
}
});
+ if (toRemove.length > 0) GameModel.remove(toRemove);
});
});
});
- },
+ }
}
module.exports = GameModel;
"VALUES " +
"(" + Date.now() + "," + uid + ",?)";
db.run(query, content, function(err) {
- cb(err, {nid: this.lastID});
+ cb(err, { id: this.lastID });
});
});
},
const query =
"SELECT * " +
"FROM News " +
- "WHERE id > " + cursor + " " +
+ "WHERE added < " + cursor + " " +
+ "ORDER BY added DESC " +
"LIMIT 10"; //TODO: 10 currently hard-coded
db.all(query, (err,newsList) => {
cb(err, newsList);
* solution: text
*/
-const ProblemModel =
-{
- checkProblem: function(p)
- {
+const ProblemModel = {
+ checkProblem: function(p) {
return (
p.id.toString().match(/^[0-9]+$/) &&
p.vid.toString().match(/^[0-9]+$/) &&
);
},
- create: function(p, cb)
- {
+ create: function(p, cb) {
db.serialize(function() {
const query =
"INSERT INTO Problems " +
"VALUES " +
"(" + Date.now() + "," + p.uid + "," + p.vid + ",'" + p.fen + "',?,?)";
db.run(query, [p.instruction,p.solution], function(err) {
- cb(err, {pid: this.lastID});
+ cb(err, { id: this.lastID });
});
});
},
- getAll: function(cb)
- {
+ getAll: function(cb) {
db.serialize(function() {
const query =
"SELECT * " +
});
},
- getOne: function(id, cb)
- {
+ getOne: function(id, cb) {
db.serialize(function() {
const query =
"SELECT * " +
});
},
- safeUpdate: function(prob, uid)
- {
+ safeUpdate: function(prob, uid) {
db.serialize(function() {
const query =
"UPDATE Problems " +
});
},
- safeRemove: function(id, uid)
- {
+ safeRemove: function(id, uid) {
db.serialize(function() {
const query =
"DELETE FROM Problems " +
* sessionToken: token in cookies for authentication
* notify: boolean (send email notifications for corr games)
* created: datetime
+ * newsRead: datetime
*/
-const UserModel =
-{
- checkNameEmail: function(o)
- {
+const UserModel = {
+ checkNameEmail: function(o) {
return (
(!o.name || !!(o.name.match(/^[\w-]+$/))) &&
(!o.email || !!(o.email.match(/^[\w.+-]+@[\w.+-]+$/)))
);
},
- create: function(name, email, notify, cb)
- {
+ create: function(name, email, notify, cb) {
db.serialize(function() {
const query =
"INSERT INTO Users " +
"(name, email, notify, created) VALUES " +
"('" + name + "','" + email + "'," + notify + "," + Date.now() + ")";
db.run(query, function(err) {
- cb(err, {uid: this.lastID});
+ cb(err, { id: this.lastID });
});
});
},
// Find one user by id, name, email, or token
- getOne: function(by, value, cb)
- {
+ getOne: function(by, value, cb) {
const delimiter = (typeof value === "string" ? "'" : "");
db.serialize(function() {
const query =
/////////
// MODIFY
- setLoginToken: function(token, uid)
- {
+ setLoginToken: function(token, id) {
db.serialize(function() {
const query =
"UPDATE Users " +
"SET loginToken = '" + token + "',loginTime = " + Date.now() + " " +
- "WHERE id = " + uid;
+ "WHERE id = " + id;
db.run(query);
});
},
- setNewsRead: function(uid)
- {
+ setNewsRead: function(id) {
db.serialize(function() {
const query =
"UPDATE Users " +
"SET newsRead = " + Date.now() + " " +
- "WHERE id = " + uid;
+ "WHERE id = " + id;
db.run(query);
});
},
// Set session token only if empty (first login)
// NOTE: weaker security (but avoid to re-login everywhere after each logout)
// TODO: option would be to reset all tokens periodically, e.g. every 3 months
- trySetSessionToken: function(uid, cb)
- {
+ trySetSessionToken: function(id, cb) {
db.serialize(function() {
let query =
"SELECT sessionToken " +
"FROM Users " +
- "WHERE id = " + uid;
+ "WHERE id = " + id;
db.get(query, (err,ret) => {
const token = ret.sessionToken || genToken(params.token.length);
query =
// Also empty the login token to invalidate future attempts
"SET loginToken = NULL" +
(!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
- "WHERE id = " + uid;
+ "WHERE id = " + id;
db.run(query);
cb(token);
});
});
},
- updateSettings: function(user)
- {
+ updateSettings: function(user) {
db.serialize(function() {
const query =
"UPDATE Users " +
/////////////////
// NOTIFICATIONS
- notify: function(user, message)
- {
+ notify: function(user, message) {
const subject = "vchess.club - notification";
const body = "Hello " + user.name + " !" + `
` + message;
sendEmail(params.mail.noreply, user.email, subject, body);
},
- tryNotify: function(id, message)
- {
+ tryNotify: function(id, message) {
UserModel.getOne("id", id, (err,user) => {
- if (!err && user.notify)
- UserModel.notify(user, message);
+ if (!err && user.notify) UserModel.notify(user, message);
});
},
////////////
// CLEANING
- cleanUsersDb: function()
- {
+ cleanUsersDb: function() {
const tsNow = Date.now();
// 86400000 = 24 hours in milliseconds
const day = 86400000;
"SELECT id, sessionToken, created, name, email " +
"FROM Users";
db.all(query, (err, users) => {
+ let toRemove = [];
users.forEach(u => {
- // Remove unlogged users for > 24h
+ // Remove users unlogged for > 24h
if (!u.sessionToken && tsNow - u.created > day)
{
notify(
"Your account has been deleted because " +
"you didn't log in for 24h after registration"
);
- db.run("DELETE FROM Users WHERE id = " + u.id);
}
});
+ if (toRemove.length > 0) {
+ db.run(
+ "DELETE FROM Users " +
+ "WHERE id IN (" + toRemove.join(",") + ")"
+ );
+ }
});
});
},
* description: varchar
*/
-const VariantModel =
-{
- getAll: function(callback)
- {
+const VariantModel = {
+ getAll: function(callback) {
db.serialize(function() {
const query =
"SELECT * " +
const params = require("../config/parameters");
router.post("/challenges", access.logged, access.ajax, (req,res) => {
- if (ChallengeModel.checkChallenge(req.body.chall))
- {
- let challenge =
- {
+ if (ChallengeModel.checkChallenge(req.body.chall)) {
+ let challenge = {
fen: req.body.chall.fen,
cadence: req.body.chall.cadence,
randomness: req.body.chall.randomness,
to: req.body.chall.to, //string: user name (may be empty)
};
const insertChallenge = () => {
- ChallengeModel.create(challenge, (err,ret) => {
- res.json(err || {cid:ret.cid});
+ ChallengeModel.create(challenge, (err, ret) => {
+ res.json(err || ret);
});
};
- if (req.body.chall.to)
- {
+ if (req.body.chall.to) {
UserModel.getOne("name", challenge.to, (err,user) => {
if (err || !user)
res.json(err || {errmsg: "Typo in player name"});
- else
- {
+ else {
challenge.to = user.id; //ready now to insert challenge
insertChallenge();
if (user.notify)
"New challenge: " + params.siteURL + "/#/?disp=corr");
}
});
- }
- else
- insertChallenge();
+ } else insertChallenge();
}
});
router.get("/challenges", access.ajax, (req,res) => {
const uid = req.query.uid;
- if (uid.match(/^[0-9]+$/))
- {
+ if (uid.match(/^[0-9]+$/)) {
ChallengeModel.getByUser(uid, (err,challenges) => {
- res.json(err || {challenges:challenges});
+ res.json(err || { challenges: challenges });
});
}
});
router.delete("/challenges", access.logged, access.ajax, (req,res) => {
const cid = req.query.id;
- if (cid.match(/^[0-9]+$/))
- {
+ if (cid.match(/^[0-9]+$/)) {
ChallengeModel.safeRemove(cid, req.userId);
res.json({});
}
const cid = req.body.cid;
if (
Array.isArray(gameInfo.players) &&
- gameInfo.players.some(p => p.uid == req.userId) &&
+ gameInfo.players.some(p => p.id == req.userId) &&
(!cid || cid.toString().match(/^[0-9]+$/)) &&
GameModel.checkGameInfo(gameInfo)
) {
if (!!cid) ChallengeModel.remove(cid);
GameModel.create(
gameInfo.vid, gameInfo.fen, gameInfo.cadence, gameInfo.players,
- (err,ret) => {
+ (err, ret) => {
const oppIdx = (gameInfo.players[0].id == req.userId ? 1 : 0);
const oppId = gameInfo.players[oppIdx].id;
UserModel.tryNotify(oppId,
- "Game started: " + params.siteURL + "/#/game/" + ret.gid);
- res.json({gameId: ret.gid});
+ "Game started: " + params.siteURL + "/#/game/" + ret.id);
+ res.json(err || ret);
}
);
}
});
+// Get only one game (for Game page)
router.get("/games", access.ajax, (req,res) => {
const gameId = req.query["gid"];
- if (gameId)
- {
- if (gameId.match(/^[0-9]+$/))
- {
- GameModel.getOne(gameId, (err,game) => {
- res.json({game: game});
- });
- }
+ if (!!gameId && gameId.match(/^[0-9]+$/)) {
+ GameModel.getOne(gameId, (err, game) => {
+ res.json({ game: game });
+ });
}
- else
- {
- // Get by (non-)user ID:
- const userId = req.query["uid"];
- if (userId.match(/^[0-9]+$/))
- {
- const excluded = !!req.query["excluded"];
- GameModel.getByUser(userId, excluded, (err,games) => {
- res.json({games: games});
- });
- }
+});
+
+// Get by (non-)user ID, for Hall
+router.get("/observedgames", access.ajax, (req,res) => {
+ const userId = req.query["uid"];
+ const cursor = req.query["cursor"];
+ if (!!userId.match(/^[0-9]+$/) && !!cursor.match(/^[0-9]+$/)) {
+ GameModel.getObserved(userId, (err, games) => {
+ res.json({ games: games });
+ });
+ }
+});
+
+// Get by user ID, for MyGames page
+router.get("/runninggames", access.ajax, access.logged, (req,res) => {
+ GameModel.getRunning(req.userId, (err, games) => {
+ res.json({ games: games });
+ });
+});
+
+router.get("/completedgames", access.ajax, access.logged, (req,res) => {
+ const cursor = req.query["cursor"];
+ if (!!cursor.match(/^[0-9]+$/)) {
+ GameModel.getCompleted(req.userId, cursor, (err, games) => {
+ res.json({ games: games });
+ });
}
});
router.put("/games", access.logged, access.ajax, (req,res) => {
const gid = req.body.gid;
let obj = req.body.newObj;
- if (gid.toString().match(/^[0-9]+$/) && GameModel.checkGameUpdate(obj))
- {
+ if (gid.toString().match(/^[0-9]+$/) && GameModel.checkGameUpdate(obj)) {
GameModel.getPlayers(gid, (err,players) => {
- const myIdx = players.findIndex(p => p.uid == req.userId)
- if (myIdx >= 0) {
+ let myColor = '';
+ if (players.white == req.userId) myColor = 'w';
+ else if (players.black == req.userId) myColor = 'b';
+ if (!!myColor) {
// Did I mark the game for deletion?
if (!!obj.removeFlag) {
- obj.deletedBy = ["w","b"][myIdx];
+ obj.deletedBy = myColor;
delete obj["removeFlag"];
}
GameModel.update(gid, obj, (err) => {
- if (!err && (!!obj.move || !!obj.score))
- {
+ if (!err && (!!obj.move || !!obj.score)) {
// Notify opponent if he enabled notifications:
- const oppid = players[0].uid == req.userId
- ? players[1].uid
- : players[0].uid;
- const messagePrefix = obj.move
- ? "New move in game: "
- : "Game ended: ";
- UserModel.tryNotify(oppid,
- messagePrefix + params.siteURL + "/#/game/" + gid);
+ const oppid = (myColor == 'w' ? players.black : players.white);
+ const messagePrefix =
+ !!obj.move
+ ? "New move in game: "
+ : "Game ended: ";
+ UserModel.tryNotify(
+ oppid,
+ messagePrefix + params.siteURL + "/#/game/" + gid
+ );
}
res.json(err || {});
});
// Moves update also could, although logical unit in a game.
router.delete("/chats", access.logged, access.ajax, (req,res) => {
const gid = req.query["gid"];
- GameModel.getPlayers(gid, (err,players) => {
- if (players.some(p => p.uid == req.userId))
+ GameModel.getPlayers(gid, (err, players) => {
+ if ([players.white, players.black].includes(req.userId))
{
- GameModel.update(gid, {delchat: true}, () => {
+ GameModel.update(gid, { delchat: true }, () => {
res.json({});
});
}
const devs = [1]; //hard-coded list of developers IDs, allowed to post news
router.post("/news", access.logged, access.ajax, (req,res) => {
- if (devs.includes(req.userId))
- {
+ if (devs.includes(req.userId)) {
const content = sanitizeHtml(req.body.news.content);
- NewsModel.create(content, req.userId, (err,ret) => {
- res.json(err || { id: ret.nid });
+ NewsModel.create(content, req.userId, (err, ret) => {
+ res.json(err || ret);
});
}
});
router.get("/news", access.ajax, (req,res) => {
const cursor = req.query["cursor"];
- if (cursor.match(/^[0-9]+$/)) {
- NewsModel.getNext(cursor, (err,newsList) => {
+ if (!!cursor.match(/^[0-9]+$/)) {
+ NewsModel.getNext(cursor, (err, newsList) => {
res.json(err || { newsList: newsList });
});
}
const sanitizeHtml = require('sanitize-html');
router.post("/problems", access.logged, access.ajax, (req,res) => {
- if (ProblemModel.checkProblem(req.body.prob))
- {
- const problem =
- {
+ if (ProblemModel.checkProblem(req.body.prob)) {
+ const problem = {
vid: req.body.prob.vid,
fen: req.body.prob.fen,
uid: req.userId,
instruction: sanitizeHtml(req.body.prob.instruction),
solution: sanitizeHtml(req.body.prob.solution),
};
- ProblemModel.create(problem, (err,ret) => {
- res.json(err || {id:ret.pid});
+ ProblemModel.create(problem, (err, ret) => {
+ res.json(err || ret);
});
}
else
router.get("/problems", access.ajax, (req,res) => {
const probId = req.query["pid"];
- if (probId && probId.match(/^[0-9]+$/))
- {
+ if (probId && probId.match(/^[0-9]+$/)) {
ProblemModel.getOne(req.query["pid"], (err,problem) => {
res.json(err || {problem: problem});
});
- }
- else
- {
+ } else {
ProblemModel.getAll((err,problems) => {
- res.json(err || {problems:problems});
+ res.json(err || { problems: problems });
});
}
});
router.put("/problems", access.logged, access.ajax, (req,res) => {
let obj = req.body.prob;
- if (ProblemModel.checkProblem(obj))
- {
+ if (ProblemModel.checkProblem(obj)) {
obj.instruction = sanitizeHtml(obj.instruction);
obj.solution = sanitizeHtml(obj.solution);
ProblemModel.safeUpdate(obj, req.userId);
const name = req.body.name;
const email = req.body.email;
const notify = !!req.body.notify;
- if (UserModel.checkNameEmail({name: name, email: email}))
- {
- UserModel.create(name, email, notify, (err,ret) => {
- if (err)
- {
+ if (UserModel.checkNameEmail({name: name, email: email})) {
+ UserModel.create(name, email, notify, (err, ret) => {
+ if (!!err) {
const msg = err.code == "SQLITE_CONSTRAINT"
? "User name or email already in use"
: "User creation failed. Try again";
res.json({errmsg: msg});
- }
- else
- {
+ } else {
const user = {
- id: ret.uid,
+ id: ret.id,
name: name,
email: email,
};
notify: false,
newsRead: 0
};
- if (!req.cookies.token)
- callback(anonymous);
- else if (req.cookies.token.match(/^[a-z0-9]+$/))
- {
+ if (!req.cookies.token) callback(anonymous);
+ else if (req.cookies.token.match(/^[a-z0-9]+$/)) {
UserModel.getOne("sessionToken", req.cookies.token, (err, user) => {
callback(user || anonymous);
});
// NOTE: this method is safe because only IDs and names are returned
router.get("/users", access.ajax, (req,res) => {
const ids = req.query["ids"];
- if (ids.match(/^([0-9]+,?)+$/)) //NOTE: slightly too permissive
- {
+ // NOTE: slightly too permissive RegExp
+ if (ids.match(/^([0-9]+,?)+$/)) {
UserModel.getByIds(ids, (err,users) => {
res.json({users:users});
});
router.put('/update', access.logged, access.ajax, (req,res) => {
const name = req.body.name;
const email = req.body.email;
- if (UserModel.checkNameEmail({name: name, email: email}));
- {
+ if (UserModel.checkNameEmail({name: name, email: email})) {
const user = {
id: req.userId,
name: name,
// Authentication-related methods:
// to: object user (to who we send an email)
-function setAndSendLoginToken(subject, to, res)
-{
+function setAndSendLoginToken(subject, to, res) {
// Set login token and send welcome(back) email with auth link
const token = genToken(params.token.length);
UserModel.setLoginToken(token, to.id);
router.get('/sendtoken', access.unlogged, access.ajax, (req,res) => {
const nameOrEmail = decodeURIComponent(req.query.nameOrEmail);
const type = (nameOrEmail.indexOf('@') >= 0 ? "email" : "name");
- if (UserModel.checkNameEmail({[type]: nameOrEmail}))
- {
+ if (UserModel.checkNameEmail({[type]: nameOrEmail})) {
UserModel.getOne(type, nameOrEmail, (err,user) => {
access.checkRequest(res, err, user, "Unknown user", () => {
setAndSendLoginToken("Token for " + params.siteURL, user, res);
// If token older than params.tokenExpire, do nothing
if (Date.now() > user.loginTime + params.token.expire)
res.json({errmsg: "Token expired"});
- else
- {
+ else {
// Generate session token (if not exists) + destroy login token
UserModel.trySetSessionToken(user.id, (token) => {
res.cookie("token", token, {
case "notifynewgame":
if (!!clients["/mygames"]) {
obj.targets.forEach(t => {
- const k = t.sid || idToSid[t.uid];
+ const k = t.sid || idToSid[t.id];
if (!!clients["/mygames"][k]) {
Object.keys(clients["/mygames"][k]).forEach(x => {
send(
else next();
};
let loggedIn = undefined;
- if (!req.cookies.token)
- {
+ if (!req.cookies.token) {
loggedIn = false;
callback();
- }
- else
- {
+ } else {
UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
- if (!!user)
- {
+ if (!!user) {
req.userId = user.id;
req.userName = user.name;
loggedIn = true;
- }
- else
- {
+ } else {
// Token in cookies presumably wrong: erase it
res.clearCookie("token");
loggedIn = false;
unlogged: function(req, res, next) {
// Just a quick heuristic, which should be enough
const loggedIn = !!req.cookies.token;
- if (loggedIn)
- res.json({errmsg: "Error: try to delete cookies"});
+ if (loggedIn) res.json({errmsg: "Error: try to delete cookies"});
else next();
},
// Prevent direct access to AJAX results
ajax: function(req, res, next) {
- if (!req.xhr)
- res.json({errmsg: "Unauthorized access"});
+ if (!req.xhr) res.json({errmsg: "Unauthorized access"});
else next();
},
// Check for errors before callback (continue page loading). TODO: better name.
checkRequest: function(res, err, out, msg, cb) {
- if (err)
- res.json({errmsg: err.errmsg || err.toString()});
- else if (!out
- || (Array.isArray(out) && out.length == 0)
- || (typeof out === "object" && Object.keys(out).length == 0))
- {
+ if (!!err) res.json({errmsg: err.errmsg || err.toString()});
+ else if (
+ !out ||
+ (Array.isArray(out) && out.length == 0) ||
+ (typeof out === "object" && Object.keys(out).length == 0)
+ ) {
res.json({errmsg: msg});
- }
- else cb();
- },
+ } else cb();
+ }
}
const sqlite3 = require('sqlite3');
const params = require("../config/parameters")
-if (params.env == "development")
- sqlite3.verbose();
+if (params.env == "development") sqlite3.verbose();
const DbPath = __dirname.replace("/utils", "/db/vchess.sqlite");
const db = new sqlite3.Database(DbPath);
const nodemailer = require('nodemailer');
const params = require("../config/parameters");
-module.exports = function(from, to, subject, body, cb)
-{
+module.exports = function(from, to, subject, body, cb) {
// Avoid the actual sending in development mode
- if (params.env === 'development')
- {
+ if (params.env === 'development') {
console.log("New mail: from " + from + " / to " + to);
console.log("Subject: " + subject);
console.log(body);
- if (!cb)
- cb = (err) => { if (err) console.log(err); }
+ if (!cb) cb = (err) => { if (err) console.log(err); }
cb();
return;
}
// Production-only code from here:
- if (!cb)
- cb = () => {}; //default: do nothing (TODO: log somewhere)
+ // Default: do nothing (TODO: log somewhere)
+ if (!cb) cb = () => {};
// Create reusable transporter object using the default SMTP transport
const transporter = nodemailer.createTransport({
-function randString()
-{
+function randString() {
return Math.random().toString(36).substr(2); // remove `0.`
}
-module.exports = function(tokenLength)
-{
+module.exports = function(tokenLength) {
let res = "";
// 10 = min length of a rand() string
- let nbRands = Math.ceil(tokenLength/10);
- for (let i = 0; i < nbRands; i++)
- res += randString();
+ const nbRands = Math.ceil(tokenLength/10);
+ for (let i = 0; i < nbRands; i++) res += randString();
return res.substr(0, tokenLength);
}