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

View File

@ -1,22 +1,24 @@
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) {
module.exports = function(app) {
var User = app.models.User;
var router = app.loopback.Router();
router.get('/map', middleware.userMigration, challengeMap);
router.get('/learn-to-code', function(req, res) {
res.redirect(301, '/map');
});
router.get('/about', function(req, res) {
});
router.get('/about', function(req, res) {
res.redirect(301, '/map');
});
});
function challengeMap(req, res, next) {
app.use(router);
function challengeMap(req, res, next) {
var completedList = [];
if (req.user) {
@ -29,7 +31,7 @@ function challengeMap(req, res, next) {
.map(function(challenge) {
return challenge._id;
});
var challengeList = resources.
var challengeList = utils.
getChallengeMapForDisplay(completedChallengeList);
Object.keys(challengeList).forEach(function(key) {
@ -48,10 +50,9 @@ function challengeMap(req, res, next) {
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);
}
User.count(function(err, camperCount) {
if (err) { return next(err); }
res.render('challengeMap/show', {
daysRunning: daysRunning,
camperCount: numberWithCommas(camperCount),
@ -60,6 +61,5 @@ function challengeMap(req, res, next) {
completedChallengeList: completedChallengeList
});
});
}
module.exports = router;
}
};

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

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

View File

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

View File

@ -1,11 +1,13 @@
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) {
app.use(router);
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';
@ -17,6 +19,5 @@ function index(req, res, next) {
} else {
res.render('home', { title: message });
}
}
module.exports = router;
}
};

View File

@ -1,10 +1,11 @@
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) {
function jobsDirectory(req, res, next) {
Job.find({}, function(err, jobs) {
if (err) { return next(err); }
@ -13,6 +14,5 @@ function jobsDirectory(req, res, next) {
jobs: jobs
});
});
}
module.exports = router;
}
};

View File

View File

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

View File

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

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

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

View File

@ -1,47 +1,50 @@
var express = require('express');
var router = express.Router();
module.exports = function(app) {
var router = app.loopback.Router();
router.get('/nonprofit-project-instructions', function(req, res) {
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) {
router.get('/agile', function(req, res) {
res.redirect(301, '/pmi-acp-agile-project-managers');
});
});
router.get('/live-pair-programming', function(req, res) {
router.get('/live-pair-programming', function(req, res) {
res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv');
});
});
router.get('/install-screenhero', function(req, res) {
router.get('/install-screenhero', function(req, res) {
res.redirect(301, '/field-guide/install-screenhero');
});
});
router.get('/guide-to-our-nonprofit-projects', function(req, res) {
router.get('/guide-to-our-nonprofit-projects', function(req, res) {
res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects');
});
});
router.get('/chromebook', function(req, res) {
router.get('/chromebook', function(req, res) {
res.redirect(301, '/field-guide/chromebook');
});
});
router.get('/deploy-a-website', function(req, res) {
router.get('/deploy-a-website', function(req, res) {
res.redirect(301, '/field-guide/deploy-a-website');
});
});
router.get('/gmail-shortcuts', function(req, res) {
router.get('/gmail-shortcuts', function(req, res) {
res.redirect(301, '/field-guide/gmail-shortcuts');
});
});
router.get('/nodeschool-challenges', function(req, res) {
router.get('/nodeschool-challenges', function(req, res) {
res.redirect(301, '/field-guide/nodeschool-challenges');
});
});
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());
};

View File

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

View File

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

View File

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

View File

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

29
server/config.json Normal file
View File

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

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

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

View File

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

9
server/datasources.json Normal file
View File

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

27
server/middleware.json Normal file
View File

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

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

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

View File

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

View File

@ -9,7 +9,9 @@ process.on('uncaughtException', function (err) {
process.exit(1); // eslint-disable-line
});
var express = require('express'),
var R = require('ramda'),
loopback = require('loopback'),
boot = require('loopback-boot'),
accepts = require('accepts'),
cookieParser = require('cookie-parser'),
compress = require('compression'),
@ -22,27 +24,11 @@ var express = require('express'),
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'),
/**
* 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'),
passportProviders = require('./passport-providers'),
/**
* API keys and Passport configuration.
*/
@ -51,22 +37,10 @@ var express = require('express'),
/**
* 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 () {
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,5 +1,4 @@
var async = require('async'),
path = require('path'),
var path = require('path'),
// debug = require('debug')('freecc:cntr:resources'),
cheerio = require('cheerio'),
request = require('request'),
@ -8,11 +7,9 @@ var async = require('async'),
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');
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();
});
}
}
};