Merge branch 'staging' of github.com:FreeCodeCamp/freecodecamp into staging
This commit is contained in:
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
public/js/lib/codemirror/* linguist-vendored
|
||||
*.jsx linguist-language=JavaScript
|
@ -1,3 +1,4 @@
|
||||
import assign from 'object.assign';
|
||||
import debugFactory from 'debug';
|
||||
|
||||
const debug = debugFactory('freecc:models:userIdent');
|
||||
@ -10,6 +11,39 @@ function getFirstImageFromProfile(profile) {
|
||||
null;
|
||||
}
|
||||
|
||||
// using es6 argument destructing
|
||||
function setProfileFromGithub(
|
||||
user,
|
||||
{
|
||||
profileUrl: githubURL,
|
||||
username
|
||||
},
|
||||
{
|
||||
location,
|
||||
email: githubEmail,
|
||||
id: githubId,
|
||||
'created_at': joinedGithubOn,
|
||||
blog: website,
|
||||
name
|
||||
}
|
||||
) {
|
||||
return assign(
|
||||
user,
|
||||
{ isGithubCool: true, isMigrationGrandfathered: false },
|
||||
{
|
||||
name,
|
||||
username: username.toLowerCase(),
|
||||
location,
|
||||
joinedGithubOn,
|
||||
website,
|
||||
githubId,
|
||||
githubURL,
|
||||
githubEmail,
|
||||
githubProfile: githubURL
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default function(UserIdent) {
|
||||
UserIdent.observe('before save', function(ctx, next) {
|
||||
var userIdent = ctx.currentInstance || ctx.instance;
|
||||
@ -25,7 +59,8 @@ export default function(UserIdent) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const picture = getFirstImageFromProfile(userIdent.profile);
|
||||
const { profile } = userIdent;
|
||||
const picture = getFirstImageFromProfile(profile);
|
||||
|
||||
debug('picture', picture, user.picture);
|
||||
// check if picture was found
|
||||
@ -41,19 +76,10 @@ export default function(UserIdent) {
|
||||
userChanged = true;
|
||||
}
|
||||
|
||||
// if user signed in with github
|
||||
// and user is not github cool
|
||||
// or username is different from github username
|
||||
// then make them github cool
|
||||
// and set their username from their github profile.
|
||||
if (
|
||||
userIdent.provider === 'github-login' &&
|
||||
(!user.isGithubCool ||
|
||||
user.username !== userIdent.provider.username.toLowerCase())
|
||||
) {
|
||||
// if user signed in with github refresh their info
|
||||
if (userIdent.provider === 'github-login') {
|
||||
debug("user isn't github cool or username from github is different");
|
||||
user.isGithubCool = true;
|
||||
user.username = userIdent.profile.username.toLowerCase();
|
||||
setProfileFromGithub(user, profile, profile._json);
|
||||
userChanged = true;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,12 @@
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"unique": true
|
||||
"index": {
|
||||
"mongodb": {
|
||||
"unique": true,
|
||||
"background": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
|
@ -6,7 +6,12 @@
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"unique": true
|
||||
"index": {
|
||||
"mongodb": {
|
||||
"unique": true,
|
||||
"background": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"whatDoesNonprofitDo": {
|
||||
"type": "string"
|
||||
|
@ -6,28 +6,29 @@
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"unique": true
|
||||
"index": {
|
||||
"mongodb": {
|
||||
"unique": true,
|
||||
"background": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"headline": {
|
||||
"type": "string",
|
||||
"unique": false
|
||||
"type": "string"
|
||||
},
|
||||
"timePosted": {
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"link": {
|
||||
"type": "string",
|
||||
"unique": false
|
||||
"type": "string"
|
||||
},
|
||||
"metaDescription": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"unique": false
|
||||
"default": ""
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"unique": false
|
||||
"type": "string"
|
||||
},
|
||||
"originalStoryAuthorEmail": {
|
||||
"type": "string",
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Observable } from 'rx';
|
||||
import uuid from 'node-uuid';
|
||||
import moment from 'moment';
|
||||
import debugFactory from 'debug';
|
||||
|
||||
@ -44,6 +45,16 @@ module.exports = function(User) {
|
||||
// username should be unique
|
||||
User.validatesUniquenessOf('username');
|
||||
|
||||
User.observe('before save', function({ instance: user }, next) {
|
||||
if (user) {
|
||||
user.username = user.username.trim().toLowerCase();
|
||||
user.email = typeof user.email === 'string' ?
|
||||
user.email.trim().toLowerCase() :
|
||||
user.email;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
debug('setting up user hooks');
|
||||
User.afterRemote('confirm', function(ctx) {
|
||||
ctx.req.flash('success', {
|
||||
@ -54,6 +65,11 @@ module.exports = function(User) {
|
||||
ctx.res.redirect('/email-signin');
|
||||
});
|
||||
|
||||
User.beforeRemote('create', function({ req }, notUsed, next) {
|
||||
req.body.username = 'fcc' + uuid.v4().slice(0, 8);
|
||||
next();
|
||||
});
|
||||
|
||||
User.afterRemote('login', function(ctx, user, next) {
|
||||
var res = ctx.res;
|
||||
var req = ctx.req;
|
||||
|
@ -10,11 +10,10 @@
|
||||
"index": {
|
||||
"mongodb": {
|
||||
"unique": true,
|
||||
"background": true,
|
||||
"sparse": true
|
||||
}
|
||||
},
|
||||
"lowercase": true,
|
||||
"trim": true
|
||||
}
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
@ -27,11 +26,31 @@
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"githubId": {
|
||||
"type": "string"
|
||||
},
|
||||
"githubURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"githubEmail": {
|
||||
"type": "string"
|
||||
},
|
||||
"joinedGithubOn": {
|
||||
"type": "date"
|
||||
},
|
||||
"isMigrationGrandfathered": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"lowercase": true,
|
||||
"trim": true,
|
||||
"require": true
|
||||
"require": true,
|
||||
"index": {
|
||||
"mongodb": {
|
||||
"unique": true,
|
||||
"background": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"bio": {
|
||||
"type": "string",
|
||||
|
@ -423,7 +423,7 @@
|
||||
"description": [
|
||||
"",
|
||||
"In JavaScript we can store lists or collections of data in what are called arrays",
|
||||
"Arrays are distinguished by the <code> [ </code> and <code> ] </code> around the data. Each piece of data is separated be a <code> , </code>",
|
||||
"Arrays are distinguished by the <code> [ </code> and <code> ] </code> around the data. Each piece of data is separated by a <code> , </code>",
|
||||
"Now let's create a new array called <code> myArray </code> with a <code> string </code> and a <code> number </code> with a <code> , </code> separating each one",
|
||||
"Refer to the example if you get stuck",
|
||||
""
|
||||
@ -503,7 +503,7 @@
|
||||
"difficulty":"9.98171",
|
||||
"description":[
|
||||
"",
|
||||
"We are able to modify the data store in an array be using indexes",
|
||||
"We are able to modify the data stored in an array by using indexes",
|
||||
"Example:",
|
||||
"<code>",
|
||||
"var ourArray = [1,2,3];",
|
||||
@ -536,10 +536,10 @@
|
||||
"difficulty": "9.9818",
|
||||
"description": [
|
||||
"",
|
||||
"When and array has been defined we still have the ability to make changes to it afterwards",
|
||||
"When an array has been defined we still have the ability to make changes to it afterwards",
|
||||
"One common way in which we can manipulate the data in an array is through <code> .pop() </code>",
|
||||
"<code> .pop() </code> is used to \"pop\" a value from the end of an array. We can retrieve this value by preforming the pop in a variable declaration.",
|
||||
"any type of variable can be \"popped\" from and array",
|
||||
"<code> .pop() </code> is used to \"pop\" a value from the end of an array. We can retrieve this value by performing the pop in a variable declaration.",
|
||||
"Any type of variable can be \"popped\" from an array",
|
||||
"Let's try <code> .pop() </code> now"
|
||||
],
|
||||
"tests": [
|
||||
@ -639,13 +639,13 @@
|
||||
"description":[
|
||||
"",
|
||||
"In JavaScript we can divide up our code into separate and reusable parts called functions",
|
||||
"here's and example of a function",
|
||||
"Here's an example of a function",
|
||||
"<code>",
|
||||
"function functionName (a, b){",
|
||||
" return(a + b);",
|
||||
"}",
|
||||
"</code>",
|
||||
"our function can be called like this",
|
||||
"Our function can be called like this",
|
||||
"<code>functionName();</code>",
|
||||
"Let's try creating and calling a function now called <code>myFunction</code>"
|
||||
],
|
||||
@ -677,7 +677,7 @@
|
||||
"description":[
|
||||
"",
|
||||
"A very important data type in javascript is the <code> Object </code>",
|
||||
"<code> Objects </code> a similar to <code> arrays </code> except that instead of using indexes to access and modify their data, Objects have what are called <code> properties </code>",
|
||||
"<code> Objects </code> are similar to <code> arrays </code> except that instead of using indexes to access and modify their data, Objects have what are called <code> properties </code>",
|
||||
"Here's a sample Object",
|
||||
"<code>",
|
||||
"var cat = {",
|
||||
@ -688,7 +688,7 @@
|
||||
"};",
|
||||
"</code>",
|
||||
"Objects are useful for storing data in a structured way or in a way that represents a real world object like a cat.",
|
||||
"Let's try to make a Object that represents a dog called myDog!"
|
||||
"Let's try to make an Object that represents a dog called myDog!"
|
||||
|
||||
],
|
||||
"tests":[
|
||||
|
@ -1883,7 +1883,7 @@
|
||||
"Above your right-well, inside its \"col-xs-6\" <code>div</code> element, add a <code>h4</code> element with the text \"#right-well\"."
|
||||
],
|
||||
"tests": [
|
||||
"assert($('.col-xs-12').children('h4') && $('.col-xs-12').children('h4').length > 1, 'Add an <code>h4</code> element to each of your <code><div class=\\'col-xs-6\\'></code> elements.');",
|
||||
"assert($('.col-xs-6').children('h4') && $('.col-xs-6').children('h4').length > 1, 'Add an <code>h4</code> element to each of your <code><div class=\\'col-xs-6\\'></code> elements.');",
|
||||
"assert(new RegExp('#left-well','gi').test($('h4').text()), 'One <code>h4</code> element should have the text \"#left-well\".');",
|
||||
"assert(new RegExp('#right-well','gi').test($('h4').text()), 'One <code>h4</code> element should have the text \"#right-well\".');"
|
||||
],
|
||||
@ -2036,9 +2036,9 @@
|
||||
"Add a comment at the top of your HTML that says <code>You shouldn't need to modify code below this line</code>."
|
||||
],
|
||||
"tests": [
|
||||
"assert(editor.match(/<!--/g).length > 1, 'Start a comment with <code><!--</code>.')",
|
||||
"assert(editor.match(/this line/g).length > 1, 'Your comment should have the text <code>You shouldn't need to modify code below this line</code>')",
|
||||
"assert(editor.match(/-->/g).length > 1, 'Be sure to close your comment with <code>--></code>.')"
|
||||
"assert(editor.match(/<!--/g) && editor.match(/<!--/g).length > 0, 'Start a comment with <code><!--</code>.')",
|
||||
"assert(editor.match(/this line/g) && editor.match(/this line/g).length > 0, \"Your comment should have the text <code>You shouldn't need to modify code below this line</code>\")",
|
||||
"assert(editor.match(/-->/g) && editor.match(/-->/g).length > 0, 'Be sure to close your comment with <code>--></code>.')"
|
||||
],
|
||||
"challengeSeed": [
|
||||
"<div class='container-fluid'>",
|
||||
|
@ -103,7 +103,7 @@
|
||||
"difficulty": "2.02",
|
||||
"description": [
|
||||
"Convert the given number into a roman numeral.",
|
||||
"All <a href=\"http://www.mathsisfun.com/roman-numerals.html\">roman numerals</a> answers should be provided in upper-case.",
|
||||
"All <a href=\"http://www.mathsisfun.com/roman-numerals.html\" target="_blank">roman numerals</a> answers should be provided in upper-case.",
|
||||
"Remember to use <a href='//github.com/FreeCodeCamp/freecodecamp/wiki/How-to-get-help-when-you-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try to pair program. Write your own code."
|
||||
],
|
||||
"challengeSeed": [
|
||||
@ -356,7 +356,7 @@
|
||||
" return arr1;",
|
||||
"}",
|
||||
"",
|
||||
"unite([1, 2, 3], [5, 2, 1, 4], [2, 1]);"
|
||||
"unite([1, 3, 2], [5, 2, 1, 4], [2, 1]);"
|
||||
],
|
||||
"tests": [
|
||||
"assert.deepEqual(unite([1, 3, 2], [5, 2, 1, 4], [2, 1]), [1, 3, 2, 5, 4], 'should return the union of the given arrays');",
|
||||
@ -675,7 +675,8 @@
|
||||
"tests": [
|
||||
"assert.deepEqual(steamroller([[['a']], [['b']]]), ['a', 'b'], 'should flatten nested arrays');",
|
||||
"assert.deepEqual(steamroller([1, [2], [3, [[4]]]]), [1, 2, 3, 4], 'should flatten nested arrays');",
|
||||
"assert.deepEqual(steamroller([1, [], [3, [[4]]]]), [1, 3, 4], 'should work with empty arrays');"
|
||||
"assert.deepEqual(steamroller([1, [], [3, [[4]]]]), [1, 3, 4], 'should work with empty arrays');",
|
||||
"assert.deepEqual(steamroller([1, {}, [3, [[4]]]]), [1, {}, 3, 4], 'should work with actual objects');"
|
||||
],
|
||||
"MDNlinks": [
|
||||
"Array.isArray()"
|
||||
|
@ -257,9 +257,9 @@
|
||||
"dashedName": "waypoint-target-the-same-element-with-multiple-jQuery-Selectors",
|
||||
"difficulty": 3.06,
|
||||
"description": [
|
||||
"Now you know three ways of targeting elements: by type (<code>$('button')</code>), by class (<code>($('.btn')</code>), and by id (<code>($'#target1')</code>).",
|
||||
"Now you know three ways of targeting elements: by type <code>$('button')</code>, by class <code>$('.btn')</code>), and by id <code>$('#target1')</code>).",
|
||||
"Use each of these jQuery selectors to target your <code>button</code> element with the class \"btn\" and the id \"target1\".",
|
||||
"Use the <code>addClass()</code> jQuery function to give the element one new class for each selector: \"animated\", \"shake\", and \"button-primary\"."
|
||||
"Use the <code>addClass()</code> jQuery function to give the element one new class for each selector: \"animated\", \"shake\", and \"btn-primary\"."
|
||||
],
|
||||
"tests": [
|
||||
"assert(editor.match(/\\$\\(\\'button\\'\\)/g), 'Use the <code>$\\(\\'button\\'\\)</code> selector.')",
|
||||
|
@ -92,6 +92,8 @@ var users = dbObservable
|
||||
user.username = 'fcc' + uuid.v4().slice(0, 8);
|
||||
if (user.github) {
|
||||
user.isGithubCool = true;
|
||||
} else {
|
||||
user.isMigrationGrandfathered = true;
|
||||
}
|
||||
return user;
|
||||
})
|
||||
|
@ -1,35 +1,3 @@
|
||||
/**
|
||||
* Created by nathanleniz on 5/15/15.
|
||||
* Copyright (c) 2015, Free Code Camp
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
|
||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
var R = require('ramda'),
|
||||
Rx = require('rx'),
|
||||
assign = require('object.assign'),
|
||||
|
@ -122,113 +122,123 @@ module.exports = function(app) {
|
||||
*/
|
||||
|
||||
function returnUser(req, res, next) {
|
||||
const username = req.params.username.toLowerCase();
|
||||
const { path } = req;
|
||||
User.findOne(
|
||||
{ where: { 'username': req.params.username.toLowerCase() } },
|
||||
{ where: { username } },
|
||||
function(err, user) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
if (user) {
|
||||
user.progressTimestamps =
|
||||
user.progressTimestamps.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
var timeObject = Object.create(null);
|
||||
R.forEach(function(time) {
|
||||
timeObject[moment(time).format('YYYY-MM-DD')] = time;
|
||||
}, user.progressTimestamps);
|
||||
|
||||
var tmpLongest = 1;
|
||||
var timeKeys = R.keys(timeObject);
|
||||
|
||||
user.longestStreak = 0;
|
||||
for (var i = 1; i <= timeKeys.length; i++) {
|
||||
if (moment(timeKeys[i - 1]).add(1, 'd').toString()
|
||||
=== moment(timeKeys[i]).toString()) {
|
||||
tmpLongest++;
|
||||
if (tmpLongest > user.longestStreak) {
|
||||
user.longestStreak = tmpLongest;
|
||||
}
|
||||
} else {
|
||||
tmpLongest = 1;
|
||||
}
|
||||
}
|
||||
|
||||
timeKeys = timeKeys.reverse();
|
||||
tmpLongest = 1;
|
||||
|
||||
user.currentStreak = 1;
|
||||
var today = moment(Date.now()).format('YYYY-MM-DD');
|
||||
|
||||
const yesterday = moment(today).subtract(1, 'd').toString();
|
||||
const yesteryesterday = moment(today).subtract(2, 'd').toString();
|
||||
|
||||
if (
|
||||
moment(today).toString() === moment(timeKeys[0]).toString() ||
|
||||
yesterday === moment(timeKeys[0]).toString() ||
|
||||
yesteryesterday === moment(timeKeys[0]).toString()
|
||||
) {
|
||||
for (var _i = 1; _i <= timeKeys.length; _i++) {
|
||||
|
||||
if (
|
||||
moment(timeKeys[_i - 1]).subtract(1, 'd').toString() ===
|
||||
moment(timeKeys[_i]).toString()
|
||||
) {
|
||||
|
||||
tmpLongest++;
|
||||
|
||||
if (tmpLongest > user.currentStreak) {
|
||||
user.currentStreak = tmpLongest;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
user.currentStreak = 1;
|
||||
}
|
||||
|
||||
var data = {};
|
||||
var progressTimestamps = user.progressTimestamps;
|
||||
progressTimestamps.forEach(function(timeStamp) {
|
||||
data[(timeStamp / 1000)] = 1;
|
||||
});
|
||||
|
||||
user.currentStreak = user.currentStreak || 1;
|
||||
user.longestStreak = user.longestStreak || 1;
|
||||
var challenges = user.completedChallenges.filter(function( obj ) {
|
||||
return obj.challengeType === 3 || obj.challengeType === 4;
|
||||
});
|
||||
|
||||
res.render('account/show', {
|
||||
title: 'Camper ' + user.username + '\'s portfolio',
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
location: user.location,
|
||||
githubProfile: user.githubProfile,
|
||||
linkedinProfile: user.linkedinProfile,
|
||||
codepenProfile: user.codepenProfile,
|
||||
facebookProfile: user.facebookProfile,
|
||||
twitterHandle: user.twitterHandle,
|
||||
bio: user.bio,
|
||||
picture: user.picture,
|
||||
progressTimestamps: user.progressTimestamps,
|
||||
challenges: challenges,
|
||||
calender: data,
|
||||
moment: moment,
|
||||
longestStreak: user.longestStreak +
|
||||
(user.longestStreak === 1 ? ' day' : ' days'),
|
||||
currentStreak: user.currentStreak +
|
||||
(user.currentStreak === 1 ? ' day' : ' days')
|
||||
});
|
||||
} else {
|
||||
if (!user) {
|
||||
req.flash('errors', {
|
||||
msg: "404: We couldn't find a page with that url. " +
|
||||
'Please double check the link.'
|
||||
msg: `404: We couldn't find path ${ path }`
|
||||
});
|
||||
return res.redirect('/');
|
||||
}
|
||||
if (!user.isGithubCool && !user.isMigrationGrandfathered) {
|
||||
req.flash('errors', {
|
||||
msg: `
|
||||
user ${ username } has not completed account signup
|
||||
`
|
||||
});
|
||||
return res.redirect('/');
|
||||
}
|
||||
user.progressTimestamps =
|
||||
user.progressTimestamps.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
var timeObject = Object.create(null);
|
||||
R.forEach(function(time) {
|
||||
timeObject[moment(time).format('YYYY-MM-DD')] = time;
|
||||
}, user.progressTimestamps);
|
||||
|
||||
var tmpLongest = 1;
|
||||
var timeKeys = R.keys(timeObject);
|
||||
|
||||
user.longestStreak = 0;
|
||||
for (var i = 1; i <= timeKeys.length; i++) {
|
||||
if (moment(timeKeys[i - 1]).add(1, 'd').toString()
|
||||
=== moment(timeKeys[i]).toString()) {
|
||||
tmpLongest++;
|
||||
if (tmpLongest > user.longestStreak) {
|
||||
user.longestStreak = tmpLongest;
|
||||
}
|
||||
} else {
|
||||
tmpLongest = 1;
|
||||
}
|
||||
}
|
||||
|
||||
timeKeys = timeKeys.reverse();
|
||||
tmpLongest = 1;
|
||||
|
||||
user.currentStreak = 1;
|
||||
var today = moment(Date.now()).format('YYYY-MM-DD');
|
||||
|
||||
const yesterday = moment(today).subtract(1, 'd').toString();
|
||||
const yesteryesterday = moment(today).subtract(2, 'd').toString();
|
||||
|
||||
if (
|
||||
moment(today).toString() === moment(timeKeys[0]).toString() ||
|
||||
yesterday === moment(timeKeys[0]).toString() ||
|
||||
yesteryesterday === moment(timeKeys[0]).toString()
|
||||
) {
|
||||
for (var _i = 1; _i <= timeKeys.length; _i++) {
|
||||
|
||||
if (
|
||||
moment(timeKeys[_i - 1]).subtract(1, 'd').toString() ===
|
||||
moment(timeKeys[_i]).toString()
|
||||
) {
|
||||
|
||||
tmpLongest++;
|
||||
|
||||
if (tmpLongest > user.currentStreak) {
|
||||
user.currentStreak = tmpLongest;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
user.currentStreak = 1;
|
||||
}
|
||||
|
||||
var data = {};
|
||||
var progressTimestamps = user.progressTimestamps;
|
||||
progressTimestamps.forEach(function(timeStamp) {
|
||||
data[(timeStamp / 1000)] = 1;
|
||||
});
|
||||
var challenges = user.completedChallenges.filter(function( obj ) {
|
||||
return obj.challengeType === 3 || obj.challengeType === 4;
|
||||
});
|
||||
|
||||
user.currentStreak = user.currentStreak || 1;
|
||||
user.longestStreak = user.longestStreak || 1;
|
||||
|
||||
res.render('account/show', {
|
||||
title: 'Camper ' + user.username + '\'s portfolio',
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
isMigrationGrandfathered: user.isMigrationGrandfathered,
|
||||
isGithubCool: user.isGithubCool,
|
||||
location: user.location,
|
||||
githubProfile: user.githubProfile,
|
||||
linkedinProfile: user.linkedinProfile,
|
||||
codepenProfile: user.codepenProfile,
|
||||
facebookProfile: user.facebookProfile,
|
||||
twitterHandle: user.twitterHandle,
|
||||
bio: user.bio,
|
||||
picture: user.picture,
|
||||
progressTimestamps: user.progressTimestamps,
|
||||
calender: data,
|
||||
challenges: challenges,
|
||||
moment: moment,
|
||||
longestStreak: user.longestStreak +
|
||||
(user.longestStreak === 1 ? ' day' : ' days'),
|
||||
currentStreak: user.currentStreak +
|
||||
(user.currentStreak === 1 ? ' day' : ' days')
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -2,12 +2,13 @@ require('dotenv').load();
|
||||
var pmx = require('pmx');
|
||||
pmx.init();
|
||||
|
||||
var assign = require('lodash').assign,
|
||||
loopback = require('loopback'),
|
||||
boot = require('loopback-boot'),
|
||||
expressState = require('express-state'),
|
||||
path = require('path'),
|
||||
passportProviders = require('./passport-providers');
|
||||
var uuid = require('node-uuid'),
|
||||
assign = require('lodash').assign,
|
||||
loopback = require('loopback'),
|
||||
boot = require('loopback-boot'),
|
||||
expressState = require('express-state'),
|
||||
path = require('path'),
|
||||
passportProviders = require('./passport-providers');
|
||||
|
||||
var generateKey =
|
||||
require('loopback-component-passport/lib/models/utils').generateKey;
|
||||
@ -43,6 +44,39 @@ passportConfigurator.setupModels({
|
||||
userCredentialModel: app.models.userCredential
|
||||
});
|
||||
|
||||
// using es6 argument destructing
|
||||
function setProfileFromGithub(
|
||||
user,
|
||||
{
|
||||
profileUrl: githubURL,
|
||||
username
|
||||
},
|
||||
{
|
||||
location,
|
||||
email: githubEmail,
|
||||
id: githubId,
|
||||
'created_at': joinedGithubOn,
|
||||
blog: website,
|
||||
name
|
||||
}
|
||||
) {
|
||||
return assign(
|
||||
user,
|
||||
{ isGithubCool: true, isMigrationGrandfathered: false },
|
||||
{
|
||||
name,
|
||||
username: username.toLowerCase(),
|
||||
location,
|
||||
joinedGithubOn,
|
||||
website,
|
||||
githubId,
|
||||
githubURL,
|
||||
githubEmail,
|
||||
githubProfile: githubURL
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var passportOptions = {
|
||||
emailOptional: true,
|
||||
profileToUser: function(provider, profile) {
|
||||
@ -53,9 +87,9 @@ var passportOptions = {
|
||||
emails[0].value :
|
||||
null;
|
||||
|
||||
var username = (profile.username || profile.id);
|
||||
username = typeof username === 'string' ? username.toLowerCase() :
|
||||
username;
|
||||
// create random username
|
||||
// username will be assigned when camper signups for Github
|
||||
var username = 'fcc' + uuid.v4().slice(0, 8);
|
||||
var password = generateKey('password');
|
||||
var userObj = {
|
||||
username: username,
|
||||
@ -65,8 +99,9 @@ var passportOptions = {
|
||||
if (email) {
|
||||
userObj.email = email;
|
||||
}
|
||||
|
||||
if (provider === 'github-login') {
|
||||
userObj.isGithubCool = true;
|
||||
setProfileFromGithub(userObj, profile, profile._json);
|
||||
}
|
||||
return userObj;
|
||||
}
|
||||
|
@ -5,67 +5,74 @@ block content
|
||||
.panel.panel-info(ng-controller="profileValidationController")
|
||||
.panel-heading.text-center Update your portfolio here:
|
||||
.panel-body
|
||||
if (!user.github)
|
||||
if (!user.isGithubCool)
|
||||
.row
|
||||
.col-xs-12
|
||||
a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/auth/github')
|
||||
i.fa.fa-github
|
||||
| Link GitHub with my account
|
||||
.col-xs-12
|
||||
form.form-horizontal(action='/account/profile', method='POST', novalidate='novalidate', name='profileForm' ng-show="asyncComplete")
|
||||
input(type='hidden', name='_csrf', value=_csrf)
|
||||
.form-group
|
||||
label.col-sm-3.col-sm-offset-1.control-label(for='bio') Bio (140 characters)
|
||||
.col-sm-4
|
||||
input.form-control(type='text', name='bio', autocomplete="off", ng-model='user.bio', ng-maxlength='140', id='bio')
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show='profileForm.bio.$error.maxlength && !profileForm.bio.$pristine')
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Your bio must be fewer than 140 characters.
|
||||
.form-group
|
||||
label.col-sm-3.col-sm-offset-1.control-label(for='email') Twitter
|
||||
.col-sm-4
|
||||
.input-group.twitter-input
|
||||
span.input-group-addon @
|
||||
input.form-control(type='text', name='twitterHandle', autocomplete="off", id='twitterHandle', ng-model='user.twitterHandle', ng-maxlength='15', ng-pattern="/^[A-z0-9_]+$/")
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.twitterHandle.$error.pattern")
|
||||
alert(type='danger')
|
||||
.text-left.btn-info.btn.btn-block.btn-link-social(href='http://www.freecodecamp.com/challenges/waypoint-join-our-chat-room')
|
||||
span Link your account to GitHub to update your portfolio page. Click here if you don’t have a GitHub account yet.
|
||||
.row
|
||||
.col-xs-12
|
||||
a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/auth/github')
|
||||
i.fa.fa-github
|
||||
| Link GitHub with my account
|
||||
|
||||
.row
|
||||
.col-xs-12
|
||||
form.form-horizontal(action='/account/profile', method='POST', novalidate='novalidate', name='profileForm' ng-show="asyncComplete")
|
||||
input(type='hidden', name='_csrf', value=_csrf)
|
||||
.form-group
|
||||
label.col-sm-3.col-sm-offset-1.control-label(for='bio') Bio (140 characters)
|
||||
.col-sm-4
|
||||
input.form-control(type='text', name='bio', autocomplete="off", ng-model='user.bio', ng-maxlength='140', id='bio')
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show='profileForm.bio.$error.maxlength && !profileForm.bio.$pristine')
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Your Twitter handle should only contain letters, numbers and underscores (az10_).
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show='profileForm.twitterHandle.$error.maxlength && !profileForm.twitterHandle.$pristine')
|
||||
alert(type='danger')
|
||||
| Your bio must be fewer than 140 characters.
|
||||
.form-group
|
||||
label.col-sm-3.col-sm-offset-1.control-label(for='email') Twitter
|
||||
.col-sm-4
|
||||
.input-group.twitter-input
|
||||
span.input-group-addon @
|
||||
input.form-control(type='text', name='twitterHandle', autocomplete="off", id='twitterHandle', ng-model='user.twitterHandle', ng-maxlength='15', ng-pattern="/^[A-z0-9_]+$/")
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.twitterHandle.$error.pattern")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Your Twitter handle should only contain letters, numbers and underscores (az10_).
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show='profileForm.twitterHandle.$error.maxlength && !profileForm.twitterHandle.$pristine')
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Your name must be fewer than 15 characters.
|
||||
|
||||
.form-group
|
||||
label.col-sm-3.col-sm-offset-1.control-label(for='email') CodePen
|
||||
.col-sm-4
|
||||
input.form-control(type='url', name='codepenProfile', id='codepenProfile', autocomplete="off", ng-model='user.codepenProfile', placeholder='http://')
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.codepenProfile.$error.url && !profileForm.codepenProfile.$pristine")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Your name must be fewer than 15 characters.
|
||||
| Please enter a valid URL format (http://www.example.com).
|
||||
|
||||
.form-group
|
||||
label.col-sm-3.col-sm-offset-1.control-label(for='email') CodePen
|
||||
.col-sm-4
|
||||
input.form-control(type='url', name='codepenProfile', id='codepenProfile', autocomplete="off", ng-model='user.codepenProfile', placeholder='http://')
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.codepenProfile.$error.url && !profileForm.codepenProfile.$pristine")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Please enter a valid URL format (http://www.example.com).
|
||||
.form-group
|
||||
label.col-sm-3.col-sm-offset-1.control-label(for='email') LinkedIn
|
||||
.col-sm-4
|
||||
input.form-control(type='url', name='linkedinProfile', id='linkedinProfile', autocomplete="off", ng-model='user.linkedinProfile', placeholder='http://')
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.linkedinProfile.$error.url && !profileForm.linkedinProfile.$pristine")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Please enter a valid URL format (http://www.example.com).
|
||||
|
||||
.form-group
|
||||
label.col-sm-3.col-sm-offset-1.control-label(for='email') LinkedIn
|
||||
.col-sm-4
|
||||
input.form-control(type='url', name='linkedinProfile', id='linkedinProfile', autocomplete="off", ng-model='user.linkedinProfile', placeholder='http://')
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.linkedinProfile.$error.url && !profileForm.linkedinProfile.$pristine")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Please enter a valid URL format (http://www.example.com).
|
||||
.form-group
|
||||
label.col-sm-3.col-sm-offset-1.control-label(for='email') Facebook
|
||||
.col-sm-4
|
||||
input.form-control(type='url', name='facebookProfile', id='facebookProfile', autocomplete="off", ng-model='user.facebookProfile', placeholder='http://')
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.facebookProfile.$error.url && !profileForm.facebookProfile.$pristine")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Please enter a valid URL format (http://www.example.com).
|
||||
|
||||
.form-group
|
||||
label.col-sm-3.col-sm-offset-1.control-label(for='email') Facebook
|
||||
.col-sm-4
|
||||
input.form-control(type='url', name='facebookProfile', id='facebookProfile', autocomplete="off", ng-model='user.facebookProfile', placeholder='http://')
|
||||
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.facebookProfile.$error.url && !profileForm.facebookProfile.$pristine")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Please enter a valid URL format (http://www.example.com).
|
||||
|
||||
button.btn.btn-lg.btn-block.btn-primary.btn-link-social(type='submit', ng-disabled='profileForm.$invalid')
|
||||
span.ion-edit
|
||||
| Update my info
|
||||
button.btn.btn-lg.btn-block.btn-primary.btn-link-social(type='submit', ng-disabled='!user.isGithubCool || profileForm.$invalid')
|
||||
span.ion-edit
|
||||
| Update my info
|
||||
|
||||
.panel.panel-info
|
||||
.panel-heading.text-center Manage your account here:
|
||||
|
@ -19,25 +19,6 @@ block content
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| That email address is already in use.
|
||||
.form-group
|
||||
.col-sm-6.col-sm-offset-3
|
||||
input.form-control(type='text', name='username', ng-keypress='', autocomplete="off", id='username', placeholder='username', ng-model='username', unique-username='', required, ng-minlength=5, ng-maxlength=20, ng-pattern="/^[A-z0-9_]+$/")
|
||||
.col-sm-6.col-sm-offset-3(ng-cloak, ng-show="profileForm.username.$error.pattern && !signupForm.username.$pristine")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Your username should only contain letters, numbers and underscores (az10_).
|
||||
.col-sm-6.col-sm-offset-3(ng-cloak, ng-show="signupForm.username.$error.unique && !signupForm.username.$pristine")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| This username is taken.
|
||||
.col-sm-6.col-sm-offset-3(ng-cloak, ng-show="signupForm.username.$error.minlength && !signupForm.username.$pristine")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Your username must be at least 5 characters long.
|
||||
.col-sm-6.col-sm-offset-3(ng-cloak, ng-show="signupForm.username.$error.maxlength && !signupForm.username.$pristine")
|
||||
alert(type='danger')
|
||||
span.ion-close-circled
|
||||
| Your usernames must be 20 characters or fewer.
|
||||
.form-group
|
||||
.col-sm-6.col-sm-offset-3
|
||||
input.form-control(type='password', ng-model='password', name='password', id='password', placeholder='password', required, ng-minlength=8)
|
||||
|
@ -21,15 +21,7 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height
|
||||
a.btn.signup-btn.signup-btn-nav(href='/login') Sign in
|
||||
else
|
||||
li
|
||||
if (user.username)
|
||||
a(href='/' + user.username) [ #{user.progressTimestamps.length} ]
|
||||
|
||||
else
|
||||
a(href='/account') [ #{user.progressTimestamps.length} ]
|
||||
a(href='/account') [ #{user.progressTimestamps.length} ]
|
||||
.hidden-xs.hidden-sm
|
||||
if (user.username)
|
||||
a(href='/' + user.username)
|
||||
img.profile-picture.float-right(src='#{user.picture}')
|
||||
else
|
||||
a(href='/account')
|
||||
img.profile-picture.float-right(src='#{user.picture}')
|
||||
a(href='/account')
|
||||
img.profile-picture.float-right(src='#{user.picture}')
|
||||
|
Reference in New Issue
Block a user