From d431028c73d41a22636130bd6aff562762eaf2bb Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Wed, 15 Jan 2020 14:45:26 +0100
Subject: [PATCH] Drop problems from server + draft DB cleaning functions

---
 server/TODO                |  5 --
 server/bin/www             | 15 ++++++
 server/db/create.sql       | 13 +-----
 server/models/Challenge.js | 18 ++++++++
 server/models/Game.js      | 35 +++++++++++++-
 server/models/Problem.js   | 81 --------------------------------
 server/models/User.js      | 19 ++++++++
 server/routes/all.js       |  1 -
 server/routes/problems.js  | 94 --------------------------------------
 9 files changed, 86 insertions(+), 195 deletions(-)
 delete mode 100644 server/models/Problem.js
 delete mode 100644 server/routes/problems.js

diff --git a/server/TODO b/server/TODO
index bcffc7bd..bd5d7309 100644
--- a/server/TODO
+++ b/server/TODO
@@ -2,8 +2,3 @@ Later: use http2
 https://webapplog.com/http2-node/
 https://www.npmjs.com/package/spdy
 Express 5?
-
-Finish :
- - models
- - routes
- - sockets
diff --git a/server/bin/www b/server/bin/www
index e287d503..94f39e13 100755
--- a/server/bin/www
+++ b/server/bin/www
@@ -21,6 +21,21 @@ app.set('port', port);
 
 var server = http.createServer(app);
 
+/*
+ * CRON tasks
+ */
+
+var cron = require('node-cron');
+var UserModel = require("../models/User");
+var ChallengeModel = require("../models/Challenge");
+var GameModel = require("../models/Game");
+cron.schedule('0 0 0 * * *', function() {
+	// Remove some old users, challenges and games every 24h
+	UserModel.cleanUsersDb(); //TODO: write this
+	ChallengeModel.removeOld(); //TODO: this too
+	GameModel.cleanGamesDb();
+});
+
 /**
  * Listen on provided port, on all network interfaces.
  */
diff --git a/server/db/create.sql b/server/db/create.sql
index 11c3574e..a8c8d687 100644
--- a/server/db/create.sql
+++ b/server/db/create.sql
@@ -16,18 +16,6 @@ create table Users (
   notify boolean
 );
 
-create table Problems (
-  id integer primary key,
-  added datetime,
-  uid integer,
-  vid integer,
-  fen varchar,
-  instructions text,
-  solution text,
-  foreign key (uid) references Users(id),
-  foreign key (vid) references Variants(id)
-);
-
 -- All the following tables are for correspondance play only
 -- (Live games are stored in browser)
 
@@ -51,6 +39,7 @@ create table Games (
   fen varchar, --current state
   score varchar,
   timeControl varchar,
+  created datetime, --used only for DB cleaning
   foreign key (vid) references Variants(id)
 );
 
diff --git a/server/models/Challenge.js b/server/models/Challenge.js
index f82f9e81..ba805aa1 100644
--- a/server/models/Challenge.js
+++ b/server/models/Challenge.js
@@ -94,4 +94,22 @@ const ChallengeModel =
   },
 }
 
+// TODO: adapt
+// Remove challenges older than 1 month, and 1to1 older than 36h
+//exports.removeOld = function()
+//{
+//	var tsNow = new Date().getTime();
+//	// 86400000 = 24 hours in milliseconds
+//	var day = 86400000;
+//	db.challenges.find({}, (err,challengeArray) => {
+//		challengeArray.forEach( c => {
+//			if (c._id.getTimestamp() + 30*day < tsNow //automatch
+//				|| (!!c.to && c._id.getTimestamp() + 1.5*day < tsNow)) //1 to 1
+//			{
+//				db.challenges.remove({"_id": c._id});
+//			}
+//		});
+//	});
+//}
+
 module.exports = ChallengeModel;
