1 const db
= require("../utils/database");
2 const UserModel
= require("./User");
5 * Structure table Games:
7 * vid: integer (variant id)
8 * fenStart: varchar (initial position)
9 * fen: varchar (current position)
11 * score: varchar (result)
12 * scoreMsg: varchar ("Time", "Mutual agreement"...)
14 * drawOffer: char ('w','b' or '' for none)
16 * Structure table Players:
21 * Structure table Moves:
23 * squares: varchar (description)
27 * Structure table Chats:
36 checkGameInfo: function(g
) {
38 g
.vid
.toString().match(/^[0-9]+$/) &&
39 g
.cadence
.match(/^[0-9dhms
+]+$/) &&
40 g
.fen
.match(/^[a-zA-Z0-9, /-]*$/) &&
41 g
.players
.length
== 2 &&
42 g
.players
.every(p
=> p
.id
.toString().match(/^[0-9]+$/))
46 create: function(vid
, fen
, cadence
, players
, cb
)
48 db
.serialize(function() {
50 "INSERT INTO Games " +
51 "(vid, fenStart, fen, cadence, created) " +
53 "(" + vid
+ ",'" + fen
+ "','" + fen
+ "','" + cadence
+ "'," + Date
.now() + ")";
54 db
.run(query
, function(err
) {
59 players
.forEach((p
,idx
) => {
60 const color
= (idx
==0 ? "w" : "b");
62 "INSERT INTO Players VALUES " +
63 "(" + this.lastID
+ "," + p
.id
+ ",'" + color
+ "')";
66 cb(null, {gid: this.lastID
});
72 // TODO: some queries here could be async
73 getOne: function(id
, cb
)
75 // NOTE: ignoring errors (shouldn't happen at this stage)
76 db
.serialize(function() {
78 "SELECT g.id, g.vid, g.fen, g.fenStart, g.cadence, g.created, g.score, " +
79 "g.scoreMsg, g.drawOffer, g.rematchOffer, v.name AS vname " +
84 db
.get(query
, (err
, gameInfo
) => {
86 "SELECT p.uid, p.color, u.name " +
90 "WHERE p.gid = " + id
;
91 db
.all(query
, (err2
, players
) => {
93 "SELECT squares, played, idx " +
96 db
.all(query
, (err3
, moves
) => {
98 "SELECT msg, name, added " +
101 db
.all(query
, (err4
, chats
) => {
102 const game
= Object
.assign({},
118 // For display on MyGames or Hall: no need for moves or chats
119 getByUser: function(uid
, excluded
, cb
)
121 // Some fields are not required when showing a games list:
122 const getOneLight
= (id
, cb2
) => {
124 "SELECT g.id, g.vid, g.fen, g.cadence, g.created, g.score, " +
125 "g.scoreMsg, g.deletedByWhite, g.deletedByBlack, v.name AS vname " +
128 " ON g.vid = v.id " +
129 "WHERE g.id = " + id
;
130 db
.get(query
, (err
, gameInfo
) => {
132 "SELECT p.uid, p.color, u.name " +
135 " ON p.uid = u.id " +
136 "WHERE p.gid = " + id
;
137 db
.all(query
, (err2
, players
) => {
139 "SELECT COUNT(*) AS nbMoves " +
142 db
.get(query
, (err
,ret
) => {
143 const game
= Object
.assign({},
147 movesCount: ret
.nbMoves
155 db
.serialize(function() {
158 // Special case anonymous user: show all games
160 "SELECT id AS gid " +
169 "HAVING COUNT(uid = " + uid
+ " OR NULL) " +
170 (excluded
? " = 0" : " > 0");
172 db
.all(query
, (err
,gameIds
) => {
173 if (err
|| gameIds
.length
== 0) cb(err
, []);
177 for (let i
=0; i
<gameIds
.length
; i
++) {
178 getOneLight(gameIds
[i
]["gid"], (game
) => {
179 gameArray
.push(game
);
180 gCounter
++; //TODO: let's hope this is atomic?!
181 // Call callback function only when gameArray is complete:
182 if (gCounter
== gameIds
.length
)
191 getPlayers: function(id
, cb
)
193 db
.serialize(function() {
198 db
.all(query
, (err
,players
) => {
199 return cb(err
, players
);
204 checkGameUpdate: function(obj
)
206 // Check all that is possible (required) in obj:
210 !!(obj
.move.played
.toString().match(/^[0-9]+$/)) &&
211 !!(obj
.move.idx
.toString().match(/^[0-9]+$/))
214 !obj
.drawOffer
|| !!(obj
.drawOffer
.match(/^[wbtn]$/))
216 !obj
.rematchOffer
|| !!(obj
.rematchOffer
.match(/^[wbn]$/))
218 !obj
.fen
|| !!(obj
.fen
.match(/^[a-zA-Z0-9, /-]*$/))
220 !obj
.score
|| !!(obj
.score
.match(/^[012?*\/-]+$/))
222 !obj
.scoreMsg
|| !!(obj
.scoreMsg
.match(/^[a
-zA
-Z
]+$/))
224 !obj
.chat
|| UserModel
.checkNameEmail({name: obj
.chat
.name
})
229 // obj can have fields move, chat, fen, drawOffer and/or score + message
230 update: function(id
, obj
, cb
)
232 db
.parallelize(function() {
237 // NOTE: if drawOffer is set, we should check that it's player's turn
238 // A bit overcomplicated. Let's trust the client on that for now...
241 if (obj
.drawOffer
== "n") //special "None" update
243 modifs
+= "drawOffer = '" + obj
.drawOffer
+ "',";
245 if (!!obj
.rematchOffer
)
247 if (obj
.rematchOffer
== "n") //special "None" update
248 obj
.rematchOffer
= "";
249 modifs
+= "rematchOffer = '" + obj
.rematchOffer
+ "',";
252 modifs
+= "fen = '" + obj
.fen
+ "',";
254 modifs
+= "score = '" + obj
.score
+ "',";
256 modifs
+= "scoreMsg = '" + obj
.scoreMsg
+ "',";
257 if (!!obj
.deletedBy
) {
258 const myColor
= obj
.deletedBy
== 'w' ? "White" : "Black";
259 modifs
+= "deletedBy" + myColor
+ " = true,";
261 modifs
= modifs
.slice(0,-1); //remove last comma
262 if (modifs
.length
> 0)
264 query
+= modifs
+ " WHERE id = " + id
;
267 // NOTE: move, chat and delchat are mutually exclusive
270 // Security: only update moves if index is right
272 "SELECT MAX(idx) AS maxIdx " +
275 db
.get(query
, (err
,ret
) => {
277 if (!ret
.maxIdx
|| ret
.maxIdx
+ 1 == m
.idx
) {
279 "INSERT INTO Moves (gid, squares, played, idx) VALUES " +
280 "(" + id
+ ",?," + m
.played
+ "," + m
.idx
+ ")";
281 db
.run(query
, JSON
.stringify(m
.squares
));
284 else cb({errmsg:"Wrong move index"});
291 "INSERT INTO Chats (gid, msg, name, added) VALUES ("
292 + id
+ ",?,'" + obj
.chat
.name
+ "'," + Date
.now() + ")";
293 db
.run(query
, obj
.chat
.msg
);
295 else if (obj
.delchat
)
303 if (!!obj
.deletedBy
) {
304 // Did my opponent delete it too?
307 (obj
.deletedBy
== 'w' ? "Black" : "White") +
310 "SELECT " + selection
+ " " +
313 db
.get(query
, (err
,ret
) => {
314 // If yes: just remove game
315 if (!!ret
.deletedByOpp
) GameModel
.remove(id
);
323 db
.parallelize(function() {
325 "DELETE FROM Games " +
329 "DELETE FROM Players " +
333 "DELETE FROM Moves " +
337 "DELETE FROM Chats " +
343 cleanGamesDb: function()
345 const tsNow
= Date
.now();
346 // 86400000 = 24 hours in milliseconds
347 const day
= 86400000;
348 db
.serialize(function() {
350 "SELECT id, created " +
352 db
.all(query
, (err
,games
) => {
355 "SELECT count(*) as nbMoves, max(played) AS lastMaj " +
357 "WHERE gid = " + g
.id
;
358 db
.get(query
, (err2
,mstats
) => {
359 // Remove games still not really started,
360 // with no action in the last 3 months:
361 if ((mstats
.nbMoves
== 0 && tsNow
- g
.created
> 91*day
) ||
362 (mstats
.nbMoves
== 1 && tsNow
- mstats
.lastMaj
> 91*day
))
364 GameModel
.remove(g
.id
);
373 module
.exports
= GameModel
;