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
# Seed your database with the challenges
node seed_data/seed.js
node seed/
# start the application
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",
"logoUrl": "string",
"imageUrl": "string",
"estimatedHours": 0,
"estimatedHours": "number",
"interestedCampers": [],
"confirmedCampers": [],
"currentStatus": "string"

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "bonfire",
"base": "PersistedModel",
"name": "user",
"base": "User",
"trackChanges": false,
"idInjection": true,
"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-middleware": "~2.0.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",
"method-override": "~2.3.0",
"moment": "~2.10.2",
"mongodb": "~1.4.33",
"mongoose": "~4.0.1",
"mongoose-long": "0.0.2",
"mongodb": "^2.0.33",
"morgan": "~1.5.0",
"node-slack": "0.0.7",
"nodemailer": "~1.3.0",
"passport": "~0.2.1",
"passport-facebook": "~1.0.3",
"passport-github": "~0.1.5",
"passport-google-oauth": "~0.1.5",
"passport-linkedin-oauth2": "~1.2.1",
"passport-local": "~1.0.0",
"passport-oauth": "~1.0.0",
"passport-twitter": "~1.0.2",
"passport-facebook": "^2.0.0",
"passport-google-oauth": "^0.2.0",
"passport-google-oauth2": "^0.1.6",
"passport-linkedin-oauth2": "^1.2.1",
"passport-local": "^1.0.0",
"passport-oauth": "^1.0.0",
"passport-twitter": "^1.0.3",
"ramda": "~0.10.0",
"request": "~2.53.0",
"rx": "^2.5.3",
"sanitize-html": "~1.6.1",
"sitemap": "~0.7.4",
"twit": "~1.1.20",
"uglify-js": "~2.4.15",
"validator": "~3.22.1",
@ -94,7 +95,7 @@
"gulp": "~3.8.8",
"gulp-eslint": "~0.9.0",
"gulp-inject": "~1.0.2",
"gulp-nodemon": "~1.0.4",
"gulp-nodemon": "^2.0.3",
"mocha": "~2.0.1",
"multiline": "~1.0.1",
"supertest": "~0.15.0"

View File