diff --git a/server/models/Game.js b/server/models/Game.js
index 0949fc98..3d2184b8 100644
--- a/server/models/Game.js
+++ b/server/models/Game.js
@@ -28,8 +28,9 @@ const GameModel =
 	{
 		db.serialize(function() {
 			let query =
-				"INSERT INTO Games (vid, fenStart, fen, score, timeControl) VALUES " +
-        "(" + vid + ",'" + fen + "','" + fen + "','*','" + timeControl + "')";
+				"INSERT INTO Games (vid, fenStart, fen, score, timeControl, created)"
+        + " VALUES (" + vid + ",'" + fen + "','" + fen + "','*','"
+        + timeControl + "'," + Date.now() + ")";
       db.run(query, function(err) {
         if (!!err)
           return cb(err);
@@ -174,6 +175,36 @@ const GameModel =
 			db.run(query);
 		});
 	},
+
+  cleanGamesDb: function()
+  {
+    const tsNow = Date.now();
+    // 86400000 = 24 hours in milliseconds
+    const day = 86400000;
+    db.serialize(function() {
+      let query =
+        "SELECT id,score " +
+        "FROM Games ";
+      db.all(query, (err,games) => {
+        games.forEach(g => {
+          query =
+            "SELECT max(played) AS lastMaj " +
+            "FROM Moves " +
+            "WHERE gid = " + g.id;
+          db.get(query, (err2,updated) {
+            if (!updated && tsNow - g.created > 7*day)
+              return GameModel.remove(g.id);
+            const lastMaj = updated.lastMaj;
+            if (g.score != "*" && tsNow - lastMaj > 7*day ||
+              g.score == "*" && tsNow - lastMaj > 91*day)
+            {
+              GameModel.remove(g.id);
+            }
+          });
+        });
+      });
+    });
+  },
 }
 
 module.exports = GameModel;
diff --git a/server/models/Problem.js b/server/models/Problem.js
deleted file mode 100644
index a1f99031..00000000
--- a/server/models/Problem.js
+++ /dev/null
@@ -1,81 +0,0 @@
-var db = require("../utils/database");
-
-/*
- * Structure:
- *   id: problem number (int)
- *   uid: user id (int)
- *   vid: variant id (int)
- *   added: timestamp
- *   instructions: text
- *   solution: text
- */
-
-const ProblemModel =
-{
-	create: function(uid, vid, fen, instructions, solution, cb)
-	{
-		db.serialize(function() {
-			const insertQuery =
-				"INSERT INTO Problems (added, uid, vid, fen, instructions, solution) " +
-				"VALUES (" + Date.now() + "," + uid + "," + vid + ",'" + fen + "',?,?)";
-			db.run(insertQuery, [instructions, solution], err => {
-				if (!!err)
-					return cb(err);
-				db.get("SELECT last_insert_rowid() AS rowid", cb);
-			});
-		});
-	},
-
-	getOne: function(id, callback)
-	{
-		db.serialize(function() {
-			const query =
-				"SELECT * " +
-				"FROM Problems " +
-				"WHERE id = " + id;
-			db.get(query, callback);
-		});
-	},
-
-	fetchN: function(vid, uid, type, directionStr, lastDt, MaxNbProblems, callback)
-	{
-		db.serialize(function() {
-			let typeLine = "";
-			if (uid > 0)
-				typeLine = "AND uid " + (type=="others" ? "!=" : "=") + " " + uid;
-			const query =
-				"SELECT * FROM Problems " +
-				"WHERE vid = " + vid +
-				"  AND added " + directionStr + " " + lastDt + " " + typeLine + " " +
-				"ORDER BY added " + (directionStr=="<" ? "DESC " : "") +
-				"LIMIT " + MaxNbProblems;
-			db.all(query, callback);
-		});
-	},
-
-	// TODO: update fails (but insert is OK)
-	update: function(id, uid, fen, instructions, solution, cb)
-	{
-		db.serialize(function() {
-			const query =
-				"UPDATE Problems SET " +
-					"fen = '" + fen + "', " +
-					"instructions = ?, " +
-					"solution = ? " +
-				"WHERE id = " + id + " AND uid = " + uid;
-			db.run(query, [instructions,solution], cb);
-		});
-	},
-
-	remove: function(id, uid)
-	{
-		db.serialize(function() {
-			const query =
-				"DELETE FROM Problems " +
-				"WHERE id = " + id + " AND uid = " + uid;
-			db.run(query);
-		});
-	},
-}
-
-module.exports = ProblemModel;
diff --git a/server/models/User.js b/server/models/User.js
index b2a99e05..c2e78837 100644
--- a/server/models/User.js
+++ b/server/models/User.js
@@ -142,4 +142,23 @@ const UserModel =
   }
 }
 
