diff --git a/functions/index.js b/functions/index.js index 2ab8f9f31..97ee5643d 100644 --- a/functions/index.js +++ b/functions/index.js @@ -783,113 +783,6 @@ exports.verifyUser = functions.https.onRequest(async (request, response) => { } }); -function incrementT60Bananas(uid, result, userData) { - try { - let best60; - try { - best60 = Math.max( - ...userData.personalBests.time[60].map((best) => best.wpm) - ); - if (!Number.isFinite(best60)) { - throw "Not finite"; - } - } catch (e) { - best60 = undefined; - } - - if (best60 != undefined && result.wpm < best60 - best60 * 0.25) { - // console.log("returning"); - return; - } else { - //increment - // console.log("checking"); - db.collection(`users/${uid}/bananas`) - .doc("bananas") - .get() - .then((docRef) => { - let data = docRef.data(); - if (data === undefined) { - //create doc - db.collection(`users/${uid}/bananas`).doc("bananas").set( - { - t60bananas: 1, - }, - { merge: true } - ); - } else { - //increment - db.collection(`users/${uid}/bananas`) - .doc("bananas") - .set( - { - t60bananas: admin.firestore.FieldValue.increment(1), - }, - { merge: true } - ); - } - }); - } - } catch (e) { - console.log( - "something went wrong when trying to increment bananas " + e.message - ); - } -} - -async function getIncrementedTypingStats(userData, resultObj) { - try { - let newStarted; - let newCompleted; - let newTime; - - let tt = 0; - let afk = resultObj.afkDuration; - if (afk == undefined) { - afk = 0; - } - tt = resultObj.testDuration + resultObj.incompleteTestSeconds - afk; - - if (tt > 500) - console.log( - `FUCK, INCREASING BY A LOT ${resultObj.uid}: ${JSON.stringify( - resultObj - )}` - ); - - if (userData.startedTests === undefined) { - newStarted = resultObj.restartCount + 1; - } else { - newStarted = userData.startedTests + resultObj.restartCount + 1; - } - if (userData.completedTests === undefined) { - newCompleted = 1; - } else { - newCompleted = userData.completedTests + 1; - } - if (userData.timeTyping === undefined) { - newTime = tt; - } else { - newTime = userData.timeTyping + tt; - } - // db.collection("users") - // .doc(uid) - // .update({ - // startedTests: newStarted, - // completedTests: newCompleted, - // timeTyping: roundTo2(newTime), - // }); - incrementPublicTypingStats(resultObj.restartCount + 1, 1, tt); - - return { - newStarted: newStarted, - newCompleted: newCompleted, - newTime: roundTo2(newTime), - }; - } catch (e) { - console.error(`Error while incrementing stats for user ${uid}: ${e}`); - } -} - async function getUpdatedLbMemory(userdata, mode, mode2, globallb, dailylb) { let lbmemory = userdata.lbMemory; diff --git a/mongo-todo.md b/mongo-todo.md new file mode 100644 index 000000000..d09d8b691 --- /dev/null +++ b/mongo-todo.md @@ -0,0 +1,3 @@ +- Reverse list of results on account page +- spinning wheel on account page should dissapear after data is loaded +- Account data should be updated when new result is added/test completed diff --git a/server.js b/server.js index 46aeeb5c2..df394b5e7 100644 --- a/server.js +++ b/server.js @@ -7,6 +7,8 @@ const bcrypt = require("bcrypt"); const saltRounds = 10; const { User } = require("./usermodel"); +// MIDDLEWARE & SETUP + const app = express(); const { Schema } = mongoose; @@ -33,155 +35,7 @@ function authenticateToken(req, res, next) { }); } -// API - -app.post("/api/updateName", (req, res) => { - //this might be a put/patch request - //update the name of user with given uid - const uid = req.body.uid; - const name = req.body.name; -}); - -app.post("/api/sendEmailVerification", (req, res) => { - const uid = req.body.uid; - //Add send Email verification code here - //should be a seperate sendEmailVerification function that can be called from sign up as well - res.sendStatus(200); -}); - -app.post("/api/signIn", (req, res) => { - /* Takes email and password */ - //Login and send tokens - User.findOne({ email: req.body.email }, (err, user) => { - if (err) res.status(500).send({ error: err }); - if (user == null) { - res.status(500).send({ error: "No user found with that email" }); - } - bcrypt.compare(req.body.password, user.password, (err, result) => { - if (err) - res.status(500).send({ error: "Error during password validation" }); - if (result) { - //if password matches hash - const accessToken = jwt.sign( - { name: user.name }, - process.env.ACCESS_TOKEN_SECRET - ); - const refreshToken = jwt.sign( - { name: user.name }, - process.env.REFRESH_TOKEN_SECRET - ); - user.refreshTokens.push(refreshToken); - user.save(); - const retUser = { - uid: user._id, - displayName: user.name, - email: user.email, - emailVerified: user.emailVerified, - metadata: { creationTime: user.createdAt }, - }; - res.json({ - accessToken: accessToken, - refreshToken: refreshToken, - user: retUser, - }); - } else { - //if password doesn't match hash - res.status(500).send({ error: "Password invalid" }); - } - }); - }); -}); - -app.post("/api/signUp", (req, res) => { - /* Takes name, email, password */ - //check if name has been taken - User.exists({ name: req.body.name }).then((exists) => { - //should also check if email is used - if (exists) { - //user with that name already exists - res.status(500).send({ error: "Username taken" }); - } - bcrypt.hash(req.body.password, saltRounds, function (err, hash) { - if (err) console.log(err); - const newuser = new User({ - name: req.body.name, - email: req.body.email, - emailVerified: false, - password: hash, - }); - newuser - .save() - .then((user) => { - //send email verification - - //add account created event to analytics - - //return user data and access token - const accessToken = jwt.sign( - { name: req.body.name }, - process.env.ACCESS_TOKEN_SECRET - ); - const refreshToken = jwt.sign( - { name: user.name }, - process.env.REFRESH_TOKEN_SECRET - ); - user.refreshTokens.push(refreshToken); - user.save(); - const retUser = { - uid: user._id, - displayName: user.name, - email: user.email, - emailVerified: user.emailVerified, - metadata: { creationTime: user.createdAt }, - }; - res.json({ - accessToken: accessToken, - refreshToken: refreshToken, - user: retUser, - }); - }) - .catch((e) => { - console.log(e); - res.status(500).send({ error: "Error when adding user" }); - }); - }); - }); -}); - -app.post("/api/refreshToken", (req, res) => { - const authHeader = req.headers["authorization"]; - const token = authHeader && authHeader.split(" ")[1]; - if (token == null) return res.sendStatus(401); - jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, identity) => { - if (err) return res.sendStatus(403); - User.findOne({ name: identity.name }, (err, user) => { - if (!user.refreshTokens.includes(token)) return res.sendStatus(403); - const accessToken = jwt.sign( - { name: identity.name }, - process.env.ACCESS_TOKEN_SECRET - ); - res.json({ accessToken: accessToken }); - }); - }); -}); - -app.post("/api/passwordReset", (req, res) => { - const email = req.body.email; - //send email to the passed email requesting password reset - res.sendStatus(200); -}); - -app.get("/api/fetchSnapshot", authenticateToken, (req, res) => { - /* Takes token and returns snap */ - User.findOne({ name: req.name }, (err, user) => { - if (err) res.status(500).send({ error: err }); - //populate snap object with data from user document - let snap = user; - delete snap.password; - //return user data - res.json({ snap: snap }); - }); -}); +// NON-ROUTE FUNCTIONS function validateResult(result) { if (result.wpm > result.rawWpm) { @@ -382,6 +236,42 @@ async function stripAndSave(uid, obj) { }); } +function incrementT60Bananas(uid, result, userData) { + try { + let best60; + try { + best60 = Math.max( + ...userData.personalBests.time[60].map((best) => best.wpm) + ); + if (!Number.isFinite(best60)) { + throw "Not finite"; + } + } catch (e) { + best60 = undefined; + } + + if (best60 != undefined && result.wpm < best60 - best60 * 0.25) { + // console.log("returning"); + return; + } else { + //increment + // console.log("checking"); + User.findOne({ _id: uid }, (err, user) => { + if (user.bananas === undefined) { + user.bananas.t60bananas = 1; + } else { + user.bananas.t60bananas += 1; + } + user.save(); + }); + } + } catch (e) { + console.log( + "something went wrong when trying to increment bananas " + e.message + ); + } +} + async function incrementGlobalTypingStats(userData, resultObj) { let userGlobalStats = userData.globalStats; try { @@ -458,6 +348,156 @@ async function incrementPublicTypingStats(started, completed, time) { */ } +// API + +app.post("/api/updateName", (req, res) => { + //this might be a put/patch request + //update the name of user with given uid + const uid = req.body.uid; + const name = req.body.name; +}); + +app.post("/api/sendEmailVerification", (req, res) => { + const uid = req.body.uid; + //Add send Email verification code here + //should be a seperate sendEmailVerification function that can be called from sign up as well + res.sendStatus(200); +}); + +app.post("/api/signIn", (req, res) => { + /* Takes email and password */ + //Login and send tokens + User.findOne({ email: req.body.email }, (err, user) => { + if (err) res.status(500).send({ error: err }); + if (user == null) { + res.status(500).send({ error: "No user found with that email" }); + } + bcrypt.compare(req.body.password, user.password, (err, result) => { + if (err) + res.status(500).send({ error: "Error during password validation" }); + if (result) { + //if password matches hash + const accessToken = jwt.sign( + { name: user.name }, + process.env.ACCESS_TOKEN_SECRET + ); + const refreshToken = jwt.sign( + { name: user.name }, + process.env.REFRESH_TOKEN_SECRET + ); + user.refreshTokens.push(refreshToken); + user.save(); + const retUser = { + uid: user._id, + displayName: user.name, + email: user.email, + emailVerified: user.emailVerified, + metadata: { creationTime: user.createdAt }, + }; + res.json({ + accessToken: accessToken, + refreshToken: refreshToken, + user: retUser, + }); + } else { + //if password doesn't match hash + res.status(500).send({ error: "Password invalid" }); + } + }); + }); +}); + +app.post("/api/signUp", (req, res) => { + /* Takes name, email, password */ + //check if name has been taken + User.exists({ name: req.body.name }).then((exists) => { + //should also check if email is used + if (exists) { + //user with that name already exists + res.status(500).send({ error: "Username taken" }); + } + bcrypt.hash(req.body.password, saltRounds, function (err, hash) { + if (err) console.log(err); + const newuser = new User({ + name: req.body.name, + email: req.body.email, + emailVerified: false, + password: hash, + }); + newuser + .save() + .then((user) => { + //send email verification + + //add account created event to analytics + + //return user data and access token + const accessToken = jwt.sign( + { name: req.body.name }, + process.env.ACCESS_TOKEN_SECRET + ); + const refreshToken = jwt.sign( + { name: user.name }, + process.env.REFRESH_TOKEN_SECRET + ); + user.refreshTokens.push(refreshToken); + user.save(); + const retUser = { + uid: user._id, + displayName: user.name, + email: user.email, + emailVerified: user.emailVerified, + metadata: { creationTime: user.createdAt }, + }; + res.json({ + accessToken: accessToken, + refreshToken: refreshToken, + user: retUser, + }); + }) + .catch((e) => { + console.log(e); + res.status(500).send({ error: "Error when adding user" }); + }); + }); + }); +}); + +app.post("/api/refreshToken", (req, res) => { + const authHeader = req.headers["authorization"]; + const token = authHeader && authHeader.split(" ")[1]; + if (token == null) return res.sendStatus(401); + jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, identity) => { + if (err) return res.sendStatus(403); + User.findOne({ name: identity.name }, (err, user) => { + if (!user.refreshTokens.includes(token)) return res.sendStatus(403); + const accessToken = jwt.sign( + { name: identity.name }, + process.env.ACCESS_TOKEN_SECRET + ); + res.json({ accessToken: accessToken }); + }); + }); +}); + +app.post("/api/passwordReset", (req, res) => { + const email = req.body.email; + //send email to the passed email requesting password reset + res.sendStatus(200); +}); + +app.get("/api/fetchSnapshot", authenticateToken, (req, res) => { + /* Takes token and returns snap */ + User.findOne({ name: req.name }, (err, user) => { + if (err) res.status(500).send({ error: err }); + //populate snap object with data from user document + let snap = user; + delete snap.password; + //return user data + res.json({ snap: snap }); + }); +}); + app.post("/api/testCompleted", authenticateToken, (req, res) => { //return createdId //return user data @@ -848,8 +888,6 @@ app.post("/api/analytics/testCompletedInvalid", (req, res) => { res.sendStatus(200); }); -// CLOUD FUNCTIONS - // STATIC FILES app.get("/privacy-policy", (req, res) => { res.sendFile(__dirname + "/dist/privacy-policy.html");