Merge branch 'staging' of github.com:FreeCodeCamp/freecodecamp into staging

This commit is contained in:
Quincy Larson
2015-06-04 12:24:30 -07:00
84 changed files with 4154 additions and 2722 deletions

View File

@ -95,7 +95,7 @@ DEBUG=true
mongod mongod
# Seed your database with the challenges # Seed your database with the challenges
node seed_data/seed.js node seed/
# start the application # start the application
gulp gulp

7
common/config.global.js Normal file
View File

@ -0,0 +1,7 @@
// The path where to mount the REST API app
exports.restApiRoot = '/api';
//
// The URL where the browser client can access the REST API is available
// Replace with a full url (including hostname) if your client is being
// served from a different server than your REST API.
exports.restApiUrl = exports.restApiRoot;

View File

@ -0,0 +1,16 @@
{
"name": "userCredential",
"plural": "userCredentials",
"base": "UserCredential",
"properties": {},
"validations": [],
"relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": "userId"
}
},
"acls": [],
"methods": []
}

View File

@ -0,0 +1,27 @@
//var debug = require('debug')('freecc:models:userIdent');
//
//module.exports = function(UserIdent) {
//
// UserIdent.observe('before save', function(ctx, next) {
//
// var userIdent = ctx.instance;
// userIdent.user(function(err, user) {
// if (err) { return next(err); }
// debug('got user', user.username);
//
// // check if user has picture
// // set user.picture from twitter
// if (!user.picture) {
// debug('use has no pic');
// user.picture = userIdent.profile.photos[0].value;
// user.save(function(err) {
// if (err) { return next(err); }
// next();
// });
// } else {
// debug('exiting after user ident');
// next();
// }
// });
// });
//};

View File

@ -0,0 +1,16 @@
{
"name": "userIdentity",
"plural": "userIdentities",
"base": "UserIdentity",
"properties": {},
"validations": [],
"relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": "userId"
}
},
"acls": [],
"methods": []
}

19
common/models/User.js Normal file
View File

@ -0,0 +1,19 @@
var debug = require('debug')('freecc:models:user');
module.exports = function(User) {
debug('setting up user hooks');
/*
* NOTE(berks): not sure if this is still needed
User.observe('before save', function setUsername(ctx, next) {
// set username from twitter
if (ctx.instance.username && ctx.instance.username.match(/twitter/g)) {
ctx.instance.username =
ctx.instance.username.match(/twitter/g) ?
ctx.instance.username.split('.').pop().toLowerCase() :
ctx.instance.username;
debug('username set', ctx.instance.username);
}
next();
});
*/
};

View File

@ -18,7 +18,7 @@
"projectDescription": "string", "projectDescription": "string",
"logoUrl": "string", "logoUrl": "string",
"imageUrl": "string", "imageUrl": "string",
"estimatedHours": 0, "estimatedHours": "number",
"interestedCampers": [], "interestedCampers": [],
"confirmedCampers": [], "confirmedCampers": [],
"currentStatus": "string" "currentStatus": "string"

View File