+// TODO: adapt
+//exports.cleanUsersDb = function()
+//{
+//	var tsNow = new Date().getTime();
+//	// 86400000 = 24 hours in milliseconds
+//	var day = 86400000;
+//
+//	db.users.find({}, (err,userArray) => {
+//		userArray.forEach( u => {
+//			if ((u.sessionTokens.length==0 &&
+//					u._id.getTimestamp().getTime() + day < tsNow) //unlogged
+//				|| u.updated + 365*day < tsNow) //inactive for one year
+//			{
+//				db.users.remove({"_id": u._id});
+//			}
+//		});
+//	});
+//}
+
 module.exports = UserModel;
diff --git a/server/routes/all.js b/server/routes/all.js
index fbf3433e..a890e609 100644
--- a/server/routes/all.js
+++ b/server/routes/all.js
@@ -9,7 +9,6 @@ router.get("/", access.ajax, (req,res) => {
 router.use("/", require("./challenges"));
 router.use("/", require("./games"));
 router.use("/", require("./messages"));
-router.use("/", require("./problems"));
 router.use("/", require("./users"));
 router.use("/", require("./variants"));
 
diff --git a/server/routes/problems.js b/server/routes/problems.js
deleted file mode 100644
index 777543b1..00000000
--- a/server/routes/problems.js
+++ /dev/null
@@ -1,94 +0,0 @@
-// 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');
-const MaxNbProblems = 20;
-
-function sanitizeUserInput(fen, instructions, solution)
-{
-	if (!fen.match(/^[a-zA-Z0-9, /-]*$/))
-		return "Bad characters in FEN string";
-	instructions = sanitizeHtml(instructions);
-	solution = sanitizeHtml(solution);
-	if (instructions.length == 0)
-		return "Empty instructions";
-	if (solution.length == 0)
-		return "Empty solution";
-	return {
-		fen: fen,
-		instructions: instructions,
-		solution: solution
-	};
-}
-
-// Get one problem (TODO: vid unused, here for URL de-ambiguification)
-router.get("/problems/:vid([0-9]+)/:id([0-9]+)", access.ajax, (req,res) => {
-	const pid = req.params["id"];
-	ProblemModel.getOne(pid, (err,problem) => {
-		if (!!err)
-			return res.json(err);
-		return res.json({problem: problem});
-	});
-});
-
-// Fetch N previous or next problems
-router.get("/problems/:vid([0-9]+)", access.ajax, (req,res) => {
-	const vid = req.params["vid"];
-	const directionStr = (req.query.direction == "forward" ? ">" : "<");
-	const lastDt = req.query.last_dt;
-	const type = req.query.type;
-	if (!lastDt.match(/[0-9]+/))
-		return res.json({errmsg: "Bad timestamp"});
-	if (!["others","mine"].includes(type))
-		return res.json({errmsg: "Bad type"});
-	ProblemModel.fetchN(vid, req.userId, type, directionStr, lastDt, MaxNbProblems,
-		(err,problems) => {
-			if (!!err)
-				return res.json(err);
-			return res.json({problems: problems});
-		}
-	);
-});
-
-// Upload a problem (sanitize inputs)
-router.post("/problems/:vid([0-9]+)", access.logged, access.ajax, (req,res) => {
-	const vid = req.params["vid"];
-	const s = sanitizeUserInput(
-		req.body["fen"], req.body["instructions"], req.body["solution"]);
-	if (typeof s === "string")
-		return res.json({errmsg: s});
-  ProblemModel.create(req.userId, vid, s.fen, s.instructions, s.solution,
-		(err,pid) => {
-			if (!!err)
-				return res.json(err);
-			res.json({id: pid["rowid"]});
-		}
-	);
-});
-
-// Update a problem (also sanitize inputs)
-router.put("/problems/:id([0-9]+)", access.logged, access.ajax, (req,res) => {
-	const pid = req.params["id"]; //problem ID
-	const s = sanitizeUserInput(
-		req.body["fen"], req.body["instructions"], req.body["solution"]);
-	if (typeof s === "string")
-		return res.json({errmsg: s});
-	ProblemModel.update(pid, req.userId, s.fen, s.instructions, s.solution,
-		err => {
-			if (!!err)
-				return res.json(err);
-			res.json({});
-		}
-	);
-});
-
-// Delete a problem
-router.delete("/problems/:id([0-9]+)", access.logged, access.ajax, (req,res) => {
-	const pid = req.params["id"]; //problem ID
-  ProblemModel.remove(pid, req.userId);
-	res.json({});
-});
-
-module.exports = router;
-- 
2.44.0