@ -237,7 +237,7 @@
"name": "Waypoint: Fill in the Blank with Placeholder Text",
"difficulty": 0.015,
"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.",
"\"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\"!",

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>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>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>",
" </ol>",
" </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'),
express = require('express'),
Challenge = require('../../common/models/Challenge'),
User = require('../../common/models/User'),
resources = require('../resources/resources'),
userMigration = require('../resources/middleware').userMigration,
MDNlinks = require('../../seed_data/bonfireMDNlinks');
utils = require('../utils'),
userMigration = require('../utils/middleware').userMigration,
MDNlinks = require('../../seed/bonfireMDNlinks');
var router = express.Router();
var challengeMapWithNames = resources.getChallengeMapWithNames();
var challengeMapWithIds = resources.getChallengeMapWithIds();
var challengeMapWithNames = utils.getChallengeMapWithNames();
var challengeMapWithIds = utils.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) {
// takes in an array of links, which are strings
@ -73,278 +53,302 @@ function getMDNlinks(links) {
return populatedLinks;
}
function returnNextChallenge(req, res, next) {
if (!req.user) {
return res.redirect('../challenges/learn-how-free-code-camp-works');
}
var completed = req.user.completedChallenges.map(function (elem) {
return elem._id;
});
module.exports = function(app) {
var router = app.loopback.Router();
var Challenge = app.models.Challenge;
var User = app.models.User;
req.user.uncompletedChallenges = resources.allChallengeIds()
.filter(function (elem) {
if (completed.indexOf(elem) === -1) {
return elem;
}
});
router.get(
'/challenges/next-challenge',
userMigration,
returnNextChallenge
);
// find the user's current challenge and block
// look in that block and find the index of their current challenge
// if index + 1 < block.challenges.length
// serve index + 1 challenge
// otherwise increment block key and serve the first challenge in that block
// unless the next block is undefined, which means no next block
var nextChallengeName;
router.get(
'/challenges/:challengeName',
userMigration,
returnIndividualChallenge
);
var challengeId = String(req.user.currentChallenge.challengeId);
var challengeBlock = req.user.currentChallenge.challengeBlock;
var indexOfChallenge = challengeMapWithIds[challengeBlock]
.indexOf(challengeId);
router.get('/challenges/', userMigration, returnCurrentChallenge);
router.post('/completed-challenge/', completedChallenge);
router.post('/completed-zipline-or-basejump', completedZiplineOrBasejump);
router.post('/completed-bonfire', completedBonfire);
if (indexOfChallenge + 1
< challengeMapWithIds[challengeBlock].length) {
nextChallengeName =
challengeMapWithNames[challengeBlock][++indexOfChallenge];
} else if (typeof challengeMapWithIds[++challengeBlock] !== 'undefined') {
nextChallengeName = R.head(challengeMapWithNames[challengeBlock]);
} else {
req.flash('errors', {
msg: 'It looks like you have finished all of our challenges.' +
' Great job! Now on to helping nonprofits!'
});
nextChallengeName = R.head(challengeMapWithNames[0].challenges);
}
app.use(router);
var nameString = nextChallengeName.trim()
.toLowerCase()
.replace(/\s/g, '-');
req.user.save(function(err) {
if (err) {
return next(err);
function returnNextChallenge(req, res, next) {
if (!req.user) {
return res.redirect('../challenges/learn-how-free-code-camp-works');
}
return res.redirect('../challenges/' + nameString);
});
}
function returnCurrentChallenge(req, res, next) {
if (!req.user) {
return res.redirect('../challenges/learn-how-free-code-camp-works');
}
var completed = req.user.completedChallenges.map(function (elem) {
return elem._id;
});
req.user.uncompletedChallenges = resources.allChallengeIds()
.filter(function (elem) {
if (completed.indexOf(elem) === -1) {
return elem;
}
var completed = req.user.completedChallenges.map(function (elem) {
return elem._id;
});
if (!req.user.currentChallenge) {
req.user.currentChallenge = {};
req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0];
req.user.currentChallenge.challengeName = challengeMapWithNames['0'][0];
req.user.currentChallenge.challengeBlock = '0';
req.user.uncompletedChallenges = utils.allChallengeIds()
.filter(function (elem) {
if (completed.indexOf(elem) === -1) {
return elem;
}
});
// find the user's current challenge and block
// look in that block and find the index of their current challenge
// if index + 1 < block.challenges.length
// serve index + 1 challenge
// otherwise increment block key and serve the first challenge in that block
// unless the next block is undefined, which means no next block
var nextChallengeName;
var challengeId = String(req.user.currentChallenge.challengeId);
var challengeBlock = req.user.currentChallenge.challengeBlock;
var indexOfChallenge = challengeMapWithIds[challengeBlock]
.indexOf(challengeId);
if (indexOfChallenge + 1
< challengeMapWithIds[challengeBlock].length) {
nextChallengeName =
challengeMapWithNames[challengeBlock][++indexOfChallenge];
} else if (typeof challengeMapWithIds[++challengeBlock] !== 'undefined') {
nextChallengeName = R.head(challengeMapWithNames[challengeBlock]);
} else {
req.flash('errors', {
msg: 'It looks like you have finished all of our challenges.' +
' Great job! Now on to helping nonprofits!'
});
nextChallengeName = R.head(challengeMapWithNames[0].challenges);
}
var nameString = nextChallengeName.trim()
.toLowerCase()
.replace(/\s/g, '-');
req.user.save(function(err) {
if (err) {
return next(err);
}
return res.redirect('../challenges/' + nameString);
});
}
var nameString = req.user.currentChallenge.challengeName.trim()
.toLowerCase()
.replace(/\s/g, '-')
.replace(/[^a-z0-9\-\/.]/gi, '');
req.user.save(function(err) {
if (err) {
return next(err);
function returnCurrentChallenge(req, res, next) {
if (!req.user) {
return res.redirect('../challenges/learn-how-free-code-camp-works');
}
return res.redirect('../challenges/' + nameString);
});
}
function returnIndividualChallenge(req, res, next) {
var dashedName = req.params.challengeName;
var challengeName =
(/^(bonfire|waypoint|zipline|basejump)/i).test(dashedName) ?
dashedName
.replace(/\-/g, ' ')
.split(' ')
.slice(1)
.join(' ') :
dashedName.replace(/\-/g, ' ');
Challenge.find({'name': new RegExp(challengeName, 'i')},
function(err, challengeFromMongo) {
if (err) {
return next(err);
}
// Handle not found
if (challengeFromMongo.length < 1) {
req.flash('errors', {
msg: '404: We couldn\'t find a challenge with that name. ' +
'Please double check the name.'
});
return res.redirect('/challenges');
}
var challenge = challengeFromMongo.pop();
// Redirect to full name if the user only entered a partial
var dashedNameFull = challenge.name
.toLowerCase()
.replace(/\s/g, '-')
.replace(/[^a-z0-9\-\.]/gi, '');
if (dashedNameFull !== dashedName) {
return res.redirect('../challenges/' + dashedNameFull);
} else if (req.user) {
req.user.currentChallenge = {
challengeId: challenge._id,
challengeName: challenge.name,
challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds).
map(function (key) {
return challengeMapWithIds[key]
.filter(function (elem) {
return String(elem) === String(challenge._id);
}).map(function () {
return key;
});
})
))
};
}
var challengeType = {
0: function() {
res.render('coursewares/showHTML', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
brief: challenge.description[0],
details: challenge.description.slice(1),
tests: challenge.tests,
challengeSeed: challenge.challengeSeed,
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
challengeId: challenge._id,
environment: resources.whichEnvironment(),
challengeType: challenge.challengeType
});
},
1: function() {
res.render('coursewares/showJS', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
brief: challenge.description[0],
details: challenge.description.slice(1),
tests: challenge.tests,
challengeSeed: challenge.challengeSeed,
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
challengeId: challenge._id,
challengeType: challenge.challengeType
});
},
2: function() {
res.render('coursewares/showVideo', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
details: challenge.description,
tests: challenge.tests,
video: challenge.challengeSeed[0],
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
challengeId: challenge._id,
challengeType: challenge.challengeType
});
},
3: function() {
res.render('coursewares/showZiplineOrBasejump', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
details: challenge.description,
video: challenge.challengeSeed[0],
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
challengeId: challenge._id,
challengeType: challenge.challengeType
});
},
4: function() {
res.render('coursewares/showZiplineOrBasejump', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
details: challenge.description,
video: challenge.challengeSeed[0],
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
challengeId: challenge._id,
challengeType: challenge.challengeType
});
},
5: function() {
res.render('coursewares/showBonfire', {
completedWith: null,
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
difficulty: Math.floor(+challenge.difficulty),
brief: challenge.description.shift(),
details: challenge.description,
tests: challenge.tests,
challengeSeed: challenge.challengeSeed,
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
bonfires: challenge,
challengeId: challenge._id,
MDNkeys: challenge.MDNlinks,
MDNlinks: getMDNlinks(challenge.MDNlinks),
challengeType: challenge.challengeType
});
}
};
if (req.user) {
req.user.save(function (err) {
if (err) {
return next(err);
}
return challengeType[challenge.challengeType]();
});
} else {
return challengeType[challenge.challengeType]();
}
var completed = req.user.completedChallenges.map(function (elem) {
return elem._id;
});
}
function completedBonfire(req, res, next) {
var isCompletedWith = req.body.challengeInfo.completedWith || '';
var isCompletedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
var isSolution = req.body.challengeInfo.solution;
var challengeName = req.body.challengeInfo.challengeName;
if (isCompletedWith) {
var paired = User.find({'profile.username': isCompletedWith.toLowerCase()})
.limit(1);
paired.exec(function (err, pairedWith) {
req.user.uncompletedChallenges = utils.allChallengeIds()
.filter(function (elem) {
if (completed.indexOf(elem) === -1) {
return elem;
}
});
if (!req.user.currentChallenge) {
req.user.currentChallenge = {};
req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0];
req.user.currentChallenge.challengeName = challengeMapWithNames['0'][0];
req.user.currentChallenge.challengeBlock = '0';
req.user.save(function(err) {
if (err) {
return next(err);
}
});
}
var nameString = req.user.currentChallenge.challengeName.trim()
.toLowerCase()
.replace(/\s/g, '-')
.replace(/[^a-z0-9\-\/.]/gi, '');
req.user.save(function(err) {
if (err) {
return next(err);
} else {
}
return res.redirect('../challenges/' + nameString);
});
}
function returnIndividualChallenge(req, res, next) {
var dashedName = req.params.challengeName;
var challengeName =
(/^(bonfire|waypoint|zipline|basejump)/i).test(dashedName) ?
dashedName
.replace(/\-/g, ' ')
.split(' ')
.slice(1)
.join(' ') :
dashedName.replace(/\-/g, ' ');
Challenge.find(
{ where: { name: new RegExp(challengeName, 'i') } },
function(err, challengeFromMongo) {
if (err) { return next(err); }
// Handle not found
if (challengeFromMongo.length < 1) {
req.flash('errors', {
msg: '404: We couldn\'t find a challenge with that name. ' +
'Please double check the name.'
});
return res.redirect('/challenges');
}
var challenge = challengeFromMongo.pop();
// Redirect to full name if the user only entered a partial
var dashedNameFull = challenge.name
.toLowerCase()
.replace(/\s/g, '-')
.replace(/[^a-z0-9\-\.]/gi, '');
if (dashedNameFull !== dashedName) {
return res.redirect('../challenges/' + dashedNameFull);
} else if (req.user) {
req.user.currentChallenge = {
challengeId: challenge._id,
challengeName: challenge.name,
challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds).
map(function (key) {
return challengeMapWithIds[key]
.filter(function (elem) {
return String(elem) === String(challenge._id);
}).map(function () {
return key;
});
})
))
};
}
var challengeType = {
0: function() {
res.render('coursewares/showHTML', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
brief: challenge.description[0],
details: challenge.description.slice(1),
tests: challenge.tests,
challengeSeed: challenge.challengeSeed,
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
challengeId: challenge._id,
environment: utils.whichEnvironment(),
challengeType: challenge.challengeType
});
},
1: function() {
res.render('coursewares/showJS', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
brief: challenge.description[0],
details: challenge.description.slice(1),
tests: challenge.tests,
challengeSeed: challenge.challengeSeed,
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
challengeId: challenge._id,
challengeType: challenge.challengeType
});
},
2: function() {
res.render('coursewares/showVideo', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
details: challenge.description,
tests: challenge.tests,
video: challenge.challengeSeed[0],
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
challengeId: challenge._id,
challengeType: challenge.challengeType
});
},
3: function() {
res.render('coursewares/showZiplineOrBasejump', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
details: challenge.description,
video: challenge.challengeSeed[0],
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
challengeId: challenge._id,
challengeType: challenge.challengeType
});
},
4: function() {
res.render('coursewares/showZiplineOrBasejump', {
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
details: challenge.description,
video: challenge.challengeSeed[0],
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
challengeId: challenge._id,
challengeType: challenge.challengeType
});
},
5: function() {
res.render('coursewares/showBonfire', {
completedWith: null,
title: challenge.name,
dashedName: dashedName,
name: challenge.name,
difficulty: Math.floor(+challenge.difficulty),
brief: challenge.description.shift(),
details: challenge.description,
tests: challenge.tests,
challengeSeed: challenge.challengeSeed,
verb: utils.randomVerb(),
phrase: utils.randomPhrase(),
compliment: utils.randomCompliment(),
bonfires: challenge,
challengeId: challenge._id,
MDNkeys: challenge.MDNlinks,
MDNlinks: getMDNlinks(challenge.MDNlinks),
challengeType: challenge.challengeType
});
}
};
if (req.user) {
req.user.save(function (err) {
if (err) {
return next(err);
}
return challengeType[challenge.challengeType]();
});
} else {
return challengeType[challenge.challengeType]();
}
});
}
function completedBonfire(req, res, next) {
var isCompletedWith = req.body.challengeInfo.completedWith || '';
var isCompletedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
var isSolution = req.body.challengeInfo.solution;
var challengeName = req.body.challengeInfo.challengeName;
if (isCompletedWith) {
User.find({
where: { 'profile.username': isCompletedWith.toLowerCase() },
limit: 1
}, function (err, pairedWith) {
if (err) { return next(err); }
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
@ -379,20 +383,18 @@ function completedBonfire(req, res, next) {
});
}
// User said they paired, but pair wasn't found
req.user.completedChallenges.push({
_id: challengeId,
name: challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
req.user.completedChallenges.push({
_id: challengeId,
name: challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
req.user.save(function (err, user) {
if (err) {
return next(err);
}
if (err) { return next(err); }
if (pairedWith) {
pairedWith.save(function (err, paired) {
if (err) {
@ -406,21 +408,47 @@ function completedBonfire(req, res, next) {
res.send(true);
}
});
});
} else {
req.user.completedChallenges.push({
_id: challengeId,
name: challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
});
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
});
} else {
req.user.save(function (err) {
if (err) { return next(err); }
res.send(true);
});
}
}
function completedChallenge(req, res, next) {
var isCompletedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
req.user.completedChallenges.push({
_id: challengeId,
name: challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: isSolution,
challengeType: 5
name: req.body.challengeInfo.challengeName,
solution: null,
githubLink: null,
verified: true
});
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
@ -429,70 +457,36 @@ function completedBonfire(req, res, next) {
if (err) {
return next(err);
}
// NOTE(berks): Under certain conditions the res is never ended
if (user) {
res.send(true);
res.sendStatus(200);
}
});
}
}
function completedChallenge(req, res, next) {
function completedZiplineOrBasejump(req, res, next) {
var isCompletedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
req.user.completedChallenges.push({
_id: challengeId,
completedDate: isCompletedDate,
name: req.body.challengeInfo.challengeName,
solution: null,
githubLink: null,
verified: true
});
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
req.user.save(function (err, user) {
if (err) {
return next(err);
var isCompletedWith = req.body.challengeInfo.completedWith || false;
var isCompletedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
var solutionLink = req.body.challengeInfo.publicURL;
var githubLink = req.body.challengeInfo.challengeType === '4'
? req.body.challengeInfo.githubURL : true;
var challengeType = req.body.challengeInfo.challengeType === '4' ?
4 : 3;
if (!solutionLink || !githubLink) {
req.flash('errors', {
msg: 'You haven\'t supplied the necessary URLs for us to inspect ' +
'your work.'
});
return res.sendStatus(403);
}
if (user) {
res.sendStatus(200);
}
});
}
function completedZiplineOrBasejump(req, res, next) {
var isCompletedWith = req.body.challengeInfo.completedWith || false;
var isCompletedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
var solutionLink = req.body.challengeInfo.publicURL;
var githubLink = req.body.challengeInfo.challengeType === '4'
? req.body.challengeInfo.githubURL : true;
var challengeType = req.body.challengeInfo.challengeType === '4' ?
4 : 3;
if (!solutionLink || !githubLink) {
req.flash('errors', {
msg: 'You haven\'t supplied the necessary URLs for us to inspect ' +
'your work.'
});
return res.sendStatus(403);
}
if (isCompletedWith) {
var paired = User.find({'profile.username': isCompletedWith.toLowerCase()})
.limit(1);
paired.exec(function (err, pairedWithFromMongo) {
if (err) {
return next(err);
} else {
if (isCompletedWith) {
User.find({
where: { 'profile.username': isCompletedWith.toLowerCase() },
limit: 1
}, function (err, pairedWithFromMongo) {
if (err) { return next(err); }
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
@ -512,9 +506,7 @@ function completedZiplineOrBasejump(req, res, next) {
});
req.user.save(function (err, user) {
if (err) {
return next(err);
}
if (err) { return next(err); }
if (req.user._id.toString() === pairedWith._id.toString()) {
return res.sendStatus(200);
@ -545,37 +537,36 @@ function completedZiplineOrBasejump(req, res, next) {
}
});
});
});
} else {
req.user.completedChallenges.push({
_id: challengeId,
name: req.body.challengeInfo.challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: solutionLink,
githubLink: githubLink,
challengeType: challengeType,
verified: false
});
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
}
});
} else {
req.user.completedChallenges.push({
_id: challengeId,
name: req.body.challengeInfo.challengeName,
completedWith: null,
completedDate: isCompletedDate,
solution: solutionLink,
githubLink: githubLink,
challengeType: challengeType,
verified: false
});
var index = req.user.uncompletedChallenges.indexOf(challengeId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedChallenges.splice(index, 1);
req.user.save(function (err, user) {
if (err) {
return next(err);
}
// NOTE(berks): under certain conditions this will not close
// the response.
if (user) {
return res.sendStatus(200);
}
});
}
req.user.save(function (err, user) {
if (err) {
return next(err);
}
// NOTE(berks): under certain conditions this will not close the response.
if (user) {
return res.sendStatus(200);
}
});
}
}
module.exports = router;
};

View File

@ -1,65 +1,65 @@
var R = require('ramda'),
express = require('express'),
// debug = require('debug')('freecc:cntr:challengeMap'),
User = require('../../common/models/User'),
resources = require('./../resources/resources'),
middleware = require('../resources/middleware'),
router = express.Router();
utils = require('./../utils'),
middleware = require('../utils/middleware');
router.get('/map', middleware.userMigration, challengeMap);
router.get('/learn-to-code', function(req, res) {
res.redirect(301, '/map');
});
module.exports = function(app) {
var User = app.models.User;
var router = app.loopback.Router();
router.get('/about', function(req, res) {
res.redirect(301, '/map');
});
function challengeMap(req, res, next) {
var completedList = [];
if (req.user) {
completedList = req.user.completedChallenges;
}
var noDuplicatedChallenges = R.uniq(completedList);
var completedChallengeList = noDuplicatedChallenges
.map(function(challenge) {
return challenge._id;
});
var challengeList = resources.
getChallengeMapForDisplay(completedChallengeList);
Object.keys(challengeList).forEach(function(key) {
challengeList[key].completed = challengeList[key]
.challenges.filter(function(elem) {
return completedChallengeList.indexOf(elem._id) > -1;
});
router.get('/map', middleware.userMigration, challengeMap);
router.get('/learn-to-code', function(req, res) {
res.redirect(301, '/map');
});
router.get('/about', function(req, res) {
res.redirect(301, '/map');
});
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
app.use(router);
var date1 = new Date('10/15/2014');
var date2 = new Date();
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
function challengeMap(req, res, next) {
var completedList = [];
User.count({}, function (err, camperCount) {
if (err) {
return next(err);
if (req.user) {
completedList = req.user.completedChallenges;
}
res.render('challengeMap/show', {
daysRunning: daysRunning,
camperCount: numberWithCommas(camperCount),
title: "A map of all Free Code Camp's Challenges",
challengeList: challengeList,
completedChallengeList: completedChallengeList
});
});
}
module.exports = router;
var noDuplicatedChallenges = R.uniq(completedList);
var completedChallengeList = noDuplicatedChallenges
.map(function(challenge) {
return challenge._id;
});
var challengeList = utils.
getChallengeMapForDisplay(completedChallengeList);
Object.keys(challengeList).forEach(function(key) {
challengeList[key].completed = challengeList[key]
.challenges.filter(function(elem) {
return completedChallengeList.indexOf(elem._id) > -1;
});
});
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
var date1 = new Date('10/15/2014');
var date2 = new Date();
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
User.count(function(err, camperCount) {
if (err) { return next(err); }
res.render('challengeMap/show', {
daysRunning: daysRunning,
camperCount: numberWithCommas(camperCount),
title: "A map of all Free Code Camp's Challenges",
challengeList: challengeList,
completedChallengeList: completedChallengeList
});
});
}
};

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,123 +1,128 @@
var R = require('ramda'),
express = require('express'),
// Rx = require('rx'),
// debug = require('debug')('freecc:fieldguides'),
FieldGuide = require('../../common/models/FieldGuide'),
resources = require('../resources/resources');
utils = require('../utils');
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/:fieldGuideName', returnIndividualFieldGuide);
router.get('/field-guide/', returnNextFieldGuide);
router.post('/completed-field-guide/', completedFieldGuide);
router.get('/field-guide/all-articles', showAllFieldGuides);
router.get('/field-guide/:fieldGuideName', returnIndividualFieldGuide);
router.get('/field-guide/', returnNextFieldGuide);
router.post('/completed-field-guide/', completedFieldGuide);
function returnIndividualFieldGuide(req, res, next) {
var dashedName = req.params.fieldGuideName;
if (req.user) {
var completed = req.user.completedFieldGuides;
app.use(router);
var uncompletedFieldGuides = resources.allFieldGuideIds()
.filter(function (elem) {
if (completed.indexOf(elem) === -1) {
return elem;
}
function returnIndividualFieldGuide(req, res, next) {
var dashedName = req.params.fieldGuideName;
if (req.user) {
var completed = req.user.completedFieldGuides;
var uncompletedFieldGuides = utils.allFieldGuideIds()
.filter(function (elem) {
if (completed.indexOf(elem) === -1) {
return elem;
}
});
req.user.uncompletedFieldGuides = uncompletedFieldGuides;
// TODO(berks): handle callback properly
req.user.save(function(err) {
if (err) { return next(err); }
});
req.user.uncompletedFieldGuides = uncompletedFieldGuides;
// TODO(berks): handle callback properly
req.user.save();
}
// NOTE(berks): loopback might have issue with regex here.
FieldGuide.find(
{ dashedName: new RegExp(dashedName, 'i') },
function(err, fieldGuideFromMongo) {
if (err) {
return next(err);
}
if (fieldGuideFromMongo.length < 1) {
req.flash('errors', {
msg: "404: We couldn't find a field guide entry with that name. " +
'Please double check the name.'
});
return res.redirect('/field-guide');
}
var fieldGuide = R.head(fieldGuideFromMongo);
fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, '');
if (fieldGuide.dashedName !== dashedName) {
return res.redirect('../field-guide/' + fieldGuide.dashedName);
}
res.render('field-guide/show', {
title: fieldGuide.name,
fieldGuideId: fieldGuide._id,
description: fieldGuide.description.join('')
});
}
);
}
FieldGuide.find(
{ dashedName: new RegExp(dashedName, 'i') },
function(err, fieldGuideFromMongo) {
function showAllFieldGuides(req, res) {
var allFieldGuideNamesAndIds = utils.allFieldGuideNamesAndIds();
var completedFieldGuides = [];
if (req.user && req.user.completedFieldGuides) {
completedFieldGuides = req.user.completedFieldGuides;
}
res.render('field-guide/all-articles', {
allFieldGuideNamesAndIds: allFieldGuideNamesAndIds,
completedFieldGuides: completedFieldGuides
});
}
function returnNextFieldGuide(req, res, next) {
if (!req.user) {
return res.redirect('/field-guide/how-do-i-use-this-guide');
}
var displayedFieldGuides =
FieldGuide.find({'_id': req.user.uncompletedFieldGuides[0]});
displayedFieldGuides.exec(function(err, fieldGuide) {
if (err) { return next(err); }
fieldGuide = fieldGuide.pop();
if (typeof fieldGuide === 'undefined') {
if (req.user.completedFieldGuides.length > 0) {
req.flash('success', {
msg: [
"You've read all our current Field Guide entries. You can ",
'contribute to our Field Guide ',
"<a href='https://github.com/FreeCodeCamp/freecodecamp/blob/",
"staging/seed/field-guides.json'>here</a>."
].join('')
});
}
return res.redirect('../field-guide/how-do-i-use-this-guide');
}
var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-');
return res.redirect('../field-guide/' + nameString);
});
}
function completedFieldGuide(req, res, next) {
var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId;
req.user.completedFieldGuides.push(fieldGuideId);
var index = req.user.uncompletedFieldGuides.indexOf(fieldGuideId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now());
req.user.uncompletedFieldGuides.splice(index, 1);
}
req.user.save(function (err) {
if (err) {
return next(err);
}
if (fieldGuideFromMongo.length < 1) {
req.flash('errors', {
msg: "404: We couldn't find a field guide entry with that name. " +
'Please double check the name.'
});
return res.redirect('/field-guide');
}
var fieldGuide = R.head(fieldGuideFromMongo);
fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, '');
if (fieldGuide.dashedName !== dashedName) {
return res.redirect('../field-guide/' + fieldGuide.dashedName);
}
res.render('field-guide/show', {
title: fieldGuide.name,
fieldGuideId: fieldGuide._id,
description: fieldGuide.description.join('')
});
}
);
}
function showAllFieldGuides(req, res) {
var allFieldGuideNamesAndIds = resources.allFieldGuideNamesAndIds();
var completedFieldGuides = [];
if (req.user && req.user.completedFieldGuides) {
completedFieldGuides = req.user.completedFieldGuides;
res.send(true);
});
}
res.render('field-guide/all-articles', {
allFieldGuideNamesAndIds: allFieldGuideNamesAndIds,
completedFieldGuides: completedFieldGuides
});
}
function returnNextFieldGuide(req, res, next) {
if (!req.user) {
return res.redirect('/field-guide/how-do-i-use-this-guide');
}
var displayedFieldGuides =
FieldGuide.find({'_id': req.user.uncompletedFieldGuides[0]});
displayedFieldGuides.exec(function(err, fieldGuide) {
if (err) { return next(err); }
fieldGuide = fieldGuide.pop();
if (typeof fieldGuide === 'undefined') {
if (req.user.completedFieldGuides.length > 0) {
req.flash('success', {
msg: [
"You've read all our current Field Guide entries. You can ",
'contribute to our Field Guide ',
"<a href='https://github.com/FreeCodeCamp/freecodecamp/blob/",
"staging/seed_data/field-guides.json'>here</a>."
].join('')
});
}
return res.redirect('../field-guide/how-do-i-use-this-guide');
}
var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-');
return res.redirect('../field-guide/' + nameString);
});
}
function completedFieldGuide(req, res, next) {
var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId;
req.user.completedFieldGuides.push(fieldGuideId);
var index = req.user.uncompletedFieldGuides.indexOf(fieldGuideId);
if (index > -1) {
req.user.progressTimestamps.push(Date.now());
req.user.uncompletedFieldGuides.splice(index, 1);
}
req.user.save(function (err) {
if (err) {
return next(err);
}
res.send(true);
});
}
module.exports = router;
};

View File

@ -1,22 +1,23 @@
var express = require('express');
var router = express.Router();
var message =
'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) {
if (req.user && !req.user.profile.picture) {
req.user.profile.picture =
'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png';
app.use(router);
req.user.save(function(err) {
if (err) { return next(err); }
function index(req, res, next) {
if (req.user && !req.user.profile.picture) {
req.user.profile.picture =
'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png';
req.user.save(function(err) {
if (err) { return next(err); }
res.render('home', { title: message });
});
} else {
res.render('home', { title: message });
});
} else {
res.render('home', { title: message });
}
}
}
module.exports = router;
};

View File

@ -1,18 +1,18 @@
var express = require('express');
var Job = require('../../common/models/Job');
var router = express.Router();
module.exports = function(app) {
var Job = app.models.Job;
var router = app.loopback.Router();
router.get('/jobs', jobsDirectory);
router.get('/jobs', jobsDirectory);
app.use(router);
function jobsDirectory(req, res, next) {
Job.find({}, function(err, jobs) {
if (err) { return next(err); }
function jobsDirectory(req, res, next) {
Job.find({}, function(err, jobs) {
if (err) { return next(err); }
res.render('jobs/directory', {
title: 'Junior JavaScript Engineer Jobs',
jobs: jobs
res.render('jobs/directory', {
title: 'Junior JavaScript Engineer Jobs',
jobs: jobs
});
});
});
}
module.exports = router;
}
};

View File

View File

@ -1,126 +1,130 @@
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/:nonprofitName', returnIndividualNonprofit);
router.get('/nonprofits/directory', nonprofitsDirectory);
router.get('/nonprofits/:nonprofitName', returnIndividualNonprofit);
function nonprofitsDirectory(req, res, next) {
Nonprofit.find({ estimatedHours: { $gt: 0 } }, function(err, nonprofits) {
if (err) { return next(err); }
app.use(router);
res.render('nonprofits/directory', {
title: 'Nonprofits we help',
nonprofits: nonprofits
});
});
}
function returnIndividualNonprofit(req, res, next) {
var dashedName = req.params.nonprofitName;
var nonprofitName = dashedName.replace(/\-/g, ' ');
Nonprofit.find(
{ name: new RegExp(nonprofitName, 'i') },
function(err, nonprofit) {
if (err) {
return next(err);
}
if (nonprofit.length < 1) {
req.flash('errors', {
msg: "404: We couldn't find a nonprofit with that name. " +
'Please double check the name.'
});
return res.redirect('/nonprofits');
}
nonprofit = nonprofit.pop();
var dashedNameFull = nonprofit.name.toLowerCase().replace(/\s/g, '-');
if (dashedNameFull !== dashedName) {
return res.redirect('../nonprofit/' + dashedNameFull);
}
var buttonActive = false;
if (req.user) {
if (req.user.uncompletedBonfires.length === 0) {
if (req.user.completedCoursewares.length > 63) {
var hasShownInterest =
nonprofit.interestedCampers.filter(function ( obj ) {
return obj.username === req.user.profile.username;
});
if (hasShownInterest.length === 0) {
buttonActive = true;
}
}
}
}
res.render('nonprofits/show', {
dashedName: dashedNameFull,
title: nonprofit.name,
logoUrl: nonprofit.logoUrl,
estimatedHours: nonprofit.estimatedHours,
projectDescription: nonprofit.projectDescription,
approvedOther:
nonprofit.approvedDeliverables.indexOf('other') > -1,
approvedWebsite:
nonprofit.approvedDeliverables.indexOf('website') > -1,
approvedDonor:
nonprofit.approvedDeliverables.indexOf('donor') > -1,
approvedInventory:
nonprofit.approvedDeliverables.indexOf('inventory') > -1,
approvedVolunteer:
nonprofit.approvedDeliverables.indexOf('volunteer') > -1,
approvedForm:
nonprofit.approvedDeliverables.indexOf('form') > -1,
approvedCommunity:
nonprofit.approvedDeliverables.indexOf('community') > -1,
approvedELearning:
nonprofit.approvedDeliverables.indexOf('eLearning') > -1,
websiteLink: nonprofit.websiteLink,
imageUrl: nonprofit.imageUrl,
whatDoesNonprofitDo: nonprofit.whatDoesNonprofitDo,
interestedCampers: nonprofit.interestedCampers,
assignedCampers: nonprofit.assignedCampers,
buttonActive: buttonActive,
currentStatus: nonprofit.currentStatus
});
}
);
}
/*
function interestedInNonprofit(req, res, next) {
if (req.user) {
Nonprofit.findOne(
{ name: new RegExp(req.params.nonprofitName.replace(/-/, ' '), 'i') },
function(err, nonprofit) {
function nonprofitsDirectory(req, res, next) {
Nonprofit.find(
{ where: { estimatedHours: { $gt: 0 } } },
function(err, nonprofits) {
if (err) { return next(err); }
nonprofit.interestedCampers.push({
username: req.user.profile.username,
picture: req.user.profile.picture,
timeOfInterest: Date.now()
});
nonprofit.save(function(err) {
if (err) { return next(err); }
req.flash('success', {
msg: 'Thanks for expressing interest in this nonprofit project! ' +
"We've added you to this project as an interested camper!"
});
res.redirect('back');
res.render('nonprofits/directory', {
title: 'Nonprofits we help',
nonprofits: nonprofits
});
}
);
}
}
*/
module.exports = router;
function returnIndividualNonprofit(req, res, next) {
var dashedName = req.params.nonprofitName;
var nonprofitName = dashedName.replace(/\-/g, ' ');
Nonprofit.find(
{ where: { name: new RegExp(nonprofitName, 'i') } },
function(err, nonprofit) {
if (err) {
return next(err);
}
if (nonprofit.length < 1) {
req.flash('errors', {
msg: "404: We couldn't find a nonprofit with that name. " +
'Please double check the name.'
});
return res.redirect('/nonprofits');
}
nonprofit = nonprofit.pop();
var dashedNameFull = nonprofit.name.toLowerCase().replace(/\s/g, '-');
if (dashedNameFull !== dashedName) {
return res.redirect('../nonprofit/' + dashedNameFull);
}
var buttonActive = false;
if (req.user) {
if (req.user.uncompletedBonfires.length === 0) {
if (req.user.completedCoursewares.length > 63) {
var hasShownInterest =
nonprofit.interestedCampers.filter(function ( obj ) {
return obj.username === req.user.profile.username;
});
if (hasShownInterest.length === 0) {
buttonActive = true;
}
}
}
}
res.render('nonprofits/show', {
dashedName: dashedNameFull,
title: nonprofit.name,
logoUrl: nonprofit.logoUrl,
estimatedHours: nonprofit.estimatedHours,
projectDescription: nonprofit.projectDescription,
approvedOther:
nonprofit.approvedDeliverables.indexOf('other') > -1,
approvedWebsite:
nonprofit.approvedDeliverables.indexOf('website') > -1,
approvedDonor:
nonprofit.approvedDeliverables.indexOf('donor') > -1,
approvedInventory:
nonprofit.approvedDeliverables.indexOf('inventory') > -1,
approvedVolunteer:
nonprofit.approvedDeliverables.indexOf('volunteer') > -1,
approvedForm:
nonprofit.approvedDeliverables.indexOf('form') > -1,
approvedCommunity:
nonprofit.approvedDeliverables.indexOf('community') > -1,
approvedELearning:
nonprofit.approvedDeliverables.indexOf('eLearning') > -1,
websiteLink: nonprofit.websiteLink,
imageUrl: nonprofit.imageUrl,
whatDoesNonprofitDo: nonprofit.whatDoesNonprofitDo,
interestedCampers: nonprofit.interestedCampers,
assignedCampers: nonprofit.assignedCampers,
buttonActive: buttonActive,
currentStatus: nonprofit.currentStatus
});
}
);
}
/*
function interestedInNonprofit(req, res, next) {
if (req.user) {
Nonprofit.findOne(
{ name: new RegExp(req.params.nonprofitName.replace(/-/, ' '), 'i') },
function(err, nonprofit) {
if (err) { return next(err); }
nonprofit.interestedCampers.push({
username: req.user.profile.username,
picture: req.user.profile.picture,
timeOfInterest: Date.now()
});
nonprofit.save(function(err) {
if (err) { return next(err); }
req.flash('success', {
msg: 'Thanks for expressing interest in this nonprofit project! ' +
"We've added you to this project as an interested camper!"
});
res.redirect('back');
});
}
);
}
}
*/
};

View File

@ -1,68 +1,71 @@
var express = require('express'),
passport = require('passport'),
/*
var passport = require('passport'),
passportConf = require('../../config/passport');
var router = express.Router();
var passportOptions = {
successRedirect: '/',
failureRedirect: '/login'
};
router.all('/account', passportConf.isAuthenticated);
router.get('/auth/twitter', passport.authenticate('twitter'));
router.get(
'/auth/twitter/callback',
passport.authenticate('twitter', {
module.exports = function(app) {
var router = app.loopback.Router();
var passportOptions = {
successRedirect: '/',
failureRedirect: '/login'
})
);
};
router.get(
'/auth/linkedin',
passport.authenticate('linkedin', {
state: 'SOME STATE'
})
);
router.all('/account', passportConf.isAuthenticated);
router.get(
'/auth/linkedin/callback',
passport.authenticate('linkedin', passportOptions)
);
router.get('/auth/twitter', passport.authenticate('twitter'));
router.get(
'/auth/facebook',
passport.authenticate('facebook', {scope: ['email', 'user_location']})
);
router.get(
'/auth/twitter/callback',
passport.authenticate('twitter', {
successRedirect: '/',
failureRedirect: '/login'
})
);
router.get(
'/auth/facebook/callback',
passport.authenticate('facebook', passportOptions), function (req, res) {
res.redirect(req.session.returnTo || '/');
}
);
router.get(
'/auth/linkedin',
passport.authenticate('linkedin', {
state: 'SOME STATE'
})
);
router.get('/auth/github', passport.authenticate('github'));
router.get(
'/auth/linkedin/callback',
passport.authenticate('linkedin', passportOptions)
);
router.get(
'/auth/github/callback',
passport.authenticate('github', passportOptions), function (req, res) {
res.redirect(req.session.returnTo || '/');
}
);
router.get(
'/auth/facebook',
passport.authenticate('facebook', {scope: ['email', 'user_location']})
);
router.get(
'/auth/google',
passport.authenticate('google', {scope: 'profile email'})
);
router.get(
'/auth/facebook/callback',
passport.authenticate('facebook', passportOptions), function (req, res) {
res.redirect(req.session.returnTo || '/');
}
);
router.get(
'/auth/google/callback',
passport.authenticate('google', passportOptions), function (req, res) {
res.redirect(req.session.returnTo || '/');
}
);
router.get('/auth/github', passport.authenticate('github'));
module.exports = router;
router.get(
'/auth/github/callback',
passport.authenticate('github', passportOptions), function (req, res) {
res.redirect(req.session.returnTo || '/');
}
);
router.get(
'/auth/google',
passport.authenticate('google', {scope: 'profile email'})
);
router.get(
'/auth/google/callback',
passport.authenticate('google', passportOptions), function (req, res) {
res.redirect(req.session.returnTo || '/');
}
);
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');
var router = express.Router();
module.exports = function(app) {
var router = app.loopback.Router();
router.get('/nonprofit-project-instructions', function(req, res) {
res.redirect(
301,
'/field-guide/how-do-free-code-camp\'s-nonprofit-projects-work'
);
});
router.get('/nonprofit-project-instructions', function(req, res) {
res.redirect(
301,
'/field-guide/how-do-free-code-camp\'s-nonprofit-projects-work'
);
});
router.get('/agile', function(req, res) {
res.redirect(301, '/pmi-acp-agile-project-managers');
});
router.get('/agile', function(req, res) {
res.redirect(301, '/pmi-acp-agile-project-managers');
});
router.get('/live-pair-programming', function(req, res) {
res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv');
});
router.get('/live-pair-programming', function(req, res) {
res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv');
});
router.get('/install-screenhero', function(req, res) {
res.redirect(301, '/field-guide/install-screenhero');
});
router.get('/install-screenhero', function(req, res) {
res.redirect(301, '/field-guide/install-screenhero');
});
router.get('/guide-to-our-nonprofit-projects', function(req, res) {
res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects');
});
router.get('/guide-to-our-nonprofit-projects', function(req, res) {
res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects');
});
router.get('/chromebook', function(req, res) {
res.redirect(301, '/field-guide/chromebook');
});
router.get('/chromebook', function(req, res) {
res.redirect(301, '/field-guide/chromebook');
});
router.get('/deploy-a-website', function(req, res) {
res.redirect(301, '/field-guide/deploy-a-website');
});
router.get('/deploy-a-website', function(req, res) {
res.redirect(301, '/field-guide/deploy-a-website');
});
router.get('/gmail-shortcuts', function(req, res) {
res.redirect(301, '/field-guide/gmail-shortcuts');
});
router.get('/gmail-shortcuts', function(req, res) {
res.redirect(301, '/field-guide/gmail-shortcuts');
});
router.get('/nodeschool-challenges', function(req, res) {
res.redirect(301, '/field-guide/nodeschool-challenges');
});
router.get('/nodeschool-challenges', function(req, res) {
res.redirect(301, '/field-guide/nodeschool-challenges');
});
router.get('/privacy', function(req, res) {
res.redirect(301, '/field-guide/what-is-the-free-code-camp-privacy-policy?');
});
router.get('/privacy', function(req, res) {
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());
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

View File

@ -1,18 +1,15 @@
var async = require('async'),
path = require('path'),
// debug = require('debug')('freecc:cntr:resources'),
cheerio = require('cheerio'),
request = require('request'),
R = require('ramda'),
_ = require('lodash'),
fs = require('fs'),
var path = require('path'),
// debug = require('debug')('freecc:cntr:resources'),
cheerio = require('cheerio'),
request = require('request'),
R = require('ramda'),
_ = require('lodash'),
fs = require('fs'),
Story = require('../../common/models/Story'),
Comment = require('../../common/models/Comment'),
resources = require('./resources.json'),
nonprofits = require('../../seed_data/nonprofits.json'),
fieldGuides = require('../../seed_data/field-guides.json');
resources = require('./resources.json'),
nonprofits = require('../../seed/nonprofits.json'),
fieldGuides = require('../../seed/field-guides.json');
/**
* Cached values
@ -41,12 +38,12 @@ Array.zip = function(left, right, combinerFunction) {
if (!challengeMap) {
var localChallengeMap = {};
var files = fs.readdirSync(
path.join(__dirname, '../../seed_data/challenges')
path.join(__dirname, '../../seed/challenges')
);
var keyCounter = 0;
files = files.map(function (file) {
return require(
path.join(__dirname, '../../seed_data/challenges/' + file)
path.join(__dirname, '../../seed/challenges/' + file)
);
});
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();
});
}
}
};