"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:
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 != "*")
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
}
}
};
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";
// 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 "";
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 "";
}
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 "";
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",
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",
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",
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",
"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?",
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?",
"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",
"Who's there?": "Who's there?",
With: "With",
"Write news": "Write news",
+ "Wrong time control": "Wrong time control",
"Your message": "Your message",
// Variants boxes:
"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",
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",
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",
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",
"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?",
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?",
"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:
"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",
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",
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",
"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 ?",
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 ?",
"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:
eogMessage = "Draw";
break;
case "?":
- eogMessage = "Unknown";
+ eogMessage = "Undetermined result";
break;
}
return eogMessage;
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;
}
role="dialog"
data-checkbox="modalNewprob"
)
- .card(@keyup.enter="sendProblem()")
+ .card
label#closeNewprob.modal-close(for="modalNewprob")
fieldset
label(for="selectVariant") {{ st.tr["Variant"] }}
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 }})
@click="deleteProblem(curproblem)"
)
| {{ st.tr["Delete"] }}
- p.clickable(
+ p.oneInstructions.clickable(
v-html="parseHtml(curproblem.instruction)"
@click="curproblem.showSolution=!curproblem.showSolution"
)
},
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))
);
sendProblem: function() {
const error = checkProblem(this.curproblem);
if (error) {
- alert(error);
+ alert(this.st.tr[error]);
return;
}
const edit = this.curproblem.id > 0;
& > *
margin: 0
+p.oneInstructions
+ margin: 0
+ padding: 2px 5px
+ background-color: lightgreen
+
#topPage
span.vname
font-weight: bold
v-html="content"
)
ComputerGame(
+ ref="compgame"
v-show="display=='computer'"
:game-info="gameInfo"
- @game-over="stopGame"
@game-stopped="gameStopped"
)
</template>
gameInfo: {
vname: "",
mode: "versus",
- fen: "",
- score: "*"
+ fen: ""
}
};
},
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() {
// 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(`
- <!doctype html>
- <h1>= message</h1>
- <h2>= error.status</h2>
- <pre>#{error.stack}</pre>
- `);
+ res.send(
+ "<h1>" + err.message + "</h1>" +
+ "<h2>" + err.status + "</h2>" +
+ "<pre>" + err.stack + "</pre>"
+ );
});
module.exports = app;
{
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() {
"(" + 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() {
" OR uid = " + uid +
" OR target = " + uid;
db.all(query, (err,challenges) => {
- return cb(err, challenges);
+ cb(err, challenges);
});
});
},
});
},
- safeRemove: function(id, uid, cb)
+ safeRemove: function(id, uid)
{
db.serialize(function() {
const query =
"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);
});
});
},
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)
"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});
+ }
});
});
},
// 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 " +
" 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 " +
" 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);
+ });
});
- });
+ }
});
});
});
(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<gameIds.length; i++)
+ if (err || gameIds.length == 0)
+ cb(err, []);
+ else
{
- GameModel.getOne(gameIds[i]["gid"], true, (err2,game) => {
- 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<gameIds.length; i++)
+ {
+ GameModel.getOne(gameIds[i]["gid"], true, (err2,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);
+ });
+ }
}
});
});
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() {
"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)
query += modifs + " WHERE id = " + id;
db.run(query);
}
- if (!!obj.move)
+ if (obj.move)
{
const m = obj.move;
query =
"(" + 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 ("
"VALUES " +
"(" + Date.now() + "," + uid + ",?)";
db.run(query, content, function(err) {
- return cb(err, {nid: this.lastID});
+ cb(err, {nid: this.lastID});
});
});
},
"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);
});
},
}
{
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)
"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});
});
});
},
"SELECT * " +
"FROM Problems";
db.all(query, (err,problems) => {
- return cb(err, problems);
+ cb(err, problems);
});
});
},
"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);
});
},
}
{
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" ? "'" : "");
/////////
// 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);
});
},
// 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 =
", email = '" + user.email + "'" +
", notify = " + user.notify + " " +
"WHERE id = " + user.id;
- db.run(query, cb);
+ db.run(query);
});
},
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);
});
},
-// 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;
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;
-// 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 || {});
});
});
-// 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;
-// 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
}
});
-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;
-// 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');
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,
};
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) => {
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({});
// 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});
});
});
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)
// 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();
},
}
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
// 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);
});
}
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);
}