@ -1,5 +1,5 @@
{ {
"name": "bonfire", "name": "story",
"base": "PersistedModel", "base": "PersistedModel",
"trackChanges": false, "trackChanges": false,
"idInjection": true, "idInjection": true,

View File

@ -1,6 +1,6 @@
{ {
"name": "bonfire", "name": "user",
"base": "PersistedModel", "base": "User",
"trackChanges": false, "trackChanges": false,
"idInjection": true, "idInjection": true,
"properties": { "properties": {

19
common/screens/App.jsx Normal file
View File

@ -0,0 +1,19 @@
var React = require('react'),
RouteHandler = require('react-router').RouteHandler,
// ## components
Nav = require('./nav'),
Footer = require('./footer');
var App = React.createClass({
render: function() {
return (
<div>
<Nav />
<RouteHandler />
<Footer />
</div>
);
}
});
module.exports = App;

34
common/screens/Router.jsx Normal file
View File

@ -0,0 +1,34 @@
var React = require('react'),
// react router
Router = require('react-router'),
Route = Router.Route,
// NotFound = Router.NotFoundRoute,
DefaultRoute = Router.DefaultRoute,
// # Components
App = require('./App.jsx'),
Bonfires = require('./bonfires');
var routes = (
<Route
name='app'
path='/'
handler={ App }>
<Route
name='bonfires'
path='/bonfires/?:bonfireName?'
handler={ Bonfires } />
<DefaultRoute
handler={ Bonfires } />
</Route>
);
module.exports = function(Location) {
return Router.create({
routes: routes,
location: Location
});
};

View File

@ -0,0 +1,63 @@
var Action = require('thundercats').Action,
executeBonfire = require('./executeBonfire'),
getModel = require('../../utils/getModel'),
debug = require('debug')('freecc:common:bonfires');
var BonfireActions = Action.createActions([
'setUserCode',
'testUserCode',
'setResults',
'setDisplay',
'setBonfire',
'getBonfire',
'handleBonfireError',
'openCompletionModal'
]);
BonfireActions
.getBonfire
.subscribe(function(params) {
var Bonfire = getModel('bonfire');
var bonfireName = params.bonfireName ?
params.bonfireName.replace(/\-/g, ' ') :
'meet bonfire';
debug('getting bonfire for: ', bonfireName);
var regQuery = { name: { like: bonfireName, options: 'i' } };
Bonfire.find(
{ where: regQuery },
function(err, bonfire) {
if (err) {
return debug('bonfire get err', err);
}
if (!bonfire || bonfire.length < 1) {
return debug('404 no bonfire found for ', bonfireName);
}
bonfire = bonfire.pop();
if (bonfire) {
debug(
'found bonfire %s for route %s',
bonfire.name,
bonfireName
);
}
BonfireActions.setBonfire(bonfire);
}
);
});
BonfireActions
.testUserCode
.subscribe(function({ userCode, tests }) {
debug('test bonfire');
executeBonfire(userCode, tests, function(err, { output, results }) {
if (err) {
debug('error running tests', err);
return BonfireActions.setDisplay(err);
}
BonfireActions.setDisplay(output);
BonfireActions.setResults(results);
});
});
module.exports = BonfireActions;

View File

@ -0,0 +1,99 @@
var React = require('react'),
// ## mixins
{ ObservableStateMixin } = require('thundercats'),
// ## components
SidePanel = require('./SidePanel.jsx'),
Results = require('./Results.jsx'),
Display = require('../displayCode'),
Editor = require('../editor'),
{ Grid, Row, Col } = require('react-bootstrap'),
// ## flux
BonfireActions = require('./Actions'),
BonfireStore = require('./Store');
var Bonfire = React.createClass({
mixins: [ObservableStateMixin],
contextTypes: {
makePath: React.PropTypes.func.isRequired,
replaceWith: React.PropTypes.func.isRequired
},
getObservable: function() {
return BonfireStore;
},
componentDidMount: function() {
// get history object
var his = typeof window !== 'undefined' ? window.history : null;
// spinal-case bonfireName
var bonfireName = this.state.name.toLowerCase().replace(/\s/g, '-');
// create proper URI from react-router
var path = this.context.makePath('bonfires', { bonfireName: bonfireName });
// if html5 push state exists, update URI
// else we are using hash location and should just cause a re render
if (his) {
his.replaceState({ path: path }, '', path);
} else {
this.context.replaceWith('bonfires', { bonfireName: bonfireName});
}
},
_onTestBonfire: function() {
BonfireActions.testUserCode({
userCode: this.state.userCode,
tests: this.state.tests
});
},
render: function() {
var {
name,
userCode,
difficulty,
description,
results,
display
} = this.state;
var brief = description.slice(0, 1).pop();
// convert bonfire difficulty from floating point string
// to integer.
var difficultyInt = Math.floor(+difficulty);
return (
<Grid>
<Row>
<Col
xs={ 12 }
md={ 4 }>
<SidePanel
name={ name }
brief={ brief }
difficulty={ difficultyInt }
onTestBonfire={ this._onTestBonfire }
description={ description.length > 1 ? description : [] }/>
<Display
value={ display }/>
<Results
results={ results }/>
</Col>
<Col
xs={ 12 }
md={ 8 }>
<Editor
onValueChange={ BonfireActions.setUserCode }
value={ userCode }/>
</Col>
</Row>
</Grid>
);
}
});
module.exports = Bonfire;

View File

@ -0,0 +1,62 @@
var React = require('react'),
classNames = require('classnames'),
{ Grid, Row, Col } = require('react-bootstrap');
var Results = React.createClass({
propTypes: {
results: React.PropTypes.array
},
_renderText: function(text, textClass) {
return (
<Col
xs={ 11 }
className={ classNames(textClass) }>
{ text }
</Col>
);
},
_renderResult: function(results) {
return results.map(function(result, idx) {
var err = result.err;
var iconClass = {
'ion-close-circled big-error-icon': err,
'ion-checkmark-circled big-success-icon': !err
};
var textClass = {
'test-output wrappable': true,
'test-vertical-center': !err
};
return (
<div key={ idx }>
<Row>
<Col
xs={ 1 }
className='text-center'>
<i className={ classNames(iconClass) }></i>
</Col>
{ this._renderText(result.text, textClass) }
{ err ? this._renderText(err, textClass) : null }
</Row>
<div className='ten-pixel-break'></div>
</div>
);
}.bind(this));
},
render: function() {
var results = this.props.results;
if (!results || results.length && results.length === 0) {
return null;
}
return (
<Grid>
{ this._renderResult(this.props.results) }
</Grid>
);
}
});
module.exports = Results;

View File

@ -0,0 +1,129 @@
var React = require('react'),
// ## components
{
Well,
Row,
Col,
Button,
} = require('react-bootstrap');
var SidePanel = React.createClass({
propTypes: {
name: React.PropTypes.string,
brief: React.PropTypes.string,
description: React.PropTypes.array,
difficulty: React.PropTypes.number,
onTestBonfire: React.PropTypes.func
},
getDefaultProps: function() {
return {
name: 'Welcome to Bonfires!',
difficulty: 5,
brief: 'This is a brief description'
};
},
getInitialState: function() {
return {
isMoreInfoOpen: false
};
},
_toggleMoreInfo: function() {
this.setState({
isMoreInfoOpen: !this.state.isMoreInfoOpen
});
},
_renderFlames: function() {
var difficulty = this.props.difficulty;
return [1, 2, 3, 4, 5].map(num => {
var className = 'ion-ios-flame';
if (num > difficulty) {
className += '-outline';
}
return (
<i
key={ num }
className={ className }/>
);
});
},
_renderMoreInfo: function(isDescription) {
var description = this.props.description.map((sentance, index) => {
return <p key={ index }>{ sentance }</p>;
});
if (isDescription && this.state.isMoreInfoOpen) {
return (
<Row>
<Col xs={ 12 }>
{ description }
</Col>
</Row>
);
}
return null;
},
_renderMoreInfoButton: function(isDescription) {
if (isDescription) {
return (
<Button
onClick={ this._toggleMoreInfo }
bsStyle='primary'
block={ true }
className='btn-primary-ghost'>
<span className='ion-arrow-down-b'></span>
More information
</Button>
);
}
return null;
},
render: function() {
var isDescription = this.props.description &&
this.props.description.length > 1;
return (
<div>
<h1 className='text-center'>{ this.props.name }</h1>
<h2 className='text-center'>
<div className='bonfire-flames'>
Difficulty:&nbsp;
{ this._renderFlames() }
</div>
</h2>
<Well>
<Row>
<Col xs={ 12 }>
<div className='bonfire-instructions'>
<p>{ this.props.brief }</p>
<div>
{ this._renderMoreInfo(isDescription) }
{ this._renderMoreInfoButton(isDescription) }
</div>
</div>
</Col>
</Row>
</Well>
<Button
bsStyle='primary'
block={ true }
className='btn-big'
onClick={ this.props.onTestBonfire }>
Run Code (ctrl + enter)
</Button>
<br />
</div>
);
}
});
module.exports = SidePanel;

View File

@ -0,0 +1,67 @@
var BonfiresActions = require('./Actions');
var { Store, setStateUtil } = require('thundercats');
var BonfiresStore = Store.create({
getInitialValue: function() {
return {
userCode: 'console.log(\'FreeCodeCamp!\')',
difficulty: 0,
description: [
'default state'
],
tests: [],
results: null
};
},
getOperations: function() {
var {
setBonfire,
setUserCode,
setResults,
setDisplay
} = BonfiresActions;
return [
setBonfire
.map(function(bonfire) {
var {
name,
description,
difficulty,
tests
} = bonfire;
var userCode = bonfire.challengeSeed;
return {
name,
userCode,
tests,
description,
difficulty
};
})
.map(setStateUtil),
setUserCode
.map(function(userCode) {
return { userCode };
})
.map(setStateUtil),
setDisplay
.map(function(display) {
return { display };
})
.map(setStateUtil),
setResults
.map(function(results) {
return { results };
})
.map(setStateUtil)
];
}
});
module.exports = BonfiresStore;

View File

@ -0,0 +1,27 @@
var debug = require('debug')('freecc:executebonfire');
var {
addTests,
runTests,
testCode
} = require('../../utils');
module.exports = executeBonfire;
function executeBonfire(userCode, tests, cb) {
// TODO: move this into componentDidMount
// ga('send', 'event', 'Bonfire', 'ran-code', bonfireName);
var testSalt = Math.random();
var { preppedCode, userTests } = addTests(userCode, tests, testSalt);
debug('sending code to web worker for testing');
testCode(preppedCode, function(err, data) {
if (err) { return cb(err); }
var results = runTests(userTests, data, testSalt);
debug('testing complete', results);
cb(null, {
output: data.output,
results
});
});
}

View File

@ -0,0 +1 @@
module.exports = require('./Bonfires.jsx');

View File

@ -0,0 +1,34 @@
var debug = require('debug')('freecc:context'),
BonfireActions = require('../bonfires/Actions'),
BonfireStore = require('../bonfires/Store');
var {
Action,
waitFor
} = require('thundercats');
var actions = Action.createActions([
'setContext',
'renderToUser'
]);
actions
.setContext
.filter(function(ctx) {
return ctx.state.path.indexOf('/bonfire') !== -1;
})
.subscribe(function(ctx) {
debug('set ctx');
BonfireActions.getBonfire(ctx.state.params);
waitFor(BonfireStore)
.firstOrDefault()
.catch(function(err) {
// handle timeout error
debug('err', err);
})
.subscribe(function() {
actions.renderToUser(ctx);
});
});
module.exports = actions;

View File

@ -0,0 +1,18 @@
var Store = require('thundercats').Store,
ContextActions = require('./Actions');
var ContextStore = Store.create({
getInitialValue: function() {
return {};
},
getOperations: function() {
return ContextActions
.renderToUser
.map(function(ctx) {
return { value: ctx };
});
}
});
module.exports = ContextStore;

View File

@ -0,0 +1,51 @@
var React = require('react'),
Tailspin = require('tailspin');
var Editor = React.createClass({
propTypes: {
value: React.PropTypes.string
},
getDefaultProps: function() {
return {
value: [
'/**',
'* Your output will go here.',
'* Console.log() -type statements',
'* will appear in your browser\'s',
'* DevTools JavaScript console.',
'**/'
].join('\n')
};
},
render: function() {
var value = this.props.value;
var options = {
lineNumbers: false,
lineWrapping: true,
mode: 'text',
readOnly: 'noCursor',
textAreaClassName: 'hide-textarea',
theme: 'monokai',
value: value
};
var config = {
setSize: ['100%', '100%']
};
return (
<form className='code'>
<div className='form-group codeMirrorView'>
<Tailspin
{ ...options }
config={ config }/>
</div>
</form>
);
}
});
module.exports = Editor;

View File

@ -0,0 +1 @@
module.exports = require('./Display.jsx');

View File

@ -0,0 +1,91 @@
var React = require('react'),
debug = require('debug')('freecc:comp:editor'),
jshint = require('jshint').JSHINT,
Tailspin = require('tailspin');
var Editor = React.createClass({
propTypes: {
onValueChange: React.PropTypes.func,
value: React.PropTypes.string
},
getDefaultProps: function() {
return {
value: 'console.log(\'freeCodeCamp is awesome\')'
};
},
getInitialState: function() {
return {
value: this.props.value
};
},
render: function() {
var options = {
autoCloseBrackets: true,
gutters: ['CodeMirror-lint-markers'],
lint: true,
linter: jshint,
lineNumbers: true,
lineWrapping: true,
mode: 'javascript',
matchBrackets: true,
runnable: true,
scrollbarStyle: 'null',
theme: 'monokai',
textAreaClassName: 'hide-textarea',
value: this.state.value,
onChange: e => {
this.setState({ value: e.target.value});
if (typeof this.props.onValueChange === 'function') {
this.props.onValueChange(e.target.value);
}
}
};
var config = {
setSize: ['100%', 'auto'],
extraKeys: {
Tab: function(cm) {
debug('tab pressed');
if (cm.somethingSelected()) {
cm.indentSelection('add');
} else {
var spaces = new Array(cm.getOption('indentUnit') + 1).join(' ');
cm.replaceSelection(spaces);
}
},
'Shift-Tab': function(cm) {
debug('shift-tab pressed');
if (cm.somethingSelected()) {
cm.indentSelection('subtract');
} else {
var spaces = new Array(cm.getOption('indentUnit') + 1).join(' ');
cm.replaceSelection(spaces);
}
},
'Ctrl-Enter': function() {
debug('C-enter pressed');
// execute bonfire action
return false;
}
}
};
return (
<div id='mainEditorPanel'>
<form className='code'>
<div className='form-group codeMirrorView'>
<Tailspin
{ ...options }
config={ config }/>
</div>
</form>
</div>
);
}
});
module.exports = Editor;

View File

@ -0,0 +1 @@
module.exports = require('./Editor.jsx');

View File

@ -0,0 +1,106 @@
var React = require('react');
var Footer = React.createClass({
render: function() {
return (
<div className='fcc-footer'>
<div className='col-xs-12 hidden-xs hidden-sm'>
<a
href='http://blog.freecodecamp.com'
target='_blank' className='ion-speakerphone'>
&nbsp;Blog&nbsp;&nbsp;
</a>
<a
ref='http://www.twitch.tv/freecodecamp'
target='_blank' className='ion-social-twitch-outline'>
&nbsp;Twitch&nbsp;&nbsp;
</a>
<a
href='http://github.com/freecodecamp'
target='_blank'
className='ion-social-github'>
&nbsp;Github&nbsp;&nbsp;
</a>
<a
href='http://twitter.com/freecodecamp'
target='_blank' className='ion-social-twitter'>
&nbsp;Twitter&nbsp;&nbsp;
</a>
<a
href='http://facebook.com/freecodecamp'
target='_blank'
className='ion-social-facebook'>
&nbsp;Facebook&nbsp;&nbsp;
</a>
<a
ref='/learn-to-code'
className='ion-information-circled'>
&nbsp;About&nbsp;&nbsp;
</a>
<a
href='/privacy'
className='ion-locked'>
&nbsp;Privacy&nbsp;&nbsp;
</a>
</div>
<div className='col-xs-12 visible-xs visible-sm'>
<a
href='http://blog.freecodecamp.com'
target='_blank' className='ion-speakerphone'>
<span className='sr-only'>
Free Code Camp\'s Blog
</span>
</a>
<a
href='http://www.twitch.tv/freecodecamp'
target='_blank'
className='ion-social-twitch-outline'>
<span className='sr-only'>
Free Code Camp Live Pair Programming on Twitch.tv
</span>
</a>
<a
href='http://github.com/freecodecamp'
target='_blank'
className='ion-social-github'>
<span className='sr-only'>
Free Code Camp on GitHub
</span>
</a>
<a
href='http://twitter.com/freecodecamp'
target='_blank'
className='ion-social-twitter'>
<span className='sr-only'>
Free Code Camp on Twitter
</span>
</a>
<a
href='http://facebook.com/freecodecamp'
target='_blank'
className='ion-social-facebook'>
<span className='sr-only'>
Free Code Camp on Facebook
</span>
</a>
<a
href='/learn-to-code'
className='ion-information-circled'>
<span className='sr-only'>
About Free Code Camp
</span>
</a>
<a
href='/privacy'
className='ion-locked'>
<span className='sr-only'>
Free Code Camp's Privacy Policy
</span>
</a>
</div>
</div>
);
}
});
module.exports = Footer;

View File

@ -0,0 +1 @@
module.exports = require('./Footer.jsx');

View File

@ -0,0 +1,81 @@
var React = require('react'),
bootStrap = require('react-bootstrap'),
Navbar = bootStrap.Navbar,
Nav = bootStrap.Nav,
NavItem = bootStrap.NavItem,
NavItemFCC = require('./NavItem.jsx');
var NavBarComp = React.createClass({
propTypes: { signedIn: React.PropTypes.bool },
getDefaultProps: function() {
return { signedIn: false };
},
_renderBrand: function() {
var fCClogo = 'https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg';
return (
<a href='/'>
<img
src={ fCClogo }
alt='learn to code javascript at Free Code Camp logo'
className='img-responsive nav-logo' />
</a>
);
},
_renderSignin: function() {
if (this.props.signedIn) {
return (
<NavItem
eventKey={ 2 }>
Show Picture
</NavItem>
);
} else {
return (
<NavItemFCC
eventKey={ 2 }
href='/login'
aClassName='btn signup-btn signup-btn-nav'>
Sign In
</NavItemFCC>
);
}
},
render: function() {
return (
<Navbar
brand={ this._renderBrand() }
fixedTop={ true }
toggleNavKey={ 0 }
className='nav-height'>
<Nav
right={ true }
eventKey={ 0 }
className='hamburger-dropdown'>
<NavItem
eventKey={ 1 }
href='/Challenges'>
Challenges
</NavItem>
<NavItem
eventKey={ 1 }
href='Chat'>
Chat
</NavItem>
<NavItem
eventKey={ 2 }
href='/bonfires'>
Bonfires
</NavItem>
{ this._renderSignin() }
</Nav>
</Navbar>
);
}
});
module.exports = NavBarComp;

View File

@ -0,0 +1,66 @@
var React = require('react/addons');
var joinClasses = require('react-bootstrap/lib/utils/joinClasses');
var classSet = React.addons.classSet;
var BootstrapMixin = require('react-bootstrap').BootstrapMixin;
var NavItem = React.createClass({
mixins: [BootstrapMixin],
propTypes: {
onSelect: React.PropTypes.func,
active: React.PropTypes.bool,
disabled: React.PropTypes.bool,
href: React.PropTypes.string,
title: React.PropTypes.string,
eventKey: React.PropTypes.any,
target: React.PropTypes.string
},
getDefaultProps: function () {
return {
href: '#'
};
},
render: function () {
var {
disabled,
active,
href,
title,
target,
children,
} = this.props,
props = this.props,
classes = {
'active': active,
'disabled': disabled
};
return (
<li {...props} className={joinClasses(props.className, classSet(classes))}>
<a
href={href}
title={title}
target={target}
className={ this.props.aClassName }
onClick={this.handleClick}
ref="anchor">
{ children }
</a>
</li>
);
},
handleClick: function (e) {
if (this.props.onSelect) {
e.preventDefault();
if (!this.props.disabled) {
this.props.onSelect(this.props.eventKey, this.props.href, this.props.target);
}
}
}
});
module.exports = NavItem;

View File

@ -0,0 +1 @@
module.exports = require('./Nav.jsx');

View File

@ -57,27 +57,28 @@
"less": "~1.7.5", "less": "~1.7.5",
"less-middleware": "~2.0.1", "less-middleware": "~2.0.1",
"lodash": "~2.4.1", "lodash": "~2.4.1",
"loopback": "^2.18.0",
"loopback-boot": "^2.8.0",
"loopback-component-passport": "^1.3.1",
"loopback-connector-mongodb": "^1.10.0",
"lusca": "~1.0.2", "lusca": "~1.0.2",
"method-override": "~2.3.0", "method-override": "~2.3.0",
"moment": "~2.10.2", "moment": "~2.10.2",
"mongodb": "~1.4.33", "mongodb": "^2.0.33",
"mongoose": "~4.0.1",
"mongoose-long": "0.0.2",
"morgan": "~1.5.0", "morgan": "~1.5.0",
"node-slack": "0.0.7", "node-slack": "0.0.7",
"nodemailer": "~1.3.0", "nodemailer": "~1.3.0",
"passport": "~0.2.1", "passport-facebook": "^2.0.0",
"passport-facebook": "~1.0.3", "passport-google-oauth": "^0.2.0",
"passport-github": "~0.1.5", "passport-google-oauth2": "^0.1.6",
"passport-google-oauth": "~0.1.5", "passport-linkedin-oauth2": "^1.2.1",
"passport-linkedin-oauth2": "~1.2.1", "passport-local": "^1.0.0",
"passport-local": "~1.0.0", "passport-oauth": "^1.0.0",
"passport-oauth": "~1.0.0", "passport-twitter": "^1.0.3",
"passport-twitter": "~1.0.2",
"ramda": "~0.10.0", "ramda": "~0.10.0",
"request": "~2.53.0", "request": "~2.53.0",
"rx": "^2.5.3",
"sanitize-html": "~1.6.1", "sanitize-html": "~1.6.1",
"sitemap": "~0.7.4",
"twit": "~1.1.20", "twit": "~1.1.20",
"uglify-js": "~2.4.15", "uglify-js": "~2.4.15",
"validator": "~3.22.1", "validator": "~3.22.1",
@ -94,7 +95,7 @@
"gulp": "~3.8.8", "gulp": "~3.8.8",
"gulp-eslint": "~0.9.0", "gulp-eslint": "~0.9.0",
"gulp-inject": "~1.0.2", "gulp-inject": "~1.0.2",
"gulp-nodemon": "~1.0.4", "gulp-nodemon": "^2.0.3",
"mocha": "~2.0.1", "mocha": "~2.0.1",
"multiline": "~1.0.1", "multiline": "~1.0.1",
"supertest": "~0.15.0" "supertest": "~0.15.0"

View File

@ -237,7 +237,7 @@
"name": "Waypoint: Fill in the Blank with Placeholder Text", "name": "Waypoint: Fill in the Blank with Placeholder Text",
"difficulty": 0.015, "difficulty": 0.015,
"description": [ "description": [
"Replace the text inside your <code>p</code> element with the first few words of the provided \"Ktty Ipsum\" text.", "Replace the text inside your <code>p</code> element with the first few words of the provided \"Kitty Ipsum\" text.",
"Web developers traditionally use \"Lorem Ipsum\" text as placeholder text. It's called \"Lorem Ipsum\" text because those are the first two words of a famous passage by Cicero of Ancient Rome.", "Web developers traditionally use \"Lorem Ipsum\" text as placeholder text. It's called \"Lorem Ipsum\" text because those are the first two words of a famous passage by Cicero of Ancient Rome.",
"\"Lorem Ipsum\" text has been used as placeholder text by typesetters since the 16th century, and this tradition continues on the web.", "\"Lorem Ipsum\" text has been used as placeholder text by typesetters since the 16th century, and this tradition continues on the web.",
"Well, 5 centuries is long enough. Since we're building a CatPhotoApp, let's use something called \"Kitty Ipsum\"!", "Well, 5 centuries is long enough. Since we're building a CatPhotoApp, let's use something called \"Kitty Ipsum\"!",

View File

@ -683,7 +683,7 @@
" <li>Fork the Free Code Camp repository and <code>open seed_data/bonfires.json</code> to become familiar with the format of our bonfires.</li>", " <li>Fork the Free Code Camp repository and <code>open seed_data/bonfires.json</code> to become familiar with the format of our bonfires.</li>",
" <li>Regardless of your bonfire's difficulty, put it as the last bonfire in the JSON file. Change one of the numbers in the ID to ensure that your bonfire has a unique ID.</li>", " <li>Regardless of your bonfire's difficulty, put it as the last bonfire in the JSON file. Change one of the numbers in the ID to ensure that your bonfire has a unique ID.</li>",
" <li>In the terminal, run <code>node seed_data/seed.js</code>. Run <code>gulp</code>. You should be able to navigate to your new bonfire in the challenge map. Whenever you make a change to bonfire.json, you'll need to reseed in order to see these changes in the browser.</li>", " <li>In the terminal, run <code>node seed_data/seed.js</code>. Run <code>gulp</code>. You should be able to navigate to your new bonfire in the challenge map. Whenever you make a change to bonfire.json, you'll need to reseed in order to see these changes in the browser.</li>",
" <li>Solved your own Bonfire. Confirmed that your tests work as expected and that your instructions are sufficiently clear.</li>", " <li>Solve your own Bonfire. Confirm that your tests work as expected and that your instructions are sufficiently clear.</li>",
" <li>Submit a pull request to Free Code Camp's Staging branch and in the pull request body, link to a gist that has your algorithmic solution.</li>", " <li>Submit a pull request to Free Code Camp's Staging branch and in the pull request body, link to a gist that has your algorithmic solution.</li>",
" </ol>", " </ol>",
" </p>", " </p>",

99
seed/index.js Normal file
View File

@ -0,0 +1,99 @@
/* eslint-disable no-process-exit */
require('dotenv').load();
var fs = require('fs'),
path = require('path'),
app = require('../server/server'),
fieldGuides = require('./field-guides.json'),
nonprofits = require('./nonprofits.json'),
jobs = require('./jobs.json');
var Challenge = app.models.Challenge;
var FieldGuide = app.models.FieldGuide;
var Nonprofit = app.models.Nonprofit;
var Job = app.models.Job;
var counter = 0;
var challenges = fs.readdirSync(path.join(__dirname, '/challenges'));
var offerings = 3 + challenges.length;
var CompletionMonitor = function() {
counter++;
console.log('call ' + counter);
if (counter < offerings) {
return;
} else {
process.exit(0);
}
};
Challenge.destroyAll(function(err, info) {
if (err) {
console.err(err);
} else {
console.log('Deleted ', info);
}
challenges.forEach(function (file) {
Challenge.create(
require('./challenges/' + file).challenges,
function (err) {
if (err) {
console.log(err);
} else {
console.log('Successfully parsed %s', file);
CompletionMonitor();
}
}
);
});
});
FieldGuide.destroyAll(function(err, info) {
if (err) {
console.error(err);
} else {
console.log('Deleted ', info);
}
FieldGuide.create(fieldGuides, function(err, data) {
if (err) {
console.log(err);
} else {
console.log('Saved ', data);
}
CompletionMonitor();
console.log('field guides');
});
});
Nonprofit.destroyAll(function(err, info) {
if (err) {
console.error(err);
} else {
console.log('Deleted ', info);
}
Nonprofit.create(nonprofits, function(err, data) {
if (err) {
console.log(err);
} else {
console.log('Saved ', data);
}
CompletionMonitor();
console.log('nonprofits');
});
});
Job.destroyAll(function(err, info) {
if (err) {
console.error(err);
} else {
console.log('Deleted ', info);
}
Job.create(jobs, function(err, data) {
if (err) {
console.log(err);
} else {
console.log('Saved ', data);
}
console.log('jobs');
CompletionMonitor();
});
});

View File

@ -1,97 +0,0 @@
require('dotenv').load();
var Challenge = require('../models/Challenge.js'),
FieldGuide = require('../models/FieldGuide.js'),
Nonprofit = require('../models/Nonprofit.js'),
Job = require('../models/Job.js'),
mongoose = require('mongoose'),
secrets = require('../config/secrets'),
fieldGuides = require('./field-guides.json'),
nonprofits = require('./nonprofits.json'),
jobs = require('./jobs.json'),
fs = require('fs');
mongoose.connect(secrets.db);
var challenges = fs.readdirSync(__dirname + '/challenges');
var counter = 0;
var offerings = 3 + challenges.length;
var CompletionMonitor = function() {
counter++;
console.log('call ' + counter);
if (counter < offerings) {
return;
} else {
process.exit(0);
}
};
Challenge.remove({}, function(err, data) {
if (err) {
console.err(err);
} else {
console.log('Deleted ', data);
}
challenges.forEach(function (file) {
Challenge.create(require('./challenges/' + file).challenges, function (err, data) {
if (err) {
console.log(err);
} else {
console.log('Successfully parsed %s', file);
CompletionMonitor();
}
});
});
});
FieldGuide.remove({}, function(err, data) {
if (err) {
console.error(err);
} else {
console.log('Deleted ', data);
}
FieldGuide.create(fieldGuides, function(err, data) {
if (err) {
console.log(err);
} else {
console.log('Saved ', data);
}
CompletionMonitor();
});
console.log('field guides');
});
Nonprofit.remove({}, function(err, data) {
if (err) {
console.error(err);
} else {
console.log('Deleted ', data);
}
Nonprofit.create(nonprofits, function(err, data) {
if (err) {
console.log(err);
} else {
console.log('Saved ', data);
}
CompletionMonitor();
});
console.log('nonprofits');
});
Job.remove({}, function(err, data) {
if (err) {
console.error(err);
} else {
console.log('Deleted ', data);
}
Job.create(jobs, function(err, data) {
if (err) {
console.log(err);
} else {
console.log('Saved ', data);
}
CompletionMonitor();
});
console.log('jobs');
});

View File

@ -0,0 +1,4 @@
module.exports = function enableAuthentication(app) {
// enable authentication
app.enableAuth();
};

View File

@ -31,33 +31,13 @@
*/ */
var R = require('ramda'), var R = require('ramda'),
express = require('express'), utils = require('../utils'),
Challenge = require('../../common/models/Challenge'), userMigration = require('../utils/middleware').userMigration,
User = require('../../common/models/User'), MDNlinks = require('../../seed/bonfireMDNlinks');
resources = require('../resources/resources'),
userMigration = require('../resources/middleware').userMigration,
MDNlinks = require('../../seed_data/bonfireMDNlinks');
var router = express.Router(); var challengeMapWithNames = utils.getChallengeMapWithNames();
var challengeMapWithNames = resources.getChallengeMapWithNames(); var challengeMapWithIds = utils.getChallengeMapWithIds();
var challengeMapWithIds = resources.getChallengeMapWithIds();
router.get(
'/challenges/next-challenge',
userMigration,
returnNextChallenge
);
router.get(
'/challenges/:challengeName',
userMigration,
returnIndividualChallenge
);
router.get('/challenges/', userMigration, returnCurrentChallenge);
router.post('/completed-challenge/', completedChallenge);
router.post('/completed-zipline-or-basejump', completedZiplineOrBasejump);
router.post('/completed-bonfire', completedBonfire);
function getMDNlinks(links) { function getMDNlinks(links) {
// takes in an array of links, which are strings // takes in an array of links, which are strings
@ -73,7 +53,31 @@ function getMDNlinks(links) {
return populatedLinks; return populatedLinks;
} }
function returnNextChallenge(req, res, next) { module.exports = function(app) {
var router = app.loopback.Router();
var Challenge = app.models.Challenge;
var User = app.models.User;
router.get(
'/challenges/next-challenge',
userMigration,
returnNextChallenge
);
router.get(
'/challenges/:challengeName',
userMigration,
returnIndividualChallenge
);
router.get('/challenges/', userMigration, returnCurrentChallenge);
router.post('/completed-challenge/', completedChallenge);
router.post('/completed-zipline-or-basejump', completedZiplineOrBasejump);
router.post('/completed-bonfire', completedBonfire);
app.use(router);
function returnNextChallenge(req, res, next) {
if (!req.user) { if (!req.user) {
return res.redirect('../challenges/learn-how-free-code-camp-works'); return res.redirect('../challenges/learn-how-free-code-camp-works');
} }
@ -81,7 +85,7 @@ function returnNextChallenge(req, res, next) {
return elem._id; return elem._id;
}); });
req.user.uncompletedChallenges = resources.allChallengeIds() req.user.uncompletedChallenges = utils.allChallengeIds()
.filter(function (elem) { .filter(function (elem) {
if (completed.indexOf(elem) === -1) { if (completed.indexOf(elem) === -1) {
return elem; return elem;
@ -125,9 +129,9 @@ function returnNextChallenge(req, res, next) {
} }
return res.redirect('../challenges/' + nameString); return res.redirect('../challenges/' + nameString);
}); });
} }
function returnCurrentChallenge(req, res, next) { function returnCurrentChallenge(req, res, next) {
if (!req.user) { if (!req.user) {
return res.redirect('../challenges/learn-how-free-code-camp-works'); return res.redirect('../challenges/learn-how-free-code-camp-works');
} }
@ -135,7 +139,7 @@ function returnCurrentChallenge(req, res, next) {
return elem._id; return elem._id;
}); });
req.user.uncompletedChallenges = resources.allChallengeIds() req.user.uncompletedChallenges = utils.allChallengeIds()
.filter(function (elem) { .filter(function (elem) {
if (completed.indexOf(elem) === -1) { if (completed.indexOf(elem) === -1) {
return elem; return elem;
@ -162,9 +166,9 @@ function returnCurrentChallenge(req, res, next) {
} }
return res.redirect('../challenges/' + nameString); return res.redirect('../challenges/' + nameString);
}); });
} }
function returnIndividualChallenge(req, res, next) { function returnIndividualChallenge(req, res, next) {
var dashedName = req.params.challengeName; var dashedName = req.params.challengeName;
var challengeName = var challengeName =
@ -176,11 +180,11 @@ function returnIndividualChallenge(req, res, next) {
.join(' ') : .join(' ') :
dashedName.replace(/\-/g, ' '); dashedName.replace(/\-/g, ' ');
Challenge.find({'name': new RegExp(challengeName, 'i')}, Challenge.find(
{ where: { name: new RegExp(challengeName, 'i') } },
function(err, challengeFromMongo) { function(err, challengeFromMongo) {
if (err) { if (err) { return next(err); }
return next(err);
}
// Handle not found // Handle not found
if (challengeFromMongo.length < 1) { if (challengeFromMongo.length < 1) {
req.flash('errors', { req.flash('errors', {
@ -224,11 +228,11 @@ function returnIndividualChallenge(req, res, next) {
details: challenge.description.slice(1), details: challenge.description.slice(1),
tests: challenge.tests, tests: challenge.tests,
challengeSeed: challenge.challengeSeed, challengeSeed: challenge.challengeSeed,
verb: resources.randomVerb(), verb: utils.randomVerb(),
phrase: resources.randomPhrase(), phrase: utils.randomPhrase(),
compliment: resources.randomCompliment(), compliment: utils.randomCompliment(),
challengeId: challenge._id, challengeId: challenge._id,
environment: resources.whichEnvironment(), environment: utils.whichEnvironment(),
challengeType: challenge.challengeType challengeType: challenge.challengeType
}); });
}, },
@ -242,9 +246,9 @@ function returnIndividualChallenge(req, res, next) {
details: challenge.description.slice(1), details: challenge.description.slice(1),
tests: challenge.tests, tests: challenge.tests,
challengeSeed: challenge.challengeSeed, challengeSeed: challenge.challengeSeed,
verb: resources.randomVerb(), verb: utils.randomVerb(),
phrase: resources.randomPhrase(), phrase: utils.randomPhrase(),
compliment: resources.randomCompliment(), compliment: utils.randomCompliment(),
challengeId: challenge._id, challengeId: challenge._id,
challengeType: challenge.challengeType challengeType: challenge.challengeType
}); });
@ -258,9 +262,9 @@ function returnIndividualChallenge(req, res, next) {
details: challenge.description, details: challenge.description,
tests: challenge.tests, tests: challenge.tests,
video: challenge.challengeSeed[0], video: challenge.challengeSeed[0],
verb: resources.randomVerb(), verb: utils.randomVerb(),
phrase: resources.randomPhrase(), phrase: utils.randomPhrase(),
compliment: resources.randomCompliment(), compliment: utils.randomCompliment(),
challengeId: challenge._id, challengeId: challenge._id,
challengeType: challenge.challengeType challengeType: challenge.challengeType
}); });
@ -273,9 +277,9 @@ function returnIndividualChallenge(req, res, next) {
name: challenge.name, name: challenge.name,
details: challenge.description, details: challenge.description,
video: challenge.challengeSeed[0], video: challenge.challengeSeed[0],
verb: resources.randomVerb(), verb: utils.randomVerb(),
phrase: resources.randomPhrase(), phrase: utils.randomPhrase(),
compliment: resources.randomCompliment(), compliment: utils.randomCompliment(),
challengeId: challenge._id, challengeId: challenge._id,
challengeType: challenge.challengeType challengeType: challenge.challengeType
}); });
@ -288,9 +292,9 @@ function returnIndividualChallenge(req, res, next) {
name: challenge.name, name: challenge.name,
details: challenge.description, details: challenge.description,
video: challenge.challengeSeed[0], video: challenge.challengeSeed[0],
verb: resources.randomVerb(), verb: utils.randomVerb(),
phrase: resources.randomPhrase(), phrase: utils.randomPhrase(),
compliment: resources.randomCompliment(), compliment: utils.randomCompliment(),
challengeId: challenge._id, challengeId: challenge._id,
challengeType: challenge.challengeType challengeType: challenge.challengeType
}); });
@ -307,9 +311,9 @@ function returnIndividualChallenge(req, res, next) {
details: challenge.description, details: challenge.description,
tests: challenge.tests, tests: challenge.tests,
challengeSeed: challenge.challengeSeed, challengeSeed: challenge.challengeSeed,
verb: resources.randomVerb(), verb: utils.randomVerb(),
phrase: resources.randomPhrase(), phrase: utils.randomPhrase(),
compliment: resources.randomCompliment(), compliment: utils.randomCompliment(),
bonfires: challenge, bonfires: challenge,
challengeId: challenge._id, challengeId: challenge._id,
MDNkeys: challenge.MDNlinks, MDNkeys: challenge.MDNlinks,
@ -329,9 +333,9 @@ function returnIndividualChallenge(req, res, next) {
return challengeType[challenge.challengeType](); return challengeType[challenge.challengeType]();
} }
}); });
} }
function completedBonfire(req, res, next) { function completedBonfire(req, res, next) {
var isCompletedWith = req.body.challengeInfo.completedWith || ''; var isCompletedWith = req.body.challengeInfo.completedWith || '';
var isCompletedDate = Math.round(+new Date()); var isCompletedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId; var challengeId = req.body.challengeInfo.challengeId;
@ -339,12 +343,12 @@ function completedBonfire(req, res, next) {
var challengeName = req.body.challengeInfo.challengeName; var challengeName = req.body.challengeInfo.challengeName;
if (isCompletedWith) { if (isCompletedWith) {
var paired = User.find({'profile.username': isCompletedWith.toLowerCase()}) User.find({
.limit(1); where: { 'profile.username': isCompletedWith.toLowerCase() },
paired.exec(function (err, pairedWith) { limit: 1
if (err) { }, function (err, pairedWith) {
return next(err); if (err) { return next(err); }
} else {
var index = req.user.uncompletedChallenges.indexOf(challengeId); var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) { if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0); req.user.progressTimestamps.push(Date.now() || 0);
@ -388,11 +392,9 @@ function completedBonfire(req, res, next) {
challengeType: 5 challengeType: 5
}); });
req.user.save(function (err, user) { req.user.save(function (err, user) {
if (err) { if (err) { return next(err); }
return next(err);
}
if (pairedWith) { if (pairedWith) {
pairedWith.save(function (err, paired) { pairedWith.save(function (err, paired) {
if (err) { if (err) {
@ -406,7 +408,6 @@ function completedBonfire(req, res, next) {
res.send(true); res.send(true);
} }
}); });
}
}); });
} else { } else {
req.user.completedChallenges.push({ req.user.completedChallenges.push({
@ -425,19 +426,14 @@ function completedBonfire(req, res, next) {
req.user.uncompletedChallenges.splice(index, 1); req.user.uncompletedChallenges.splice(index, 1);
} }
req.user.save(function (err, user) { req.user.save(function (err) {
if (err) { if (err) { return next(err); }
return next(err);
}
// NOTE(berks): Under certain conditions the res is never ended
if (user) {
res.send(true); res.send(true);
}
}); });
} }
} }
function completedChallenge(req, res, next) { function completedChallenge(req, res, next) {
var isCompletedDate = Math.round(+new Date()); var isCompletedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId; var challengeId = req.body.challengeInfo.challengeId;
@ -465,9 +461,9 @@ function completedChallenge(req, res, next) {
res.sendStatus(200); res.sendStatus(200);
} }
}); });
} }
function completedZiplineOrBasejump(req, res, next) { function completedZiplineOrBasejump(req, res, next) {
var isCompletedWith = req.body.challengeInfo.completedWith || false; var isCompletedWith = req.body.challengeInfo.completedWith || false;
var isCompletedDate = Math.round(+new Date()); var isCompletedDate = Math.round(+new Date());
@ -486,13 +482,11 @@ function completedZiplineOrBasejump(req, res, next) {
} }
if (isCompletedWith) { if (isCompletedWith) {
var paired = User.find({'profile.username': isCompletedWith.toLowerCase()}) User.find({
.limit(1); where: { 'profile.username': isCompletedWith.toLowerCase() },
limit: 1
paired.exec(function (err, pairedWithFromMongo) { }, function (err, pairedWithFromMongo) {
if (err) { if (err) { return next(err); }
return next(err);
} else {
var index = req.user.uncompletedChallenges.indexOf(challengeId); var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) { if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0); req.user.progressTimestamps.push(Date.now() || 0);
@ -512,9 +506,7 @@ function completedZiplineOrBasejump(req, res, next) {
}); });
req.user.save(function (err, user) { req.user.save(function (err, user) {
if (err) { if (err) { return next(err); }
return next(err);
}
if (req.user._id.toString() === pairedWith._id.toString()) { if (req.user._id.toString() === pairedWith._id.toString()) {
return res.sendStatus(200); return res.sendStatus(200);
@ -545,7 +537,6 @@ function completedZiplineOrBasejump(req, res, next) {
} }
}); });
}); });
}
}); });
} else { } else {
@ -570,12 +561,12 @@ function completedZiplineOrBasejump(req, res, next) {
if (err) { if (err) {
return next(err); return next(err);
} }
// NOTE(berks): under certain conditions this will not close the response. // NOTE(berks): under certain conditions this will not close
// the response.
if (user) { if (user) {
return res.sendStatus(200); return res.sendStatus(200);
} }
}); });
} }
} }
};
module.exports = router;

