From: Benjamin Auder Date: Thu, 20 Feb 2020 12:20:16 +0000 (+0100) Subject: Revise server code + a few fixes in trnalsations and ComputerGame X-Git-Url: https://git.auder.net/variants/%24%7Bvname%7D/current/%7B%7B%20asset%28%27mixstore/images/banner.png%27%29%20%7D%7D?a=commitdiff_plain;h=866842c3c310524c034922870234120ed2a16cbf;p=vchess.git Revise server code + a few fixes in trnalsations and ComputerGame --- diff --git a/client/src/components/ComputerGame.vue b/client/src/components/ComputerGame.vue index b1d2b370..a5e03b31 100644 --- a/client/src/components/ComputerGame.vue +++ b/client/src/components/ComputerGame.vue @@ -35,12 +35,6 @@ export default { "gameInfo.fen": function() { this.launchGame(); }, - "gameInfo.score": function(newScore) { - if (newScore != "*") { - this.game.score = newScore; //user action - if (!this.compThink) this.$emit("game-stopped"); //otherwise wait for comp - } - } }, created: function() { // Computer moves web worker logic: @@ -63,7 +57,7 @@ export default { let moveIdx = 0; let self = this; (function executeMove() { - self.$refs["basegame"].play(compMove[moveIdx++]); + self.$refs["basegame"].play(compMove[moveIdx++], "received"); if (moveIdx >= compMove.length) { self.compThink = false; if (self.game.score != "*") @@ -117,7 +111,7 @@ export default { gameOver: function(score, scoreMsg) { this.game.score = score; this.game.scoreMsg = scoreMsg; - this.$emit("game-over", score); //bubble up to Rules.vue + if (!this.compThink) this.$emit("game-stopped"); //otherwise wait for comp } } }; diff --git a/client/src/components/UpsertUser.vue b/client/src/components/UpsertUser.vue index eb1b6c22..eb8adffc 100644 --- a/client/src/components/UpsertUser.vue +++ b/client/src/components/UpsertUser.vue @@ -151,7 +151,7 @@ export default { error = checkNameEmail({ [type]: this.nameOrEmail }); } else error = checkNameEmail(this.st.user); if (error) { - alert(error); + alert(this.st.tr[error]); return; } this.infoMsg = "Processing... Please wait"; diff --git a/client/src/data/challengeCheck.js b/client/src/data/challengeCheck.js index 789952f8..a535b886 100644 --- a/client/src/data/challengeCheck.js +++ b/client/src/data/challengeCheck.js @@ -10,12 +10,12 @@ export function checkChallenge(c) { // Basic alphanumeric check for opponent name if (c.to) { // NOTE: slightly redundant (see data/userCheck.js) - if (!c.to.match(/^[\w]+$/)) return "Wrong characters in opponent name"; + if (!c.to.match(/^[\w]+$/)) return "Name: alphanumerics and underscore"; } // Allow custom FEN (and check it) only for individual challenges if (c.fen.length > 0 && !!c.to) { - if (!V.IsGoodFen(c.fen)) return "Bad FEN string"; + if (!V.IsGoodFen(c.fen)) return "Errors in FEN"; } else c.fen = ""; return ""; diff --git a/client/src/data/problemCheck.js b/client/src/data/problemCheck.js index 652daaed..d0978cbc 100644 --- a/client/src/data/problemCheck.js +++ b/client/src/data/problemCheck.js @@ -2,7 +2,7 @@ export function checkProblem(p) { const vid = parseInt(p.vid); if (isNaN(vid) || vid <= 0) return "Please select a variant"; - if (!V.IsGoodFen(p.fen)) return "Bad FEN string"; + if (!V.IsGoodFen(p.fen)) return "Errors in FEN"; return ""; } diff --git a/client/src/data/userCheck.js b/client/src/data/userCheck.js index 745d6d6e..0dccf1fb 100644 --- a/client/src/data/userCheck.js +++ b/client/src/data/userCheck.js @@ -1,12 +1,12 @@ export function checkNameEmail(o) { if (typeof o.name === "string") { if (o.name.length == 0) return "Empty name"; - if (!o.name.match(/^[\w]+$/)) return "Bad characters in name"; + if (!o.name.match(/^[\w]+$/)) return "Name: alphanumerics and underscore"; } if (typeof o.email === "string") { if (o.email.length == 0) return "Empty email"; - if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/)) return "Bad characters in email"; + if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/)) return "Invalid email"; } return ""; diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 0b2d7892..70f8969d 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -5,9 +5,9 @@ export const translations = { All: "All", Analyse: "Analyse", "Any player": "Any player", + Apply: "Apply", "Are you sure?": "Are you sure?", "Authentication successful!": "Authentication successful!", - Apply: "Apply", "Back to list": "Back to list", "Black to move": "Black to move", "Black surrender": "Black surrender", @@ -20,13 +20,11 @@ export const translations = { Challenge: "Challenge", "Challenge declined": "Challenge declined", "Chat here": "Chat here", - "Connection token sent. Check your emails!": - "Connection token sent. Check your emails!", + "Connection token sent. Check your emails!": "Connection token sent. Check your emails!", Contact: "Contact", "Correspondance challenges": "Correspondance challenges", "Correspondance games": "Correspondance games", - "Database error: stop private browsing, or update your browser": - "Database error: stop private browsing, or update your browser", + "Database error: stop private browsing, or update your browser": "Database error: stop private browsing, or update your browser", Delete: "Delete", Download: "Download", Draw: "Draw", @@ -35,12 +33,14 @@ export const translations = { Email: "Email", "Email sent!": "Email sent!", "Empty message": "Empty message", + "Errors in FEN": "Errors in FEN", "Example game": "Example game", Go: "Go", green: "green", Hall: "Hall", "Highlight last move and checks?": "Highlight last move and checks?", Instructions: "Instructions", + "Invalid email": "Invalid email", "is not online": "is not online", Language: "Language", "Live challenges": "Live challenges", @@ -49,14 +49,16 @@ export const translations = { Login: "Login", Logout: "Logout", "Logout successful!": "Logout successful!", + "Missing email": "Missing email", + "Missing name": "Missing name", "Modifications applied!": "Modifications applied!", "Move played:": "Move played:", "Mutual agreement": "Mutual agreement", "My games": "My games", "My problems": "My problems", + "Name: alphanumerics and underscore": "Name: alphanumerics and underscore", "Name or Email": "Name or Email", - "New connexion detected: tab now offline": - "New connexion detected: tab now offline", + "New connexion detected: tab now offline": "New connexion detected: tab now offline", "New correspondance game:": "New correspondance game:", "New game": "New game", "New problem": "New problem", @@ -71,10 +73,8 @@ export const translations = { "Play sounds?": "Play sounds?", "Play with?": "Play with?", Players: "Players", - "Please log in to accept corr challenges": - "Please log in to accept corr challenges", - "Please log in to play correspondance games": - "Please log in to play correspondance games", + "Please log in to accept corr challenges": "Please log in to accept corr challenges", + "Please log in to play correspondance games": "Please log in to play correspondance games", "Please select a variant": "Please select a variant", Practice: "Practice", "Prefix?": "Prefix?", @@ -82,8 +82,7 @@ export const translations = { Problems: "Problems", "participant(s):": "participant(s):", Register: "Register", - "Registration complete! Please check your emails": - "Registration complete! Please check your emails", + "Registration complete! Please check your emails": "Registration complete! Please check your emails", "Remove game?": "Remove game?", Resign: "Resign", "Resign the game?": "Resign the game?", @@ -102,8 +101,7 @@ export const translations = { "Terminate game?": "Terminate game?", "Three repetitions": "Three repetitions", Time: "Time", - To: "To", - Unknown: "Unknown", + "Undetermined result": "Undetermined result", Update: "Update", "User name": "User name", Variant: "Variant", @@ -115,6 +113,7 @@ export const translations = { "Who's there?": "Who's there?", With: "With", "Write news": "Write news", + "Wrong time control": "Wrong time control", "Your message": "Your message", // Variants boxes: diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 17438e50..3c79b660 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -9,7 +9,6 @@ export const translations = { "Are you sure?": "¿Está usted seguro?", "Authentication successful!": "¡Autenticación exitosa!", "Back to list": "Volver a la lista", - Black: "Negras", "Black to move": "Juegan las negras", "Black surrender": "Las negras abandonan", "Black win": "Las negras gagnan", @@ -21,13 +20,11 @@ export const translations = { Challenge: "Desafiar", "Challenge declined": "Desafío rechazado", "Chat here": "Chat aquí", - "Connection token sent. Check your emails!": - "Token de conexión enviado. ¡Revisa tus correos!", + "Connection token sent. Check your emails!": "Token de conexión enviado. ¡Revisa tus correos!", Contact: "Contacto", "Correspondance challenges": "Desafíos por correspondencia", "Correspondance games": "Partidas por correspondencia", - "Database error: stop private browsing, or update your browser": - "Error de la base de datos: detener la navegación privada, o actualizar su navegador", + "Database error: stop private browsing, or update your browser": "Error de la base de datos: detener la navegación privada, o actualizar su navegador", Delete: "Borrar", Download: "Descargar", Draw: "Tablas", @@ -36,12 +33,14 @@ export const translations = { Email: "Email", "Email sent!": "¡Email enviado!", "Empty message": "Mensaje vacio", + "Errors in FEN": "FEN errónea", "Example game": "Ejemplo de partida", Go: "Go", green: "verde", Hall: "Salón", "Highlight last move and checks?": "¿Resaltar el último movimiento y jaques?", Instructions: "Instrucciones", + "Invalid email": "Email inválido", "is not online": "no está en línea", Language: "Idioma", "Live challenges": "Desafíos en vivo", @@ -50,14 +49,16 @@ export const translations = { Login: "Login", Logout: "Logout", "Logout successful!": "¡Desconexión exitosa!", + "Missing email": "Email falta", + "Missing name": "Nombre falta", "Modifications applied!": "¡Modificaciones aplicadas!", "Move played:": "Movimiento jugado:", "Mutual agreement": "Acuerdo mutuo", "My games": "Mis partidas", "My problems": "Mis problemas", + "Name: alphanumerics and underscore": "Nombre: alfanuméricos y underscore", "Name or Email": "Nombre o Email", - "New connexion detected: tab now offline": - "Nueva conexión detectada: pestaña ahora desconectada", + "New connexion detected: tab now offline": "Nueva conexión detectada: pestaña ahora desconectada", "New correspondance game:": "Nueva partida por correspondencia:", "New game": "Nueva partida", "New problem": "Nuevo problema", @@ -72,10 +73,8 @@ export const translations = { "Play sounds?": "¿Permitir sonidos?", "Play with?": "¿Jugar con?", Players: "Jugadores", - "Please log in to accept corr challenges": - "Inicia sesión para aceptar los desafíos por correspondencia", - "Please log in to play correspondance games": - "Inicia sesión para jugar partidas por correspondancia", + "Please log in to accept corr challenges": "Inicia sesión para aceptar los desafíos por correspondencia", + "Please log in to play correspondance games": "Inicia sesión para jugar partidas por correspondancia", "Please select a variant": "Por favor seleccione una variante", Practice: "Práctica", "Prefix?": "¿Prefijo?", @@ -83,8 +82,7 @@ export const translations = { Problems: "Problemas", "participant(s):": "participante(s):", Register: "Registrarse", - "Registration complete! Please check your emails": - "¡Registro completo! Por favor revise sus correos electrónicos", + "Registration complete! Please check your emails": "¡Registro completo! Por favor revise sus correos electrónicos", "Remove game?": "¿Eliminar la partida?", Resign: "Abandonar", "Resign the game?": "¿Abandonar la partida?", @@ -103,20 +101,19 @@ export const translations = { "Terminate game?": "¿Terminar la partida?", "Three repetitions": "Tres repeticiones", Time: "Tiempo", - To: "A", - Unknown: "Desconocido", + "Undetermined result": "Resultado indeterminado", Update: "Actualización", "User name": "Nombre de usuario", Variant: "Variante", Variants: "Variantes", Versus: "Contra", - White: "Blancas", "White to move": "Juegan las blancas", "White surrender": "Las blancas abandonan", "White win": "Las blancas gagnan", "Who's there?": "¿Quién está ahí?", With: "Con", "Write news": "Escribir una news", + "Wrong time control": "Cadencia errónea", "Your message": "Tu mensaje", // Variants boxes: diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index e02cf5e9..33503d30 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -9,7 +9,6 @@ export const translations = { "Authentication successful!": "Authentification réussie !", "Are you sure?": "Étes vous sûr?", "Back to list": "Retour à la liste", - Black: "Noirs", "Black to move": "Trait aux noirs", "Black surrender": "Les noirs abandonnent", "Black win": "Les noirs gagnent", @@ -21,29 +20,27 @@ export const translations = { Challenge: "Défier", "Challenge declined": "Défi refusé", "Chat here": "Chattez ici", - "Connection token sent. Check your emails!": - "Token de connection envoyé. Allez voir vos emails !", + "Connection token sent. Check your emails!": "Token de connection envoyé. Allez voir vos emails !", Contact: "Contact", "Correspondance challenges": "Défis par correspondance", "Correspondance games": "Parties par correspondance", - "Database error: stop private browsing, or update your browser": - "Erreur de base de données : arrêtez la navigation privée, ou mettez à jour votre navigateur", + "Database error: stop private browsing, or update your browser": "Erreur de base de données : arrêtez la navigation privée, ou mettez à jour votre navigateur", Delete: "Supprimer", Download: "Télécharger", Draw: "Nulle", - "Draw offer only in your turn": - "Proposition de nulle seulement sur votre temps", + "Draw offer only in your turn": "Proposition de nulle seulement sur votre temps", Edit: "Éditer", Email: "Email", "Email sent!": "Email envoyé !", "Empty message": "Message vide", + "Errors in FEN": "FEN erronée", "Example game": "Partie exemple", Go: "Go", green: "vert", Hall: "Salon", - "Highlight last move and checks?": - "Mettre en valeur le dernier coup et les échecs ?", + "Highlight last move and checks?": "Mettre en valeur le dernier coup et les échecs ?", Instructions: "Instructions", + "Invalid email": "Email invalide", "is not online": "n'est pas en ligne", Language: "Langue", "Live challenges": "Défis en direct", @@ -52,14 +49,16 @@ export const translations = { Login: "Login", Logout: "Logout", "Logout successful!": "Déconnection réussie !", + "Missing email": "Email manquant", + "Missing name": "Nom manquant", "Modifications applied!": "Modifications effectuées !", "Move played:": "Coup joué :", "Mutual agreement": "Accord mutuel", "My games": "Mes parties", "My problems": "Mes problèmes", + "Name: alphanumerics and underscore": "Nom: alphanumériques et underscore", "Name or Email": "Nom ou Email", - "New connexion detected: tab now offline": - "Nouvelle connexion détectée : onglet désormais hors ligne", + "New connexion detected: tab now offline": "Nouvelle connexion détectée : onglet désormais hors ligne", "New correspondance game:": "Nouvelle partie par corespondance :", "New game": "Nouvelle partie", "New problem": "Nouveau problème", @@ -74,10 +73,8 @@ export const translations = { "Play sounds?": "Jouer les sons ?", "Play with?": "Jouer avec ?", Players: "Joueurs", - "Please log in to accept corr challenges": - "Identifiez vous pour accepter des défis par correspondance", - "Please log in to play correspondance games": - "Identifiez vous pour jouer des parties par correspondance", + "Please log in to accept corr challenges": "Identifiez vous pour accepter des défis par correspondance", + "Please log in to play correspondance games": "Identifiez vous pour jouer des parties par correspondance", "Please select a variant": "Sélectionnez une variante SVP", Practice: "Pratiquer", "Prefix?": "Préfixe ?", @@ -85,8 +82,7 @@ export const translations = { Problems: "Problèmes", "participant(s):": "participant(s) :", Register: "S'enregistrer", - "Registration complete! Please check your emails": - "Enregistrement terminé ! Allez voir vos emails", + "Registration complete! Please check your emails": "Enregistrement terminé ! Allez voir vos emails", "Remove game?": "Supprimer la partie ?", Resign: "Abandonner", "Resign the game?": "Abandonner la partie ?", @@ -105,20 +101,19 @@ export const translations = { "Terminate game?": "Stopper la partie ?", "Three repetitions": "Triple répétition", Time: "Temps", - To: "À", - Unknown: "Inconnu", + "Undetermined result": "Résultat indéterminé", Update: "Mise à jour", "User name": "Nom d'utilisateur", Variant: "Variante", Variants: "Variantes", Versus: "Contre", - White: "Blancs", "White to move": "Trait aux blancs", "White surrender": "Les blancs abandonnent", "White win": "Les blancs gagnent", "Who's there?": "Qui est là ?", With: "Avec", "Write news": "Écrire une news", + "Wrong time control": "Cadence erronée", "Your message": "Votre message", // Variants boxes: diff --git a/client/src/utils/scoring.js b/client/src/utils/scoring.js index 9c6d25fa..c659b7d1 100644 --- a/client/src/utils/scoring.js +++ b/client/src/utils/scoring.js @@ -12,7 +12,7 @@ export function getScoreMessage(score) { eogMessage = "Draw"; break; case "?": - eogMessage = "Unknown"; + eogMessage = "Undetermined result"; break; } return eogMessage; diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 6cc58e97..82bd769b 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -563,28 +563,32 @@ export default { case "newgame": { // NOTE: it may be live or correspondance const game = data.data; - let locGame = this.games.find(g => g.id == game.id); - if (!locGame) { - let newGame = game; - newGame.type = this.classifyObject(game); - newGame.vname = this.getVname(game.vid); - if (!game.score) - //if new game from Hall - newGame.score = "*"; - newGame.rids = [game.rid]; - delete newGame["rid"]; - this.games.push(newGame); - if ( - (newGame.type == "live" && this.gdisplay == "corr") || - (newGame.type == "corr" && this.gdisplay == "live") - ) { - document - .getElementById("btnG" + newGame.type) - .classList.add("somethingnew"); + // Ignore games where I play (corr games) + if (game.players.every(p => p.id != this.st.user.id)) + { + let locGame = this.games.find(g => g.id == game.id); + if (!locGame) { + let newGame = game; + newGame.type = this.classifyObject(game); + newGame.vname = this.getVname(game.vid); + if (!game.score) + //if new game from Hall + newGame.score = "*"; + newGame.rids = [game.rid]; + delete newGame["rid"]; + this.games.push(newGame); + if ( + (newGame.type == "live" && this.gdisplay == "corr") || + (newGame.type == "corr" && this.gdisplay == "live") + ) { + document + .getElementById("btnG" + newGame.type) + .classList.add("somethingnew"); + } + } else { + // Append rid (if not already in list) + if (!locGame.rids.includes(game.rid)) locGame.rids.push(game.rid); } - } else { - // Append rid (if not already in list) - if (!locGame.rids.includes(game.rid)) locGame.rids.push(game.rid); } break; } diff --git a/client/src/views/Problems.vue b/client/src/views/Problems.vue index e17bead7..3a700eab 100644 --- a/client/src/views/Problems.vue +++ b/client/src/views/Problems.vue @@ -8,7 +8,7 @@ main role="dialog" data-checkbox="modalNewprob" ) - .card(@keyup.enter="sendProblem()") + .card label#closeNewprob.modal-close(for="modalNewprob") fieldset label(for="selectVariant") {{ st.tr["Variant"] }} @@ -45,7 +45,7 @@ main button(@click="sendProblem()") {{ st.tr["Send"] }} #dialog.text-center {{ st.tr[infoMsg] }} .row(v-if="showOne") - .col-sm-12.col-md-10.col-md-offset-2 + .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2 #topPage span.vname {{ curproblem.vname }} span.uname ({{ curproblem.uname }}) @@ -60,7 +60,7 @@ main @click="deleteProblem(curproblem)" ) | {{ st.tr["Delete"] }} - p.clickable( + p.oneInstructions.clickable( v-html="parseHtml(curproblem.instruction)" @click="curproblem.showSolution=!curproblem.showSolution" ) @@ -273,7 +273,7 @@ export default { }, displayProblem: function(p) { return ( - (this.selectedVar == 0 || p.vid == this.selectedVar) && + (!this.selectedVar || p.vid == this.selectedVar) && ((this.onlyMines && p.uid == this.st.user.id) || (!this.onlyMines && p.uid != this.st.user.id)) ); @@ -293,7 +293,7 @@ export default { sendProblem: function() { const error = checkProblem(this.curproblem); if (error) { - alert(error); + alert(this.st.tr[error]); return; } const edit = this.curproblem.id > 0; @@ -358,6 +358,11 @@ textarea & > * margin: 0 +p.oneInstructions + margin: 0 + padding: 2px 5px + background-color: lightgreen + #topPage span.vname font-weight: bold diff --git a/client/src/views/Rules.vue b/client/src/views/Rules.vue index 8ac03a91..f864e9e4 100644 --- a/client/src/views/Rules.vue +++ b/client/src/views/Rules.vue @@ -29,9 +29,9 @@ main v-html="content" ) ComputerGame( + ref="compgame" v-show="display=='computer'" :game-info="gameInfo" - @game-over="stopGame" @game-stopped="gameStopped" ) @@ -54,8 +54,7 @@ export default { gameInfo: { vname: "", mode: "versus", - fen: "", - score: "*" + fen: "" } }; }, @@ -119,12 +118,11 @@ export default { this.gameInProgress = true; this.display = "computer"; this.gameInfo.mode = mode; - this.gameInfo.score = "*"; - this.gameInfo.fen = V.GenRandInitFen(); + this.$set(this.gameInfo, "fen", V.GenRandInitFen()); }, // user is willing to stop the game: - stopGame: function(score) { - this.gameInfo.score = score || "?"; + stopGame: function() { + this.$refs["compgame"].gameOver("?", "Undetermined result"); }, // The game is effectively stopped: gameStopped: function() { diff --git a/server/app.js b/server/app.js index 4f9d167d..b8a6aec7 100644 --- a/server/app.js +++ b/server/app.js @@ -54,17 +54,12 @@ app.use(function(req, res, next) { // error handler app.use(function(err, req, res, next) { - // set locals, only providing error in development - res.locals.message = err.message; - res.locals.error = (req.app.get('env') === 'development' ? err : {}); - // render the error page res.status(err.status || 500); - res.send(` - -

