diff --git a/common/models/user.json b/common/models/user.json index 647a64836d..01a3b3a6bc 100644 --- a/common/models/user.json +++ b/common/models/user.json @@ -133,6 +133,10 @@ "type": "boolean", "default": true }, + "lockdownMode": { + "type": "boolean", + "default": false + }, "currentChallenge": { "type": {} }, diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 3846498915..1102da7f62 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -210,6 +210,7 @@ module.exports = function(app) { function returnIndividualChallenge(req, res, next) { const origChallengeName = req.params.challengeName; + const solutionCode = req.query.solution; const unDashedName = unDasherize(origChallengeName); const challengeName = challengesRegex.test(unDashedName) ? @@ -239,7 +240,12 @@ module.exports = function(app) { } if (dasherize(challenge.name) !== origChallengeName) { - return Observable.just('/challenges/' + dasherize(challenge.name)); + return Observable.just( + '/challenges/' + + dasherize(challenge.name) + + '?solution=' + + encodeURIComponent(solutionCode) + ); } // save user does nothing if user does not exist diff --git a/server/boot/user.js b/server/boot/user.js index 9c6450db9c..09e1034bcc 100644 --- a/server/boot/user.js +++ b/server/boot/user.js @@ -1,12 +1,12 @@ -import _ from 'lodash'; -import async from 'async'; +import dedent from 'dedent'; import moment from 'moment'; import debugFactory from 'debug'; -import { ifNoUser401 } from '../utils/middleware'; +import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware'; const debug = debugFactory('freecc:boot:user'); const daysBetween = 1.5; +const sendNonUserToMap = ifNoUserRedirectTo('/map'); function calcCurrentStreak(cals) { const revCals = cals.concat([Date.now()]).slice().reverse(); @@ -52,7 +52,7 @@ function dayDiff([head, tail]) { module.exports = function(app) { var router = app.loopback.Router(); var User = app.models.User; - var Story = app.models.Story; + // var Story = app.models.Story; router.get('/login', function(req, res) { res.redirect(301, '/signin'); @@ -68,14 +68,21 @@ module.exports = function(app) { router.post('/reset-password', postReset); router.get('/email-signup', getEmailSignup); router.get('/email-signin', getEmailSignin); - router.get('/account/api', getAccountAngular); + router.get( + '/toggle-lockdown-mode', + sendNonUserToMap, + toggleLockdownMode + ); router.post( '/account/delete', ifNoUser401, postDeleteAccount ); - router.get('/account/unlink/:provider', getOauthUnlink); - router.get('/account', getAccount); + router.get( + '/account', + sendNonUserToMap, + getAccount + ); router.get('/vote1', vote1); router.get('/vote2', vote2); // Ensure this is the last route! @@ -116,18 +123,8 @@ module.exports = function(app) { } function getAccount(req, res) { - if (!req.user) { - return res.redirect('/'); - } - res.render('account/account', { - title: 'Manage your Free Code Camp Account' - }); - } - - function getAccountAngular(req, res) { - res.json({ - user: req.user || {} - }); + const { username } = req.user; + return res.redirect('/' + username); } function returnUser(req, res, next) { @@ -145,14 +142,6 @@ module.exports = function(app) { }); return res.redirect('/'); } - if (!user.isGithubCool && !user.isMigrationGrandfathered) { - req.flash('errors', { - msg: ` - user ${ username } has not completed account signup - ` - }); - return res.redirect('/'); - } var cals = user .progressTimestamps @@ -214,6 +203,37 @@ module.exports = function(app) { ); } + function toggleLockdownMode(req, res, next) { + if (req.user.lockdownMode === true) { + req.user.lockdownMode = false; + return req.user.save(function(err) { + if (err) { return next(err); } + + req.flash('success', { + msg: dedent` + Other people can now view all your challenge solutions. + You can change this back at any time in the "Manage My Account" + section at the bottom of this page. + ` + }); + res.redirect('/' + req.user.username); + }); + } + req.user.lockdownMode = true; + return req.user.save(function(err) { + if (err) { return next(err); } + + req.flash('success', { + msg: dedent` + All your challenge solutions are now hidden from other people. + You can change this back at any time in the "Manage My Account" + section at the bottom of this page. + ` + }); + res.redirect('/' + req.user.username); + }); + } + function postDeleteAccount(req, res, next) { User.destroyById(req.user.id, function(err) { if (err) { return next(err); } @@ -223,25 +243,6 @@ module.exports = function(app) { }); } - function getOauthUnlink(req, res, next) { - var provider = req.params.provider; - User.findById(req.user.id, function(err, user) { - if (err) { return next(err); } - - user[provider] = null; - user.tokens = - _.reject(user.tokens, function(token) { - return token.kind === provider; - }); - - user.save(function(err) { - if (err) { return next(err); } - req.flash('info', { msg: provider + ' account has been unlinked.' }); - res.redirect('/account'); - }); - }); - } - function getReset(req, res) { if (!req.accessToken) { req.flash('errors', { msg: 'access token invalid' }); @@ -314,6 +315,7 @@ module.exports = function(app) { }); } + /* function updateUserStoryPictures(userId, picture, username, cb) { Story.find({ 'author.userId': userId }, function(err, stories) { if (err) { return cb(err); } @@ -334,31 +336,30 @@ module.exports = function(app) { }); }); } + */ - function vote1(req, res) { + function vote1(req, res, next) { if (req.user) { req.user.tshirtVote = 1; - req.user.save(function (err) { - if (err) { - return next(err); - } - req.flash('success', {msg: 'Thanks for voting!'}); + req.user.save(function(err) { + if (err) { return next(err); } + + req.flash('success', { msg: 'Thanks for voting!' }); res.redirect('/map'); }); } else { - req.flash('error', {msg: 'You must be signed in to vote.'}); + req.flash('error', { msg: 'You must be signed in to vote.' }); res.redirect('/map'); } } - function vote2(req, res) { + function vote2(req, res, next) { if (req.user) { req.user.tshirtVote = 2; - req.user.save(function (err) { - if (err) { - return next(err); - } - req.flash('success', {msg: 'Thanks for voting!'}); + req.user.save(function(err) { + if (err) { return next(err); } + + req.flash('success', { msg: 'Thanks for voting!' }); res.redirect('/map'); }); } else { diff --git a/server/views/account/account.jade b/server/views/account/account.jade deleted file mode 100644 index d4d00c46ce..0000000000 --- a/server/views/account/account.jade +++ /dev/null @@ -1,75 +0,0 @@ -extends ../layout -block content - script. - var challengeName = 'Account View' - .panel.panel-info - .panel-heading.text-center Manage your account here - .panel-body - .row - .col-xs-12 - if (!user.isGithubCool) - a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/link/github') - i.fa.fa-github - | Link my GitHub to unlock this profile - else - a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/link/github') - i.fa.fa-github - | Update my profile from GitHub - - if (!user.twitter) - .col-xs-12 - a.btn.btn-lg.btn-block.btn-twitter.btn-link-social(href='/link/twitter') - i.fa.fa-twitter - | Add my Twitter to my profile - if (!user.facebook) - .col-xs-12 - a.btn.btn-lg.btn-block.btn-facebook.btn-link-social(href='/link/facebook') - i.fa.fa-facebook - | Add my Facebook to my profile - if (!user.linkedin) - .col-xs-12 - a.btn.btn-lg.btn-block.btn-linkedin.btn-link-social(href='/link/linkedin') - i.fa.fa-linkedin - | Add my LinkedIn to my profile - if (!user.google) - .col-xs-12 - a.btn.btn-lg.btn-block.btn-google-plus.btn-link-social(href='/link/google') - i.fa.fa-google-plus - | Add my Google+ to my profile - .big-spacer - .col-xs-12 - a.btn.btn-lg.btn-block.btn-warning.btn-link-social(href='/logout') - span.ion-android-exit - | Sign me out of Free Code Camp - .col-xs-12 - a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='mailto:team@freecodecamp.com') - span.ion-email - | Email us at team@freecodecamp.com - .col-xs-12 - a.btn.btn-lg.btn-block.btn-danger.btn-link-social.confirm-deletion - span.ion-trash-b - | Delete my Free Code Camp account - script. - $('.confirm-deletion').on("click", function() { - $('#modal-dialog').modal('show'); - }); - #modal-dialog.modal.animated.wobble - .modal-dialog - .modal-content - .modal-header - a.close(href='#', data-dismiss='modal', aria-hidden='true') × - h3 Are you really leaving us? - .modal-body - p Pro Tip: If you tweet feedback to  - a(href="https://twitter.com/intent/tweet?text=Hey%20@freecodecamp") @FreeCodeCamp - | , we'll act quickly on it! - .modal-footer - a.btn.btn-success.btn-block(href='#', data-dismiss='modal', aria-hidden='true') - span.ion-happy - | Nevermind, I'll stick around - br - form(action='/account/delete', method='POST') - input(type='hidden', name='_csrf', value=_csrf) - button.btn.btn-danger.btn-block(type='submit') - span.ion-trash-b - | Yes, delete my account diff --git a/server/views/account/show.jade b/server/views/account/show.jade index c559f84aa3..d633f7f7b7 100644 --- a/server/views/account/show.jade +++ b/server/views/account/show.jade @@ -3,48 +3,66 @@ block content script(src="/bower_components/cal-heatmap/cal-heatmap.min.js") script. var challengeName = 'Profile View'; + if (user && user.username === username) + .panel.panel-info + .panel-heading.text-center Update Your Portfolio + .panel-body + .row + .col-xs-12 + if (!user.isGithubCool) + a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/link/github') + i.fa.fa-github + | Link my GitHub to unlock this profile + else + a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/link/github') + i.fa.fa-github + | Update my profile from GitHub + if (!user.twitter) + a.btn.btn-lg.btn-block.btn-twitter.btn-link-social(href='/link/twitter') + i.fa.fa-twitter + | Add my Twitter to my profile + if (!user.facebook) + a.btn.btn-lg.btn-block.btn-facebook.btn-link-social(href='/link/facebook') + i.fa.fa-facebook + | Add my Facebook to my profile + if (!user.linkedin) + a.btn.btn-lg.btn-block.btn-linkedin.btn-link-social(href='/link/linkedin') + i.fa.fa-linkedin + | Add my LinkedIn to my profile + if (!user.google) + a.btn.btn-lg.btn-block.btn-google-plus.btn-link-social(href='/link/google') + i.fa.fa-google-plus + | Add my Google+ to my profile + .panel.panel-info .panel-heading.text-center h1 #{username}'s portfolio .panel-body - if (user && user.username === username) - .row.text-center - .col-xs-12.col-sm-10.col-sm-offset-1 - a.btn.btn-big.btn-primary.btn-block(href="/account") Manage my account - .button-spacer - .col-xs-12.col-sm-10.col-sm-offset-1 - a.btn.btn-big.btn-success.btn-block(href="/signout") Sign out of Free Code Camp - .spacer .row - .col-xs-12 - .col-xs-12.col-sm-12.col-md-5 - if picture - img.img-center.img-responsive.public-profile-img(src=picture) - else - img.img-center.img-responsive.public-profile-img(src='https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png') - h1.text-center.negative-5.profile-social-icons - if (twitter) - a.fa.fa-twitter-square.text-primary(title="@#{username}'s Twitter Profile", href='https://twitter.com/' + twitter, target='_blank') - if (github) - a.fa.fa-github-square.text-primary(title="@#{username}'s GitHub Profile", href=github, target='_blank') - if (linkedin) - a.fa.fa-linkedin-square.text-primary(title="@#{username}'s LinkedIn Profile", href=linkedin, target='_blank') - if (facebook) - a.fa.fa-facebook-square.text-primary(title="@#{username}'s Facebook Profile", href='https://facebook.com/' + facebook, target='_blank') - if (google) - a.fa.fa-google-plus-square.text-primary(title="@#{username}'s Google Profile", href='https://plus.google.com/' + google, target='_blank') - .visible-md.visible-lg - .col-xs-12.col-sm-12.col-md-4.text-justify - h1.flat-top.wrappable= name - h3.flat-top.bolded.wrappable= location - .visible-xs.visible-sm - .col-xs-12.col-sm-12.col-md-4.text-center - h1.flat-top.wrappable= name - h3.flat-top.bolded.wrappable= location - .col-xs-12.col-sm-12.col-md-3.text-center - .background-svg.img-center - .points-on-top - = "[ " + (progressTimestamps.length) + " ]" + .col-xs-12.col-sm-10.col-sm-offset-1.col-md-8.col-md-offset-2.text-center + if picture + img.img-center.img-responsive.public-profile-img(src=picture) + else + img.img-center.img-responsive.public-profile-img(src='https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png') + h1.text-center.negative-5.profile-social-icons + if (twitter) + a.fa.fa-twitter-square.text-primary(title="@#{username}'s Twitter Profile", href='https://twitter.com/' + twitter, target='_blank') + if (github) + a.fa.fa-github-square.text-primary(title="@#{username}'s GitHub Profile", href=github, target='_blank') + if (linkedin) + a.fa.fa-linkedin-square.text-primary(title="@#{username}'s LinkedIn Profile", href=linkedin, target='_blank') + if (facebook) + a.fa.fa-facebook-square.text-primary(title="@#{username}'s Facebook Profile", href='https://facebook.com/' + facebook, target='_blank') + if (google) + a.fa.fa-google-plus-square.text-primary(title="@#{username}'s Google Profile", href='https://plus.google.com/' + google, target='_blank') + h1.flat-top.wrappable= name + h1.flat-top.wrappable= location + h1.flat-top.text-primary= "[ " + (progressTimestamps.length) + " ]" + if (user && user.username !== username) + a.btn.btn-lg.btn-block.btn-twitter.btn-link-social(href='/link/twitter') + i.fa.fa-plus-square + | Add them to my personal leaderboard + .spacer .hidden-xs.hidden-sm.col-md-12 #cal-heatmap.d3-centered @@ -79,23 +97,22 @@ block content h4.col-sm-6.text-left Current Streak: #{currentStreak} #{currentStreak + currentStreak === 1 ? ' day' : ' days'} - if (challenges.length > 0) - .col-sm-12 - table.table.table-striped - thead - tr - th.col-xs-4 Challenge - th.col-xs-2 Completed - th.col-xs-6 Link - for challenge in challenges - tr - td.col-xs-4 - a(href='/challenges/' + challenge.name, target='_blank')= challenge.name - td.col-xs-2= moment(challenge.completedDate, 'x').format("MMM DD, YYYY") - td.col-xs-6 - a(href=challenge.solution, target='_blank') View my solution - if (user && user.username === username) - br + if (user.username == username || !user.lockdownMode) + if (challenges.length > 0) + .col-sm-12 + table.table.table-striped + thead + tr + th.col-xs-4 Project + th.col-xs-2 Completed + th.col-xs-6 Link + for challenge in challenges + tr + td.col-xs-4 + a(href='/challenges/' + challenge.name, target='_blank')= challenge.name + td.col-xs-2= moment(challenge.completedDate, 'x').format("MMM DD, YYYY") + td.col-xs-6 + a(href=challenge.solution, target='_blank') View my project if (bonfires.length > 0) .col-sm-12 table.table.table-striped @@ -106,9 +123,60 @@ block content th.col-xs-6 Solution for bonfire in bonfires tr - td.col-xs-4 - a(href='/challenges/' + bonfire.name, target='_blank')= bonfire.name + td.col-xs-4= bonfire.name td.col-xs-2= moment(bonfire.completedDate, 'x').format("MMM DD, YYYY") td.col-xs-6 - pre.wrappable= bonfire.solution - br + a(href='/challenges/' + bonfire.name + '?solution=' + encodeURIComponent(bonfire.solution), target='_blank') View my solution + + if (user && user.username === username) + .panel.panel-info + .panel-heading.text-center Manage Your Account + .panel-body + .col-xs-12 + a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='mailto:team@freecodecamp.com') + span.ion-email + | Email us at team@freecodecamp.com + if (!user.lockdownMode) + .col-xs-12 + a.btn.btn-lg.btn-block.btn-info.btn-link-social(href='/toggle-lockdown-mode') + span.ion-locked + | Hide all my solutions from other people + else + .col-xs-12 + a.btn.btn-lg.btn-block.btn-info.btn-link-social(href='/toggle-lockdown-mode') + span.ion-unlocked + | Let other people see all my solutions + .col-xs-12 + a.btn.btn-lg.btn-block.btn-warning.btn-link-social(href='/logout') + span.ion-android-exit + | Sign me out of Free Code Camp + .col-xs-12 + a.btn.btn-lg.btn-block.btn-danger.btn-link-social.confirm-deletion + span.ion-trash-b + | Delete my Free Code Camp account + script. + $('.confirm-deletion').on("click", function () { + $('#modal-dialog').modal('show'); + }); + #modal-dialog.modal.animated.wobble + .modal-dialog + .modal-content + .modal-header + a.close(href='#', data-dismiss='modal', aria-hidden='true') × + h3 You don't really want to delete your account, do you? + .modal-body + p This will really delete all your data, including all your progress, news stories and brownie points. + p We won't be able to recover any of it for you later, even if you change your mind. + p If there's something we could do better, send us an email instead and we'll do our best:   + a(href="mailto:team@freecodecamp.com") team@freecodecamp.com + | . + .modal-footer + a.btn.btn-success.btn-block(href='#', data-dismiss='modal', aria-hidden='true') + span.ion-happy + | Nevermind, I don't want to delete all my progress + .btn-spacer + form(action='/account/delete', method='POST') + input(type='hidden', name='_csrf', value=_csrf) + button.btn.btn-danger.btn-block(type='submit') + span.ion-trash-b + | I am 100% sure I want to delete all my progress diff --git a/server/views/partials/navbar.jade b/server/views/partials/navbar.jade index 546334932d..6a51f98145 100644 --- a/server/views/partials/navbar.jade +++ b/server/views/partials/navbar.jade @@ -22,31 +22,8 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height li a.btn.signup-btn.signup-btn-nav(href='/login') Sign in else - if user.isGithubCool li a(href='/' + user.username) [ #{user.progressTimestamps.length} ] .hidden-xs.hidden-sm a(href='/' + user.username) img.profile-picture.float-right(src='#{user.picture}') - else - li - a(href='/account') [ #{user.progressTimestamps.length} ] - .hidden-xs.hidden-sm - a(href='/account') - img.profile-picture.float-right(src='#{user.picture}') -script. - $(document).ready(function() { - $('.learn-btn').click(function(e) { - var challengeDashedName = null; - e.preventDefault(); - if (typeof dashedName === "string") { - return location.reload(); - } - if (typeof localStorage !== 'undefined') { - challengeDashedName = localStorage.getItem('currentDashedName'); - } - window.location = challengeDashedName ? - '/challenges/' + challengeDashedName : - '/map'; - }); - });