View File

@ -1,22 +1,24 @@
var R = require('ramda'), var R = require('ramda'),
express = require('express'),
// debug = require('debug')('freecc:cntr:challengeMap'), // debug = require('debug')('freecc:cntr:challengeMap'),
User = require('../../common/models/User'), utils = require('./../utils'),
resources = require('./../resources/resources'), middleware = require('../utils/middleware');
middleware = require('../resources/middleware'),
router = express.Router();
router.get('/map', middleware.userMigration, challengeMap);
router.get('/learn-to-code', function(req, res) { module.exports = function(app) {
var User = app.models.User;
var router = app.loopback.Router();
router.get('/map', middleware.userMigration, challengeMap);
router.get('/learn-to-code', function(req, res) {
res.redirect(301, '/map'); res.redirect(301, '/map');
}); });
router.get('/about', function(req, res) {
router.get('/about', function(req, res) {
res.redirect(301, '/map'); res.redirect(301, '/map');
}); });
function challengeMap(req, res, next) { app.use(router);
function challengeMap(req, res, next) {
var completedList = []; var completedList = [];
if (req.user) { if (req.user) {
@ -29,7 +31,7 @@ function challengeMap(req, res, next) {
.map(function(challenge) { .map(function(challenge) {
return challenge._id; return challenge._id;
}); });
var challengeList = resources. var challengeList = utils.
getChallengeMapForDisplay(completedChallengeList); getChallengeMapForDisplay(completedChallengeList);
Object.keys(challengeList).forEach(function(key) { Object.keys(challengeList).forEach(function(key) {
@ -48,10 +50,9 @@ function challengeMap(req, res, next) {
var timeDiff = Math.abs(date2.getTime() - date1.getTime()); var timeDiff = Math.abs(date2.getTime() - date1.getTime());
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24)); var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
User.count({}, function (err, camperCount) { User.count(function(err, camperCount) {
if (err) { if (err) { return next(err); }
return next(err);
}
res.render('challengeMap/show', { res.render('challengeMap/show', {
daysRunning: daysRunning, daysRunning: daysRunning,
camperCount: numberWithCommas(camperCount), camperCount: numberWithCommas(camperCount),
@ -60,6 +61,5 @@ function challengeMap(req, res, next) {
completedChallengeList: completedChallengeList completedChallengeList: completedChallengeList
}); });
}); });
} }
};
module.exports = router;

27
server/boot/explorer.js Normal file
View File

@ -0,0 +1,27 @@
module.exports = function mountLoopBackExplorer(app) {
var explorer;
try {
explorer = require('loopback-explorer');
} catch(err) {
// Print the message only when the app was started via `app.listen()`.
// Do not print any message when the project is used as a component.
app.once('started', function() {
console.log(
'Run `npm install loopback-explorer` to enable the LoopBack explorer'
);
});
return;
}
var restApiRoot = app.get('restApiRoot');
var explorerApp = explorer(app, { basePath: restApiRoot });
app.use('/explorer', explorerApp);
app.once('started', function() {
var baseUrl = app.get('url').replace(/\/$/, '');
// express 4.x (loopback 2.x) uses `mountpath`
// express 3.x (loopback 1.x) uses `route`
var explorerPath = explorerApp.mountpath || explorerApp.route;
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
});
};

View File

@ -1,22 +1,25 @@
var R = require('ramda'), var R = require('ramda'),
express = require('express'), // Rx = require('rx'),
// debug = require('debug')('freecc:fieldguides'), // debug = require('debug')('freecc:fieldguides'),
FieldGuide = require('../../common/models/FieldGuide'), utils = require('../utils');
resources = require('../resources/resources');
var router = express.Router(); module.exports = function(app) {
var router = app.loopback.Router();
var FieldGuide = app.models.FieldGuide;
router.get('/field-guide/all-articles', showAllFieldGuides); router.get('/field-guide/all-articles', showAllFieldGuides);
router.get('/field-guide/:fieldGuideName', returnIndividualFieldGuide); router.get('/field-guide/:fieldGuideName', returnIndividualFieldGuide);
router.get('/field-guide/', returnNextFieldGuide); router.get('/field-guide/', returnNextFieldGuide);
router.post('/completed-field-guide/', completedFieldGuide); router.post('/completed-field-guide/', completedFieldGuide);
function returnIndividualFieldGuide(req, res, next) { app.use(router);
function returnIndividualFieldGuide(req, res, next) {
var dashedName = req.params.fieldGuideName; var dashedName = req.params.fieldGuideName;
if (req.user) { if (req.user) {
var completed = req.user.completedFieldGuides; var completed = req.user.completedFieldGuides;
var uncompletedFieldGuides = resources.allFieldGuideIds() var uncompletedFieldGuides = utils.allFieldGuideIds()
.filter(function (elem) { .filter(function (elem) {
if (completed.indexOf(elem) === -1) { if (completed.indexOf(elem) === -1) {
return elem; return elem;
@ -24,9 +27,12 @@ function returnIndividualFieldGuide(req, res, next) {
}); });
req.user.uncompletedFieldGuides = uncompletedFieldGuides; req.user.uncompletedFieldGuides = uncompletedFieldGuides;
// TODO(berks): handle callback properly // TODO(berks): handle callback properly
req.user.save(); req.user.save(function(err) {
if (err) { return next(err); }
});
} }
// NOTE(berks): loopback might have issue with regex here.
FieldGuide.find( FieldGuide.find(
{ dashedName: new RegExp(dashedName, 'i') }, { dashedName: new RegExp(dashedName, 'i') },
function(err, fieldGuideFromMongo) { function(err, fieldGuideFromMongo) {
@ -56,10 +62,10 @@ function returnIndividualFieldGuide(req, res, next) {
}); });
} }
); );
} }
function showAllFieldGuides(req, res) { function showAllFieldGuides(req, res) {
var allFieldGuideNamesAndIds = resources.allFieldGuideNamesAndIds(); var allFieldGuideNamesAndIds = utils.allFieldGuideNamesAndIds();
var completedFieldGuides = []; var completedFieldGuides = [];
if (req.user && req.user.completedFieldGuides) { if (req.user && req.user.completedFieldGuides) {
@ -69,9 +75,9 @@ function showAllFieldGuides(req, res) {
allFieldGuideNamesAndIds: allFieldGuideNamesAndIds, allFieldGuideNamesAndIds: allFieldGuideNamesAndIds,
completedFieldGuides: completedFieldGuides completedFieldGuides: completedFieldGuides
}); });
} }
function returnNextFieldGuide(req, res, next) { function returnNextFieldGuide(req, res, next) {
if (!req.user) { if (!req.user) {
return res.redirect('/field-guide/how-do-i-use-this-guide'); return res.redirect('/field-guide/how-do-i-use-this-guide');
} }
@ -90,7 +96,7 @@ function returnNextFieldGuide(req, res, next) {
"You've read all our current Field Guide entries. You can ", "You've read all our current Field Guide entries. You can ",
'contribute to our Field Guide ', 'contribute to our Field Guide ',
"<a href='https://github.com/FreeCodeCamp/freecodecamp/blob/", "<a href='https://github.com/FreeCodeCamp/freecodecamp/blob/",
"staging/seed_data/field-guides.json'>here</a>." "staging/seed/field-guides.json'>here</a>."
].join('') ].join('')
}); });
} }
@ -99,9 +105,9 @@ function returnNextFieldGuide(req, res, next) {
var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-'); var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-');
return res.redirect('../field-guide/' + nameString); return res.redirect('../field-guide/' + nameString);
}); });
} }
function completedFieldGuide(req, res, next) { function completedFieldGuide(req, res, next) {
var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId; var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId;
req.user.completedFieldGuides.push(fieldGuideId); req.user.completedFieldGuides.push(fieldGuideId);
@ -118,6 +124,5 @@ function completedFieldGuide(req, res, next) {
} }
res.send(true); res.send(true);
}); });
} }
};
module.exports = router;