= message

-

= error.status

-
#{error.stack}
- `); + res.send( + "

" + err.message + "

" + + "

" + err.status + "

" + + "
" + err.stack + "
" + ); }); module.exports = app; diff --git a/server/models/Challenge.js b/server/models/Challenge.js index 0a375c81..b7c20b88 100644 --- a/server/models/Challenge.js +++ b/server/models/Challenge.js @@ -16,18 +16,14 @@ const ChallengeModel = { checkChallenge: function(c) { - if (!c.vid.toString().match(/^[0-9]+$/)) - return "Wrong variant ID"; - if (!c.cadence.match(/^[0-9dhms +]+$/)) - return "Wrong characters in time control"; - if (!c.fen.match(/^[a-zA-Z0-9, /-]*$/)) - return "Bad FEN string"; - if (!!c.to) - return UserModel.checkNameEmail({name: c.to}); - return ""; + return ( + c.vid.toString().match(/^[0-9]+$/) && + c.cadence.match(/^[0-9dhms +]+$/) && + c.fen.match(/^[a-zA-Z0-9, /-]*$/) && + (!c.to || UserModel.checkNameEmail({name: c.to})) + ); }, - // fen cannot be undefined create: function(c, cb) { db.serialize(function() { @@ -38,26 +34,12 @@ const ChallengeModel = "(" + Date.now() + "," + c.uid + "," + (!!c.to ? c.to + "," : "") + c.vid + ",'" + c.fen + "','" + c.cadence + "')"; db.run(query, function(err) { - return cb(err, {cid: this.lastID}); + cb(err, {cid: this.lastID}); }); }); }, - getOne: function(id, cb) - { - db.serialize(function() { - const query = - "SELECT * " + - "FROM Challenges " + - "WHERE id = " + id; - db.get(query, (err,challenge) => { - return cb(err, challenge); - }); - }); - }, - - // All challenges except where target is defined and not me, - // and I'm not the sender. + // All challenges related to user with ID uid getByUser: function(uid, cb) { db.serialize(function() { @@ -68,7 +50,7 @@ const ChallengeModel = " OR uid = " + uid + " OR target = " + uid; db.all(query, (err,challenges) => { - return cb(err, challenges); + cb(err, challenges); }); }); }, @@ -83,7 +65,7 @@ const ChallengeModel = }); }, - safeRemove: function(id, uid, cb) + safeRemove: function(id, uid) { db.serialize(function() { const query = @@ -91,10 +73,8 @@ const ChallengeModel = "FROM Challenges " + "WHERE id = " + id + " AND uid = " + uid; db.get(query, (err,chall) => { - if (!chall) - return cb({errmsg: "Not your challenge"}); - ChallengeModel.remove(id); - cb(null); + if (!err && chall) + ChallengeModel.remove(id); }); }); }, diff --git a/server/models/Game.js b/server/models/Game.js index fb249a7c..ae91ac94 100644 --- a/server/models/Game.js +++ b/server/models/Game.js @@ -34,17 +34,13 @@ const UserModel = require("./User"); const GameModel = { checkGameInfo: function(g) { - if (!g.vid.toString().match(/^[0-9]+$/)) - return "Wrong variant ID"; - if (!g.cadence.match(/^[0-9dhms +]+$/)) - return "Wrong characters in time control"; - if (!g.fen.match(/^[a-zA-Z0-9, /-]*$/)) - return "Bad FEN string"; - if (g.players.length != 2) - return "Need exactly 2 players"; - if (g.players.some(p => !p.id.toString().match(/^[0-9]+$/))) - return "Wrong characters in player ID"; - return ""; + return ( + g.vid.toString().match(/^[0-9]+$/) && + g.cadence.match(/^[0-9dhms +]+$/) && + g.fen.match(/^[a-zA-Z0-9, /-]*$/) && + g.players.length == 2 && + g.players.every(p => p.id.toString().match(/^[0-9]+$/)) + ); }, create: function(vid, fen, cadence, players, cb) @@ -56,16 +52,19 @@ const GameModel = "VALUES " + "(" + vid + ",'" + fen + "','" + fen + "','*','" + cadence + "'," + Date.now() + ",'')"; db.run(query, function(err) { - if (!!err) - return cb(err); - players.forEach((p,idx) => { - const color = (idx==0 ? "w" : "b"); - query = - "INSERT INTO Players VALUES " + - "(" + this.lastID + "," + p.id + ",'" + color + "')"; - db.run(query); - }); - cb(null, {gid: this.lastID}); + if (err) + cb(err) + else + { + players.forEach((p,idx) => { + const color = (idx==0 ? "w" : "b"); + query = + "INSERT INTO Players VALUES " + + "(" + this.lastID + "," + p.id + ",'" + color + "')"; + db.run(query); + }); + cb(null, {gid: this.lastID}); + } }); }); }, @@ -73,10 +72,9 @@ const GameModel = // TODO: some queries here could be async getOne: function(id, light, cb) { + // NOTE: ignoring errors (shouldn't happen at this stage) db.serialize(function() { let query = - // NOTE: g.scoreMsg can be NULL - // (in this case score = "*" and no reason to look at it) "SELECT g.id, g.vid, g.fen, g.fenStart, g.cadence, g.created, g.score, " + "g.scoreMsg, g.drawOffer, v.name AS vname " + "FROM Games g " + @@ -84,8 +82,6 @@ const GameModel = " ON g.vid = v.id " + "WHERE g.id = " + id; db.get(query, (err,gameInfo) => { - if (!!err) - return cb(err); query = "SELECT p.uid, p.color, u.name " + "FROM Players p " + @@ -93,41 +89,39 @@ const GameModel = " ON p.uid = u.id " + "WHERE p.gid = " + id; db.all(query, (err2,players) => { - if (!!err2) - return cb(err2); if (light) { const game = Object.assign({}, gameInfo, {players: players} ); - return cb(null, game); + cb(null, game); } - query = - "SELECT squares, played, idx " + - "FROM Moves " + - "WHERE gid = " + id; - db.all(query, (err3,moves) => { - if (!!err3) - return cb(err3); + else + { + // Full game requested: query = - "SELECT msg, name, added " + - "FROM Chats " + + "SELECT squares, played, idx " + + "FROM Moves " + "WHERE gid = " + id; - db.all(query, (err4,chats) => { - if (!!err4) - return cb(err4); - const game = Object.assign({}, - gameInfo, - { - players: players, - moves: moves, - chats: chats, - } - ); - return cb(null, game); + 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); + }); }); - }); + } }); }); }); @@ -156,21 +150,22 @@ const GameModel = (excluded ? " = 0" : " > 0"); } db.all(query, (err,gameIds) => { - if (!!err || gameIds.length == 0) - return cb(err, []); - let gameArray = []; - let kounter = 0; - for (let i=0; i { - if (!!err2) - return cb(err2); - gameArray.push(game); - kounter++; //TODO: let's hope this is atomic?! - // Call callback function only when gameArray is complete: - if (kounter == gameIds.length) - return cb(null, gameArray); - }); + let gameArray = []; + let gCounter = 0; + for (let i=0; i { + 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); + }); + } } }); }); @@ -192,27 +187,27 @@ const GameModel = checkGameUpdate: function(obj) { // Check all that is possible (required) in obj: - if (!!obj.move) - { - if (!obj.move.played.toString().match(/^[0-9]+$/)) - return "Wrong move played time"; - if (!obj.move.idx.toString().match(/^[0-9]+$/)) - return "Wrong move index"; - } - if (!!obj.drawOffer && !obj.drawOffer.match(/^[wbtn]$/)) - return "Wrong draw offer format"; - if (!!obj.fen && !obj.fen.match(/^[a-zA-Z0-9, /-]*$/)) - return "Wrong FEN string"; - if (!!obj.score && !obj.score.match(/^[012?*\/-]+$/)) - return "Wrong characters in score"; - if (!!obj.scoreMsg && !obj.scoreMsg.match(/^[a-zA-Z ]+$/)) - return "Wrong characters in score message"; - if (!!obj.chat) - return UserModel.checkNameEmail({name: obj.chat.name}); - return ""; + return ( + ( + !obj.move || ( + obj.move.played.toString().match(/^[0-9]+$/) && + obj.move.idx.toString().match(/^[0-9]+$/) + ) + ) && ( + !obj.drawOffer || obj.drawOffer.match(/^[wbtn]$/) + ) && ( + !obj.fen || obj.fen.match(/^[a-zA-Z0-9, /-]*$/) + ) && ( + !obj.score || obj.score.match(/^[012?*\/-]+$/) + ) && ( + !obj.scoreMsg || obj.scoreMsg.match(/^[a-zA-Z ]+$/) + ) && ( + !obj.chat || UserModel.checkNameEmail({name: obj.chat.name}) + ) + ); }, - // obj can have fields move, chat, fen, drawOffer and/or score + // obj can have fields move, chat, fen, drawOffer and/or score + message update: function(id, obj) { db.parallelize(function() { @@ -220,21 +215,19 @@ const GameModel = "UPDATE Games " + "SET "; let modifs = ""; - if (!!obj.message) - modifs += "message = message || ' ' || '" + obj.message + "',"; // NOTE: if drawOffer is set, we should check that it's player's turn // A bit overcomplicated. Let's trust the client on that for now... - if (!!obj.drawOffer) + if (obj.drawOffer) { if (obj.drawOffer == "n") //Special "None" update obj.drawOffer = ""; modifs += "drawOffer = '" + obj.drawOffer + "',"; } - if (!!obj.fen) + if (obj.fen) modifs += "fen = '" + obj.fen + "',"; - if (!!obj.score) + if (obj.score) modifs += "score = '" + obj.score + "',"; - if (!!obj.scoreMsg) + if (obj.scoreMsg) modifs += "scoreMsg = '" + obj.scoreMsg + "',"; modifs = modifs.slice(0,-1); //remove last comma if (modifs.length > 0) @@ -242,7 +235,7 @@ const GameModel = query += modifs + " WHERE id = " + id; db.run(query); } - if (!!obj.move) + if (obj.move) { const m = obj.move; query = @@ -250,7 +243,7 @@ const GameModel = "(" + id + ",?," + m.played + "," + m.idx + ")"; db.run(query, JSON.stringify(m.squares)); } - if (!!obj.chat) + if (obj.chat) { query = "INSERT INTO Chats (gid, msg, name, added) VALUES (" diff --git a/server/models/News.js b/server/models/News.js index 3f98fd67..12f4bf59 100644 --- a/server/models/News.js +++ b/server/models/News.js @@ -19,7 +19,7 @@ const NewsModel = "VALUES " + "(" + Date.now() + "," + uid + ",?)"; db.run(query, content, function(err) { - return cb(err, {nid: this.lastID}); + cb(err, {nid: this.lastID}); }); }); }, @@ -33,29 +33,29 @@ const NewsModel = "WHERE id > " + cursor + " " + "LIMIT 10"; //TODO: 10 currently hard-coded db.all(query, (err,newsList) => { - return cb(err, newsList); + cb(err, newsList); }); }); }, - update: function(news, cb) + update: function(news) { db.serialize(function() { let query = "UPDATE News " + "SET content = ? " + "WHERE id = " + news.id; - db.run(query, news.content, cb); + db.run(query, news.content); }); }, - remove: function(id, cb) + remove: function(id) { db.serialize(function() { const query = "DELETE FROM News " + "WHERE id = " + id; - db.run(query, cb); + db.run(query); }); }, } diff --git a/server/models/Problem.js b/server/models/Problem.js index 8460fec2..136fb649 100644 --- a/server/models/Problem.js +++ b/server/models/Problem.js @@ -15,13 +15,11 @@ const ProblemModel = { checkProblem: function(p) { - if (!p.id.toString().match(/^[0-9]+$/)) - return "Wrong problem ID"; - if (!p.vid.toString().match(/^[0-9]+$/)) - return "Wrong variant ID"; - if (!p.fen.match(/^[a-zA-Z0-9, /-]*$/)) - return "Bad FEN string"; - return ""; + return ( + p.id.toString().match(/^[0-9]+$/) && + p.vid.toString().match(/^[0-9]+$/) && + p.fen.match(/^[a-zA-Z0-9, /-]*$/) + ); }, create: function(p, cb) @@ -33,7 +31,7 @@ const ProblemModel = "VALUES " + "(" + Date.now() + "," + p.uid + "," + p.vid + ",'" + p.fen + "',?,?)"; db.run(query, [p.instruction,p.solution], function(err) { - return cb(err, {pid: this.lastID}); + cb(err, {pid: this.lastID}); }); }); }, @@ -45,7 +43,7 @@ const ProblemModel = "SELECT * " + "FROM Problems"; db.all(query, (err,problems) => { - return cb(err, problems); + cb(err, problems); }); }); }, @@ -58,49 +56,33 @@ const ProblemModel = "FROM Problems " + "WHERE id = " + id; db.get(query, (err,problem) => { - return cb(err, problem); + cb(err, problem); }); }); }, - update: function(prob, cb) + safeUpdate: function(prob, uid) { db.serialize(function() { - let query = + const query = "UPDATE Problems " + "SET " + "vid = " + prob.vid + "," + "fen = '" + prob.fen + "'," + "instruction = ?," + "solution = ? " + - "WHERE id = " + prob.id; - db.run(query, [prob.instruction,prob.solution], cb); + "WHERE id = " + prob.id + " AND uid = " + uid; + db.run(query, [prob.instruction,prob.solution]); }); }, - remove: function(id) + safeRemove: function(id, uid) { db.serialize(function() { const query = "DELETE FROM Problems " + - "WHERE id = " + id; - db.run(query); - }); - }, - - safeRemove: function(id, uid, cb) - { - db.serialize(function() { - const query = - "SELECT 1 " + - "FROM Problems " + "WHERE id = " + id + " AND uid = " + uid; - db.get(query, (err,prob) => { - if (!prob) - return cb({errmsg: "Not your problem"}); - ProblemModel.remove(id); - cb(null); - }); + db.run(query); }); }, } diff --git a/server/models/User.js b/server/models/User.js index 6c0b1539..f3adb31d 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -19,40 +19,26 @@ const UserModel = { checkNameEmail: function(o) { - if (typeof o.name === "string") - { - if (o.name.length == 0) - return "Empty name"; - if (!o.name.match(/^[\w]+$/)) - return "Bad characters in name"; - } - if (typeof o.email === "string") - { - if (o.email.length == 0) - return "Empty email"; - if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/)) - return "Bad characters in email"; - } - return ""; //NOTE: not required, but more consistent... (?!) + return ( + (!o.name || o.name.match(/^[\w]+$/)) && + (!o.email || o.email.match(/^[\w.+-]+@[\w.+-]+$/)) + ); }, - // NOTE: parameters are already cleaned (in controller), thus no sanitization here - create: function(name, email, notify, callback) + create: function(name, email, notify, cb) { db.serialize(function() { - const insertQuery = + const query = "INSERT INTO Users " + "(name, email, notify, created) VALUES " + - "('" + name + "', '" + email + "', " + notify + "," + Date.now() + ")"; - db.run(insertQuery, err => { - if (!!err) - return callback(err); - db.get("SELECT last_insert_rowid() AS rowid", callback); + "('" + name + "','" + email + "'," + notify + "," + Date.now() + ")"; + db.run(query, function(err) { + cb(err, {uid: this.lastID}); }); }); }, - // Find one user (by id, name, email, or token) + // Find one user by id, name, email, or token getOne: function(by, value, cb) { const delimiter = (typeof value === "string" ? "'" : ""); @@ -78,14 +64,14 @@ const UserModel = ///////// // MODIFY - setLoginToken: function(token, uid, cb) + setLoginToken: function(token, uid) { db.serialize(function() { const query = "UPDATE Users " + - "SET loginToken = '" + token + "', loginTime = " + Date.now() + " " + + "SET loginToken = '" + token + "',loginTime = " + Date.now() + " " + "WHERE id = " + uid; - db.run(query, cb); + db.run(query); }); }, @@ -94,28 +80,26 @@ const UserModel = // TODO: option would be to reset all tokens periodically, e.g. every 3 months trySetSessionToken: function(uid, cb) { - // Also empty the login token to invalidate future attempts db.serialize(function() { - const querySessionToken = + let query = "SELECT sessionToken " + "FROM Users " + "WHERE id = " + uid; - db.get(querySessionToken, (err,ret) => { - if (!!err) - return cb(err); + db.get(query, (err,ret) => { const token = ret.sessionToken || genToken(params.token.length); - const queryUpdate = + query = "UPDATE Users " + + // Also empty the login token to invalidate future attempts "SET loginToken = NULL" + (!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " + "WHERE id = " + uid; - db.run(queryUpdate); - cb(null, token); + db.run(query); + cb(token); }); }); }, - updateSettings: function(user, cb) + updateSettings: function(user) { db.serialize(function() { const query = @@ -124,7 +108,7 @@ const UserModel = ", email = '" + user.email + "'" + ", notify = " + user.notify + " " + "WHERE id = " + user.id; - db.run(query, cb); + db.run(query); }); }, @@ -142,9 +126,8 @@ const UserModel = tryNotify: function(id, message) { UserModel.getOne("id", id, (err,user) => { - if (!!err || !user.notify) - return; //NOTE: error is ignored here - UserModel.notify(user, message); + if (!err && user.notify) + UserModel.notify(user, message); }); }, diff --git a/server/routes/challenges.js b/server/routes/challenges.js index 4bbce8e2..efc69701 100644 --- a/server/routes/challenges.js +++ b/server/routes/challenges.js @@ -1,60 +1,63 @@ -// AJAX methods to get, create, update or delete a challenge - let router = require("express").Router(); const access = require("../utils/access"); const ChallengeModel = require("../models/Challenge"); const UserModel = require("../models/User"); //for name check const params = require("../config/parameters"); -router.get("/challenges", (req,res) => { - if (!req.query["uid"].match(/^[0-9]+$/)) - res.json({errmsg: "Bad user ID"}); - ChallengeModel.getByUser(req.query["uid"], (err,challenges) => { - res.json(err || {challenges:challenges}); - }); -}); - router.post("/challenges", access.logged, access.ajax, (req,res) => { - const error = ChallengeModel.checkChallenge(req.body.chall); - if (!!error) - return res.json({errmsg:error}); - let challenge = + if (ChallengeModel.checkChallenge(req.body.chall)) { - fen: req.body.chall.fen, - cadence: req.body.chall.cadence, - vid: req.body.chall.vid, - uid: req.userId, - to: req.body.chall.to, //string: user name (may be empty) - }; - const insertChallenge = () => { - ChallengeModel.create(challenge, (err,ret) => { - return res.json(err || {cid:ret.cid}); - }); - }; - if (!!req.body.chall.to) - { - UserModel.getOne("name", challenge.to, (err,user) => { - if (!!err || !user) - return res.json(err || {errmsg: "Typo in player name"}); - challenge.to = user.id; //ready now to insert challenge + let challenge = + { + fen: req.body.chall.fen, + cadence: req.body.chall.cadence, + vid: req.body.chall.vid, + uid: req.userId, + to: req.body.chall.to, //string: user name (may be empty) + }; + const insertChallenge = () => { + ChallengeModel.create(challenge, (err,ret) => { + res.json(err || {cid:ret.cid}); + }); + }; + if (req.body.chall.to) + { + UserModel.getOne("name", challenge.to, (err,user) => { + if (!!err || !user) + res.json(err || {errmsg: "Typo in player name"}); + else + { + challenge.to = user.id; //ready now to insert challenge + insertChallenge(); + if (user.notify) + UserModel.notify( + user, + "New challenge: " + params.siteURL + "/#/?disp=corr"); + } + }); + } + else insertChallenge(); - if (user.notify) - UserModel.notify( - user, - "New challenge: " + params.siteURL + "/#/?disp=corr"); + } +}); + +router.get("/challenges", access.ajax, (req,res) => { + const uid = req.query.uid; + if (uid.match(/^[0-9]+$/)) + { + ChallengeModel.getByUser(uid, (err,challenges) => { + res.json(err || {challenges:challenges}); }); } - else - insertChallenge(); }); router.delete("/challenges", access.logged, access.ajax, (req,res) => { const cid = req.query.id; - if (!cid.match(/^[0-9]+$/)) - res.json({errmsg: "Bad challenge ID"}); - ChallengeModel.safeRemove(cid, req.userId, err => { - res.json(err || {}); //TODO: just "return err" because is empty if no errors - }); + if (cid.match(/^[0-9]+$/)) + { + ChallengeModel.safeRemove(cid, req.userId); + res.json({}); + } }); module.exports = router; diff --git a/server/routes/games.js b/server/routes/games.js index f130f787..8bd9131e 100644 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -2,100 +2,84 @@ let router = require("express").Router(); const UserModel = require("../models/User"); const ChallengeModel = require('../models/Challenge'); const GameModel = require('../models/Game'); -const VariantModel = require('../models/Variant'); const access = require("../utils/access"); const params = require("../config/parameters"); // From main hall, start game between players 0 and 1 router.post("/games", access.logged, access.ajax, (req,res) => { const gameInfo = req.body.gameInfo; - if (!Array.isArray(gameInfo.players) || - gameInfo.players.every(p => p.id != req.userId)) - { - return res.json({errmsg: "Cannot start someone else's game"}); - } const cid = req.body.cid; - // Check all entries of gameInfo + cid: - let error = GameModel.checkGameInfo(gameInfo); - if (!error) - { - if (!cid.toString().match(/^[0-9]+$/)) - error = "Wrong challenge ID"; - } - if (!!error) - return res.json({errmsg:error}); - ChallengeModel.remove(cid); - GameModel.create( - gameInfo.vid, gameInfo.fen, gameInfo.cadence, gameInfo.players, - (err,ret) => { - access.checkRequest(res, err, ret, "Cannot create game", () => { + if ( + Array.isArray(gameInfo.players) && + gameInfo.players.some(p => p.id == req.userId) && + cid.toString().match(/^[0-9]+$/) && + GameModel.checkGameInfo(gameInfo) + ) { + ChallengeModel.remove(cid); + GameModel.create( + gameInfo.vid, gameInfo.fen, gameInfo.cadence, gameInfo.players, + (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}); - }); - } - ); + } + ); + } }); router.get("/games", access.ajax, (req,res) => { const gameId = req.query["gid"]; - if (!!gameId) + if (gameId) { - if (!gameId.match(/^[0-9]+$/)) - return res.json({errmsg: "Wrong game ID"}); - GameModel.getOne(gameId, false, (err,game) => { - access.checkRequest(res, err, game, "Game not found", () => { + if (gameId.match(/^[0-9]+$/)) + { + GameModel.getOne(gameId, false, (err,game) => { res.json({game: game}); }); - }); + } } else { // Get by (non-)user ID: const userId = req.query["uid"]; - if (!userId.match(/^[0-9]+$/)) - return res.json({errmsg: "Wrong user ID"}); - const excluded = !!req.query["excluded"]; - GameModel.getByUser(userId, excluded, (err,games) => { - if (!!err) - return res.json({errmsg: err.errmsg || err.toString()}); - res.json({games: games}); - }); + if (userId.match(/^[0-9]+$/)) + { + const excluded = !!req.query["excluded"]; + GameModel.getByUser(userId, excluded, (err,games) => { + res.json({games: games}); + }); + } } }); -// New move + fen update + score, potentially -// TODO: if newmove fail, takeback in GUI +// New move + fen update + score + chats... router.put("/games", access.logged, access.ajax, (req,res) => { const gid = req.body.gid; - let error = ""; - if (!gid.toString().match(/^[0-9]+$/)) - error = "Wrong game ID"; const obj = req.body.newObj; - error = GameModel.checkGameUpdate(obj); - if (!!error) - return res.json({errmsg: error}); - GameModel.update(gid, obj); //no callback here (several operations) - if (!!obj.move || !!obj.score) + if (gid.toString().match(/^[0-9]+$/) && GameModel.checkGameUpdate(obj)) { - // Notify opponent if he enabled notifications: GameModel.getPlayers(gid, (err,players) => { - if (!err) + if (players.some(p => p.id == req.userId)) { - 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); + GameModel.update(gid, obj); + if (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); + } + res.json({}); } }); } - res.json({}); //TODO: what if some update action fails? }); module.exports = router; diff --git a/server/routes/messages.js b/server/routes/messages.js index 3f13db4d..9355ff92 100644 --- a/server/routes/messages.js +++ b/server/routes/messages.js @@ -1,23 +1,17 @@ -// Router for contact form sending - let router = require("express").Router(); -const mailer = require(__dirname.replace("/routes", "/utils/mailer")); +const access = require("../utils/access"); +const sendEmail = require(__dirname.replace("/routes", "/utils/mailer")); const params = require(__dirname.replace("/routes", "/config/parameters")); // Send a message through contact form -router.post("/messages", (req,res,next) => { - if (!req.xhr) - return res.json({errmsg: "Unauthorized access"}); +router.post("/messages", access.ajax, (req,res) => { const from = req.body["email"]; // Replace potential newline characters in subject const subject = req.body["subject"].replace(/\r?\n|\r/g, " "); const body = req.body["content"]; - mailer(from, params.mail.contact, subject, body, err => { - if (!!err) - return res.json({errmsg:err}); - // OK, everything fine - res.json({}); //ignored + sendEmail(from, params.mail.contact, subject, body, err => { + res.json(err || {}); }); }); diff --git a/server/routes/news.js b/server/routes/news.js index dbd6d382..80b91299 100644 --- a/server/routes/news.js +++ b/server/routes/news.js @@ -1,50 +1,46 @@ -// AJAX methods to get, create, update or delete a problem - let router = require("express").Router(); const access = require("../utils/access"); const NewsModel = require("../models/News"); const sanitizeHtml = require('sanitize-html'); -const devs = [1]; //hard-coded list of developers, allowed to post news +const devs = [1]; //hard-coded list of developers IDs, allowed to post news -router.get("/news", (req,res) => { - const cursor = req.query["cursor"]; - if (!cursor.match(/^[0-9]+$/)) - return res.json({errmsg: "Bad cursor value"}); - NewsModel.getNext(cursor, (err,newsList) => { - res.json(err || {newsList:newsList}); - }); +router.post("/news", access.logged, access.ajax, (req,res) => { + 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}); + }); + } }); -router.post("/news", access.logged, access.ajax, (req,res) => { - if (!devs.includes(req.userId)) - return res.json({errmsg: "Not allowed to post"}); - const content = sanitizeHtml(req.body.news.content); - NewsModel.create(content, req.userId, (err,ret) => { - return res.json(err || {id:ret.nid}); - }); +router.get("/news", access.ajax, (req,res) => { + const cursor = req.query["cursor"]; + if (cursor.match(/^[0-9]+$/)) + { + NewsModel.getNext(cursor, (err,newsList) => { + res.json(err || {newsList:newsList}); + }); + } }); router.put("/news", access.logged, access.ajax, (req,res) => { - if (!devs.includes(req.userId)) - return res.json({errmsg: "Not allowed to edit"}); let news = req.body.news; - if (!news.id.toString().match(/^[0-9]+$/)) - res.json({errmsg: "Bad news ID"}); - news.content = sanitizeHtml(news.content); - NewsModel.update(news, (err) => { - res.json(err || {}); - }); + if (devs.includes(req.userId) && news.id.toString().match(/^[0-9]+$/)) + { + news.content = sanitizeHtml(news.content); + NewsModel.update(news); + res.json({}); + } }); router.delete("/news", access.logged, access.ajax, (req,res) => { - if (!devs.includes(req.userId)) - return res.json({errmsg: "Not allowed to delete"}); const nid = req.query.id; - if (!nid.toString().match(/^[0-9]+$/)) - res.json({errmsg: "Bad news ID"}); - NewsModel.remove(nid, err => { - res.json(err || {}); - }); + if (devs.includes(req.userId) && nid.toString().match(/^[0-9]+$/)) + { + NewsModel.remove(nid); + res.json({}); + } }); module.exports = router; diff --git a/server/routes/problems.js b/server/routes/problems.js index 02088357..64c173a1 100644 --- a/server/routes/problems.js +++ b/server/routes/problems.js @@ -1,20 +1,33 @@ -// AJAX methods to get, create, update or delete a problem - let router = require("express").Router(); const access = require("../utils/access"); const ProblemModel = require("../models/Problem"); const sanitizeHtml = require('sanitize-html'); +router.post("/problems", access.logged, access.ajax, (req,res) => { + 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}); + }); + } + else + res.json({}); +}); + router.get("/problems", (req,res) => { const probId = req.query["pid"]; - if (!!probId) + if (probId && probId.match(/^[0-9]+$/)) { - if (!probId.match(/^[0-9]+$/)) - return res.json({errmsg: "Wrong problem ID"}); ProblemModel.getOne(req.query["pid"], (err,problem) => { - access.checkRequest(res, err, problem, "Problem not found", () => { - res.json({problem: problem}); - }); + res.json(err || {problem: problem}); }); } else @@ -25,42 +38,22 @@ router.get("/problems", (req,res) => { } }); -router.post("/problems", access.logged, access.ajax, (req,res) => { - const error = ProblemModel.checkProblem(req.body.prob); - if (!!error) - return res.json({errmsg:error}); - 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) => { - return res.json(err || {id:ret.pid}); - }); -}); - router.put("/problems", access.logged, access.ajax, (req,res) => { let obj = req.body.prob; - const error = ProblemModel.checkProblem(obj); - if (!!error) - return res.json({errmsg: error}); - obj.instruction = sanitizeHtml(obj.instruction); - obj.solution = sanitizeHtml(obj.solution); - ProblemModel.update(obj, (err) => { - res.json(err || {}); - }); + if (ProblemModel.checkProblem(obj)) + { + obj.instruction = sanitizeHtml(obj.instruction); + obj.solution = sanitizeHtml(obj.solution); + ProblemModel.safeUpdate(obj, req.userId); + } + res.json({}); }); router.delete("/problems", access.logged, access.ajax, (req,res) => { const pid = req.query.id; - if (!pid.toString().match(/^[0-9]+$/)) - res.json({errmsg: "Bad problem ID"}); - ProblemModel.safeRemove(pid, req.userId, err => { - res.json(err || {}); - }); + if (pid.toString().match(/^[0-9]+$/)) + ProblemModel.safeRemove(pid, req.userId); + res.json({}); }); module.exports = router; diff --git a/server/routes/users.js b/server/routes/users.js index 0302b1c0..f9f1d86f 100644 --- a/server/routes/users.js +++ b/server/routes/users.js @@ -1,5 +1,3 @@ -// AJAX methods to get, create, update or delete a user - let router = require("express").Router(); const UserModel = require('../models/User'); const sendEmail = require('../utils/mailer'); @@ -7,10 +5,33 @@ const genToken = require("../utils/tokenGenerator"); const access = require("../utils/access"); const params = require("../config/parameters"); +router.post('/register', access.unlogged, access.ajax, (req,res) => { + 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) + res.json({errmsg: "User creation failed. Try again"}); + else + { + const user = { + id: ret.uid, + name: name, + email: email, + }; + setAndSendLoginToken("Welcome to " + params.siteURL, user, res); + res.json({}); + } + }); + } +}); + // NOTE: this method is safe because the sessionToken must be guessed router.get("/whoami", access.ajax, (req,res) => { const callback = (user) => { - return res.json({ + res.json({ name: user.name, email: user.email, id: user.id, @@ -19,80 +40,72 @@ router.get("/whoami", access.ajax, (req,res) => { }; const anonymous = {name:"", email:"", id:0, notify:false}; if (!req.cookies.token) - return callback(anonymous); - if (!req.cookies.token.match(/^[a-z0-9]+$/)) - return res.json({errmsg: "Bad token"}); - UserModel.getOne("sessionToken", req.cookies.token, function(err, user) { - if (!!err || !user) - callback(anonymous); - else - callback(user); - }); + 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 && !ids.match(/^([0-9]+,?)+$/)) //NOTE: slightly too permissive - return res.json({errmsg: "Bad IDs array"}); - UserModel.getByIds(ids, (err,users) => { - if (!!err) - return res.json({errmsg: err.toString()}); - return res.json({users:users}); - }); + if (ids.match(/^([0-9]+,?)+$/)) //NOTE: slightly too permissive + { + 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})); + { + const user = { + id: req.userId, + name: name, + email: email, + notify: !!req.body.notify, + }; + UserModel.updateSettings(user); + res.json({}); + } }); +// Authentication-related methods: + // to: object user (to who we send an email) 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, err => { - if (!!err) - return res.json({errmsg: err.toString()}); - const body = - "Hello " + to.name + "!" + ` + UserModel.setLoginToken(token, to.id); + const body = + "Hello " + to.name + "!" + ` ` + - "Access your account here: " + - params.siteURL + "/#/authenticate/" + token + ` + "Access your account here: " + + params.siteURL + "/#/authenticate/" + token + ` ` + - "Token will expire in " + params.token.expire/(1000*60) + " minutes." - sendEmail(params.mail.noreply, to.email, subject, body, err => { - res.json(err || {}); - }); - }); + "Token will expire in " + params.token.expire/(1000*60) + " minutes." + sendEmail(params.mail.noreply, to.email, subject, body); } -router.post('/register', access.unlogged, access.ajax, (req,res) => { - const name = req.body.name; - const email = req.body.email; - const notify = !!req.body.notify; - const error = UserModel.checkNameEmail({name: name, email: email}); - if (!!error) - return res.json({errmsg: error}); - UserModel.create(name, email, notify, (err,uid) => { - if (!!err) - return res.json({errmsg: err.toString()}); - const user = { - id: uid["rowid"], - name: name, - email: email, - }; - setAndSendLoginToken("Welcome to " + params.siteURL, user, res); - }); -}); - router.get('/sendtoken', access.unlogged, access.ajax, (req,res) => { const nameOrEmail = decodeURIComponent(req.query.nameOrEmail); const type = (nameOrEmail.indexOf('@') >= 0 ? "email" : "name"); - const error = UserModel.checkNameEmail({[type]: nameOrEmail}); - if (!!error) - return res.json({errmsg: error}); - UserModel.getOne(type, nameOrEmail, (err,user) => { - access.checkRequest(res, err, user, "Unknown user", () => { - setAndSendLoginToken("Token for " + params.siteURL, user, res); + 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); + res.json({}); + }); }); - }); + } }); router.get('/authenticate', access.unlogged, access.ajax, (req,res) => { @@ -102,45 +115,28 @@ router.get('/authenticate', access.unlogged, access.ajax, (req,res) => { access.checkRequest(res, err, user, "Invalid token", () => { // If token older than params.tokenExpire, do nothing if (Date.now() > user.loginTime + params.token.expire) - return res.json({errmsg: "Token expired"}); - // Generate session token (if not exists) + destroy login token - UserModel.trySetSessionToken(user.id, (err,token) => { - if (!!err) - return res.json({errmsg: err.toString()}); - // Set cookie - res.cookie("token", token, { - httpOnly: true, - secure: !!params.siteURL.match(/^https/), - maxAge: params.cookieExpire, - }); - res.json({ - id: user.id, - name: user.name, - email: user.email, - notify: user.notify, + res.json({errmsg: "Token expired"}); + else + { + // Generate session token (if not exists) + destroy login token + UserModel.trySetSessionToken(user.id, (token) => { + res.cookie("token", token, { + httpOnly: true, + secure: !!params.siteURL.match(/^https/), + maxAge: params.cookieExpire, + }); + res.json({ + id: user.id, + name: user.name, + email: user.email, + notify: user.notify, + }); }); - }); + } }); }); }); -router.put('/update', access.logged, access.ajax, (req,res) => { - const name = req.body.name; - const email = req.body.email; - const error = UserModel.checkNameEmail({name: name, email: email}); - if (!!error) - return res.json({errmsg: error}); - const user = { - id: req.userId, - name: name, - email: email, - notify: !!req.body.notify, - }; - UserModel.updateSettings(user, err => { - res.json(err ? {errmsg: err.toString()} : {}); - }); -}); - router.get('/logout', access.logged, access.ajax, (req,res) => { res.clearCookie("token"); res.json({}); diff --git a/server/routes/variants.js b/server/routes/variants.js index 8153c7a7..3f7417e3 100644 --- a/server/routes/variants.js +++ b/server/routes/variants.js @@ -1,15 +1,12 @@ // Get variants list (always needed) let router = require("express").Router(); -const createError = require('http-errors'); const VariantModel = require("../models/Variant"); const access = require("../utils/access"); -router.get('/variants', access.ajax, function(req, res, next) { +router.get('/variants', access.ajax, function(req, res) { VariantModel.getAll((err,variants) => { - if (!!err) - return next(err); - res.json({variantArray:variants}); + res.json(err || {variantArray:variants}); }); }); diff --git a/server/utils/access.js b/server/utils/access.js index d51c4b77..049e9eb6 100644 --- a/server/utils/access.js +++ b/server/utils/access.js @@ -6,8 +6,8 @@ module.exports = logged: function(req, res, next) { const callback = () => { if (!loggedIn) - return res.json({errmsg: "Not logged in"}); - next(); + res.json({errmsg: "Not logged in"}); + else next(); }; let loggedIn = undefined; if (!req.cookies.token) @@ -40,27 +40,27 @@ module.exports = // Just a quick heuristic, which should be enough const loggedIn = !!req.cookies.token; if (loggedIn) - return res.json({errmsg: "Already logged in"}); - next(); + res.json({errmsg: "Already logged in"}); + else next(); }, // Prevent direct access to AJAX results ajax: function(req, res, next) { if (!req.xhr) - return res.json({errmsg: "Unauthorized access"}); - next(); + 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) - return res.json({errmsg: err.errmsg || err.toString()}); - if (!out + 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)) { - return res.json({errmsg: msg}); + res.json({errmsg: msg}); } - cb(); + else cb(); }, } diff --git a/server/utils/mailer.js b/server/utils/mailer.js index 60cc9f26..b0a0bace 100644 --- a/server/utils/mailer.js +++ b/server/utils/mailer.js @@ -10,10 +10,14 @@ module.exports = function(from, to, subject, body, cb) console.log("Subject: " + subject); console.log(body); if (!cb) - cb = (err) => { if (!!err) console.log(err); } - return cb(); + cb = (err) => { if (err) console.log(err); } + cb(); + return; } - else if (!cb) + + // Production-only code from here: + + if (!cb) cb = () => {}; //default: do nothing (TODO: log somewhere) // Create reusable transporter object using the default SMTP transport @@ -38,10 +42,8 @@ module.exports = function(from, to, subject, body, cb) // Send mail with the defined transport object transporter.sendMail(mailOptions, (error, info) => { - if (!!error) - return cb(error); // Ignore info. Option: //console.log('Message sent: %s', info.messageId); - return cb(); + cb(error); }); } diff --git a/server/utils/tokenGenerator.js b/server/utils/tokenGenerator.js index 858bc3bf..2c21b4e5 100644 --- a/server/utils/tokenGenerator.js +++ b/server/utils/tokenGenerator.js @@ -3,11 +3,12 @@ function randString() return Math.random().toString(36).substr(2); // remove `0.` } -module.exports = function(tlen) +module.exports = function(tokenLength) { let res = ""; - let nbRands = Math.ceil(tlen/10); //10 = min length of a rand() string + // 10 = min length of a rand() string + let nbRands = Math.ceil(tokenLength/10); for (let i = 0; i < nbRands; i++) res += randString(); - return res.substr(0, tlen); + return res.substr(0, tokenLength); }