65aedfde3b4b8363e09b32727826a24dee03e3f6
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)
13 * score: varchar (result)
14 * scoreMsg: varchar ("Time", "Mutual agreement"...)
16 * drawOffer: char ('w','b' or '' for none)
17 * rematchOffer: char (similar to drawOffer)
19 * deletedByWhite: boolean
20 * deletedByBlack: boolean
22 * Structure table Moves:
24 * squares: varchar (description)
28 * Structure table Chats:
37 checkGameInfo: function(g
) {
39 g
.vid
.toString().match(/^[0-9]+$/) &&
40 g
.cadence
.match(/^[0-9dhms
+]+$/) &&
41 g
.randomness
.match(/^[0-2]$/) &&
42 g
.fen
.match(/^[a-zA-Z0-9, /-]*$/) &&
43 g
.players
.length
== 2 &&
44 g
.players
.every(p
=> p
.toString().match(/^[0-9]+$/))
48 create: function(vid
, fen
, randomness
, cadence
, players
, cb
) {
49 db
.serialize(function() {
51 "INSERT INTO Games " +
53 "vid, fenStart, fen, randomness, " +
59 vid
+ ",'" + fen
+ "','" + fen
+ "'," + randomness
+ "," +
60 "'" + players
[0] + "','" + players
[1] + "," +
61 "'" + cadence
+ "'," + Date
.now() +
63 db
.run(query
, function(err
) {
64 cb(err
, { id: this.lastId
});
69 // TODO: some queries here could be async
70 getOne: function(id
, cb
) {
71 // NOTE: ignoring errors (shouldn't happen at this stage)
72 db
.serialize(function() {
75 "g.id, g.vid, g.fen, g.fenStart, g.cadence, g.created, " +
76 "g.white, g.black, g.score, g.scoreMsg, " +
77 "g.drawOffer, g.rematchOffer, v.name AS vname " +
82 db
.get(query
, (err
, gameInfo
) => {
84 "SELECT squares, played, idx " +
87 db
.all(query
, (err3
, moves
) => {
89 "SELECT msg, name, added " +
92 db
.all(query
, (err4
, chats
) => {
93 const game
= Object
.assign(
108 // For display on Hall: no need for moves or chats
109 getObserved: function(uid
, cursor
, cb
) {
110 db
.serialize(function() {
112 "SELECT g.id, g.vid, g.cadence, g.created, " +
113 " g.score, g.white, g.black " +
115 if (uid
> 0) query
+=
117 " created < " + cursor
+ " AND " +
118 " white <> " + uid
+ " AND " +
119 " black <> " + uid
+ " ";
121 "ORDER BY created DESC " +
122 "LIMIT 20"; //TODO: 20 hard-coded...
123 db
.all(query
, (err
, games
) => {
124 // Query players names
127 if (!pids
[g
.white
]) pids
[g
.white
] = true;
128 if (!pids
[g
.black
]) pids
[g
.black
] = true;
130 UserModel
.getByIds(Object
.keys(pids
), (err2
, users
) => {
132 users
.forEach(u
=> { names
[u
.id
] = u
.name
; });
142 white: names
[g
.white
],
143 black: names
[g
.black
]
153 // For display on MyGames: registered user only
154 getRunning: function(uid
, cb
) {
155 db
.serialize(function() {
157 "SELECT g.id, g.cadence, g.created, g.score, " +
158 "g.white, g.black, v.name AS vname " +
161 " ON g.vid = v.id " +
162 "WHERE white = " + uid
+ " OR black = " + uid
;
163 db
.all(query
, (err
, games
) => {
164 // Get movesCount (could be done in // with next query)
166 "SELECT gid, COUNT(*) AS nbMoves " +
168 "WHERE gid IN " + "(" + games
.map(g
=> g
.id
).join(",") + ") " +
170 db
.all(query
, (err
, mstats
) => {
171 let movesCounts
= {};
172 mstats
.forEach(ms
=> { movesCounts
[ms
.gid
] = ms
.nbMoves
; });
173 // Query player names
176 if (!pids
[g
.white
]) pids
[g
.white
] = true;
177 if (!pids
[g
.black
]) pids
[g
.black
] = true;
179 UserModel
.getByIds(pids
, (err2
, users
) => {
181 users
.forEach(u
=> { names
[u
.id
] = u
.name
; });
191 movesCount: movesCounts
[g
.id
],
192 white: names
[g
.white
],
193 black: names
[g
.black
]
204 // These games could be deleted on some side. movesCount not required
205 getCompleted: function(uid
, cursor
, cb
) {
206 db
.serialize(function() {
208 "SELECT g.id, g.cadence, g.created, g.score, g.scoreMsg, " +
209 "g.white, g.black, g.deletedByWhite, g.deletedByBlack, " +
213 " ON g.vid = v.id " +
215 " created < " + cursor
+ " AND " +
217 " (" + uid
+ " = white AND NOT deletedByWhite) OR " +
218 " (" + uid
+ " = black AND NOT deletedByBlack)" +
221 "ORDER BY created DESC " +
223 db
.all(query
, (err
, games
) => {
224 // Query player names
227 if (!pids
[g
.white
]) pids
[g
.white
] = true;
228 if (!pids
[g
.black
]) pids
[g
.black
] = true;
230 UserModel
.getByIds(pids
, (err2
, users
) => {
232 users
.forEach(u
=> { names
[u
.id
] = u
.name
; });
242 scoreMsg: g
.scoreMsg
,
243 white: names
[g
.white
],
244 black: names
[g
.black
],
245 deletedByWhite: g
.deletedByWhite
,
246 deletedByBlack: g
.deletedByBlack
256 getPlayers: function(id
, cb
) {
257 db
.serialize(function() {
259 "SELECT white, black " +
262 db
.all(query
, (err
, players
) => {
263 return cb(err
, players
);
268 checkGameUpdate: function(obj
) {
269 // Check all that is possible (required) in obj:
273 !!(obj
.move.played
.toString().match(/^[0-9]+$/)) &&
274 !!(obj
.move.idx
.toString().match(/^[0-9]+$/))
277 !obj
.drawOffer
|| !!(obj
.drawOffer
.match(/^[wbtn]$/))
279 !obj
.rematchOffer
|| !!(obj
.rematchOffer
.match(/^[wbn]$/))
281 !obj
.fen
|| !!(obj
.fen
.match(/^[a-zA-Z0-9, /-]*$/))
283 !obj
.score
|| !!(obj
.score
.match(/^[012?*\/-]+$/))
285 !obj
.scoreMsg
|| !!(obj
.scoreMsg
.match(/^[a
-zA
-Z
]+$/))
287 !obj
.chat
|| UserModel
.checkNameEmail({name: obj
.chat
.name
})
292 // obj can have fields move, chat, fen, drawOffer and/or score + message
293 update: function(id
, obj
, cb
) {
294 db
.parallelize(function() {
299 // NOTE: if drawOffer is set, we should check that it's player's turn
300 // A bit overcomplicated. Let's trust the client on that for now...
303 if (obj
.drawOffer
== "n") //special "None" update
305 modifs
+= "drawOffer = '" + obj
.drawOffer
+ "',";
307 if (!!obj
.rematchOffer
)
309 if (obj
.rematchOffer
== "n") //special "None" update
310 obj
.rematchOffer
= "";
311 modifs
+= "rematchOffer = '" + obj
.rematchOffer
+ "',";
314 modifs
+= "fen = '" + obj
.fen
+ "',";
316 modifs
+= "score = '" + obj
.score
+ "',";
318 modifs
+= "scoreMsg = '" + obj
.scoreMsg
+ "',";
319 if (!!obj
.deletedBy
) {
320 const myColor
= obj
.deletedBy
== 'w' ? "White" : "Black";
321 modifs
+= "deletedBy" + myColor
+ " = true,";
323 modifs
= modifs
.slice(0,-1); //remove last comma
324 if (modifs
.length
> 0)
326 query
+= modifs
+ " WHERE id = " + id
;
329 // NOTE: move, chat and delchat are mutually exclusive
332 // Security: only update moves if index is right
334 "SELECT MAX(idx) AS maxIdx " +
337 db
.get(query
, (err
,ret
) => {
339 if (!ret
.maxIdx
|| ret
.maxIdx
+ 1 == m
.idx
) {
341 "INSERT INTO Moves (gid, squares, played, idx) VALUES " +
342 "(" + id
+ ",?," + m
.played
+ "," + m
.idx
+ ")";
343 db
.run(query
, JSON
.stringify(m
.squares
));
346 else cb({errmsg:"Wrong move index"});
353 "INSERT INTO Chats (gid, msg, name, added) VALUES ("
354 + id
+ ",?,'" + obj
.chat
.name
+ "'," + Date
.now() + ")";
355 db
.run(query
, obj
.chat
.msg
);
357 else if (obj
.delchat
)
365 if (!!obj
.deletedBy
) {
366 // Did my opponent delete it too?
369 (obj
.deletedBy
== 'w' ? "Black" : "White") +
372 "SELECT " + selection
+ " " +
375 db
.get(query
, (err
,ret
) => {
376 // If yes: just remove game
377 if (!!ret
.deletedByOpp
) GameModel
.remove(id
);
383 remove: function(id_s
) {
386 ? " IN (" + id_s
.join(",") + ")"
388 db
.parallelize(function() {
390 "DELETE FROM Games " +
391 "WHERE id " + suffix
;
394 "DELETE FROM Moves " +
395 "WHERE gid " + suffix
;
398 "DELETE FROM Chats " +
399 "WHERE gid " + suffix
;
404 cleanGamesDb: function() {
405 const tsNow
= Date
.now();
406 // 86400000 = 24 hours in milliseconds
407 const day
= 86400000;
408 db
.serialize(function() {
410 "SELECT id, created " +
412 db
.all(query
, (err
, games
) => {
414 "SELECT gid, count(*) AS nbMoves, MAX(played) AS lastMaj " +
417 db
.get(query
, (err2
, mstats
) => {
418 // Reorganize moves data to avoid too many array lookups:
419 let movesGroups
= {};
420 mstats
.forEach(ms
=> {
421 movesGroups
[ms
.gid
] = {
426 // Remove games still not really started,
427 // with no action in the last 3 months:
432 !movesGroups
[g
.id
] &&
433 tsNow
- g
.created
> 91*day
437 movesGroups
[g
.id
].nbMoves
== 1 &&
438 tsNow
- movesGroups
[g
.id
].lastMaj
> 91*day
444 if (toRemove
.length
> 0) GameModel
.remove(toRemove
);
451 module
.exports
= GameModel
;