diff --git a/package.json b/package.json index 4863f4d47a..7fe7c38bd8 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "node-slack": "0.0.7", "node-uuid": "^1.4.3", "nodemailer": "~1.3.0", + "object.assign": "^3.0.0", "passport-facebook": "^2.0.0", "passport-google-oauth2": "^0.1.6", "passport-linkedin-oauth2": "^1.2.1", diff --git a/public/js/main_0.0.2.js b/public/js/main_0.0.2.js index 65ec9f170c..1b0e0812a1 100644 --- a/public/js/main_0.0.2.js +++ b/public/js/main_0.0.2.js @@ -331,6 +331,8 @@ $(document).ready(function() { $('#story-submit').on('click', storySubmitButtonHandler); + // $('#reset-button').on('click', resetEditor); + var commentSubmitButtonHandler = function commentSubmitButtonHandler() { $('#comment-button').unbind('click'); var data = $('#comment-box').val(); diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 3c7d85f8c8..16e2ba371a 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -31,9 +31,15 @@ */ var R = require('ramda'), + Rx = require('rx'), + assign = require('object.assign'), debug = require('debug')('freecc:challenges'), utils = require('../utils'), + + // this would be so much cleaner with destructering... saveUser = require('../utils/rx').saveUser, + observableQueryFromModel = require('../utils/rx').observableQueryFromModel, + userMigration = require('../utils/middleware').userMigration, ifNoUserRedirectTo = require('../utils/middleware').ifNoUserRedirectTo; @@ -55,6 +61,16 @@ function unDasherize(name) { return ('' + name).replace(/\-/g, ' '); } +function updateUserProgress(user, challengeId, completedChallenge) { + var index = user.uncompletedChallenges.indexOf(challengeId); + if (index > -1) { + user.progressTimestamps.push(Date.now()); + user.uncompletedChallenges.splice(index, 1); + } + user.completedChallenges.push(completedChallenge); + return user; +} + module.exports = function(app) { var router = app.loopback.Router(); var Challenge = app.models.Challenge; @@ -143,6 +159,7 @@ module.exports = function(app) { return elem; } }); + if (!req.user.currentChallenge) { req.user.currentChallenge = {}; req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0]; @@ -201,11 +218,11 @@ module.exports = function(app) { challengeId: challenge.id, challengeName: challenge.name, dashedName: challenge.dashedName, - challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds). - map(function (key) { + challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds) + .map(function (key) { return challengeMapWithIds[key] .filter(function (elem) { - return String(elem) === challenge.id; + return elem === ('' + challenge.id); }) .map(function () { return key; @@ -261,143 +278,124 @@ module.exports = function(app) { } function completedBonfire(req, res, next) { - var isCompletedWith = req.body.challengeInfo.completedWith || ''; - var isCompletedDate = Math.round(+new Date()); + debug('compltedBonfire'); + var completedWith = req.body.challengeInfo.completedWith || false; var challengeId = req.body.challengeInfo.challengeId; - var isSolution = req.body.challengeInfo.solution; - var challengeName = req.body.challengeInfo.challengeName; - if (isCompletedWith) { - User.find({ - where: { username: isCompletedWith.toLowerCase() }, - limit: 1 - }, function (err, pairedWith) { - if (err) { return next(err); } + var challengeData = { + id: challengeId, + name: req.body.challengeInfo.challengeName, + completedDate: Math.round(+new Date()), + solution: req.body.challengeInfo.solution, + challengeType: 5 + }; - var index = req.user.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedChallenges.splice(index, 1); - } - pairedWith = pairedWith.pop(); + observableQueryFromModel( + User, + 'findOne', + { where: { username: ('' + completedWith).toLowerCase() } } + ) + .doOnNext(function(pairedWith) { + debug('paired with ', pairedWith); if (pairedWith) { - - index = pairedWith.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - pairedWith.progressTimestamps.push(Date.now() || 0); - pairedWith.uncompletedChallenges.splice(index, 1); - - } - - pairedWith.completedChallenges.push({ - id: challengeId, - name: challengeName, - completedWith: req.user.id, - completedDate: isCompletedDate, - solution: isSolution, - challengeType: 5 - }); - - req.user.completedChallenges.push({ - id: challengeId, - name: challengeName, - completedWith: pairedWith.id, - completedDate: isCompletedDate, - solution: isSolution, - challengeType: 5 - }); + updateUserProgress( + pairedWith, + challengeId, + assign({ completedWith: req.user.id }, challengeData) + ); } - // User said they paired, but pair wasn't found - req.user.completedChallenges.push({ - id: challengeId, - name: challengeName, - completedWith: null, - completedDate: isCompletedDate, - solution: isSolution, - challengeType: 5 - }); - - req.user.save(function (err, user) { - if (err) { return next(err); } - - if (pairedWith) { - pairedWith.save(function (err, paired) { - if (err) { - return next(err); - } - if (user && paired) { - return res.send(true); - } - }); - } else if (user) { - res.send(true); + }) + .withLatestFrom( + Rx.Observable.just(req.user), + function(pairedWith, user) { + debug('yo'); + return { + user: user, + pairedWith: pairedWith + }; + } + ) + // side effects should always be done in do's and taps + .doOnNext(function(dats) { + updateUserProgress( + dats.user, + challengeId, + dats.pairedWith ? + // paired programmer found and adding to data + assign({ completedWith: dats.pairedWith.id }, challengeData) : + // user said they paired, but pair wasn't found + challengeData + ); + }) + // not iterate users + .flatMap(function(dats) { + debug('flatmap'); + return Rx.Observable.from([dats.user, dats.pairedWith]); + }) + // save user + .flatMap(function(user) { + // save user will do nothing if user is falsey + debug('saving user', user.username); + return saveUser(user); + }) + .subscribe( + function(user) { + debug('onNext'); + if (user) { + debug('user %s saved', user.username); } - }); - }); - } else { - req.user.completedChallenges.push({ - id: challengeId, - name: challengeName, - completedWith: null, - completedDate: isCompletedDate, - solution: isSolution, - challengeType: 5 - }); - - var index = req.user.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedChallenges.splice(index, 1); - } - - req.user.save(function (err) { - if (err) { return next(err); } - res.send(true); - }); - } + }, + next, + function() { + debug('completed'); + return res.status(200).send(true); + } + ); } function completedChallenge(req, res, next) { - var isCompletedDate = Math.round(+new Date()); + var completedDate = Math.round(+new Date()); var challengeId = req.body.challengeInfo.challengeId; - req.user.completedChallenges.push({ - id: challengeId, - completedDate: isCompletedDate, - name: req.body.challengeInfo.challengeName, - solution: null, - githubLink: null, - verified: true - }); - var index = req.user.uncompletedChallenges.indexOf(challengeId); - - if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedChallenges.splice(index, 1); - } - - req.user.save(function (err, user) { - if (err) { - return next(err); + updateUserProgress( + req.user, + challengeId, + { + id: challengeId, + completedDate: completedDate, + name: req.body.challengeInfo.challengeName, + solution: null, + githubLink: null, + verified: true } - if (user) { - res.sendStatus(200); - } - }); + ); + + saveUser(req.user) + .subscribe( + function() { }, + next, + function() { + res.sendStatus(200); + } + ); } function completedZiplineOrBasejump(req, res, next) { - var isCompletedWith = req.body.challengeInfo.completedWith || false; - var isCompletedDate = Math.round(+new Date()); + var completedWith = req.body.challengeInfo.completedWith || false; + var completedDate = Math.round(+new Date()); var challengeId = req.body.challengeInfo.challengeId; var solutionLink = req.body.challengeInfo.publicURL; - var githubLink = req.body.challengeInfo.challengeType === '4' - ? req.body.challengeInfo.githubURL : true; + + var githubLink = req.body.challengeInfo.challengeType === '4' ? + req.body.challengeInfo.githubURL : + true; + var challengeType = req.body.challengeInfo.challengeType === '4' ? - 4 : 3; + 4 : + 3; + if (!solutionLink || !githubLink) { req.flash('errors', { msg: 'You haven\'t supplied the necessary URLs for us to inspect ' + @@ -406,93 +404,64 @@ module.exports = function(app) { return res.sendStatus(403); } - if (isCompletedWith) { - User.find({ - where: { username: isCompletedWith.toLowerCase() }, - limit: 1 - }, function (err, pairedWithFromMongo) { - if (err) { return next(err); } - var index = req.user.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedChallenges.splice(index, 1); + var challengeData = { + id: challengeId, + name: req.body.challengeInfo.challengeName, + completedDate: completedDate, + solution: solutionLink, + githubLink: githubLink, + challengeType: challengeType, + verified: false + }; + + observableQueryFromModel( + User, + 'findOne', + { where: { username: completedWith.toLowerCase() } } + ) + .doOnNext(function(pairedWith) { + if (pairedWith) { + updateUserProgress( + pairedWith, + challengeId, + assign({ completedWith: req.user.id }, challengeData) + ); } - var pairedWith = pairedWithFromMongo.pop(); - - req.user.completedChallenges.push({ - id: challengeId, - name: req.body.challengeInfo.challengeName, - completedWith: pairedWith.id, - completedDate: isCompletedDate, - solution: solutionLink, - githubLink: githubLink, - challengeType: challengeType, - verified: false - }); - - req.user.save(function (err, user) { - if (err) { return next(err); } - - if (req.user.id.toString() === pairedWith.id.toString()) { - return res.sendStatus(200); + }) + .withLatestFrom(Rx.Observable.just(req.user), function(user, pairedWith) { + return { + user: user, + pairedWith: pairedWith + }; + }) + .doOnNext(function(dats) { + updateUserProgress( + dats.user, + challengeId, + dats.pairedWith ? + assign({ completedWith: dats.pairedWith.id }, challengeData) : + challengeData + ); + }) + .flatMap(function(dats) { + return Rx.Observable.from([dats.user, dats.pairedWith]); + }) + // save users + .flatMap(function(user) { + // save user will do nothing if user is falsey + return saveUser(user); + }) + .subscribe( + function(user) { + if (user) { + debug('user %s saved', user.username); } - index = pairedWith.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - pairedWith.progressTimestamps.push(Date.now() || 0); - pairedWith.uncompletedChallenges.splice(index, 1); - - } - - pairedWith.completedChallenges.push({ - id: challengeId, - name: req.body.challengeInfo.coursewareName, - completedWith: req.user.id, - completedDate: isCompletedDate, - solution: solutionLink, - githubLink: githubLink, - challengeType: challengeType, - verified: false - }); - pairedWith.save(function (err, paired) { - if (err) { - return next(err); - } - if (user && paired) { - return res.sendStatus(200); - } - }); - }); - }); - } else { - - req.user.completedChallenges.push({ - id: challengeId, - name: req.body.challengeInfo.challengeName, - completedWith: null, - completedDate: isCompletedDate, - solution: solutionLink, - githubLink: githubLink, - challengeType: challengeType, - verified: false - }); - - var index = req.user.uncompletedChallenges.indexOf(challengeId); - if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedChallenges.splice(index, 1); - } - - req.user.save(function (err, user) { - if (err) { - return next(err); + }, + next, + function() { + return res.status(200).send(true); } - // NOTE(berks): under certain conditions this will not close - // the response. - if (user) { - return res.sendStatus(200); - } - }); - } + ); } function challengeMap(req, res, next) { diff --git a/server/boot/user.js b/server/boot/user.js index f36fbe56a2..4575a6decf 100644 --- a/server/boot/user.js +++ b/server/boot/user.js @@ -4,7 +4,7 @@ var _ = require('lodash'), crypto = require('crypto'), nodemailer = require('nodemailer'), moment = require('moment'), - //debug = require('debug')('freecc:cntr:userController'), + // debug = require('debug')('freecc:cntr:userController'), secrets = require('../../config/secrets'); diff --git a/server/views/account/show.jade b/server/views/account/show.jade index 0f7add7096..f1edc4d3c7 100644 --- a/server/views/account/show.jade +++ b/server/views/account/show.jade @@ -47,88 +47,32 @@ block content .background-svg.img-center .points-on-top = "[ " + (progressTimestamps.length) + " ]" - - - .row - .col-xs-12 - if (website1Title && website1Link && website1Image) - .row - .col-xs-12.col-md-5 - img.img-center.img-responsive.portfolio-image(src=website1Image, alt="@#{username}'s #{website1Title}") - .col-xs-12.col-md-7 - h3.text-center.wrappable.flat-top= website1Title - a.btn.btn-lg.btn-block.btn-info(href=website1Link, target='_blank') - i.fa.icon-beaker - | Try it out - br - if (website1Title && website1Link && !website1Image) - .col-xs-12.col-md-12 - h3.text-center.wrappable.flat-top= website1Title - a.btn.btn-lg.btn-block.btn-info(href=website1Link, target='_blank') - i.fa.icon-beaker - | Try it out - br - if (website2Title && website2Link && website2Image) - .row - .col-xs-12.col-md-5 - img.img-responsive.portfolio-image.img-center(src=website2Image, alt="@#{username}'s #{website2Title}") - .col-xs-12.col-md-7 - h3.text-center.wrappable.flat-top= website2Title - a.btn.btn-lg.btn-block.btn-info(href=website2Link, target='_blank') - i.fa.icon-beaker - | Try it out - br - if (website2Title && website2Link && !website2Image) - .col-xs-12.col-md-12 - h3.text-center.wrappable.flat-top= website2Title - a.btn.btn-lg.btn-block.btn-info(href=website2Link, target='_blank') - i.fa.icon-beaker - | Try it out - br - if (website3Title && website3Link && website3Image) - .row - .col-xs-12.col-md-5 - img.img-responsive.portfolio-image.img-center(src=website3Image, alt="@#{username}'s #{website1Title}") - .col-xs-12.col-md-7 - h3.text-center.wrappable.flat-top= website3Title - a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank') - i.fa.icon-beaker - | Try it out - if (website3Title && website3Link && !website3Image) - .col-xs-12.col-md-12 - h3.text-center.wrappable.flat-top= website3Title - a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank') - i.fa.icon-beaker - | Try it out - .spacer .hidden-xs.hidden-sm.col-md-12 #cal-heatmap.d3-centered script. $(document).ready(function () { - setTimeout(function () { - var cal = new CalHeatMap(); - var calendar = !{JSON.stringify(calender)}; - cal.init({ - itemSelector: "#cal-heatmap", - domain: "month", - subDomain: "x_day", - domainGutter: 10, - data: calendar, - cellSize: 15, - align: 'center', - cellRadius: 3, - cellPadding: 2, - tooltip: true, - range: 6, - start: new Date().setDate(new Date().getDate() - 150), - legendColors: ["#cccccc", "#215f1e"], - legend: [1, 2, 3], - label: { - position: "top" - } - }); - }, 300); + var cal = new CalHeatMap(); + var calendar = !{JSON.stringify(calender)}; + cal.init({ + itemSelector: "#cal-heatmap", + domain: "month", + subDomain: "x_day", + domainGutter: 10, + data: calendar, + cellSize: 15, + align: 'center', + cellRadius: 3, + cellPadding: 2, + tooltip: true, + range: 6, + start: new Date().setDate(new Date().getDate() - 150), + legendColors: ["#cccccc", "#215f1e"], + legend: [1, 2, 3], + label: { + position: "top" + } + }); }); .row .hidden-xs.col-sm-12.text-center