View File

@ -1,11 +1,13 @@
var express = require('express');
var router = express.Router();
var message = var message =
'Learn to Code JavaScript and get a Coding Job by Helping Nonprofits'; 'Learn to Code JavaScript and get a Coding Job by Helping Nonprofits';
router.get('/', index); module.exports = function(app) {
var router = app.loopback.Router();
router.get('/', index);
function index(req, res, next) { app.use(router);
function index(req, res, next) {
if (req.user && !req.user.profile.picture) { if (req.user && !req.user.profile.picture) {
req.user.profile.picture = req.user.profile.picture =
'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png'; 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png';
@ -17,6 +19,5 @@ function index(req, res, next) {
} else { } else {
res.render('home', { title: message }); res.render('home', { title: message });
} }
} }
};
module.exports = router;

View File

@ -1,10 +1,11 @@
var express = require('express'); module.exports = function(app) {
var Job = require('../../common/models/Job'); var Job = app.models.Job;
var router = express.Router(); var router = app.loopback.Router();
router.get('/jobs', jobsDirectory); router.get('/jobs', jobsDirectory);
app.use(router);
function jobsDirectory(req, res, next) { function jobsDirectory(req, res, next) {
Job.find({}, function(err, jobs) { Job.find({}, function(err, jobs) {
if (err) { return next(err); } if (err) { return next(err); }
@ -13,6 +14,5 @@ function jobsDirectory(req, res, next) {
jobs: jobs jobs: jobs
}); });
}); });
} }
};
module.exports = router;

View File

View File

@ -1,28 +1,33 @@
var express = require('express'),
Nonprofit = require('../../common/models/Nonprofit');
var router = express.Router(); module.exports = function(app) {
var router = app.loopback.Router();
var Nonprofit = app.models.Nonprofit;
router.get('/nonprofits/directory', nonprofitsDirectory); router.get('/nonprofits/directory', nonprofitsDirectory);
router.get('/nonprofits/:nonprofitName', returnIndividualNonprofit); router.get('/nonprofits/:nonprofitName', returnIndividualNonprofit);
function nonprofitsDirectory(req, res, next) { app.use(router);
Nonprofit.find({ estimatedHours: { $gt: 0 } }, function(err, nonprofits) {
function nonprofitsDirectory(req, res, next) {
Nonprofit.find(
{ where: { estimatedHours: { $gt: 0 } } },
function(err, nonprofits) {
if (err) { return next(err); } if (err) { return next(err); }
res.render('nonprofits/directory', { res.render('nonprofits/directory', {
title: 'Nonprofits we help', title: 'Nonprofits we help',
nonprofits: nonprofits nonprofits: nonprofits
}); });
}); }
} );
}
function returnIndividualNonprofit(req, res, next) { function returnIndividualNonprofit(req, res, next) {
var dashedName = req.params.nonprofitName; var dashedName = req.params.nonprofitName;
var nonprofitName = dashedName.replace(/\-/g, ' '); var nonprofitName = dashedName.replace(/\-/g, ' ');
Nonprofit.find( Nonprofit.find(
{ name: new RegExp(nonprofitName, 'i') }, { where: { name: new RegExp(nonprofitName, 'i') } },
function(err, nonprofit) { function(err, nonprofit) {
if (err) { if (err) {
return next(err); return next(err);
@ -95,10 +100,10 @@ function returnIndividualNonprofit(req, res, next) {
}); });
} }
); );
} }
/* /*
function interestedInNonprofit(req, res, next) { function interestedInNonprofit(req, res, next) {
if (req.user) { if (req.user) {
Nonprofit.findOne( Nonprofit.findOne(
{ name: new RegExp(req.params.nonprofitName.replace(/-/, ' '), 'i') }, { name: new RegExp(req.params.nonprofitName.replace(/-/, ' '), 'i') },
@ -120,7 +125,6 @@ function interestedInNonprofit(req, res, next) {
} }
); );
} }
} }
*/ */
};
module.exports = router;

View File

@ -1,68 +1,71 @@
var express = require('express'), /*
passport = require('passport'), var passport = require('passport'),
passportConf = require('../../config/passport'); passportConf = require('../../config/passport');
var router = express.Router(); module.exports = function(app) {
var passportOptions = { var router = app.loopback.Router();
var passportOptions = {
successRedirect: '/', successRedirect: '/',
failureRedirect: '/login' failureRedirect: '/login'
}; };
router.all('/account', passportConf.isAuthenticated); router.all('/account', passportConf.isAuthenticated);
router.get('/auth/twitter', passport.authenticate('twitter')); router.get('/auth/twitter', passport.authenticate('twitter'));
router.get( router.get(
'/auth/twitter/callback', '/auth/twitter/callback',
passport.authenticate('twitter', { passport.authenticate('twitter', {
successRedirect: '/', successRedirect: '/',
failureRedirect: '/login' failureRedirect: '/login'
}) })
); );
router.get( router.get(
'/auth/linkedin', '/auth/linkedin',
passport.authenticate('linkedin', { passport.authenticate('linkedin', {
state: 'SOME STATE' state: 'SOME STATE'
}) })
); );
router.get( router.get(
'/auth/linkedin/callback', '/auth/linkedin/callback',
passport.authenticate('linkedin', passportOptions) passport.authenticate('linkedin', passportOptions)
); );
router.get( router.get(
'/auth/facebook', '/auth/facebook',
passport.authenticate('facebook', {scope: ['email', 'user_location']}) passport.authenticate('facebook', {scope: ['email', 'user_location']})
); );
router.get( router.get(
'/auth/facebook/callback', '/auth/facebook/callback',
passport.authenticate('facebook', passportOptions), function (req, res) { passport.authenticate('facebook', passportOptions), function (req, res) {
res.redirect(req.session.returnTo || '/'); res.redirect(req.session.returnTo || '/');
} }
); );
router.get('/auth/github', passport.authenticate('github')); router.get('/auth/github', passport.authenticate('github'));
router.get( router.get(
'/auth/github/callback', '/auth/github/callback',
passport.authenticate('github', passportOptions), function (req, res) { passport.authenticate('github', passportOptions), function (req, res) {
res.redirect(req.session.returnTo || '/'); res.redirect(req.session.returnTo || '/');
} }
); );
router.get( router.get(
'/auth/google', '/auth/google',
passport.authenticate('google', {scope: 'profile email'}) passport.authenticate('google', {scope: 'profile email'})
); );
router.get( router.get(
'/auth/google/callback', '/auth/google/callback',
passport.authenticate('google', passportOptions), function (req, res) { passport.authenticate('google', passportOptions), function (req, res) {
res.redirect(req.session.returnTo || '/'); res.redirect(req.session.returnTo || '/');
} }
); );
module.exports = router; app.use(router);
};
*/

494
server/boot/randomAPIs.js Normal file
View File

