.row
.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
// Menu (top of page):
- // Left: hall, variants, mygames
+ // Left: hall, variants, problems, mygames
// Right: usermenu, settings, flag
nav
label.drawer-toggle(for="drawerControl")
| {{ st.tr["Hall"] }}
router-link(to="/variants")
| {{ st.tr["Variants"] }}
+ router-link(to="/problems")
+ | {{ st.tr["Problems"] }}
router-link(to="/mygames")
| {{ st.tr["My games"] }}
#rightMenu
router-link.menuitem(to="/about") {{ st.tr["About"] }}
p.clickable(onClick="doClick('modalContact')")
| {{ st.tr["Contact"] }}
- a.menuitem(href="https://forum.vchess.club")
- | {{ st.tr["Forum"] }}
</template>
<script>
border: none
& > label.drawer-toggle
font-size: 1.2rem
- //padding: 0 0 0 10px
+ position: absolute
+ top: -12px
+ //padding: -5px 0 0 10px
[type="checkbox"].drawer+*
right: -767px
@media screen and (max-width: 767px)
footer
border: none
+
+// Styles for diagrams and board (partial).
+// TODO: where to put that ?
+
+.light-square-diag
+ background-color: #e5e5ca
+
+.dark-square-diag
+ background-color: #6f8f57
+
+div.board
+ float: left
+ height: 0
+ display: inline-block
+ position: relative
+
+div.board8
+ width: 12.5%
+ padding-bottom: 12.5%
+
+div.board10
+ width: 10%
+ padding-bottom: 10%
+
+div.board11
+ width: 9.09%
+ padding-bottom: 9.1%
+
+img.piece
+ width: 100%
+
+img.piece, img.mark-square
+ max-width: 100%
+ height: auto
+ display: block
+
+img.mark-square
+ opacity: 0.6
+ width: 76%
+ position: absolute
+ top: 12%
+ left: 12%
+ opacity: .7
+
+.in-shadow
+ filter: brightness(50%)
</style>
return (f.charCodeAt()<=90 ? "w"+f.toLowerCase() : "b"+f);
}
- // Check if FEN describe a position
+ // Check if FEN describe a board situation correctly
static IsGoodFen(fen)
{
const fenParsed = V.ParseFen(fen);
if (!!newMove) //if stop + launch new game, get undefined move
this.play(newMove, "receive");
},
+ // ...Or to undo (corr game, move not validated)
+ "game.moveToUndo": function(move) {
+ if (!!move)
+ this.undo(move);
+ },
},
computed: {
showMoves: function() {
// NOTE: no variants with reserve of size != 8
-div.board
- float: left
- height: 0
- display: inline-block
- position: relative
-
-div.board8
- width: 12.5%
- padding-bottom: 12.5%
-
-div.board10
- width: 10%
- padding-bottom: 10%
-
-div.board11
- width: 9.09%
- padding-bottom: 9.1%
-
.game
width: 100%
margin: 0
height: auto
display: block
-img.piece
- width: 100%
-
-img.piece, img.mark-square
- max-width: 100%
- height: auto
- display: block
-
-img.mark-square
- opacity: 0.6
- width: 76%
- position: absolute
- top: 12%
- left: 12%
- opacity: .7
-
img.ghost
position: absolute
opacity: 0.4
.highlight
background-color: #00cc66 !important
-.in-shadow
- filter: brightness(50%)
-
.incheck
background-color: #cc3300 !important
label(for="mailSubject") {{ st.tr["Subject"] }}
input#mailSubject(type="text")
fieldset
- label(for="mailContent") {{ st.tr["Content"] }} *
- br
- textarea#mailContent
+ textarea#mailContent(:placeholder="st.tr['Your message']")
button(@click="trySendMessage()") {{ st.tr["Send"] }}
#dialog.text-center {{ st.tr[infoMsg] }}
</template>
--- /dev/null
+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";
+}
name: "logout",
component: loadView("Logout"),
},
+ {
+ path: "/problems",
+ name: "myproblems",
+ component: loadView("Problems"),
+ },
{
path: "/mygames",
name: "mygames",
"All": "All",
"Analyse": "Analyse",
"Analyse in Dark mode makes no sense!": "Analyse in Dark mode makes no sense!",
+ "Are you sure?": "Are you sure?",
"Authentication successful!": "Authentication successful!",
"Apply": "Apply",
"Black": "Black",
"Chat here": "Chat here",
"Connection token sent. Check your emails!": "Connection token sent. Check your emails!",
"Contact": "Contact",
- "Content": "Content",
"Correspondance challenges": "Correspondance challenges",
"Correspondance games": "Correspondance games",
"Database error:": "Database error:",
"Empty message": "Empty message",
"Error while loading database:": "Error while loading database:",
"Example game": "Example game",
- "Forum": "Forum",
"From": "From",
"Game retrieval failed:": "Game retrieval failed:",
"Game removal failed:": "Game removal failed:",
"green": "green",
"Hall": "Hall",
"Highlight last move and checks?": "Highlight last move and checks?",
+ "Instructions": "Instructions",
"Language": "Language",
"Live challenges": "Live challenges",
"Live games": "Live games",
"Modifications applied!": "Modifications applied!",
"Mutual agreement": "Mutual agreement",
"My games": "My games",
+ "My problems": "My problems",
"Name": "Name",
"Name or Email": "Name or Email",
"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",
"No subject. Send anyway?": "No subject. Send anyway?",
"None": "None",
"Notifications by email": "Notifications by email",
"Practice": "Practice",
"Prefix?": "Prefix?",
"Processing... Please wait": "Processing... Please wait",
+ "Problems": "Problems",
"participant(s):": "participant(s):",
"Register": "Register",
"Registration complete! Please check your emails": "Registration complete! Please check your emails",
"Result": "Result",
"Rules": "Rules",
"Send": "Send",
- "Show possible moves?": "Show possible moves?",
"Self-challenge is forbidden": "Self-challenge is forbidden",
"Send challenge": "Send challenge",
+ "Send problem": "Send problem",
"Settings": "Settings",
+ "Show possible moves?": "Show possible moves?",
+ "Solution": "Solution",
"Stop game": "Stop game",
"Subject": "Subject",
"Terminate game?": "Terminate game?",
"White to move": "White to move",
"White win": "White win",
"Who's there?": "Who's there?",
+ "Your message": "Your message",
// Variants boxes:
"Balanced sliders & leapers": "Balanced sliders & leapers",
"Analyse": "Analizar",
"Analyse in Dark mode makes no sense!": "¡Analizar en modo Dark no tiene sentido!",
"Apply": "Aplicar",
+ "Are you sure?": "¿Está usted seguro?",
"Authentication successful!": "¡Autenticación exitosa!",
"Black": "Negras",
"Black to move": "Juegan las negras",
"Chat here": "Chat aquí",
"Connection token sent. Check your emails!": "Token de conexión enviado. ¡Revisa tus correos!",
"Contact": "Contacto",
- "Content": "Contenido",
"Correspondance challenges": "Desafíos por correspondencia",
"Correspondance games": "Partidas por correspondencia",
"Database error:": "Error de la base de datos:",
"Empty message": "Mensaje vacio",
"Error while loading database:": "Error al cargar la base de datos:",
"Example game": "Ejemplo de partida",
- "Forum": "Foro",
"From": "De",
"Game retrieval failed:": "La recuperación de la partida falló:",
"Game removal failed:": "La eliminación de la partida falló:",
"green": "verde",
"Hall": "Salón",
"Highlight last move and checks?": "¿Resaltar el último movimiento y jaques?",
+ "Instructions": "Instrucciones",
"Language": "Idioma",
"Live challenges": "Desafíos en vivo",
"Live games": "Partidas en vivo",
"Modifications applied!": "¡Modificaciones aplicadas!",
"Mutual agreement": "Acuerdo mutuo",
"My games": "Mis partidas",
+ "My problems": "Mis problemas",
"Name": "Nombre",
"Name or Email": "Nombre o Email",
"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",
"No subject. Send anyway?": "Sin asunto. ¿Enviar sin embargo?",
"None": "Ninguno",
"Notifications by email": "Notificaciones por email",
"Practice": "Práctica",
"Prefix?": "¿Prefijo?",
"Processing... Please wait": "Procesando... por favor espere",
+ "Problems": "Problemas",
"participant(s):": "participante(s):",
"Register": "Registrarse",
"Registration complete! Please check your emails": "¡Registro completo! Por favor revise sus correos electrónicos",
"Result": "Resultado",
"Rules": "Reglas",
"Send": "Enviar",
- "Show possible moves?": "¿Mostrar posibles movimientos?",
"Self-challenge is forbidden": "Auto desafío está prohibido",
"Send challenge": "Enviar desafío",
+ "Send problem": "Enviar problema",
"Settings": "Configuraciones",
+ "Show possible moves?": "¿Mostrar posibles movimientos?",
+ "Solution": "Solución",
"Stop game": "Terminar la partida",
"Subject": "Asunto",
"Terminate game?": "¿Terminar la partida?",
"White to move": "Juegan las blancas",
"White win": "Las blancas gagnan",
"Who's there?": "¿Quién está ahí?",
+ "Your message": "Tu mensaje",
// Variants boxes:
"Balanced sliders & leapers": "Modos de desplazamiento equilibrados",
"Analyse in Dark mode makes no sense!": "Analyser en mode Dark n'a pas de sens !",
"Apply": "Appliquer",
"Authentication successful!": "Authentification réussie !",
+ "Are you sure?": "Étes vous sûr?",
"Black": "Noirs",
"Black to move": "Trait aux noirs",
"Black win": "Les noirs gagnent",
"Chat here": "Chattez ici",
"Connection token sent. Check your emails!": "Token de connection envoyé. Allez voir vos emails !",
"Contact": "Contact",
- "Content": "Contenu",
"Correspondance challenges": "Défis par correspondance",
"Correspondance games": "Parties par correspondance",
"Database error:": "Erreur de base de données :",
"Empty message": "Message vide",
"Error while loading database:": "Erreur lors du chargement de la base de données :",
"Example game": "Partie exemple",
- "Forum": "Forum",
"From": "De",
"Game retrieval failed:": "Échec de la récupération de la partie :",
"Game removal failed:": "Échec de la suppresion de la partie :",
"green": "vert",
"Hall": "Salon",
"Highlight last move and checks?": "Mettre en valeur le dernier coup et les échecs ?",
+ "Instructions": "Instructions",
"Language": "Langue",
"Live challenges": "Défis en direct",
"Live games": "Parties en direct",
"Modifications applied!": "Modifications effectuées !",
"Mutual agreement": "Accord mutuel",
"My games": "Mes parties",
+ "My problems": "Mes problèmes",
"Name": "Nom",
"Name or Email": "Nom ou Email",
"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",
"No subject. Send anyway?": "Pas de sujet. Envoyer quand-même ??",
"None": "Aucun",
"Notifications by email": "Notifications par email",
"Practice": "Pratiquer",
"Prefix?": "Préfixe ?",
"Processing... Please wait": "Traitement en cours... Attendez SVP",
+ "Problems": "Problèmes",
"participant(s):": "participant(s) :",
"Register": "S'enregistrer",
"Registration complete! Please check your emails": "Enregistrement terminé ! Allez voir vos emails",
"Result": "Résultat",
"Rules": "Règles",
"Send": "Envoyer",
- "Show possible moves?": "Montrer les coups possibles ?",
"Self-challenge is forbidden": "Interdit de s'auto-défier",
"Send challenge": "Envoyer défi",
+ "Send problem": "Envoyer problème",
"Settings": "Réglages",
+ "Show possible moves?": "Montrer les coups possibles ?",
+ "Solution": "Solution",
"Stop game": "Arrêter la partie",
"Subject": "Sujet",
"Terminate game?": "Stopper la partie ?",
"White to move": "Trait aux blancs",
"White win": "Les blancs gagnent",
"Who's there?": "Qui est là ?",
+ "Your message": "Votre message",
// Variants boxes:
"Balanced sliders & leapers": "Modes de déplacement équilibrés",
GameStorage.get(this.gameRef.id, afterRetrieval);
}
},
- // Post-process a move (which was just played)
+ // Post-process a move (which was just played in BaseGame)
processMove: function(move) {
+ if (this.game.type == "corr" && move.color == this.game.mycolor)
+ {
+ if (!confirm(this.st.tr["Are you sure?"]))
+ return this.$set(this.game, "moveToUndo", move);
+ }
// Update storage (corr or live) if I play in the game
const colorIdx = ["w","b"].indexOf(move.color);
const nextIdx = ["w","b"].indexOf(this.vr.turn);
p(v-html="infoMessage")
input#modalNewgame.modal(type="checkbox")
div#newgameDiv(role="dialog" data-checkbox="modalNewgame")
- .card(@keyup.enter="newChallenge()")
+ .card
label#closeNewgame.modal-close(for="modalNewgame")
- fieldset
- label(for="selectVariant") {{ st.tr["Variant"] }} *
- select#selectVariant(v-model="newchallenge.vid")
- option(v-for="v in st.variants" :value="v.id"
- :selected="newchallenge.vid==v.id")
- | {{ v.name }}
- fieldset
- label(for="cadence") {{ st.tr["Cadence"] }} *
- div#predefinedCadences
- button 3+2
- button 5+3
- button 15+5
- input#cadence(type="text" v-model="newchallenge.cadence"
- placeholder="5+0, 1h+30s, 7d+1d ...")
- fieldset(v-if="st.user.id > 0")
- label(for="selectPlayers") {{ st.tr["Play with?"] }}
- input#selectPlayers(type="text" v-model="newchallenge.to")
- fieldset(v-if="st.user.id > 0 && newchallenge.to.length > 0")
- label(for="inputFen") FEN
- input#inputFen(type="text" v-model="newchallenge.fen")
+ form(@submit.prevent="newChallenge()" @keyup.enter="newChallenge()")
+ fieldset
+ label(for="selectVariant") {{ st.tr["Variant"] }} *
+ select#selectVariant(v-model="newchallenge.vid")
+ option(v-for="v in st.variants" :value="v.id"
+ :selected="newchallenge.vid==v.id")
+ | {{ v.name }}
+ fieldset
+ label(for="cadence") {{ st.tr["Cadence"] }} *
+ div#predefinedCadences
+ button 3+2
+ button 5+3
+ button 15+5
+ input#cadence(type="text" v-model="newchallenge.cadence"
+ placeholder="5+0, 1h+30s, 7d+1d ...")
+ fieldset(v-if="st.user.id > 0")
+ label(for="selectPlayers") {{ st.tr["Play with?"] }}
+ input#selectPlayers(type="text" v-model="newchallenge.to")
+ fieldset(v-if="st.user.id > 0 && newchallenge.to.length > 0")
+ label(for="inputFen") FEN
+ input#inputFen(type="text" v-model="newchallenge.fen")
button(@click="newChallenge()") {{ st.tr["Send challenge"] }}
.row
.col-sm-12
import { ajax } from "@/utils/ajax";
import GameList from "@/components/GameList.vue";
export default {
- name: "my-games",
+ name: "my-my-games",
components: {
GameList,
},
--- /dev/null
+<template lang="pug">
+main
+ input#modalNewprob.modal(type="checkbox" @change="infoMsg=''")
+ div#newprobDiv(role="dialog" data-checkbox="modalNewprob")
+ .card(@keyup.enter="newProblem()")
+ label#closeNewprob.modal-close(for="modalNewprob")
+ form(@submit.prevent="newProblem()" @keyup.enter="newProblem()")
+ fieldset
+ label(for="selectVariant") {{ st.tr["Variant"] }}
+ select#selectVariant(v-model="newproblem.vid" @change="loadVariant()")
+ option(v-for="v in [emptyVar].concat(st.variants)" :value="v.id"
+ :selected="newproblem.vid==v.id")
+ | {{ v.name }}
+ fieldset
+ label(for="inputFen") FEN
+ input#inputFen(type="text" v-model="newproblem.fen" @input="tryGetDiagram()")
+ fieldset
+ textarea#instructions(:placeholder="st.tr['Instructions']")
+ textarea#solution(:placeholder="st.tr['Solution']")
+ #preview
+ div(v-html="curDiag")
+ p instru: v-html=... .replace("\n", "<br/>") --> si pas de tags détectés !
+ p solution: v-html=...
+ button(@click="newProblem()") {{ st.tr["Send problem"] }}
+ #dialog.text-center {{ st.tr[infoMsg] }}
+ .row
+ .col-sm-12
+ button#newProblem(onClick="doClick('modalNewprob')") {{ st.tr["New problem"] }}
+ .row
+ .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
+ label(for="checkboxMine") {{ st.tr["My problems"] }}
+ input#checkboxMine(type="checkbox" v-model="onlyMines")
+ label(for="selectVariant") {{ st.tr["Variant"] }}
+ select#selectVariant(v-model="newproblem.vid")
+ option(v-for="v in [emptyVar].concat(st.variants)" :value="v.id")
+ | {{ v.name }}
+ // TODO: nice problems printing :: same as in preview ==> subComponent (inlined?)
+ div(v-for="p in problems" v-show="showProblem(p)")
+ p {{ p.vid }}
+ p {{ p.fen }}
+ p {{ p.instruction }}
+ p {{ p.solution }}
+</template>
+
+<script>
+import { store } from "@/store";
+import { ajax } from "@/utils/ajax";
+import { checkProblem } from "@/data/problemCheck";
+import { getDiagram } from "@/utils/printDiagram";
+export default {
+ name: "my-problems",
+ data: function() {
+ return {
+ emptyVar: {
+ vid: 0,
+ vname: "",
+ },
+ newproblem: {
+ vid: 0,
+ fen: "",
+ instruction: "",
+ solution: "",
+ },
+ onlyMines: false,
+ st: store.state,
+ problems: [],
+ infoMsg: "",
+ curVar: 0,
+ curDiag: "",
+ };
+ },
+ created: function() {
+ ajax("/problems", "GET", (res) => {
+ this.problems = res.problems;
+ });
+ },
+ methods: {
+ showProblem: function(p) {
+ return (this.vid == 0 || p.vid == this.vid) &&
+ (!this.onlyMines || p.uid != this.st.user.id);
+ },
+ loadVariant: async function() {
+ if (this.newproblem.vid == 0)
+ return;
+ this.curVar = 0;
+ const variant = this.st.variants.find(v => v.id == this.newproblem.vid);
+ const vModule = await import("@/variants/" + variant.name + ".js");
+ window.V = vModule.VariantRules;
+ this.curVar = this.newproblem.vid;
+ this.tryGetDiagram(); //the FEN might be already filled
+ },
+ tryGetDiagram: async function() {
+ if (this.newproblem.vid == 0)
+ return;
+ // Check through curVar if V is ready:
+ if (this.curVar == this.newproblem.vid && V.IsGoodFen(this.newproblem.fen))
+ {
+ const parsedFen = V.ParseFen(this.newproblem.fen);
+ const args = {
+ position: parsedFen.position,
+ orientation: parsedFen.turn,
+ };
+ this.curDiag = getDiagram(args);
+ }
+ else
+ this.curDiag = "<p>FEN not yet correct</p>";
+ },
+ newProblem: function() {
+ const error = checkProblem(this.newproblem);
+ if (!!error)
+ return alert(error);
+ ajax("/problems", "POST", {prob:this.newproblem}, (ret) => {
+ this.infoMsg = this.st.tr["Problem sent!"];
+ let newProblem = Object.Assign({}, this.newproblem);
+ newProblem.id = ret.id;
+ this.problems = this.problems.concat(newProblem);
+ });
+ },
+ },
+};
+</script>
+
+<style lang="sass" scoped>
+#newProblem
+ display: block
+ margin: 10px auto 5px auto
+</style>
ul:not(.browser-default) > li
list-style-type: disc
-
-.light-square-diag
- background-color: #e5e5ca
-
-.dark-square-diag
- background-color: #6f8f57
-
-// TODO: following is duplicated (Board.vue)
-div.board
- float: left
- height: 0
- display: inline-block
- position: relative
-
-div.board8
- width: 12.5%
- padding-bottom: 12.5%
-
-div.board10
- width: 10%
- padding-bottom: 10%
-
-div.board11
- width: 9.09%
- padding-bottom: 9.1%
-
-img.piece
- width: 100%
-
-img.piece, img.mark-square
- max-width: 100%
- height: auto
- display: block
-
-img.mark-square
- opacity: 0.6
- width: 76%
- position: absolute
- top: 12%
- left: 12%
- opacity: .7
-
-.in-shadow
- filter: brightness(50%)
</style>
notify boolean
);
--- All the following tables are for correspondance play only
--- (Live games are stored in browser)
+create table Problems (
+ id integer primary key,
+ added datetime,
+ uid integer,
+ vid integer,
+ instruction text,
+ solution text,
+ foreign key (uid) references Users(id),
+ foreign key (vid) references Variants(id)
+);
create table Challenges (
id integer primary key,
if ((mstats.nbMoves == 0 && tsNow - g.created > 91*day) ||
(mstats.nbMoves == 1 && tsNow - mstats.lastMaj > 91*day))
{
- return GameModel.remove(g.id);
+ GameModel.remove(g.id);
}
});
});
--- /dev/null
+var db = require("../utils/database");
+
+/*
+ * Structure:
+ * id: integer
+ * added: datetime
+ * uid: user id (int)
+ * vid: variant id (int)
+ * fen: varchar (optional)
+ * instruction: text
+ * solution: text
+ */
+
+const ProblemModel =
+{
+ checkProblem: function(p)
+ {
+ if (!p.vid.toString().match(/^[0-9]+$/))
+ return "Wrong variant ID";
+ if (!p.fen.match(/^[a-zA-Z0-9, /-]*$/))
+ return "Bad FEN string";
+ return "";
+ },
+
+ create: function(p, cb)
+ {
+ db.serialize(function() {
+ const query =
+ "INSERT INTO Problems " +
+ "(added, uid, vid, fen, instruction, solution) " +
+ "VALUES " +
+ "(" + Date.now() + "," + p.uid + ",'" + p.fen + "',?,?)";
+ db.run(query, p.instruction, p.solution, function(err) {
+ return cb(err, {pid: this.lastID});
+ });
+ });
+ },
+
+ getAll: function(cb)
+ {
+ db.serialize(function() {
+ const query =
+ "SELECT * " +
+ "FROM Problems";
+ db.all(query, (err,problems) => {
+ return cb(err, problems);
+ });
+ });
+ },
+
+ getOne: function(id, cb)
+ {
+ db.serialize(function() {
+ const query =
+ "SELECT * " +
+ "FROM Problems " +
+ "WHERE id = " + id;
+ db.get(query, (err,problem) => {
+ return cb(err, problem);
+ });
+ });
+ },
+
+ update: function(id, prob)
+ {
+ db.serialize(function() {
+ let query =
+ "UPDATE Problems " +
+ "SET " +
+ "vid = " + prob.vid + "," +
+ "fen = " + prob.fen + "," +
+ "instruction = " + prob.instruction + "," +
+ "solution = " + prob.solution + " " +
+ "WHERE id = " + id;
+ db.run(query);
+ });
+ },
+
+ remove: function(id)
+ {
+ 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"});
+ ProvlemModel.remove(id);
+ cb(null);
+ });
+ });
+ },
+}
+
+module.exports = ProblemModel;
},
"dependencies": {
"readable-stream": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz",
- "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
router.use("/", require("./messages"));
router.use("/", require("./users"));
router.use("/", require("./variants"));
+router.use("/", require("./problems"));
module.exports = router;
--- /dev/null
+// 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.get("/problems", (req,res) => {
+ const probId = req.query["pid"];
+ if (!!probId)
+ {
+ 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});
+ });
+ });
+ }
+ else
+ {
+ ProblemModel.getAll((err,problems) => {
+ res.json(err || {problems:problems});
+ });
+ }
+});
+
+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 || {pid:ret.pid});
+ });
+});
+
+router.put("/problems", access.logged, access.ajax, (req,res) => {
+ const pid = req.body.pid;
+ let error = "";
+ if (!pid.toString().match(/^[0-9]+$/))
+ error = "Wrong problem ID";
+ let obj = req.body.newProb;
+ error = ProblemModel.checkProblem(obj);
+ obj.instruction = sanitizeHtml(obj.instruction);
+ obj.solution = sanitizeHtml(obj.solution);
+ if (!!error)
+ return res.json({errmsg: error});
+ ProblemModel.update(pid, obj, (err) => {
+ res.json(err || {});
+ });
+});
+
+router.delete("/problems", access.logged, access.ajax, (req,res) => {
+ const pid = req.query.id;
+ if (!pid.match(/^[0-9]+$/))
+ res.json({errmsg: "Bad problem ID"});
+ ProblemModel.safeRemove(pid, req.userId, err => {
+ res.json(err || {});
+ });
+});
+
+module.exports = router;