@ -0,0 +1,494 @@
var Rx = require('rx'),
Twit = require('twit'),
async = require('async'),
moment = require('moment'),
Slack = require('node-slack'),
request = require('request'),
debug = require('debug')('freecc:cntr:resources'),
constantStrings = require('../utils/constantStrings.json'),
secrets = require('../../config/secrets');
var slack = new Slack(secrets.slackHook);
module.exports = function(app) {
var router = app.loopback.Router();
var User = app.models.User;
var Challenge = app.models.Challenge;
var Story = app.models.Store;
var FieldGuide = app.models.FieldGuide;
var Nonprofit = app.models.Nonprofit;
router.get('/api/github', githubCalls);
router.get('/api/blogger', bloggerCalls);
router.get('/api/trello', trelloCalls);
router.get('/api/codepen/twitter/:screenName', twitter);
router.get('/sitemap.xml', sitemap);
router.post('/get-help', getHelp);
router.post('/get-pair', getPair);
router.get('/chat', chat);
router.get('/twitch', twitch);
router.get('/pmi-acp-agile-project-managers', agileProjectManagers);
router.get('/pmi-acp-agile-project-managers-form', agileProjectManagersForm);
router.get('/nonprofits', nonprofits);
router.get('/nonprofits-form', nonprofitsForm);
router.get('/jobs-form', jobsForm);
router.get('/submit-cat-photo', catPhotoSubmit);
router.get('/unsubscribe/:email', unsubscribe);
router.get('/unsubscribed', unsubscribed);
router.get('/cats.json', getCats);
router.get('/api/slack', slackInvite);
app.use(router);
function slackInvite(req, res, next) {
if (req.user) {
if (req.user.email) {
var invite = {
'email': req.user.email,
'token': process.env.SLACK_KEY,
'set_active': true
};
var headers = {
'User-Agent': 'Node Browser/0.0.1',
'Content-Type': 'application/x-www-form-urlencoded'
};
var options = {
url: 'https://freecodecamp.slack.com/api/users.admin.invite',
method: 'POST',
headers: headers,
form: invite
};
request(options, function (error, response) {
if (!error && response.statusCode === 200) {
req.flash('success', {
msg: 'We\'ve successfully requested an invite for you.' +
' Please check your email and follow the ' +
'instructions from Slack.'
});
req.user.sentSlackInvite = true;
req.user.save(function(err) {
if (err) {
return next(err);
}
return res.redirect('back');
});
} else {
req.flash('errors', {
msg: 'The invitation email did not go through for some reason.' +
' Please try again or <a href=\'mailto:team@' +
'freecodecamp.com?subject=' +
'slack%20invite%20failed%20to%20send\'>' +
'email us</a>.'
});
return res.redirect('back');
}
});
} else {
req.flash('notice', {
msg: 'Before we can send your Slack invite, we need your email ' +
'address. Please update your profile information here.'
});
return res.redirect('/account');
}
} else {
req.flash('notice', {
msg: 'You need to sign in to Free Code Camp before ' +
'we can send you a Slack invite.'
});
return res.redirect('/account');
}
}
function twitter(req, res, next) {
// sends out random tweets about javascript
var T = new Twit({
'consumer_key': secrets.twitter.consumerKey,
'consumer_secret': secrets.twitter.consumerSecret,
'access_token': secrets.twitter.token,
'access_token_secret': secrets.twitter.tokenSecret
});
var screenName;
if (req.params.screenName) {
screenName = req.params.screenName;
} else {
screenName = 'freecodecamp';
}
T.get(
'statuses/user_timeline',
{
'screen_name': screenName,
count: 10
},
function(err, data) {
if (err) { return next(err); }
return res.json(data);
}
);
}
function getHelp(req, res) {
var userName = req.user.profile.username;
var code = req.body.payload.code ? '\n```\n' +
req.body.payload.code + '\n```\n'
: '';
var challenge = req.body.payload.challenge;
slack.send({
text: '*@' + userName + '* wants help with ' + challenge + '. ' +
code + 'Hey, *@' + userName + '*, if no one helps you right ' +
'away, try typing out your problem in detail to me. Like this: ' +
'http://en.wikipedia.org/wiki/Rubber_duck_debugging',
channel: '#help',
username: 'Debuggy the Rubber Duck',
'icon_url': 'https://pbs.twimg.com/profile_images/' +
'3609875545/569237541c920fa78d78902069615caf.jpeg'
});
return res.sendStatus(200);
}
function getPair(req, res) {
var userName = req.user.profile.username;
var challenge = req.body.payload.challenge;
slack.send({
text: [
'Anyone want to pair with *@',
userName,
'* on ',
challenge,
'?\nMake sure you install Screen Hero here: ',
'http://freecodecamp.com/field-guide/how-do-i-install-screenhero\n',
'Then start your pair program session with *@',
userName,
'* by typing \"/hero @',
userName,
'\" into Slack.\n And *@',
userName,
'*, be sure to launch Screen Hero, then keep coding. ',
'Another camper may pair with you soon.'
].join(''),
channel: '#letspair',
username: 'Companion Cube',
'icon_url':
'https://lh3.googleusercontent.com/-f6xDPDV2rPE/AAAAAAAAAAI/' +
'AAAAAAAAAAA/mdlESXQu11Q/photo.jpg'
});
return res.sendStatus(200);
}
function sitemap(req, res, next) {
var appUrl = 'http://www.freecodecamp.com';
var now = moment(new Date()).format('YYYY-MM-DD');
// TODO(berks): refactor async to rx
async.parallel({
users: function(callback) {
User.find(
{
where: { 'profile.username': { nlike: '' } },
fields: { 'profile.username': true }
},
function(err, users) {
if (err) {
debug('User err: ', err);
callback(err);
} else {
Rx.Observable.from(users)
.map(function(user) {
return user.profile.username;
})
.toArray()
.subscribe(
function(usernames) {
callback(null, usernames);
},
callback
);
}
});
},
challenges: function (callback) {
Challenge.find(
{ fields: { name: true } },
function (err, challenges) {
if (err) {
debug('Challenge err: ', err);
callback(err);
} else {
Rx.Observable.from(challenges)
.map(function(challenge) {
return challenge.name;
})
.toArray()
.subscribe(
callback.bind(callback, null),
callback
);
}
});
},
stories: function (callback) {
Story.find(
{ field: { link: true } },
function (err, stories) {
if (err) {
debug('Story err: ', err);
callback(err);
} else {
Rx.Observable.from(stories)
.map(function(story) {
return story.link;
})
.toArray()
.subscribe(
callback.bind(callback, null),
callback
);
}
}
);
},
nonprofits: function (callback) {
Nonprofit.find(
{ field: { name: true } },
function(err, nonprofits) {
if (err) {
debug('User err: ', err);
callback(err);
} else {
Rx.Observable.from(nonprofits)
.map(function(nonprofit) {
return nonprofit.name;
})
.toArray()
.subscribe(
callback.bind(callback, null),
callback
);
}
});
},
fieldGuides: function(callback) {
FieldGuide.find(
{ field: { name: true } },
function(err, fieldGuides) {
if (err) {
debug('User err: ', err);
callback(err);
} else {
Rx.Observable.from(fieldGuides)
.map(function(fieldGuide) {
return fieldGuide.name;
})
.toArray()
.subscribe(
callback.bind(callback, null),
callback
);
}
});
}
}, function(err, results) {
if (err) {
return next(err);
}
setTimeout(function() {
res.header('Content-Type', 'application/xml');
res.render('resources/sitemap', {
appUrl: appUrl,
now: now,
users: results.users,
challenges: results.challenges,
stories: results.stories,
nonprofits: results.nonprofits,
fieldGuides: results.fieldGuides
});
}, 0);
}
);
}
function chat(req, res) {
if (req.user && req.user.progressTimestamps.length > 5) {
res.redirect('http://freecodecamp.slack.com');
} else {
res.render('resources/chat', {
title: 'Watch us code live on Twitch.tv'
});
}
}
function jobsForm(req, res) {
res.render('resources/jobs-form', {
title: 'Employer Partnership Form for Job Postings,' +
' Recruitment and Corporate Sponsorships'
});
}
function catPhotoSubmit(req, res) {
res.send(
'Success! You have submitted your cat photo. Return to your website ' +
'by typing any letter into your code editor.'
);
}
function nonprofits(req, res) {
res.render('resources/nonprofits', {
title: 'A guide to our Nonprofit Projects'
});
}
function nonprofitsForm(req, res) {
res.render('resources/nonprofits-form', {
title: 'Nonprofit Projects Proposal Form'
});
}
function agileProjectManagers(req, res) {
res.render('resources/pmi-acp-agile-project-managers', {
title: 'Get Agile Project Management Experience for the PMI-ACP'
});
}
function agileProjectManagersForm(req, res) {
res.render('resources/pmi-acp-agile-project-managers-form', {
title: 'Agile Project Management Program Application Form'
});
}
function twitch(req, res) {
res.render('resources/twitch', {
title: 'Enter Free Code Camp\'s Chat Rooms'
});
}
function unsubscribe(req, res, next) {
User.findOne({ email: req.params.email }, function(err, user) {
if (user) {
if (err) {
return next(err);
}
user.sendMonthlyEmail = false;
user.save(function () {
if (err) {
return next(err);
}
res.redirect('/unsubscribed');
});
} else {
res.redirect('/unsubscribed');
}
});
}
function unsubscribed(req, res) {
res.render('resources/unsubscribed', {
title: 'You have been unsubscribed'
});
}
function githubCalls(req, res, next) {
var githubHeaders = {
headers: {
'User-Agent': constantStrings.gitHubUserAgent
},
port: 80
};
request(
[
'https://api.github.com/repos/freecodecamp/',
'freecodecamp/pulls?client_id=',
secrets.github.clientID,
'&client_secret=',
secrets.github.clientSecret
].join(''),
githubHeaders,
function(err, status1, pulls) {
if (err) { return next(err); }
pulls = pulls ?
Object.keys(JSON.parse(pulls)).length :
'Can\'t connect to github';
request(
[
'https://api.github.com/repos/freecodecamp/',
'freecodecamp/issues?client_id=',
secrets.github.clientID,
'&client_secret=',
secrets.github.clientSecret
].join(''),
githubHeaders,
function (err, status2, issues) {
if (err) { return next(err); }
issues = ((pulls === parseInt(pulls, 10)) && issues) ?
Object.keys(JSON.parse(issues)).length - pulls :
"Can't connect to GitHub";
res.send({
issues: issues,
pulls: pulls
});
}
);
}
);
}
function trelloCalls(req, res, next) {
request(
'https://trello.com/1/boards/BA3xVpz9/cards?key=' +
secrets.trello.key,
function(err, status, trello) {
if (err) { return next(err); }
trello = (status && status.statusCode === 200) ?
(JSON.parse(trello)) :
'Can\'t connect to to Trello';
res.end(JSON.stringify(trello));
});
}
function bloggerCalls(req, res, next) {
request(
'https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/' +
'posts?key=' +
secrets.blogger.key,
function (err, status, blog) {
if (err) { return next(err); }
blog = (status && status.statusCode === 200) ?
JSON.parse(blog) :
'Can\'t connect to Blogger';
res.end(JSON.stringify(blog));
}
);
}
function getCats(req, res) {
res.send(
[
{
'name': 'cute',
'imageLink': 'https://encrypted-tbn3.gstatic.com/images' +
'?q=tbn:ANd9GcRaP1ecF2jerISkdhjr4R9yM9-8ClUy-TA36MnDiFBukd5IvEME0g'
},
{
'name': 'grumpy',
'imageLink': 'http://cdn.grumpycats.com/wp-content/uploads/' +
'2012/09/GC-Gravatar-copy.png'
},
{
'name': 'mischievous',
'imageLink': 'http://www.kittenspet.com/wp-content' +
'/uploads/2012/08/cat_with_funny_face_3-200x200.jpg'
}
]
);
}
};

View File

@ -1,47 +1,50 @@
var express = require('express'); module.exports = function(app) {
var router = express.Router(); var router = app.loopback.Router();
router.get('/nonprofit-project-instructions', function(req, res) { router.get('/nonprofit-project-instructions', function(req, res) {
res.redirect( res.redirect(
301, 301,
'/field-guide/how-do-free-code-camp\'s-nonprofit-projects-work' '/field-guide/how-do-free-code-camp\'s-nonprofit-projects-work'
); );
}); });
router.get('/agile', function(req, res) { router.get('/agile', function(req, res) {
res.redirect(301, '/pmi-acp-agile-project-managers'); res.redirect(301, '/pmi-acp-agile-project-managers');
}); });
router.get('/live-pair-programming', function(req, res) { router.get('/live-pair-programming', function(req, res) {
res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv'); res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv');
}); });
router.get('/install-screenhero', function(req, res) { router.get('/install-screenhero', function(req, res) {
res.redirect(301, '/field-guide/install-screenhero'); res.redirect(301, '/field-guide/install-screenhero');
}); });
router.get('/guide-to-our-nonprofit-projects', function(req, res) { router.get('/guide-to-our-nonprofit-projects', function(req, res) {
res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects'); res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects');
}); });
router.get('/chromebook', function(req, res) { router.get('/chromebook', function(req, res) {
res.redirect(301, '/field-guide/chromebook'); res.redirect(301, '/field-guide/chromebook');
}); });
router.get('/deploy-a-website', function(req, res) { router.get('/deploy-a-website', function(req, res) {
res.redirect(301, '/field-guide/deploy-a-website'); res.redirect(301, '/field-guide/deploy-a-website');
}); });
router.get('/gmail-shortcuts', function(req, res) { router.get('/gmail-shortcuts', function(req, res) {
res.redirect(301, '/field-guide/gmail-shortcuts'); res.redirect(301, '/field-guide/gmail-shortcuts');
}); });
router.get('/nodeschool-challenges', function(req, res) { router.get('/nodeschool-challenges', function(req, res) {
res.redirect(301, '/field-guide/nodeschool-challenges'); res.redirect(301, '/field-guide/nodeschool-challenges');
}); });
router.get('/privacy', function(req, res) { router.get('/privacy', function(req, res) {
res.redirect(301, '/field-guide/what-is-the-free-code-camp-privacy-policy?'); res.redirect(
}); 301, '/field-guide/what-is-the-free-code-camp-privacy-policy?'
);
});
module.exports = router; app.use(router);
};

4
server/boot/restApi.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = function mountRestApi(app) {
var restApiRoot = app.get('restApiRoot');
app.use(restApiRoot, app.loopback.rest());
};

View File

@ -1,33 +1,35 @@
var nodemailer = require('nodemailer'), var nodemailer = require('nodemailer'),
sanitizeHtml = require('sanitize-html'), sanitizeHtml = require('sanitize-html'),
express = require('express'),
moment = require('moment'), moment = require('moment'),
mongodb = require('mongodb'), mongodb = require('mongodb'),
// debug = require('debug')('freecc:cntr:story'), // debug = require('debug')('freecc:cntr:story'),
Story = require('../../common/models/Story'), utils = require('../utils'),
Comment = require('../../common/models/Comment'),
User = require('../../common/models/User'),
resources = require('../resources/resources'),
MongoClient = mongodb.MongoClient, MongoClient = mongodb.MongoClient,
secrets = require('../../config/secrets'), secrets = require('../../config/secrets');
router = express.Router();
router.get('/stories/hotStories', hotJSON); module.exports = function(app) {
router.get('/stories/recentStories', recentJSON); var router = app.loopback.Router();
router.get('/stories/comments/:id', comments); var User = app.models.User;
router.post('/stories/comment/', commentSubmit); var Story = app.models.Story;
router.post('/stories/comment/:id/comment', commentOnCommentSubmit);
router.put('/stories/comment/:id/edit', commentEdit);
router.get('/stories/submit', submitNew);
router.get('/stories/submit/new-story', preSubmit);
router.post('/stories/preliminary', newStory);
router.post('/stories/', storySubmission);
router.get('/news/', hot);
router.post('/stories/search', getStories);
router.get('/news/:storyName', returnIndividualStory);
router.post('/stories/upvote/', upvote);
function hotRank(timeValue, rank) { router.get('/stories/hotStories', hotJSON);
router.get('/stories/recentStories', recentJSON);
router.get('/stories/comments/:id', comments);
router.post('/stories/comment/', commentSubmit);
router.post('/stories/comment/:id/comment', commentOnCommentSubmit);
router.put('/stories/comment/:id/edit', commentEdit);
router.get('/stories/submit', submitNew);
router.get('/stories/submit/new-story', preSubmit);
router.post('/stories/preliminary', newStory);
router.post('/stories/', storySubmission);
router.get('/news/', hot);
router.post('/stories/search', getStories);
router.get('/news/:storyName', returnIndividualStory);
router.post('/stories/upvote/', upvote);
app.use(router);
function hotRank(timeValue, rank) {
/* /*
* Hotness ranking algorithm: http://amix.dk/blog/post/19588 * Hotness ranking algorithm: http://amix.dk/blog/post/19588
* tMS = postedOnDate - foundationTime; * tMS = postedOnDate - foundationTime;
@ -39,10 +41,9 @@ function hotRank(timeValue, rank) {
var z = Math.log(rank) / Math.log(10); var z = Math.log(rank) / Math.log(10);
hotness = z + (timeValue / time48Hours); hotness = z + (timeValue / time48Hours);
return hotness; return hotness;
}
} function hotJSON(req, res, next) {
function hotJSON(req, res, next) {
var story = Story.find({}).sort({'timePosted': -1}).limit(1000); var story = Story.find({}).sort({'timePosted': -1}).limit(1000);
story.exec(function(err, stories) { story.exec(function(err, stories) {
if (err) { if (err) {
@ -60,9 +61,9 @@ function hotJSON(req, res, next) {
}).slice(0, sliceVal)); }).slice(0, sliceVal));
}); });
} }
function recentJSON(req, res, next) { function recentJSON(req, res, next) {
var story = Story.find({}).sort({'timePosted': -1}).limit(100); var story = Story.find({}).sort({'timePosted': -1}).limit(100);
story.exec(function(err, stories) { story.exec(function(err, stories) {
if (err) { if (err) {
@ -70,40 +71,40 @@ function recentJSON(req, res, next) {
} }
return res.json(stories); return res.json(stories);
}); });
} }
function hot(req, res) { function hot(req, res) {
return res.render('stories/index', { return res.render('stories/index', {
title: 'Hot stories currently trending on Camper News', title: 'Hot stories currently trending on Camper News',
page: 'hot' page: 'hot'
}); });
} }
function submitNew(req, res) { function submitNew(req, res) {
return res.render('stories/index', { return res.render('stories/index', {
title: 'Submit a new story to Camper News', title: 'Submit a new story to Camper News',
page: 'submit' page: 'submit'
}); });
} }
/* /*
* no used anywhere * no used anywhere
function search(req, res) { function search(req, res) {
return res.render('stories/index', { return res.render('stories/index', {
title: 'Search the archives of Camper News', title: 'Search the archives of Camper News',
page: 'search' page: 'search'
}); });
} }
function recent(req, res) { function recent(req, res) {
return res.render('stories/index', { return res.render('stories/index', {
title: 'Recently submitted stories on Camper News', title: 'Recently submitted stories on Camper News',
page: 'recent' page: 'recent'
}); });
} }
*/ */
function preSubmit(req, res) { function preSubmit(req, res) {
var data = req.query; var data = req.query;
var cleanData = sanitizeHtml(data.url, { var cleanData = sanitizeHtml(data.url, {
@ -131,10 +132,10 @@ function preSubmit(req, res) {
storyImage: image, storyImage: image,
storyMetaDescription: description storyMetaDescription: description
}); });
} }
function returnIndividualStory(req, res, next) { function returnIndividualStory(req, res, next) {
var dashedName = req.params.storyName; var dashedName = req.params.storyName;
var storyName = dashedName.replace(/\-/g, ' ').trim(); var storyName = dashedName.replace(/\-/g, ' ').trim();
@ -191,9 +192,9 @@ function returnIndividualStory(req, res, next) {
hasUserVoted: userVoted hasUserVoted: userVoted
}); });
}); });
} }
function getStories(req, res, next) { function getStories(req, res, next) {
MongoClient.connect(secrets.db, function(err, database) { MongoClient.connect(secrets.db, function(err, database) {
if (err) { if (err) {
return next(err); return next(err);
@ -233,9 +234,9 @@ function getStories(req, res, next) {
return res.sendStatus(404); return res.sendStatus(404);
}); });
}); });
} }
function upvote(req, res, next) { function upvote(req, res, next) {
var data = req.body.data; var data = req.body.data;
Story.find({'_id': data.id}, function(err, story) { Story.find({'_id': data.id}, function(err, story) {
if (err) { if (err) {
@ -252,9 +253,12 @@ function upvote(req, res, next) {
story.markModified('rank'); story.markModified('rank');
story.save(); story.save();
// NOTE(Berks): This logic is full of wholes and race conditions // NOTE(Berks): This logic is full of wholes and race conditions
// this could be the source of many 'can't set headers after they are sent' // this could be the source of many 'can't set headers after
// they are sent'
// errors. This needs cleaning // errors. This needs cleaning
User.findOne({'_id': story.author.userId}, function(err, user) { User.findOne(
{ where: { id: story.author.userId } },
function(err, user) {
if (err) { return next(err); } if (err) { return next(err); }
user.progressTimestamps.push(Date.now() || 0); user.progressTimestamps.push(Date.now() || 0);
@ -267,23 +271,26 @@ function upvote(req, res, next) {
return next(err); return next(err);
} }
}); });
}); }
);
return res.send(story); return res.send(story);
}); });
} }
function comments(req, res, next) { function comments(req, res, next) {
var data = req.params.id; var data = req.params.id;
Comment.find({'_id': data}, function(err, comment) { Comment.find(
{ where: {'_id': data } },
function(err, comment) {
if (err) { if (err) {
return next(err); return next(err);
} }
comment = comment.pop(); comment = comment.pop();
return res.send(comment); return res.send(comment);
}); });
} }
function newStory(req, res, next) { function newStory(req, res, next) {
if (!req.user) { if (!req.user) {
return next(new Error('Must be logged in')); return next(new Error('Must be logged in'));
} }
@ -305,7 +312,9 @@ function newStory(req, res, next) {
if (url.search(/^https?:\/\//g) === -1) { if (url.search(/^https?:\/\//g) === -1) {
url = 'http://' + url; url = 'http://' + url;
} }
Story.find({'link': url}, function(err, story) { Story.find(
{ where: {'link': url} },
function(err, story) {
if (err) { if (err) {
return next(err); return next(err);
} }
@ -318,8 +327,9 @@ function newStory(req, res, next) {
storyURL: '/news/' + story.pop().storyLink storyURL: '/news/' + story.pop().storyLink
}); });
} }
resources.getURLTitle(url, processResponse); utils.getURLTitle(url, processResponse);
}); }
);
function processResponse(err, story) { function processResponse(err, story) {
if (err) { if (err) {
@ -340,9 +350,9 @@ function newStory(req, res, next) {
}); });
} }
} }
} }
function storySubmission(req, res, next) { function storySubmission(req, res, next) {
var data = req.body.data; var data = req.body.data;
if (!req.user) { if (!req.user) {
return next(new Error('Not authorized')); return next(new Error('Not authorized'));
@ -360,7 +370,7 @@ function storySubmission(req, res, next) {
} }
Story.count({ Story.count({
storyLink: new RegExp('^' + storyLink + '(?: [0-9]+)?$', 'i') storyLink: { like: new RegExp('^' + storyLink + '(?: [0-9]+)?$', 'i') }
}, function (err, storyCount) { }, function (err, storyCount) {
if (err) { if (err) {
return next(err); return next(err);
@ -416,9 +426,9 @@ function storySubmission(req, res, next) {
}); });
}); });
}); });
} }
function commentSubmit(req, res, next) { function commentSubmit(req, res, next) {
var data = req.body.data; var data = req.body.data;
if (!req.user) { if (!req.user) {
return next(new Error('Not authorized')); return next(new Error('Not authorized'));
@ -453,25 +463,29 @@ function commentSubmit(req, res, next) {
}); });
commentSave(comment, Story, res, next); commentSave(comment, Story, res, next);
} }
function commentOnCommentSubmit(req, res, next) { function commentOnCommentSubmit(req, res, next) {
var data = req.body.data; var data = req.body.data;
if (!req.user) { if (!req.user) {
return next(new Error('Not authorized')); return next(new Error('Not authorized'));
} }
var sanitizedBody = sanitizeHtml(data.body, var sanitizedBody = sanitizeHtml(
data.body,
{ {
allowedTags: [], allowedTags: [],
allowedAttributes: [] allowedAttributes: []
}).replace(/&quot;/g, '"'); }
).replace(/&quot;/g, '"');
if (data.body !== sanitizedBody) { if (data.body !== sanitizedBody) {
req.flash('errors', { req.flash('errors', {
msg: 'HTML is not allowed' msg: 'HTML is not allowed'
}); });
return res.send(true); return res.send(true);
} }
var comment = new Comment({ var comment = new Comment({
associatedPost: data.associatedPost, associatedPost: data.associatedPost,
body: sanitizedBody, body: sanitizedBody,
@ -490,11 +504,11 @@ function commentOnCommentSubmit(req, res, next) {
commentOn: Date.now() commentOn: Date.now()
}); });
commentSave(comment, Comment, res, next); commentSave(comment, Comment, res, next);
} }
function commentEdit(req, res, next) { function commentEdit(req, res, next) {
Comment.find({'_id': req.params.id}, function(err, cmt) { Comment.find({ id: req.params.id }, function(err, cmt) {
if (err) { if (err) {
return next(err); return next(err);
} }
@ -504,7 +518,6 @@ function commentEdit(req, res, next) {
return next(new Error('Not authorized')); return next(new Error('Not authorized'));
} }
var sanitizedBody = sanitizeHtml(req.body.body, { var sanitizedBody = sanitizeHtml(req.body.body, {
allowedTags: [], allowedTags: [],
allowedAttributes: [] allowedAttributes: []
@ -518,7 +531,7 @@ function commentEdit(req, res, next) {
cmt.body = sanitizedBody; cmt.body = sanitizedBody;
cmt.commentOn = Date.now(); cmt.commentOn = Date.now();
cmt.save(function (err) { cmt.save(function(err) {
if (err) { if (err) {
return next(err); return next(err);
} }
@ -527,9 +540,9 @@ function commentEdit(req, res, next) {
}); });
} }
function commentSave(comment, Context, res, next) { function commentSave(comment, Context, res, next) {
comment.save(function(err, data) { comment.save(function(err, data) {
if (err) { if (err) {
return next(err); return next(err);
@ -538,7 +551,7 @@ function commentSave(comment, Context, res, next) {
// Based on the context retrieve the parent // Based on the context retrieve the parent
// object of the comment (Story/Comment) // object of the comment (Story/Comment)
Context.find({ Context.find({
'_id': data.associatedPost id: data.associatedPost
}, function (err, associatedContext) { }, function (err, associatedContext) {
if (err) { if (err) {
return next(err); return next(err);
@ -604,6 +617,5 @@ function commentSave(comment, Context, res, next) {
return next(err); return next(err);
} }
}); });
} }
};
module.exports = router;

View File

@ -3,65 +3,70 @@ var _ = require('lodash'),
async = require('async'), async = require('async'),
crypto = require('crypto'), crypto = require('crypto'),
nodemailer = require('nodemailer'), nodemailer = require('nodemailer'),
passport = require('passport'),
moment = require('moment'), moment = require('moment'),
express = require('express'),
debug = require('debug')('freecc:cntr:userController'), debug = require('debug')('freecc:cntr:userController'),
User = require('../../common/models/User'), secrets = require('../../config/secrets');
secrets = require('../../config/secrets'),
resources = require('./../resources/resources');
var router = express.Router(); module.exports = function(app) {
router.get('/login', function(req, res) { var router = app.loopback.Router();
var User = app.models.User;
var Story = app.models.Story;
var Comment = app.models.Comment;
router.get('/login', function(req, res) {
res.redirect(301, '/signin'); res.redirect(301, '/signin');
}); });
router.get('/logout', function(req, res) { router.get('/logout', function(req, res) {
res.redirect(301, '/signout'); res.redirect(301, '/signout');
}); });
router.get('/signin', getSignin); router.get('/signin', getSignin);
router.post('/signin', postSignin); // router.post('/signin', postSignin);
router.get('/signout', signout); router.get('/signout', signout);
router.get('/forgot', getForgot); router.get('/forgot', getForgot);
router.post('/forgot', postForgot); router.post('/forgot', postForgot);
router.get('/reset/:token', getReset); router.get('/reset/:token', getReset);
router.post('/reset/:token', postReset); router.post('/reset/:token', postReset);
router.get('/email-signup', getEmailSignup); router.get('/email-signup', getEmailSignup);
router.get('/email-signin', getEmailSignin); router.get('/email-signin', getEmailSignin);
router.post('/email-signup', postEmailSignup); router.post('/email-signup', postEmailSignup);
router.post('/email-signin', postSignin); // router.post('/email-signin', postSignin);
router.get('/account/api', getAccountAngular); router.get('/account/api', getAccountAngular);
router.get('/api/checkUniqueUsername/:username', checkUniqueUsername); router.get('/api/checkUniqueUsername/:username', checkUniqueUsername);
router.get('/api/checkExistingUsername/:username', checkExistingUsername); router.get('/api/checkExistingUsername/:username', checkExistingUsername);
router.get('/api/checkUniqueEmail/:email', checkUniqueEmail); router.get('/api/checkUniqueEmail/:email', checkUniqueEmail);
router.post('/account/profile', postUpdateProfile); router.post('/account/profile', postUpdateProfile);
router.post('/account/password', postUpdatePassword); router.post('/account/password', postUpdatePassword);
router.post('/account/delete', postDeleteAccount); router.post('/account/delete', postDeleteAccount);
router.get('/account/unlink/:provider', getOauthUnlink); router.get('/account/unlink/:provider', getOauthUnlink);
router.get('/account', getAccount); router.get('/account', getAccount);
// Ensure this is the last route! // Ensure this is the last route!
router.get('/:username', returnUser); router.get('/:username', returnUser);
/** app.use(router);
/**
* GET /signin * GET /signin
* Siginin page. * Siginin page.
*/ */
function getSignin (req, res) { function getSignin (req, res) {
if (req.user) { if (req.user) {
return res.redirect('/'); return res.redirect('/');
} }
res.render('account/signin', { res.render('account/signin', {
title: 'Free Code Camp Login' title: 'Free Code Camp Login'
}); });
} }
/** /**
* POST /signin * POST /signin
* Sign in using email and password. * Sign in using email and password.
*/ */
function postSignin (req, res, next) { /*
* TODO(berks): this should be done using loopback
function postSignin (req, res, next) {
req.assert('email', 'Email is not valid').isEmail(); req.assert('email', 'Email is not valid').isEmail();
req.assert('password', 'Password cannot be blank').notEmpty(); req.assert('password', 'Password cannot be blank').notEmpty();
@ -94,52 +99,53 @@ function postSignin (req, res, next) {
return res.redirect(req.session.returnTo || '/'); return res.redirect(req.session.returnTo || '/');
}); });
})(req, res, next); })(req, res, next);
} }
*/
/** /**
* GET /signout * GET /signout
* Log out. * Log out.
*/ */
function signout (req, res) { function signout (req, res) {
req.logout(); req.logout();
res.redirect('/'); res.redirect('/');
} }
/** /**
* GET /email-signup * GET /email-signup
* Signup page. * Signup page.
*/ */
function getEmailSignin (req, res) { function getEmailSignin (req, res) {
if (req.user) { if (req.user) {
return res.redirect('/'); return res.redirect('/');
} }
res.render('account/email-signin', { res.render('account/email-signin', {
title: 'Sign in to your Free Code Camp Account' title: 'Sign in to your Free Code Camp Account'
}); });
} }
/** /**
* GET /signin * GET /signin
* Signup page. * Signup page.
*/ */
function getEmailSignup (req, res) { function getEmailSignup (req, res) {
if (req.user) { if (req.user) {
return res.redirect('/'); return res.redirect('/');
} }
res.render('account/email-signup', { res.render('account/email-signup', {
title: 'Create Your Free Code Camp Account' title: 'Create Your Free Code Camp Account'
}); });
} }
/** /**
* POST /email-signup * POST /email-signup
* Create a new local account. * Create a new local account.
*/ */
function postEmailSignup (req, res, next) { function postEmailSignup (req, res, next) {
req.assert('email', 'valid email required').isEmail(); req.assert('email', 'valid email required').isEmail();
var errors = req.validationErrors(); var errors = req.validationErrors();
@ -233,34 +239,34 @@ function postEmailSignup (req, res, next) {
}); });
}); });
}); });
} }
/** /**
* GET /account * GET /account
* Profile page. * Profile page.
*/ */
function getAccount (req, res) { function getAccount (req, res) {
res.render('account/account', { res.render('account/account', {
title: 'Manage your Free Code Camp Account' title: 'Manage your Free Code Camp Account'
}); });
} }
/** /**
* Angular API Call * Angular API Call
*/ */
function getAccountAngular (req, res) { function getAccountAngular (req, res) {
res.json({ res.json({
user: req.user user: req.user
}); });
} }
/** /**
* Unique username check API Call * Unique username check API Call
*/ */
function checkUniqueUsername (req, res, next) { function checkUniqueUsername (req, res, next) {
User.count( User.count(
{ 'profile.username': req.params.username.toLowerCase() }, { 'profile.username': req.params.username.toLowerCase() },
function (err, data) { function (err, data) {
@ -271,13 +277,13 @@ function checkUniqueUsername (req, res, next) {
return res.send(false); return res.send(false);
} }
}); });
} }
/** /**
* Existing username check * Existing username check
*/ */
function checkExistingUsername (req, res, next) { function checkExistingUsername (req, res, next) {
User.count( User.count(
{ 'profile.username': req.params.username.toLowerCase() }, { 'profile.username': req.params.username.toLowerCase() },
function (err, data) { function (err, data) {
@ -289,13 +295,13 @@ function checkExistingUsername (req, res, next) {
} }
} }
); );
} }
/** /**
* Unique email check API Call * Unique email check API Call
*/ */
function checkUniqueEmail (req, res, next) { function checkUniqueEmail (req, res, next) {
User.count( User.count(
{ email: decodeURIComponent(req.params.email).toLowerCase() }, { email: decodeURIComponent(req.params.email).toLowerCase() },
function (err, data) { function (err, data) {
@ -307,15 +313,15 @@ function checkUniqueEmail (req, res, next) {
} }
} }
); );
} }
/** /**
* GET /campers/:username * GET /campers/:username
* Public Profile page. * Public Profile page.
*/ */
function returnUser (req, res, next) { function returnUser (req, res, next) {
User.find( User.find(
{ 'profile.username': req.params.username.toLowerCase() }, { 'profile.username': req.params.username.toLowerCase() },
function(err, user) { function(err, user) {
@ -325,8 +331,8 @@ function returnUser (req, res, next) {
} }
if (user[0]) { if (user[0]) {
user = user[0]; user = user[0];
user.progressTimestamps =
user.progressTimestamps = user.progressTimestamps.sort(function(a, b) { user.progressTimestamps.sort(function(a, b) {
return a - b; return a - b;
}); });
@ -442,14 +448,14 @@ function returnUser (req, res, next) {
} }
} }
); );
} }
/** /**
* POST /account/profile * POST /account/profile
* Update profile information. * Update profile information.
*/ */
function postUpdateProfile (req, res, next) { function postUpdateProfile (req, res, next) {
User.findById(req.user.id, function(err) { User.findById(req.user.id, function(err) {
if (err) { return next(err); } if (err) { return next(err); }
@ -486,36 +492,41 @@ function postUpdateProfile (req, res, next) {
}); });
return res.redirect('/account'); return res.redirect('/account');
} }
user.email = req.body.email.trim() || ''; var body = req.body || {};
user.profile.name = req.body.name.trim() || ''; user.email = body.email.trim() || '';
user.profile.username = req.body.username.trim() || ''; user.profile.name = body.name.trim() || '';
user.profile.location = req.body.location.trim() || ''; user.profile.username = body.username.trim() || '';
user.profile.githubProfile = req.body.githubProfile.trim() || ''; user.profile.location = body.location.trim() || '';
user.profile.facebookProfile = req.body.facebookProfile.trim() || '';
user.profile.linkedinProfile = req.body.linkedinProfile.trim() || '';
user.profile.codepenProfile = req.body.codepenProfile.trim() || '';
user.profile.twitterHandle = req.body.twitterHandle.trim() || '';
user.profile.bio = req.body.bio.trim() || '';
user.profile.picture = req.body.picture.trim() || user.profile.githubProfile = body.githubProfile.trim() || '';
user.profile.facebookProfile = body.facebookProfile.trim() || '';
user.profile.linkedinProfile = body.linkedinProfile.trim() || '';
user.profile.codepenProfile = body.codepenProfile.trim() || '';
user.profile.twitterHandle = body.twitterHandle.trim() || '';
user.profile.bio = body.bio.trim() || '';
user.profile.picture = body.picture.trim() ||
'https://s3.amazonaws.com/freecodecamp/' + 'https://s3.amazonaws.com/freecodecamp/' +
'camper-image-placeholder.png'; 'camper-image-placeholder.png';
user.portfolio.website1Title = req.body.website1Title.trim() || ''; user.portfolio.website1Title = body.website1Title.trim() || '';
user.portfolio.website1Link = req.body.website1Link.trim() || ''; user.portfolio.website1Link = body.website1Link.trim() || '';
user.portfolio.website1Image = req.body.website1Image.trim() || ''; user.portfolio.website1Image = body.website1Image.trim() || '';
user.portfolio.website2Title = req.body.website2Title.trim() || '';
user.portfolio.website2Link = req.body.website2Link.trim() || ''; user.portfolio.website2Title = body.website2Title.trim() || '';
user.portfolio.website2Image = req.body.website2Image.trim() || ''; user.portfolio.website2Link = body.website2Link.trim() || '';
user.portfolio.website3Title = req.body.website3Title.trim() || ''; user.portfolio.website2Image = body.website2Image.trim() || '';
user.portfolio.website3Link = req.body.website3Link.trim() || '';
user.portfolio.website3Image = req.body.website3Image.trim() || ''; user.portfolio.website3Title = body.website3Title.trim() || '';
user.portfolio.website3Link = body.website3Link.trim() || '';
user.portfolio.website3Image = body.website3Image.trim() || '';
user.save(function (err) { user.save(function (err) {
if (err) { if (err) {
return next(err); return next(err);
} }
resources.updateUserStoryPictures( updateUserStoryPictures(
user._id.toString(), user._id.toString(),
user.profile.picture, user.profile.picture,
user.profile.username, user.profile.username,
@ -532,15 +543,17 @@ function postUpdateProfile (req, res, next) {
); );
}); });
}); });
} }
/** /**
* POST /account/password * POST /account/password
* Update current password. * Update current password.
*/ */
function postUpdatePassword (req, res, next) { function postUpdatePassword (req, res, next) {
req.assert('password', 'Password must be at least 4 characters long').len(4); req.assert('password', 'Password must be at least 4 characters long')
.len(4);
req.assert('confirmPassword', 'Passwords do not match') req.assert('confirmPassword', 'Passwords do not match')
.equals(req.body.password); .equals(req.body.password);
@ -563,28 +576,28 @@ function postUpdatePassword (req, res, next) {
res.redirect('/account'); res.redirect('/account');
}); });
}); });
} }
/** /**
* POST /account/delete * POST /account/delete
* Delete user account. * Delete user account.
*/ */
function postDeleteAccount (req, res, next) { function postDeleteAccount (req, res, next) {
User.remove({ _id: req.user.id }, function(err) { User.destroyById(req.user.id, function(err) {
if (err) { return next(err); } if (err) { return next(err); }
req.logout(); req.logout();
req.flash('info', { msg: 'Your account has been deleted.' }); req.flash('info', { msg: 'Your account has been deleted.' });
res.redirect('/'); res.redirect('/');
}); });
} }
/** /**
* GET /account/unlink/:provider * GET /account/unlink/:provider
* Unlink OAuth provider. * Unlink OAuth provider.
*/ */
function getOauthUnlink (req, res, next) { function getOauthUnlink (req, res, next) {
var provider = req.params.provider; var provider = req.params.provider;
User.findById(req.user.id, function(err, user) { User.findById(req.user.id, function(err, user) {
if (err) { return next(err); } if (err) { return next(err); }
@ -601,21 +614,25 @@ function getOauthUnlink (req, res, next) {
res.redirect('/account'); res.redirect('/account');
}); });
}); });
} }
/** /**
* GET /reset/:token * GET /reset/:token
* Reset Password page. * Reset Password page.
*/ */
function getReset (req, res, next) { function getReset (req, res, next) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
return res.redirect('/'); return res.redirect('/');
} }
User User.findOne(
.findOne({ resetPasswordToken: req.params.token }) {
.where('resetPasswordExpires').gt(Date.now()) where: {
.exec(function(err, user) { resetPasswordToken: req.params.token,
resetPasswordExpires: Date.now()
}
},
function(err, user) {
if (err) { return next(err); } if (err) { return next(err); }
if (!user) { if (!user) {
req.flash('errors', { req.flash('errors', {
@ -628,14 +645,14 @@ function getReset (req, res, next) {
token: req.params.token token: req.params.token
}); });
}); });
} }
/** /**
* POST /reset/:token * POST /reset/:token
* Process the reset password request. * Process the reset password request.
*/ */
function postReset (req, res, next) { function postReset (req, res, next) {
var errors = req.validationErrors(); var errors = req.validationErrors();
if (errors) { if (errors) {
@ -645,10 +662,14 @@ function postReset (req, res, next) {
async.waterfall([ async.waterfall([
function(done) { function(done) {
User User.findOne(
.findOne({ resetPasswordToken: req.params.token }) {
.where('resetPasswordExpires').gt(Date.now()) where: {
.exec(function(err, user) { resetPasswordToken: req.params.token,
resetPasswordExpires: Date.now()
}
},
function(err, user) {
if (err) { return next(err); } if (err) { return next(err); }
if (!user) { if (!user) {
req.flash('errors', { req.flash('errors', {
@ -702,28 +723,28 @@ function postReset (req, res, next) {
if (err) { return next(err); } if (err) { return next(err); }
res.redirect('/'); res.redirect('/');
}); });
} }
/** /**
* GET /forgot * GET /forgot
* Forgot Password page. * Forgot Password page.
*/ */
function getForgot (req, res) { function getForgot (req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
return res.redirect('/'); return res.redirect('/');
} }
res.render('account/forgot', { res.render('account/forgot', {
title: 'Forgot Password' title: 'Forgot Password'
}); });
} }
/** /**
* POST /forgot * POST /forgot
* Create a random token, then the send user an email with a reset link. * Create a random token, then the send user an email with a reset link.
*/ */
function postForgot (req, res, next) { function postForgot (req, res, next) {
var errors = req.validationErrors(); var errors = req.validationErrors();
if (errors) { if (errors) {
@ -801,6 +822,60 @@ function postForgot (req, res, next) {
if (err) { return next(err); } if (err) { return next(err); }
res.redirect('/forgot'); res.redirect('/forgot');
}); });
} }
module.exports = router; function updateUserStoryPictures(userId, picture, username, cb) {
var counter = 0,
foundStories,
foundComments;
Story.find({ 'author.userId': userId }, function (err, stories) {
if (err) {
return cb(err);
}
foundStories = stories;
counter++;
saveStoriesAndComments();
});
Comment.find({ 'author.userId': userId }, function (err, comments) {
if (err) {
return cb(err);
}
foundComments = comments;
counter++;
saveStoriesAndComments();
});
function saveStoriesAndComments() {
if (counter !== 2) {
return;
}
var tasks = [];
R.forEach(function (comment) {
comment.author.picture = picture;
comment.author.username = username;
comment.markModified('author');
tasks.push(function (cb) {
comment.save(cb);
});
}, foundComments);
R.forEach(function (story) {
story.author.picture = picture;
story.author.username = username;
story.markModified('author');
tasks.push(function (cb) {
story.save(cb);
});
}, foundStories);
async.parallel(tasks, function (err) {
if (err) {
return cb(err);
}
cb();
});
}
}
};

View File

@ -1,446 +0,0 @@
var express = require('express'),
async = require('async'),
moment = require('moment'),
Twit = require('twit'),
Slack = require('node-slack'),
request = require('request'),
debug = require('debug')('freecc:cntr:resources'),
constantStrings = require('../resources/constantStrings.json'),
User = require('../../common/models/User'),
Challenge = require('../../common/models/Challenge'),
Story = require('../../common/models/Story'),
FieldGuide = require('../../common/models/FieldGuide'),
Nonprofit = require('../../common/models/Nonprofit'),
secrets = require('../../config/secrets');
var slack = new Slack(secrets.slackHook);
var router = express.Router();
router.get('/api/github', githubCalls);
router.get('/api/blogger', bloggerCalls);
router.get('/api/trello', trelloCalls);
router.get('/api/codepen/twitter/:screenName', twitter);
router.get('/sitemap.xml', sitemap);
router.post('/get-help', getHelp);
router.post('/get-pair', getPair);
router.get('/chat', chat);
router.get('/twitch', twitch);
router.get('/pmi-acp-agile-project-managers', agileProjectManagers);
router.get('/pmi-acp-agile-project-managers-form', agileProjectManagersForm);
router.get('/nonprofits', nonprofits);
router.get('/nonprofits-form', nonprofitsForm);
router.get('/jobs-form', jobsForm);
router.get('/submit-cat-photo', catPhotoSubmit);
router.get('/unsubscribe/:email', unsubscribe);
router.get('/unsubscribed', unsubscribed);
router.get('/cats.json', getCats);
router.get('/api/slack', slackInvite);
function slackInvite(req, res, next) {
if (req.user) {
if (req.user.email) {
var invite = {
'email': req.user.email,
'token': process.env.SLACK_KEY,
'set_active': true
};
var headers = {
'User-Agent': 'Node Browser/0.0.1',
'Content-Type': 'application/x-www-form-urlencoded'
};
var options = {
url: 'https://freecodecamp.slack.com/api/users.admin.invite',
method: 'POST',
headers: headers,
form: invite
};
request(options, function (error, response) {
if (!error && response.statusCode === 200) {
req.flash('success', {
msg: 'We\'ve successfully requested an invite for you.' +
' Please check your email and follow the instructions from Slack.'
});
req.user.sentSlackInvite = true;
req.user.save(function(err) {
if (err) {
return next(err);
}
return res.redirect('back');
});
} else {
req.flash('errors', {
msg: 'The invitation email did not go through for some reason.' +
' Please try again or <a href=\'mailto:team@' +
'freecodecamp.com?subject=' +
'slack%20invite%20failed%20to%20send\'>' +
'email us</a>.'
});
return res.redirect('back');
}
});
} else {
req.flash('notice', {
msg: 'Before we can send your Slack invite, we need your email ' +
'address. Please update your profile information here.'
});
return res.redirect('/account');
}
} else {
req.flash('notice', {
msg: 'You need to sign in to Free Code Camp before ' +
'we can send you a Slack invite.'
});
return res.redirect('/account');
}
}
function twitter(req, res, next) {
// sends out random tweets about javascript
var T = new Twit({
'consumer_key': secrets.twitter.consumerKey,
'consumer_secret': secrets.twitter.consumerSecret,
'access_token': secrets.twitter.token,
'access_token_secret': secrets.twitter.tokenSecret
});
var screenName;
if (req.params.screenName) {
screenName = req.params.screenName;
} else {
screenName = 'freecodecamp';
}
T.get(
'statuses/user_timeline',
{
'screen_name': screenName,
count: 10
},
function(err, data) {
if (err) { return next(err); }
return res.json(data);
}
);
}
function getHelp(req, res) {
var userName = req.user.profile.username;
var code = req.body.payload.code ? '\n```\n' +
req.body.payload.code + '\n```\n'
: '';
var challenge = req.body.payload.challenge;
slack.send({
text: '*@' + userName + '* wants help with ' + challenge + '. ' +
code + 'Hey, *@' + userName + '*, if no one helps you right ' +
'away, try typing out your problem in detail to me. Like this: ' +
'http://en.wikipedia.org/wiki/Rubber_duck_debugging',
channel: '#help',
username: 'Debuggy the Rubber Duck',
'icon_url': 'https://pbs.twimg.com/profile_images/' +
'3609875545/569237541c920fa78d78902069615caf.jpeg'
});
return res.sendStatus(200);
}
function getPair(req, res) {
var userName = req.user.profile.username;
var challenge = req.body.payload.challenge;
slack.send({
text: [
'Anyone want to pair with *@',
userName,
'* on ',
challenge,
'?\nMake sure you install Screen Hero here: ',
'http://freecodecamp.com/field-guide/how-do-i-install-screenhero\n',
'Then start your pair program session with *@',
userName,
'* by typing \"/hero @',
userName,
'\" into Slack.\n And *@',
userName,
'*, be sure to launch Screen Hero, then keep coding. ',
'Another camper may pair with you soon.'
].join(''),
channel: '#letspair',
username: 'Companion Cube',
'icon_url': 'https://lh3.googleusercontent.com/-f6xDPDV2rPE/AAAAAAAAAAI/' +
'AAAAAAAAAAA/mdlESXQu11Q/photo.jpg'
});
return res.sendStatus(200);
}
function sitemap(req, res, next) {
var appUrl = 'http://www.freecodecamp.com';
var now = moment(new Date()).format('YYYY-MM-DD');
async.parallel({
users: function(callback) {
User.aggregate()
.group({_id: 1, usernames: { $addToSet: '$profile.username'}})
.match({'profile.username': { $ne: ''}})
.exec(function(err, users) {
if (err) {
debug('User err: ', err);
callback(err);
} else {
callback(null, users[0].usernames);
}
});
},
challenges: function (callback) {
Challenge.aggregate()
.group({_id: 1, names: { $addToSet: '$name'}})
.exec(function (err, challenges) {
if (err) {
debug('Challenge err: ', err);
callback(err);
} else {
callback(null, challenges[0].names);
}
});
},
stories: function (callback) {
Story.aggregate()
.group({_id: 1, links: {$addToSet: '$link'}})
.exec(function (err, stories) {
if (err) {
debug('Story err: ', err);
callback(err);
} else {
callback(null, stories[0].links);
}
});
},
nonprofits: function (callback) {
Nonprofit.aggregate()
.group({_id: 1, names: { $addToSet: '$name'}})
.exec(function (err, nonprofits) {
if (err) {
debug('User err: ', err);
callback(err);
} else {
callback(null, nonprofits[0].names);
}
});
},
fieldGuides: function (callback) {
FieldGuide.aggregate()
.group({_id: 1, names: { $addToSet: '$name'}})
.exec(function (err, fieldGuides) {
if (err) {
debug('User err: ', err);
callback(err);
} else {
callback(null, fieldGuides[0].names);
}
});
}
}, function (err, results) {
if (err) {
return next(err);
} else {
setTimeout(function() {
res.header('Content-Type', 'application/xml');
res.render('resources/sitemap', {
appUrl: appUrl,
now: now,
users: results.users,
challenges: results.challenges,
stories: results.stories,
nonprofits: results.nonprofits,
fieldGuides: results.fieldGuides
});
}, 0);
}
}
);
}
function chat(req, res) {
if (req.user && req.user.progressTimestamps.length > 5) {
res.redirect('http://freecodecamp.slack.com');
} else {
res.render('resources/chat', {
title: 'Watch us code live on Twitch.tv'
});
}
}
function jobsForm(req, res) {
res.render('resources/jobs-form', {
title: 'Employer Partnership Form for Job Postings,' +
' Recruitment and Corporate Sponsorships'
});
}
function catPhotoSubmit(req, res) {
res.send(
'Success! You have submitted your cat photo. Return to your website ' +
'by typing any letter into your code editor.'
);
}
function nonprofits(req, res) {
res.render('resources/nonprofits', {
title: 'A guide to our Nonprofit Projects'
});
}
function nonprofitsForm(req, res) {
res.render('resources/nonprofits-form', {
title: 'Nonprofit Projects Proposal Form'
});
}
function agileProjectManagers(req, res) {
res.render('resources/pmi-acp-agile-project-managers', {
title: 'Get Agile Project Management Experience for the PMI-ACP'
});
}
function agileProjectManagersForm(req, res) {
res.render('resources/pmi-acp-agile-project-managers-form', {
title: 'Agile Project Management Program Application Form'
});
}
function twitch(req, res) {
res.render('resources/twitch', {
title: 'Enter Free Code Camp\'s Chat Rooms'
});
}
function unsubscribe(req, res, next) {
User.findOne({ email: req.params.email }, function(err, user) {
if (user) {
if (err) {
return next(err);
}
user.sendMonthlyEmail = false;
user.save(function () {
if (err) {
return next(err);
}
res.redirect('/unsubscribed');
});
} else {
res.redirect('/unsubscribed');
}
});
}
function unsubscribed(req, res) {
res.render('resources/unsubscribed', {
title: 'You have been unsubscribed'
});
}
function githubCalls(req, res, next) {
var githubHeaders = {
headers: {
'User-Agent': constantStrings.gitHubUserAgent
},
port: 80
};
request(
[
'https://api.github.com/repos/freecodecamp/',
'freecodecamp/pulls?client_id=',
secrets.github.clientID,
'&client_secret=',
secrets.github.clientSecret
].join(''),
githubHeaders,
function(err, status1, pulls) {
if (err) { return next(err); }
pulls = pulls ?
Object.keys(JSON.parse(pulls)).length :
'Can\'t connect to github';
request(
[
'https://api.github.com/repos/freecodecamp/',
'freecodecamp/issues?client_id=',
secrets.github.clientID,
'&client_secret=',
secrets.github.clientSecret
].join(''),
githubHeaders,
function (err, status2, issues) {
if (err) { return next(err); }
issues = ((pulls === parseInt(pulls, 10)) && issues) ?
Object.keys(JSON.parse(issues)).length - pulls :
"Can't connect to GitHub";
res.send({
issues: issues,
pulls: pulls
});
}
);
}
);
}
function trelloCalls(req, res, next) {
request(
'https://trello.com/1/boards/BA3xVpz9/cards?key=' +
secrets.trello.key,
function(err, status, trello) {
if (err) { return next(err); }
trello = (status && status.statusCode === 200) ?
(JSON.parse(trello)) :
'Can\'t connect to to Trello';
res.end(JSON.stringify(trello));
});
}
function bloggerCalls(req, res, next) {
request(
'https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/' +
'posts?key=' +
secrets.blogger.key,
function (err, status, blog) {
if (err) { return next(err); }
blog = (status && status.statusCode === 200) ?
JSON.parse(blog) :
'Can\'t connect to Blogger';
res.end(JSON.stringify(blog));
}
);
}
function getCats(req, res) {
res.send(
[
{
'name': 'cute',
'imageLink': 'https://encrypted-tbn3.gstatic.com/images' +
'?q=tbn:ANd9GcRaP1ecF2jerISkdhjr4R9yM9-8ClUy-TA36MnDiFBukd5IvEME0g'
},
{
'name': 'grumpy',
'imageLink': 'http://cdn.grumpycats.com/wp-content/uploads/' +
'2012/09/GC-Gravatar-copy.png'
},
{
'name': 'mischievous',
'imageLink': 'http://www.kittenspet.com/wp-content' +
'/uploads/2012/08/cat_with_funny_face_3-200x200.jpg'
}
]
);
}
module.exports = router;

View File

@ -0,0 +1,18 @@
module.exports = {
host: '127.0.0.1',
sessionSecret: process.env.SESSION_SECRET,
trello: {
key: process.env.TRELLO_KEY,
secret: process.env.TRELLO_SECRET
},
blogger: {
key: process.env.BLOGGER_KEY
},
github: {
clientID: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET
}
};

29
server/config.json Normal file
View File

@ -0,0 +1,29 @@
{
"restApiRoot": "/api",
"host": "0.0.0.0",
"port": 3000,
"remoting": {
"context": {
"enableHttpContext": false
},
"rest": {
"normalizeHttpPath": false,
"xml": false
},
"json": {
"strict": false,
"limit": "100kb"
},
"urlencoded": {
"extended": true,
"limit": "100kb"
},
"cors": {
"origin": true,
"credentials": true
},
"errorHandler": {
"disableStackTrace": false
}
}
}

5
server/config.local.js Normal file
View File

@ -0,0 +1,5 @@
var globalConfig = require('../common/config.global');
module.exports = {
restApiRoot: globalConfig.restApi
};

View File

@ -0,0 +1,6 @@
module.exports = {
db: {
connector: 'mongodb',
url: process.env.MONGOHQ_URL
}
};

9
server/datasources.json Normal file
View File

@ -0,0 +1,9 @@
{
"db": {
"name": "db",
"connector": "mongodb",
"host": "127.0.0.1",
"database": "foobar",
"port": 27017
}
}

27
server/middleware.json Normal file
View File

@ -0,0 +1,27 @@
{
"initial:before": {
"loopback#favicon": {}
},
"initial": {
"compression": {}
},
"session": {
},
"auth": {
},
"parse": {
},
"routes": {
},
"files": {
"loopback#static": {
"params": "$!../public"
}
},
"final": {
"loopback#urlNotFound": {}
},
"final:after": {
"errorhandler": {}
}
}

69
server/model-config.json Normal file
View File

@ -0,0 +1,69 @@
{
"_meta": {
"sources": [
"loopback/common/models",
"loopback/server/models",
"../common/models",
"./models"
]
},
"User": {
"dataSource": "db"
},
"AccessToken": {
"dataSource": "db",
"public": false
},
"ACL": {
"dataSource": "db",
"public": false
},
"RoleMapping": {
"dataSource": "db",
"public": false
},
"Role": {
"dataSource": "db",
"public": false
},
"bonfire": {
"dataSource": "db",
"public": true
},
"challenge": {
"dataSource": "db",
"public": true
},
"comment": {
"dataSource": "db",
"public": true
},
"fieldGuide": {
"dataSource": "db",
"public": true
},
"job": {
"dataSource": "db",
"public": true
},
"nonprofit": {
"dataSource": "db",
"public": true
},
"story": {
"dataSource": "db",
"public": true
},
"user": {
"dataSource": "db",
"public": true
},
"userCredential": {
"dataSource": "db",
"public": true
},
"userIdentity": {
"dataSource": "db",
"public": true
}
}

View File

@ -0,0 +1,122 @@
var successRedirect = '/';
var failureRedirect = '/login';
module.exports = {
local: {
provider: 'local',
module: 'passport-local',
usernameField: 'email',
passwordField: 'password',
authPath: '/auth/local',
successRedirect: successRedirect,
failureRedirect: failureRedirect,
failureFlash: true
},
'facebook-login': {
provider: 'facebook',
module: 'passport-facebook',
clientID: process.env.FACEBOOK_ID,
clientSecret: process.env.FACEBOOK_SECRET,
authPath: '/auth/facebook',
callbackURL: '/auth/facebook/callback',
callbackPath: '/auth/facebook/callback',
successRedirect: successRedirect,
failureRedirect: failureRedirect,
scope: ['email'],
failureFlash: true
},
'facebook-link': {
provider: 'facebook',
module: 'passport-facebook',
clientID: process.env.FACEBOOK_ID,
clientSecret: process.env.FACEBOOK_SECRET,
authPath: '/link/facebook',
callbackURL: '/link/facebook/callback',
callbackPath: '/link/facebook/callback',
successRedirect: successRedirect,
failureRedirect: failureRedirect,
scope: ['email', 'user_likes'],
link: true,
failureFlash: true
},
'google-login': {
provider: 'google',
module: 'passport-google-oauth2',
clientID: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
authPath: '/auth/google',
callbackURL: '/auth/google/callback',
callbackPath: '/auth/google/callback',
successRedirect: successRedirect,
failureRedirect: failureRedirect,
scope: ['email', 'profile'],
failureFlash: true
},
'google-link': {
provider: 'google',
module: 'passport-google-oauth2',
clientID: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
authPath: '/link/google',
callbackURL: '/link/google/callback',
callbackPath: '/link/google/callback',
successRedirect: successRedirect,
failureRedirect: failureRedirect,
scope: ['email', 'profile'],
link: true,
failureFlash: true
},
'twitter-login': {
provider: 'twitter',
authScheme: 'oauth',
module: 'passport-twitter',
authPath: '/auth/twitter',
callbackURL: '/auth/twitter/callback',
callbackPath: '/auth/twitter/callback',
successRedirect: successRedirect,
failureRedirect: failureRedirect,
consumerKey: process.env.TWITTER_KEY,
consumerSecret: process.env.TWITTER_SECRET,
failureFlash: true
},
'twitter-link': {
provider: 'twitter',
authScheme: 'oauth',
module: 'passport-twitter',
authPath: '/link/twitter',
callbackURL: '/link/twitter/callback',
callbackPath: '/link/twitter/callback',
successRedirect: successRedirect,
failureRedirect: failureRedirect,
consumerKey: process.env.TWITTER_KEY,
consumerSecret: process.env.TWITTER_SECRET,
failureFlash: true
},
'linkedin-login': {
provider: 'linkedin',
authScheme: 'oauth',
module: 'passport-linkedin-oauth2',
authPath: '/auth/linkedin',
callbackURL: '/auth/linkedin/callback',
callbackPath: '/auth/linkedin/callback',
successRedirect: successRedirect,
failureRedirect: failureRedirect,
clientID: process.env.LINKEDIN_ID,
clientSecret: process.env.LINKEDIN_SECRET,
scope: ['r_fullprofile', 'r_emailaddress'],
failureFlash: true
},
'linkedin-link': {
provider: 'linkedin',
authScheme: 'oauth',
module: 'passport-linkedin-oauth2',
authPath: '/link/linkedin',
callbackURL: '/link/linkedin/callback',
callbackPath: '/link/linkedin/callback',
successRedirect: successRedirect,
failureRedirect: failureRedirect,
clientID: process.env.LINKEDIN_ID,
clientSecret: process.env.LINKEDIN_SECRET,
scope: ['r_fullprofile', 'r_emailaddress'],
failureFlash: true
}
};

View File

@ -9,7 +9,9 @@ process.on('uncaughtException', function (err) {
process.exit(1); // eslint-disable-line process.exit(1); // eslint-disable-line
}); });
var express = require('express'), var R = require('ramda'),
loopback = require('loopback'),
boot = require('loopback-boot'),
accepts = require('accepts'), accepts = require('accepts'),
cookieParser = require('cookie-parser'), cookieParser = require('cookie-parser'),
compress = require('compression'), compress = require('compression'),
@ -22,27 +24,11 @@ var express = require('express'),
MongoStore = require('connect-mongo')(session), MongoStore = require('connect-mongo')(session),
flash = require('express-flash'), flash = require('express-flash'),
path = require('path'), path = require('path'),
mongoose = require('mongoose'),
passport = require('passport'),
expressValidator = require('express-validator'), expressValidator = require('express-validator'),
// request = require('request'),
forceDomain = require('forcedomain'), forceDomain = require('forcedomain'),
lessMiddleware = require('less-middleware'), lessMiddleware = require('less-middleware'),
/** passportProviders = require('./passport-providers'),
* routers.
*/
homeRouter = require('./boot/home'),
userRouter = require('./boot/user'),
fieldGuideRouter = require('./boot/fieldGuide'),
challengeMapRouter = require('./boot/challengeMap'),
challengeRouter = require('./boot/challenge'),
jobsRouter = require('./boot/jobs'),
redirectsRouter = require('./boot/redirects'),
utilityRouter = require('./boot/utility'),
storyRouter = require('./boot/story'),
passportRouter = require('./boot/passport'),
/** /**
* API keys and Passport configuration. * API keys and Passport configuration.
*/ */
@ -51,22 +37,10 @@ var express = require('express'),
/** /**
* Create Express server. * Create Express server.
*/ */
var app = express(); var app = loopback();
var PassportConfigurator =
/** require('loopback-component-passport').PassportConfigurator;
* Connect to MongoDB. var passportConfigurator = new PassportConfigurator(app);
*/
mongoose.connect(secrets.db);
mongoose.connection.on('error', function () {
console.error(
'MongoDB Connection Error. Please make sure that MongoDB is running.'
);
});
/**
* Express configuration.
*/
app.set('port', process.env.PORT || 3000); app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views')); app.set('views', path.join(__dirname, 'views'));
@ -101,8 +75,7 @@ app.use(session({
'autoReconnect': true 'autoReconnect': true
}) })
})); }));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash()); app.use(flash());
app.disable('x-powered-by'); app.disable('x-powered-by');
@ -191,6 +164,8 @@ app.use(helmet.csp({
safari5: false safari5: false
})); }));
passportConfigurator.init();
app.use(function (req, res, next) { app.use(function (req, res, next) {
// Make user object available in templates. // Make user object available in templates.
res.locals.user = req.user; res.locals.user = req.user;
@ -198,9 +173,14 @@ app.use(function (req, res, next) {
}); });
app.use( app.use(
express.static(path.join(__dirname, '../public'), { maxAge: 86400000 }) loopback.static(path.join(__dirname, '../public'), { maxAge: 86400000 })
); );
boot(app, {
appRootDir: __dirname,
dev: process.env.NODE_ENV
});
app.use(function (req, res, next) { app.use(function (req, res, next) {
// Remember original destination before login. // Remember original destination before login.
var path = req.path.split('/')[1]; var path = req.path.split('/')[1];
@ -213,17 +193,17 @@ app.use(function (req, res, next) {
next(); next();
}); });
// add sub routers passportConfigurator.setupModels({
app.use(fieldGuideRouter); userModel: app.models.user,
app.use(challengeMapRouter); userIdentityModel: app.models.userIdentity,
app.use(challengeRouter); userCredentialModel: app.models.userCredential
app.use(jobsRouter); });
app.use(redirectsRouter);
app.use(utilityRouter); R.keys(passportProviders).map(function(strategy) {
app.use(storyRouter); var config = passportProviders[strategy];
app.use(passportRouter); config.session = config.session !== false;
app.use(homeRouter); passportConfigurator.configureProvider(strategy, config);
app.use(userRouter); });
/** /**
* OAuth sign-in routes. * OAuth sign-in routes.
@ -273,12 +253,19 @@ if (process.env.NODE_ENV === 'development') {
* Start Express server. * Start Express server.
*/ */
app.listen(app.get('port'), function () { app.start = function() {
app.listen(app.get('port'), function () {
console.log( console.log(
'FreeCodeCamp server listening on port %d in %s mode', 'FreeCodeCamp server listening on port %d in %s mode',
app.get('port'), app.get('port'),
app.get('env') app.get('env')
); );
}); });
};
// start the server if `$ node server.js`
if (require.main === module) {
app.start();
}
module.exports = app; module.exports = app;

View File

@ -1,5 +1,4 @@
var async = require('async'), var path = require('path'),
path = require('path'),
// debug = require('debug')('freecc:cntr:resources'), // debug = require('debug')('freecc:cntr:resources'),
cheerio = require('cheerio'), cheerio = require('cheerio'),
request = require('request'), request = require('request'),
@ -8,11 +7,9 @@ var async = require('async'),
fs = require('fs'), fs = require('fs'),
Story = require('../../common/models/Story'),
Comment = require('../../common/models/Comment'),
resources = require('./resources.json'), resources = require('./resources.json'),
nonprofits = require('../../seed_data/nonprofits.json'), nonprofits = require('../../seed/nonprofits.json'),
fieldGuides = require('../../seed_data/field-guides.json'); fieldGuides = require('../../seed/field-guides.json');
/** /**
* Cached values * Cached values
@ -41,12 +38,12 @@ Array.zip = function(left, right, combinerFunction) {
if (!challengeMap) { if (!challengeMap) {
var localChallengeMap = {}; var localChallengeMap = {};
var files = fs.readdirSync( var files = fs.readdirSync(
path.join(__dirname, '../../seed_data/challenges') path.join(__dirname, '../../seed/challenges')
); );
var keyCounter = 0; var keyCounter = 0;
files = files.map(function (file) { files = files.map(function (file) {
return require( return require(
path.join(__dirname, '../../seed_data/challenges/' + file) path.join(__dirname, '../../seed/challenges/' + file)
); );
}); });
files = files.sort(function (a, b) { files = files.sort(function (a, b) {
@ -215,59 +212,5 @@ module.exports = {
} }
}); });
})(); })();
},
updateUserStoryPictures: function (userId, picture, username, cb) {
var counter = 0,
foundStories,
foundComments;
Story.find({'author.userId': userId}, function (err, stories) {
if (err) {
return cb(err);
}
foundStories = stories;
counter++;
saveStoriesAndComments();
});
Comment.find({'author.userId': userId}, function (err, comments) {
if (err) {
return cb(err);
}
foundComments = comments;
counter++;
saveStoriesAndComments();
});
function saveStoriesAndComments() {
if (counter !== 2) {
return;
}
var tasks = [];
R.forEach(function (comment) {
comment.author.picture = picture;
comment.author.username = username;
comment.markModified('author');
tasks.push(function (cb) {
comment.save(cb);
});
}, foundComments);
R.forEach(function (story) {
story.author.picture = picture;
story.author.username = username;
story.markModified('author');
tasks.push(function (cb) {
story.save(cb);
});
}, foundStories);
async.parallel(tasks, function (err) {
if (err) {
return cb(err);
}
cb();
});
}
} }
}; };