Merge branch 'staging' of github.com:FreeCodeCamp/freecodecamp into staging
This commit is contained in:
@ -95,7 +95,7 @@ DEBUG=true
|
|||||||
mongod
|
mongod
|
||||||
|
|
||||||
# Seed your database with the challenges
|
# Seed your database with the challenges
|
||||||
node seed_data/seed.js
|
node seed/
|
||||||
|
|
||||||
# start the application
|
# start the application
|
||||||
gulp
|
gulp
|
||||||
|
7
common/config.global.js
Normal file
7
common/config.global.js
Normal 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;
|
16
common/models/User-Credential.json
Normal file
16
common/models/User-Credential.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "userCredential",
|
||||||
|
"plural": "userCredentials",
|
||||||
|
"base": "UserCredential",
|
||||||
|
"properties": {},
|
||||||
|
"validations": [],
|
||||||
|
"relations": {
|
||||||
|
"user": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "user",
|
||||||
|
"foreignKey": "userId"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [],
|
||||||
|
"methods": []
|
||||||
|
}
|
27
common/models/User-Identity.js
Normal file
27
common/models/User-Identity.js
Normal 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();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
//};
|
16
common/models/User-Identity.json
Normal file
16
common/models/User-Identity.json
Normal 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
19
common/models/User.js
Normal 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();
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
};
|
@ -18,7 +18,7 @@
|
|||||||
"projectDescription": "string",
|
"projectDescription": "string",
|
||||||
"logoUrl": "string",
|
"logoUrl": "string",
|
||||||
"imageUrl": "string",
|
"imageUrl": "string",
|
||||||
"estimatedHours": 0,
|
"estimatedHours": "number",
|
||||||
"interestedCampers": [],
|
"interestedCampers": [],
|
||||||
"confirmedCampers": [],
|
"confirmedCampers": [],
|
||||||
"currentStatus": "string"
|
"currentStatus": "string"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "bonfire",
|
"name": "story",
|
||||||
"base": "PersistedModel",
|
"base": "PersistedModel",
|
||||||
"trackChanges": false,
|
"trackChanges": false,
|
||||||
"idInjection": true,
|
"idInjection": true,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bonfire",
|
"name": "user",
|
||||||
"base": "PersistedModel",
|
"base": "User",
|
||||||
"trackChanges": false,
|
"trackChanges": false,
|
||||||
"idInjection": true,
|
"idInjection": true,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
19
common/screens/App.jsx
Normal file
19
common/screens/App.jsx
Normal 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
34
common/screens/Router.jsx
Normal 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
|
||||||
|
});
|
||||||
|
};
|
63
common/screens/bonfires/Actions.js
Normal file
63
common/screens/bonfires/Actions.js
Normal 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;
|
99
common/screens/bonfires/Bonfires.jsx
Normal file
99
common/screens/bonfires/Bonfires.jsx
Normal 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;
|
62
common/screens/bonfires/Results.jsx
Normal file
62
common/screens/bonfires/Results.jsx
Normal 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;
|
129
common/screens/bonfires/SidePanel.jsx
Normal file
129
common/screens/bonfires/SidePanel.jsx
Normal 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:
|
||||||
|
{ 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;
|
67
common/screens/bonfires/Store.js
Normal file
67
common/screens/bonfires/Store.js
Normal 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;
|
27
common/screens/bonfires/executeBonfire.js
Normal file
27
common/screens/bonfires/executeBonfire.js
Normal 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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
1
common/screens/bonfires/index.js
Normal file
1
common/screens/bonfires/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./Bonfires.jsx');
|
34
common/screens/context/Actions.js
Normal file
34
common/screens/context/Actions.js
Normal 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;
|
18
common/screens/context/Store.js
Normal file
18
common/screens/context/Store.js
Normal 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;
|
51
common/screens/displayCode/Display.jsx
Normal file
51
common/screens/displayCode/Display.jsx
Normal 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;
|
1
common/screens/displayCode/index.js
Normal file
1
common/screens/displayCode/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./Display.jsx');
|
91
common/screens/editor/Editor.jsx
Normal file
91
common/screens/editor/Editor.jsx
Normal 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;
|
1
common/screens/editor/index.js
Normal file
1
common/screens/editor/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./Editor.jsx');
|
106
common/screens/footer/Footer.jsx
Normal file
106
common/screens/footer/Footer.jsx
Normal 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'>
|
||||||
|
Blog
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
ref='http://www.twitch.tv/freecodecamp'
|
||||||
|
target='_blank' className='ion-social-twitch-outline'>
|
||||||
|
Twitch
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href='http://github.com/freecodecamp'
|
||||||
|
target='_blank'
|
||||||
|
className='ion-social-github'>
|
||||||
|
Github
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href='http://twitter.com/freecodecamp'
|
||||||
|
target='_blank' className='ion-social-twitter'>
|
||||||
|
Twitter
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href='http://facebook.com/freecodecamp'
|
||||||
|
target='_blank'
|
||||||
|
className='ion-social-facebook'>
|
||||||
|
Facebook
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
ref='/learn-to-code'
|
||||||
|
className='ion-information-circled'>
|
||||||
|
About
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href='/privacy'
|
||||||
|
className='ion-locked'>
|
||||||
|
Privacy
|
||||||
|
</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;
|
1
common/screens/footer/index.js
Normal file
1
common/screens/footer/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./Footer.jsx');
|
81
common/screens/nav/Nav.jsx
Normal file
81
common/screens/nav/Nav.jsx
Normal 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;
|
66
common/screens/nav/NavItem.jsx
Normal file
66
common/screens/nav/NavItem.jsx
Normal 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;
|
1
common/screens/nav/index.js
Normal file
1
common/screens/nav/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./Nav.jsx');
|
27
package.json
27
package.json
@ -57,27 +57,28 @@
|
|||||||
"less": "~1.7.5",
|
"less": "~1.7.5",
|
||||||
"less-middleware": "~2.0.1",
|
"less-middleware": "~2.0.1",
|
||||||
"lodash": "~2.4.1",
|
"lodash": "~2.4.1",
|
||||||
|
"loopback": "^2.18.0",
|
||||||
|
"loopback-boot": "^2.8.0",
|
||||||
|
"loopback-component-passport": "^1.3.1",
|
||||||
|
"loopback-connector-mongodb": "^1.10.0",
|
||||||
"lusca": "~1.0.2",
|
"lusca": "~1.0.2",
|
||||||
"method-override": "~2.3.0",
|
"method-override": "~2.3.0",
|
||||||
"moment": "~2.10.2",
|
"moment": "~2.10.2",
|
||||||
"mongodb": "~1.4.33",
|
"mongodb": "^2.0.33",
|
||||||
"mongoose": "~4.0.1",
|
|
||||||
"mongoose-long": "0.0.2",
|
|
||||||
"morgan": "~1.5.0",
|
"morgan": "~1.5.0",
|
||||||
"node-slack": "0.0.7",
|
"node-slack": "0.0.7",
|
||||||
"nodemailer": "~1.3.0",
|
"nodemailer": "~1.3.0",
|
||||||
"passport": "~0.2.1",
|
"passport-facebook": "^2.0.0",
|
||||||
"passport-facebook": "~1.0.3",
|
"passport-google-oauth": "^0.2.0",
|
||||||
"passport-github": "~0.1.5",
|
"passport-google-oauth2": "^0.1.6",
|
||||||
"passport-google-oauth": "~0.1.5",
|
"passport-linkedin-oauth2": "^1.2.1",
|
||||||
"passport-linkedin-oauth2": "~1.2.1",
|
"passport-local": "^1.0.0",
|
||||||
"passport-local": "~1.0.0",
|
"passport-oauth": "^1.0.0",
|
||||||
"passport-oauth": "~1.0.0",
|
"passport-twitter": "^1.0.3",
|
||||||
"passport-twitter": "~1.0.2",
|
|
||||||
"ramda": "~0.10.0",
|
"ramda": "~0.10.0",
|
||||||
"request": "~2.53.0",
|
"request": "~2.53.0",
|
||||||
|
"rx": "^2.5.3",
|
||||||
"sanitize-html": "~1.6.1",
|
"sanitize-html": "~1.6.1",
|
||||||
"sitemap": "~0.7.4",
|
|
||||||
"twit": "~1.1.20",
|
"twit": "~1.1.20",
|
||||||
"uglify-js": "~2.4.15",
|
"uglify-js": "~2.4.15",
|
||||||
"validator": "~3.22.1",
|
"validator": "~3.22.1",
|
||||||
@ -94,7 +95,7 @@
|
|||||||
"gulp": "~3.8.8",
|
"gulp": "~3.8.8",
|
||||||
"gulp-eslint": "~0.9.0",
|
"gulp-eslint": "~0.9.0",
|
||||||
"gulp-inject": "~1.0.2",
|
"gulp-inject": "~1.0.2",
|
||||||
"gulp-nodemon": "~1.0.4",
|
"gulp-nodemon": "^2.0.3",
|
||||||
"mocha": "~2.0.1",
|
"mocha": "~2.0.1",
|
||||||
"multiline": "~1.0.1",
|
"multiline": "~1.0.1",
|
||||||
"supertest": "~0.15.0"
|
"supertest": "~0.15.0"
|
||||||
|
@ -237,7 +237,7 @@
|
|||||||
"name": "Waypoint: Fill in the Blank with Placeholder Text",
|
"name": "Waypoint: Fill in the Blank with Placeholder Text",
|
||||||
"difficulty": 0.015,
|
"difficulty": 0.015,
|
||||||
"description": [
|
"description": [
|
||||||
"Replace the text inside your <code>p</code> element with the first few words of the provided \"Ktty Ipsum\" text.",
|
"Replace the text inside your <code>p</code> element with the first few words of the provided \"Kitty Ipsum\" text.",
|
||||||
"Web developers traditionally use \"Lorem Ipsum\" text as placeholder text. It's called \"Lorem Ipsum\" text because those are the first two words of a famous passage by Cicero of Ancient Rome.",
|
"Web developers traditionally use \"Lorem Ipsum\" text as placeholder text. It's called \"Lorem Ipsum\" text because those are the first two words of a famous passage by Cicero of Ancient Rome.",
|
||||||
"\"Lorem Ipsum\" text has been used as placeholder text by typesetters since the 16th century, and this tradition continues on the web.",
|
"\"Lorem Ipsum\" text has been used as placeholder text by typesetters since the 16th century, and this tradition continues on the web.",
|
||||||
"Well, 5 centuries is long enough. Since we're building a CatPhotoApp, let's use something called \"Kitty Ipsum\"!",
|
"Well, 5 centuries is long enough. Since we're building a CatPhotoApp, let's use something called \"Kitty Ipsum\"!",
|
@ -683,7 +683,7 @@
|
|||||||
" <li>Fork the Free Code Camp repository and <code>open seed_data/bonfires.json</code> to become familiar with the format of our bonfires.</li>",
|
" <li>Fork the Free Code Camp repository and <code>open seed_data/bonfires.json</code> to become familiar with the format of our bonfires.</li>",
|
||||||
" <li>Regardless of your bonfire's difficulty, put it as the last bonfire in the JSON file. Change one of the numbers in the ID to ensure that your bonfire has a unique ID.</li>",
|
" <li>Regardless of your bonfire's difficulty, put it as the last bonfire in the JSON file. Change one of the numbers in the ID to ensure that your bonfire has a unique ID.</li>",
|
||||||
" <li>In the terminal, run <code>node seed_data/seed.js</code>. Run <code>gulp</code>. You should be able to navigate to your new bonfire in the challenge map. Whenever you make a change to bonfire.json, you'll need to reseed in order to see these changes in the browser.</li>",
|
" <li>In the terminal, run <code>node seed_data/seed.js</code>. Run <code>gulp</code>. You should be able to navigate to your new bonfire in the challenge map. Whenever you make a change to bonfire.json, you'll need to reseed in order to see these changes in the browser.</li>",
|
||||||
" <li>Solved your own Bonfire. Confirmed that your tests work as expected and that your instructions are sufficiently clear.</li>",
|
" <li>Solve your own Bonfire. Confirm that your tests work as expected and that your instructions are sufficiently clear.</li>",
|
||||||
" <li>Submit a pull request to Free Code Camp's Staging branch and in the pull request body, link to a gist that has your algorithmic solution.</li>",
|
" <li>Submit a pull request to Free Code Camp's Staging branch and in the pull request body, link to a gist that has your algorithmic solution.</li>",
|
||||||
" </ol>",
|
" </ol>",
|
||||||
" </p>",
|
" </p>",
|
99
seed/index.js
Normal file
99
seed/index.js
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
@ -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');
|
|
||||||
});
|
|
4
server/boot/authentication.js
Normal file
4
server/boot/authentication.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module.exports = function enableAuthentication(app) {
|
||||||
|
// enable authentication
|
||||||
|
app.enableAuth();
|
||||||
|
};
|
@ -31,33 +31,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var R = require('ramda'),
|
var R = require('ramda'),
|
||||||
express = require('express'),
|
utils = require('../utils'),
|
||||||
Challenge = require('../../common/models/Challenge'),
|
userMigration = require('../utils/middleware').userMigration,
|
||||||
User = require('../../common/models/User'),
|
MDNlinks = require('../../seed/bonfireMDNlinks');
|
||||||
resources = require('../resources/resources'),
|
|
||||||
userMigration = require('../resources/middleware').userMigration,
|
|
||||||
MDNlinks = require('../../seed_data/bonfireMDNlinks');
|
|
||||||
|
|
||||||
var router = express.Router();
|
var challengeMapWithNames = utils.getChallengeMapWithNames();
|
||||||
var challengeMapWithNames = resources.getChallengeMapWithNames();
|
var challengeMapWithIds = utils.getChallengeMapWithIds();
|
||||||
var challengeMapWithIds = resources.getChallengeMapWithIds();
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/challenges/next-challenge',
|
|
||||||
userMigration,
|
|
||||||
returnNextChallenge
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/challenges/:challengeName',
|
|
||||||
userMigration,
|
|
||||||
returnIndividualChallenge
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get('/challenges/', userMigration, returnCurrentChallenge);
|
|
||||||
router.post('/completed-challenge/', completedChallenge);
|
|
||||||
router.post('/completed-zipline-or-basejump', completedZiplineOrBasejump);
|
|
||||||
router.post('/completed-bonfire', completedBonfire);
|
|
||||||
|
|
||||||
function getMDNlinks(links) {
|
function getMDNlinks(links) {
|
||||||
// takes in an array of links, which are strings
|
// takes in an array of links, which are strings
|
||||||
@ -73,7 +53,31 @@ function getMDNlinks(links) {
|
|||||||
return populatedLinks;
|
return populatedLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function returnNextChallenge(req, res, next) {
|
module.exports = function(app) {
|
||||||
|
var router = app.loopback.Router();
|
||||||
|
var Challenge = app.models.Challenge;
|
||||||
|
var User = app.models.User;
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/challenges/next-challenge',
|
||||||
|
userMigration,
|
||||||
|
returnNextChallenge
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/challenges/:challengeName',
|
||||||
|
userMigration,
|
||||||
|
returnIndividualChallenge
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get('/challenges/', userMigration, returnCurrentChallenge);
|
||||||
|
router.post('/completed-challenge/', completedChallenge);
|
||||||
|
router.post('/completed-zipline-or-basejump', completedZiplineOrBasejump);
|
||||||
|
router.post('/completed-bonfire', completedBonfire);
|
||||||
|
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
function returnNextChallenge(req, res, next) {
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return res.redirect('../challenges/learn-how-free-code-camp-works');
|
return res.redirect('../challenges/learn-how-free-code-camp-works');
|
||||||
}
|
}
|
||||||
@ -81,7 +85,7 @@ function returnNextChallenge(req, res, next) {
|
|||||||
return elem._id;
|
return elem._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
req.user.uncompletedChallenges = resources.allChallengeIds()
|
req.user.uncompletedChallenges = utils.allChallengeIds()
|
||||||
.filter(function (elem) {
|
.filter(function (elem) {
|
||||||
if (completed.indexOf(elem) === -1) {
|
if (completed.indexOf(elem) === -1) {
|
||||||
return elem;
|
return elem;
|
||||||
@ -125,9 +129,9 @@ function returnNextChallenge(req, res, next) {
|
|||||||
}
|
}
|
||||||
return res.redirect('../challenges/' + nameString);
|
return res.redirect('../challenges/' + nameString);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function returnCurrentChallenge(req, res, next) {
|
function returnCurrentChallenge(req, res, next) {
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return res.redirect('../challenges/learn-how-free-code-camp-works');
|
return res.redirect('../challenges/learn-how-free-code-camp-works');
|
||||||
}
|
}
|
||||||
@ -135,7 +139,7 @@ function returnCurrentChallenge(req, res, next) {
|
|||||||
return elem._id;
|
return elem._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
req.user.uncompletedChallenges = resources.allChallengeIds()
|
req.user.uncompletedChallenges = utils.allChallengeIds()
|
||||||
.filter(function (elem) {
|
.filter(function (elem) {
|
||||||
if (completed.indexOf(elem) === -1) {
|
if (completed.indexOf(elem) === -1) {
|
||||||
return elem;
|
return elem;
|
||||||
@ -162,9 +166,9 @@ function returnCurrentChallenge(req, res, next) {
|
|||||||
}
|
}
|
||||||
return res.redirect('../challenges/' + nameString);
|
return res.redirect('../challenges/' + nameString);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function returnIndividualChallenge(req, res, next) {
|
function returnIndividualChallenge(req, res, next) {
|
||||||
var dashedName = req.params.challengeName;
|
var dashedName = req.params.challengeName;
|
||||||
|
|
||||||
var challengeName =
|
var challengeName =
|
||||||
@ -176,11 +180,11 @@ function returnIndividualChallenge(req, res, next) {
|
|||||||
.join(' ') :
|
.join(' ') :
|
||||||
dashedName.replace(/\-/g, ' ');
|
dashedName.replace(/\-/g, ' ');
|
||||||
|
|
||||||
Challenge.find({'name': new RegExp(challengeName, 'i')},
|
Challenge.find(
|
||||||
|
{ where: { name: new RegExp(challengeName, 'i') } },
|
||||||
function(err, challengeFromMongo) {
|
function(err, challengeFromMongo) {
|
||||||
if (err) {
|
if (err) { return next(err); }
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
// Handle not found
|
// Handle not found
|
||||||
if (challengeFromMongo.length < 1) {
|
if (challengeFromMongo.length < 1) {
|
||||||
req.flash('errors', {
|
req.flash('errors', {
|
||||||
@ -224,11 +228,11 @@ function returnIndividualChallenge(req, res, next) {
|
|||||||
details: challenge.description.slice(1),
|
details: challenge.description.slice(1),
|
||||||
tests: challenge.tests,
|
tests: challenge.tests,
|
||||||
challengeSeed: challenge.challengeSeed,
|
challengeSeed: challenge.challengeSeed,
|
||||||
verb: resources.randomVerb(),
|
verb: utils.randomVerb(),
|
||||||
phrase: resources.randomPhrase(),
|
phrase: utils.randomPhrase(),
|
||||||
compliment: resources.randomCompliment(),
|
compliment: utils.randomCompliment(),
|
||||||
challengeId: challenge._id,
|
challengeId: challenge._id,
|
||||||
environment: resources.whichEnvironment(),
|
environment: utils.whichEnvironment(),
|
||||||
challengeType: challenge.challengeType
|
challengeType: challenge.challengeType
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -242,9 +246,9 @@ function returnIndividualChallenge(req, res, next) {
|
|||||||
details: challenge.description.slice(1),
|
details: challenge.description.slice(1),
|
||||||
tests: challenge.tests,
|
tests: challenge.tests,
|
||||||
challengeSeed: challenge.challengeSeed,
|
challengeSeed: challenge.challengeSeed,
|
||||||
verb: resources.randomVerb(),
|
verb: utils.randomVerb(),
|
||||||
phrase: resources.randomPhrase(),
|
phrase: utils.randomPhrase(),
|
||||||
compliment: resources.randomCompliment(),
|
compliment: utils.randomCompliment(),
|
||||||
challengeId: challenge._id,
|
challengeId: challenge._id,
|
||||||
challengeType: challenge.challengeType
|
challengeType: challenge.challengeType
|
||||||
});
|
});
|
||||||
@ -258,9 +262,9 @@ function returnIndividualChallenge(req, res, next) {
|
|||||||
details: challenge.description,
|
details: challenge.description,
|
||||||
tests: challenge.tests,
|
tests: challenge.tests,
|
||||||
video: challenge.challengeSeed[0],
|
video: challenge.challengeSeed[0],
|
||||||
verb: resources.randomVerb(),
|
verb: utils.randomVerb(),
|
||||||
phrase: resources.randomPhrase(),
|
phrase: utils.randomPhrase(),
|
||||||
compliment: resources.randomCompliment(),
|
compliment: utils.randomCompliment(),
|
||||||
challengeId: challenge._id,
|
challengeId: challenge._id,
|
||||||
challengeType: challenge.challengeType
|
challengeType: challenge.challengeType
|
||||||
});
|
});
|
||||||
@ -273,9 +277,9 @@ function returnIndividualChallenge(req, res, next) {
|
|||||||
name: challenge.name,
|
name: challenge.name,
|
||||||
details: challenge.description,
|
details: challenge.description,
|
||||||
video: challenge.challengeSeed[0],
|
video: challenge.challengeSeed[0],
|
||||||
verb: resources.randomVerb(),
|
verb: utils.randomVerb(),
|
||||||
phrase: resources.randomPhrase(),
|
phrase: utils.randomPhrase(),
|
||||||
compliment: resources.randomCompliment(),
|
compliment: utils.randomCompliment(),
|
||||||
challengeId: challenge._id,
|
challengeId: challenge._id,
|
||||||
challengeType: challenge.challengeType
|
challengeType: challenge.challengeType
|
||||||
});
|
});
|
||||||
@ -288,9 +292,9 @@ function returnIndividualChallenge(req, res, next) {
|
|||||||
name: challenge.name,
|
name: challenge.name,
|
||||||
details: challenge.description,
|
details: challenge.description,
|
||||||
video: challenge.challengeSeed[0],
|
video: challenge.challengeSeed[0],
|
||||||
verb: resources.randomVerb(),
|
verb: utils.randomVerb(),
|
||||||
phrase: resources.randomPhrase(),
|
phrase: utils.randomPhrase(),
|
||||||
compliment: resources.randomCompliment(),
|
compliment: utils.randomCompliment(),
|
||||||
challengeId: challenge._id,
|
challengeId: challenge._id,
|
||||||
challengeType: challenge.challengeType
|
challengeType: challenge.challengeType
|
||||||
});
|
});
|
||||||
@ -307,9 +311,9 @@ function returnIndividualChallenge(req, res, next) {
|
|||||||
details: challenge.description,
|
details: challenge.description,
|
||||||
tests: challenge.tests,
|
tests: challenge.tests,
|
||||||
challengeSeed: challenge.challengeSeed,
|
challengeSeed: challenge.challengeSeed,
|
||||||
verb: resources.randomVerb(),
|
verb: utils.randomVerb(),
|
||||||
phrase: resources.randomPhrase(),
|
phrase: utils.randomPhrase(),
|
||||||
compliment: resources.randomCompliment(),
|
compliment: utils.randomCompliment(),
|
||||||
bonfires: challenge,
|
bonfires: challenge,
|
||||||
challengeId: challenge._id,
|
challengeId: challenge._id,
|
||||||
MDNkeys: challenge.MDNlinks,
|
MDNkeys: challenge.MDNlinks,
|
||||||
@ -329,9 +333,9 @@ function returnIndividualChallenge(req, res, next) {
|
|||||||
return challengeType[challenge.challengeType]();
|
return challengeType[challenge.challengeType]();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function completedBonfire(req, res, next) {
|
function completedBonfire(req, res, next) {
|
||||||
var isCompletedWith = req.body.challengeInfo.completedWith || '';
|
var isCompletedWith = req.body.challengeInfo.completedWith || '';
|
||||||
var isCompletedDate = Math.round(+new Date());
|
var isCompletedDate = Math.round(+new Date());
|
||||||
var challengeId = req.body.challengeInfo.challengeId;
|
var challengeId = req.body.challengeInfo.challengeId;
|
||||||
@ -339,12 +343,12 @@ function completedBonfire(req, res, next) {
|
|||||||
var challengeName = req.body.challengeInfo.challengeName;
|
var challengeName = req.body.challengeInfo.challengeName;
|
||||||
|
|
||||||
if (isCompletedWith) {
|
if (isCompletedWith) {
|
||||||
var paired = User.find({'profile.username': isCompletedWith.toLowerCase()})
|
User.find({
|
||||||
.limit(1);
|
where: { 'profile.username': isCompletedWith.toLowerCase() },
|
||||||
paired.exec(function (err, pairedWith) {
|
limit: 1
|
||||||
if (err) {
|
}, function (err, pairedWith) {
|
||||||
return next(err);
|
if (err) { return next(err); }
|
||||||
} else {
|
|
||||||
var index = req.user.uncompletedChallenges.indexOf(challengeId);
|
var index = req.user.uncompletedChallenges.indexOf(challengeId);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
req.user.progressTimestamps.push(Date.now() || 0);
|
req.user.progressTimestamps.push(Date.now() || 0);
|
||||||
@ -388,11 +392,9 @@ function completedBonfire(req, res, next) {
|
|||||||
challengeType: 5
|
challengeType: 5
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
req.user.save(function (err, user) {
|
req.user.save(function (err, user) {
|
||||||
if (err) {
|
if (err) { return next(err); }
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
if (pairedWith) {
|
if (pairedWith) {
|
||||||
pairedWith.save(function (err, paired) {
|
pairedWith.save(function (err, paired) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -406,7 +408,6 @@ function completedBonfire(req, res, next) {
|
|||||||
res.send(true);
|
res.send(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
req.user.completedChallenges.push({
|
req.user.completedChallenges.push({
|
||||||
@ -425,19 +426,14 @@ function completedBonfire(req, res, next) {
|
|||||||
req.user.uncompletedChallenges.splice(index, 1);
|
req.user.uncompletedChallenges.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
req.user.save(function (err, user) {
|
req.user.save(function (err) {
|
||||||
if (err) {
|
if (err) { return next(err); }
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
// NOTE(berks): Under certain conditions the res is never ended
|
|
||||||
if (user) {
|
|
||||||
res.send(true);
|
res.send(true);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function completedChallenge(req, res, next) {
|
function completedChallenge(req, res, next) {
|
||||||
|
|
||||||
var isCompletedDate = Math.round(+new Date());
|
var isCompletedDate = Math.round(+new Date());
|
||||||
var challengeId = req.body.challengeInfo.challengeId;
|
var challengeId = req.body.challengeInfo.challengeId;
|
||||||
@ -465,9 +461,9 @@ function completedChallenge(req, res, next) {
|
|||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function completedZiplineOrBasejump(req, res, next) {
|
function completedZiplineOrBasejump(req, res, next) {
|
||||||
|
|
||||||
var isCompletedWith = req.body.challengeInfo.completedWith || false;
|
var isCompletedWith = req.body.challengeInfo.completedWith || false;
|
||||||
var isCompletedDate = Math.round(+new Date());
|
var isCompletedDate = Math.round(+new Date());
|
||||||
@ -486,13 +482,11 @@ function completedZiplineOrBasejump(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isCompletedWith) {
|
if (isCompletedWith) {
|
||||||
var paired = User.find({'profile.username': isCompletedWith.toLowerCase()})
|
User.find({
|
||||||
.limit(1);
|
where: { 'profile.username': isCompletedWith.toLowerCase() },
|
||||||
|
limit: 1
|
||||||
paired.exec(function (err, pairedWithFromMongo) {
|
}, function (err, pairedWithFromMongo) {
|
||||||
if (err) {
|
if (err) { return next(err); }
|
||||||
return next(err);
|
|
||||||
} else {
|
|
||||||
var index = req.user.uncompletedChallenges.indexOf(challengeId);
|
var index = req.user.uncompletedChallenges.indexOf(challengeId);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
req.user.progressTimestamps.push(Date.now() || 0);
|
req.user.progressTimestamps.push(Date.now() || 0);
|
||||||
@ -512,9 +506,7 @@ function completedZiplineOrBasejump(req, res, next) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
req.user.save(function (err, user) {
|
req.user.save(function (err, user) {
|
||||||
if (err) {
|
if (err) { return next(err); }
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.user._id.toString() === pairedWith._id.toString()) {
|
if (req.user._id.toString() === pairedWith._id.toString()) {
|
||||||
return res.sendStatus(200);
|
return res.sendStatus(200);
|
||||||
@ -545,7 +537,6 @@ function completedZiplineOrBasejump(req, res, next) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@ -570,12 +561,12 @@ function completedZiplineOrBasejump(req, res, next) {
|
|||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
// NOTE(berks): under certain conditions this will not close the response.
|
// NOTE(berks): under certain conditions this will not close
|
||||||
|
// the response.
|
||||||
if (user) {
|
if (user) {
|
||||||
return res.sendStatus(200);
|
return res.sendStatus(200);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
module.exports = router;
|
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
var R = require('ramda'),
|
var R = require('ramda'),
|
||||||
express = require('express'),
|
|
||||||
// debug = require('debug')('freecc:cntr:challengeMap'),
|
// debug = require('debug')('freecc:cntr:challengeMap'),
|
||||||
User = require('../../common/models/User'),
|
utils = require('./../utils'),
|
||||||
resources = require('./../resources/resources'),
|
middleware = require('../utils/middleware');
|
||||||
middleware = require('../resources/middleware'),
|
|
||||||
router = express.Router();
|
|
||||||
|
|
||||||
router.get('/map', middleware.userMigration, challengeMap);
|
|
||||||
|
|
||||||
router.get('/learn-to-code', function(req, res) {
|
module.exports = function(app) {
|
||||||
|
var User = app.models.User;
|
||||||
|
var router = app.loopback.Router();
|
||||||
|
|
||||||
|
router.get('/map', middleware.userMigration, challengeMap);
|
||||||
|
router.get('/learn-to-code', function(req, res) {
|
||||||
res.redirect(301, '/map');
|
res.redirect(301, '/map');
|
||||||
});
|
});
|
||||||
|
router.get('/about', function(req, res) {
|
||||||
router.get('/about', function(req, res) {
|
|
||||||
res.redirect(301, '/map');
|
res.redirect(301, '/map');
|
||||||
});
|
});
|
||||||
|
|
||||||
function challengeMap(req, res, next) {
|
app.use(router);
|
||||||
|
|
||||||
|
function challengeMap(req, res, next) {
|
||||||
var completedList = [];
|
var completedList = [];
|
||||||
|
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
@ -29,7 +31,7 @@ function challengeMap(req, res, next) {
|
|||||||
.map(function(challenge) {
|
.map(function(challenge) {
|
||||||
return challenge._id;
|
return challenge._id;
|
||||||
});
|
});
|
||||||
var challengeList = resources.
|
var challengeList = utils.
|
||||||
getChallengeMapForDisplay(completedChallengeList);
|
getChallengeMapForDisplay(completedChallengeList);
|
||||||
|
|
||||||
Object.keys(challengeList).forEach(function(key) {
|
Object.keys(challengeList).forEach(function(key) {
|
||||||
@ -48,10 +50,9 @@ function challengeMap(req, res, next) {
|
|||||||
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
|
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
|
||||||
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
|
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
|
||||||
|
|
||||||
User.count({}, function (err, camperCount) {
|
User.count(function(err, camperCount) {
|
||||||
if (err) {
|
if (err) { return next(err); }
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
res.render('challengeMap/show', {
|
res.render('challengeMap/show', {
|
||||||
daysRunning: daysRunning,
|
daysRunning: daysRunning,
|
||||||
camperCount: numberWithCommas(camperCount),
|
camperCount: numberWithCommas(camperCount),
|
||||||
@ -60,6 +61,5 @@ function challengeMap(req, res, next) {
|
|||||||
completedChallengeList: completedChallengeList
|
completedChallengeList: completedChallengeList
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
module.exports = router;
|
|
||||||
|
27
server/boot/explorer.js
Normal file
27
server/boot/explorer.js
Normal 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);
|
||||||
|
});
|
||||||
|
};
|
@ -1,22 +1,25 @@
|
|||||||
var R = require('ramda'),
|
var R = require('ramda'),
|
||||||
express = require('express'),
|
// Rx = require('rx'),
|
||||||
// debug = require('debug')('freecc:fieldguides'),
|
// debug = require('debug')('freecc:fieldguides'),
|
||||||
FieldGuide = require('../../common/models/FieldGuide'),
|
utils = require('../utils');
|
||||||
resources = require('../resources/resources');
|
|
||||||
|
|
||||||
var router = express.Router();
|
module.exports = function(app) {
|
||||||
|
var router = app.loopback.Router();
|
||||||
|
var FieldGuide = app.models.FieldGuide;
|
||||||
|
|
||||||
router.get('/field-guide/all-articles', showAllFieldGuides);
|
router.get('/field-guide/all-articles', showAllFieldGuides);
|
||||||
router.get('/field-guide/:fieldGuideName', returnIndividualFieldGuide);
|
router.get('/field-guide/:fieldGuideName', returnIndividualFieldGuide);
|
||||||
router.get('/field-guide/', returnNextFieldGuide);
|
router.get('/field-guide/', returnNextFieldGuide);
|
||||||
router.post('/completed-field-guide/', completedFieldGuide);
|
router.post('/completed-field-guide/', completedFieldGuide);
|
||||||
|
|
||||||
function returnIndividualFieldGuide(req, res, next) {
|
app.use(router);
|
||||||
|
|
||||||
|
function returnIndividualFieldGuide(req, res, next) {
|
||||||
var dashedName = req.params.fieldGuideName;
|
var dashedName = req.params.fieldGuideName;
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
var completed = req.user.completedFieldGuides;
|
var completed = req.user.completedFieldGuides;
|
||||||
|
|
||||||
var uncompletedFieldGuides = resources.allFieldGuideIds()
|
var uncompletedFieldGuides = utils.allFieldGuideIds()
|
||||||
.filter(function (elem) {
|
.filter(function (elem) {
|
||||||
if (completed.indexOf(elem) === -1) {
|
if (completed.indexOf(elem) === -1) {
|
||||||
return elem;
|
return elem;
|
||||||
@ -24,9 +27,12 @@ function returnIndividualFieldGuide(req, res, next) {
|
|||||||
});
|
});
|
||||||
req.user.uncompletedFieldGuides = uncompletedFieldGuides;
|
req.user.uncompletedFieldGuides = uncompletedFieldGuides;
|
||||||
// TODO(berks): handle callback properly
|
// TODO(berks): handle callback properly
|
||||||
req.user.save();
|
req.user.save(function(err) {
|
||||||
|
if (err) { return next(err); }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE(berks): loopback might have issue with regex here.
|
||||||
FieldGuide.find(
|
FieldGuide.find(
|
||||||
{ dashedName: new RegExp(dashedName, 'i') },
|
{ dashedName: new RegExp(dashedName, 'i') },
|
||||||
function(err, fieldGuideFromMongo) {
|
function(err, fieldGuideFromMongo) {
|
||||||
@ -56,10 +62,10 @@ function returnIndividualFieldGuide(req, res, next) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showAllFieldGuides(req, res) {
|
function showAllFieldGuides(req, res) {
|
||||||
var allFieldGuideNamesAndIds = resources.allFieldGuideNamesAndIds();
|
var allFieldGuideNamesAndIds = utils.allFieldGuideNamesAndIds();
|
||||||
|
|
||||||
var completedFieldGuides = [];
|
var completedFieldGuides = [];
|
||||||
if (req.user && req.user.completedFieldGuides) {
|
if (req.user && req.user.completedFieldGuides) {
|
||||||
@ -69,9 +75,9 @@ function showAllFieldGuides(req, res) {
|
|||||||
allFieldGuideNamesAndIds: allFieldGuideNamesAndIds,
|
allFieldGuideNamesAndIds: allFieldGuideNamesAndIds,
|
||||||
completedFieldGuides: completedFieldGuides
|
completedFieldGuides: completedFieldGuides
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function returnNextFieldGuide(req, res, next) {
|
function returnNextFieldGuide(req, res, next) {
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return res.redirect('/field-guide/how-do-i-use-this-guide');
|
return res.redirect('/field-guide/how-do-i-use-this-guide');
|
||||||
}
|
}
|
||||||
@ -90,7 +96,7 @@ function returnNextFieldGuide(req, res, next) {
|
|||||||
"You've read all our current Field Guide entries. You can ",
|
"You've read all our current Field Guide entries. You can ",
|
||||||
'contribute to our Field Guide ',
|
'contribute to our Field Guide ',
|
||||||
"<a href='https://github.com/FreeCodeCamp/freecodecamp/blob/",
|
"<a href='https://github.com/FreeCodeCamp/freecodecamp/blob/",
|
||||||
"staging/seed_data/field-guides.json'>here</a>."
|
"staging/seed/field-guides.json'>here</a>."
|
||||||
].join('')
|
].join('')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -99,9 +105,9 @@ function returnNextFieldGuide(req, res, next) {
|
|||||||
var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-');
|
var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-');
|
||||||
return res.redirect('../field-guide/' + nameString);
|
return res.redirect('../field-guide/' + nameString);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function completedFieldGuide(req, res, next) {
|
function completedFieldGuide(req, res, next) {
|
||||||
var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId;
|
var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId;
|
||||||
|
|
||||||
req.user.completedFieldGuides.push(fieldGuideId);
|
req.user.completedFieldGuides.push(fieldGuideId);
|
||||||
@ -118,6 +124,5 @@ function completedFieldGuide(req, res, next) {
|
|||||||
}
|
}
|
||||||
res.send(true);
|
res.send(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
module.exports = router;
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
var express = require('express');
|
|
||||||
var router = express.Router();
|
|
||||||
var message =
|
var message =
|
||||||
'Learn to Code JavaScript and get a Coding Job by Helping Nonprofits';
|
'Learn to Code JavaScript and get a Coding Job by Helping Nonprofits';
|
||||||
|
|
||||||
router.get('/', index);
|
module.exports = function(app) {
|
||||||
|
var router = app.loopback.Router();
|
||||||
|
router.get('/', index);
|
||||||
|
|
||||||
function index(req, res, next) {
|
app.use(router);
|
||||||
|
|
||||||
|
function index(req, res, next) {
|
||||||
if (req.user && !req.user.profile.picture) {
|
if (req.user && !req.user.profile.picture) {
|
||||||
req.user.profile.picture =
|
req.user.profile.picture =
|
||||||
'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png';
|
'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png';
|
||||||
@ -17,6 +19,5 @@ function index(req, res, next) {
|
|||||||
} else {
|
} else {
|
||||||
res.render('home', { title: message });
|
res.render('home', { title: message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
module.exports = router;
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
var express = require('express');
|
module.exports = function(app) {
|
||||||
var Job = require('../../common/models/Job');
|
var Job = app.models.Job;
|
||||||
var router = express.Router();
|
var router = app.loopback.Router();
|
||||||
|
|
||||||
router.get('/jobs', jobsDirectory);
|
router.get('/jobs', jobsDirectory);
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
function jobsDirectory(req, res, next) {
|
function jobsDirectory(req, res, next) {
|
||||||
Job.find({}, function(err, jobs) {
|
Job.find({}, function(err, jobs) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
|
|
||||||
@ -13,6 +14,5 @@ function jobsDirectory(req, res, next) {
|
|||||||
jobs: jobs
|
jobs: jobs
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
module.exports = router;
|
|
||||||
|
0
server/boot/middlewares.js
Normal file
0
server/boot/middlewares.js
Normal file
@ -1,28 +1,33 @@
|
|||||||
var express = require('express'),
|
|
||||||
Nonprofit = require('../../common/models/Nonprofit');
|
|
||||||
|
|
||||||
var router = express.Router();
|
module.exports = function(app) {
|
||||||
|
var router = app.loopback.Router();
|
||||||
|
var Nonprofit = app.models.Nonprofit;
|
||||||
|
|
||||||
router.get('/nonprofits/directory', nonprofitsDirectory);
|
router.get('/nonprofits/directory', nonprofitsDirectory);
|
||||||
router.get('/nonprofits/:nonprofitName', returnIndividualNonprofit);
|
router.get('/nonprofits/:nonprofitName', returnIndividualNonprofit);
|
||||||
|
|
||||||
function nonprofitsDirectory(req, res, next) {
|
app.use(router);
|
||||||
Nonprofit.find({ estimatedHours: { $gt: 0 } }, function(err, nonprofits) {
|
|
||||||
|
function nonprofitsDirectory(req, res, next) {
|
||||||
|
Nonprofit.find(
|
||||||
|
{ where: { estimatedHours: { $gt: 0 } } },
|
||||||
|
function(err, nonprofits) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
|
|
||||||
res.render('nonprofits/directory', {
|
res.render('nonprofits/directory', {
|
||||||
title: 'Nonprofits we help',
|
title: 'Nonprofits we help',
|
||||||
nonprofits: nonprofits
|
nonprofits: nonprofits
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function returnIndividualNonprofit(req, res, next) {
|
function returnIndividualNonprofit(req, res, next) {
|
||||||
var dashedName = req.params.nonprofitName;
|
var dashedName = req.params.nonprofitName;
|
||||||
var nonprofitName = dashedName.replace(/\-/g, ' ');
|
var nonprofitName = dashedName.replace(/\-/g, ' ');
|
||||||
|
|
||||||
Nonprofit.find(
|
Nonprofit.find(
|
||||||
{ name: new RegExp(nonprofitName, 'i') },
|
{ where: { name: new RegExp(nonprofitName, 'i') } },
|
||||||
function(err, nonprofit) {
|
function(err, nonprofit) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
@ -95,10 +100,10 @@ function returnIndividualNonprofit(req, res, next) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
function interestedInNonprofit(req, res, next) {
|
function interestedInNonprofit(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
Nonprofit.findOne(
|
Nonprofit.findOne(
|
||||||
{ name: new RegExp(req.params.nonprofitName.replace(/-/, ' '), 'i') },
|
{ name: new RegExp(req.params.nonprofitName.replace(/-/, ' '), 'i') },
|
||||||
@ -120,7 +125,6 @@ function interestedInNonprofit(req, res, next) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
};
|
||||||
module.exports = router;
|
|
||||||
|
@ -1,68 +1,71 @@
|
|||||||
var express = require('express'),
|
/*
|
||||||
passport = require('passport'),
|
var passport = require('passport'),
|
||||||
passportConf = require('../../config/passport');
|
passportConf = require('../../config/passport');
|
||||||
|
|
||||||
var router = express.Router();
|
module.exports = function(app) {
|
||||||
var passportOptions = {
|
var router = app.loopback.Router();
|
||||||
|
var passportOptions = {
|
||||||
successRedirect: '/',
|
successRedirect: '/',
|
||||||
failureRedirect: '/login'
|
failureRedirect: '/login'
|
||||||
};
|
};
|
||||||
|
|
||||||
router.all('/account', passportConf.isAuthenticated);
|
router.all('/account', passportConf.isAuthenticated);
|
||||||
|
|
||||||
router.get('/auth/twitter', passport.authenticate('twitter'));
|
router.get('/auth/twitter', passport.authenticate('twitter'));
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/auth/twitter/callback',
|
'/auth/twitter/callback',
|
||||||
passport.authenticate('twitter', {
|
passport.authenticate('twitter', {
|
||||||
successRedirect: '/',
|
successRedirect: '/',
|
||||||
failureRedirect: '/login'
|
failureRedirect: '/login'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/auth/linkedin',
|
'/auth/linkedin',
|
||||||
passport.authenticate('linkedin', {
|
passport.authenticate('linkedin', {
|
||||||
state: 'SOME STATE'
|
state: 'SOME STATE'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/auth/linkedin/callback',
|
'/auth/linkedin/callback',
|
||||||
passport.authenticate('linkedin', passportOptions)
|
passport.authenticate('linkedin', passportOptions)
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/auth/facebook',
|
'/auth/facebook',
|
||||||
passport.authenticate('facebook', {scope: ['email', 'user_location']})
|
passport.authenticate('facebook', {scope: ['email', 'user_location']})
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/auth/facebook/callback',
|
'/auth/facebook/callback',
|
||||||
passport.authenticate('facebook', passportOptions), function (req, res) {
|
passport.authenticate('facebook', passportOptions), function (req, res) {
|
||||||
res.redirect(req.session.returnTo || '/');
|
res.redirect(req.session.returnTo || '/');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get('/auth/github', passport.authenticate('github'));
|
router.get('/auth/github', passport.authenticate('github'));
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/auth/github/callback',
|
'/auth/github/callback',
|
||||||
passport.authenticate('github', passportOptions), function (req, res) {
|
passport.authenticate('github', passportOptions), function (req, res) {
|
||||||
res.redirect(req.session.returnTo || '/');
|
res.redirect(req.session.returnTo || '/');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/auth/google',
|
'/auth/google',
|
||||||
passport.authenticate('google', {scope: 'profile email'})
|
passport.authenticate('google', {scope: 'profile email'})
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/auth/google/callback',
|
'/auth/google/callback',
|
||||||
passport.authenticate('google', passportOptions), function (req, res) {
|
passport.authenticate('google', passportOptions), function (req, res) {
|
||||||
res.redirect(req.session.returnTo || '/');
|
res.redirect(req.session.returnTo || '/');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
module.exports = router;
|
app.use(router);
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
494
server/boot/randomAPIs.js
Normal file
494
server/boot/randomAPIs.js
Normal 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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -1,47 +1,50 @@
|
|||||||
var express = require('express');
|
module.exports = function(app) {
|
||||||
var router = express.Router();
|
var router = app.loopback.Router();
|
||||||
|
|
||||||
router.get('/nonprofit-project-instructions', function(req, res) {
|
router.get('/nonprofit-project-instructions', function(req, res) {
|
||||||
res.redirect(
|
res.redirect(
|
||||||
301,
|
301,
|
||||||
'/field-guide/how-do-free-code-camp\'s-nonprofit-projects-work'
|
'/field-guide/how-do-free-code-camp\'s-nonprofit-projects-work'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/agile', function(req, res) {
|
router.get('/agile', function(req, res) {
|
||||||
res.redirect(301, '/pmi-acp-agile-project-managers');
|
res.redirect(301, '/pmi-acp-agile-project-managers');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/live-pair-programming', function(req, res) {
|
router.get('/live-pair-programming', function(req, res) {
|
||||||
res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv');
|
res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/install-screenhero', function(req, res) {
|
router.get('/install-screenhero', function(req, res) {
|
||||||
res.redirect(301, '/field-guide/install-screenhero');
|
res.redirect(301, '/field-guide/install-screenhero');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/guide-to-our-nonprofit-projects', function(req, res) {
|
router.get('/guide-to-our-nonprofit-projects', function(req, res) {
|
||||||
res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects');
|
res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/chromebook', function(req, res) {
|
router.get('/chromebook', function(req, res) {
|
||||||
res.redirect(301, '/field-guide/chromebook');
|
res.redirect(301, '/field-guide/chromebook');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/deploy-a-website', function(req, res) {
|
router.get('/deploy-a-website', function(req, res) {
|
||||||
res.redirect(301, '/field-guide/deploy-a-website');
|
res.redirect(301, '/field-guide/deploy-a-website');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/gmail-shortcuts', function(req, res) {
|
router.get('/gmail-shortcuts', function(req, res) {
|
||||||
res.redirect(301, '/field-guide/gmail-shortcuts');
|
res.redirect(301, '/field-guide/gmail-shortcuts');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/nodeschool-challenges', function(req, res) {
|
router.get('/nodeschool-challenges', function(req, res) {
|
||||||
res.redirect(301, '/field-guide/nodeschool-challenges');
|
res.redirect(301, '/field-guide/nodeschool-challenges');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/privacy', function(req, res) {
|
router.get('/privacy', function(req, res) {
|
||||||
res.redirect(301, '/field-guide/what-is-the-free-code-camp-privacy-policy?');
|
res.redirect(
|
||||||
});
|
301, '/field-guide/what-is-the-free-code-camp-privacy-policy?'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
app.use(router);
|
||||||
|
};
|
||||||
|
4
server/boot/restApi.js
Normal file
4
server/boot/restApi.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module.exports = function mountRestApi(app) {
|
||||||
|
var restApiRoot = app.get('restApiRoot');
|
||||||
|
app.use(restApiRoot, app.loopback.rest());
|
||||||
|
};
|
@ -1,33 +1,35 @@
|
|||||||
var nodemailer = require('nodemailer'),
|
var nodemailer = require('nodemailer'),
|
||||||
sanitizeHtml = require('sanitize-html'),
|
sanitizeHtml = require('sanitize-html'),
|
||||||
express = require('express'),
|
|
||||||
moment = require('moment'),
|
moment = require('moment'),
|
||||||
mongodb = require('mongodb'),
|
mongodb = require('mongodb'),
|
||||||
// debug = require('debug')('freecc:cntr:story'),
|
// debug = require('debug')('freecc:cntr:story'),
|
||||||
Story = require('../../common/models/Story'),
|
utils = require('../utils'),
|
||||||
Comment = require('../../common/models/Comment'),
|
|
||||||
User = require('../../common/models/User'),
|
|
||||||
resources = require('../resources/resources'),
|
|
||||||
MongoClient = mongodb.MongoClient,
|
MongoClient = mongodb.MongoClient,
|
||||||
secrets = require('../../config/secrets'),
|
secrets = require('../../config/secrets');
|
||||||
router = express.Router();
|
|
||||||
|
|
||||||
router.get('/stories/hotStories', hotJSON);
|
module.exports = function(app) {
|
||||||
router.get('/stories/recentStories', recentJSON);
|
var router = app.loopback.Router();
|
||||||
router.get('/stories/comments/:id', comments);
|
var User = app.models.User;
|
||||||
router.post('/stories/comment/', commentSubmit);
|
var Story = app.models.Story;
|
||||||
router.post('/stories/comment/:id/comment', commentOnCommentSubmit);
|
|
||||||
router.put('/stories/comment/:id/edit', commentEdit);
|
|
||||||
router.get('/stories/submit', submitNew);
|
|
||||||
router.get('/stories/submit/new-story', preSubmit);
|
|
||||||
router.post('/stories/preliminary', newStory);
|
|
||||||
router.post('/stories/', storySubmission);
|
|
||||||
router.get('/news/', hot);
|
|
||||||
router.post('/stories/search', getStories);
|
|
||||||
router.get('/news/:storyName', returnIndividualStory);
|
|
||||||
router.post('/stories/upvote/', upvote);
|
|
||||||
|
|
||||||
function hotRank(timeValue, rank) {
|
router.get('/stories/hotStories', hotJSON);
|
||||||
|
router.get('/stories/recentStories', recentJSON);
|
||||||
|
router.get('/stories/comments/:id', comments);
|
||||||
|
router.post('/stories/comment/', commentSubmit);
|
||||||
|
router.post('/stories/comment/:id/comment', commentOnCommentSubmit);
|
||||||
|
router.put('/stories/comment/:id/edit', commentEdit);
|
||||||
|
router.get('/stories/submit', submitNew);
|
||||||
|
router.get('/stories/submit/new-story', preSubmit);
|
||||||
|
router.post('/stories/preliminary', newStory);
|
||||||
|
router.post('/stories/', storySubmission);
|
||||||
|
router.get('/news/', hot);
|
||||||
|
router.post('/stories/search', getStories);
|
||||||
|
router.get('/news/:storyName', returnIndividualStory);
|
||||||
|
router.post('/stories/upvote/', upvote);
|
||||||
|
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
function hotRank(timeValue, rank) {
|
||||||
/*
|
/*
|
||||||
* Hotness ranking algorithm: http://amix.dk/blog/post/19588
|
* Hotness ranking algorithm: http://amix.dk/blog/post/19588
|
||||||
* tMS = postedOnDate - foundationTime;
|
* tMS = postedOnDate - foundationTime;
|
||||||
@ -39,10 +41,9 @@ function hotRank(timeValue, rank) {
|
|||||||
var z = Math.log(rank) / Math.log(10);
|
var z = Math.log(rank) / Math.log(10);
|
||||||
hotness = z + (timeValue / time48Hours);
|
hotness = z + (timeValue / time48Hours);
|
||||||
return hotness;
|
return hotness;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
function hotJSON(req, res, next) {
|
||||||
|
|
||||||
function hotJSON(req, res, next) {
|
|
||||||
var story = Story.find({}).sort({'timePosted': -1}).limit(1000);
|
var story = Story.find({}).sort({'timePosted': -1}).limit(1000);
|
||||||
story.exec(function(err, stories) {
|
story.exec(function(err, stories) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -60,9 +61,9 @@ function hotJSON(req, res, next) {
|
|||||||
}).slice(0, sliceVal));
|
}).slice(0, sliceVal));
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function recentJSON(req, res, next) {
|
function recentJSON(req, res, next) {
|
||||||
var story = Story.find({}).sort({'timePosted': -1}).limit(100);
|
var story = Story.find({}).sort({'timePosted': -1}).limit(100);
|
||||||
story.exec(function(err, stories) {
|
story.exec(function(err, stories) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -70,40 +71,40 @@ function recentJSON(req, res, next) {
|
|||||||
}
|
}
|
||||||
return res.json(stories);
|
return res.json(stories);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function hot(req, res) {
|
function hot(req, res) {
|
||||||
return res.render('stories/index', {
|
return res.render('stories/index', {
|
||||||
title: 'Hot stories currently trending on Camper News',
|
title: 'Hot stories currently trending on Camper News',
|
||||||
page: 'hot'
|
page: 'hot'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitNew(req, res) {
|
function submitNew(req, res) {
|
||||||
return res.render('stories/index', {
|
return res.render('stories/index', {
|
||||||
title: 'Submit a new story to Camper News',
|
title: 'Submit a new story to Camper News',
|
||||||
page: 'submit'
|
page: 'submit'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* no used anywhere
|
* no used anywhere
|
||||||
function search(req, res) {
|
function search(req, res) {
|
||||||
return res.render('stories/index', {
|
return res.render('stories/index', {
|
||||||
title: 'Search the archives of Camper News',
|
title: 'Search the archives of Camper News',
|
||||||
page: 'search'
|
page: 'search'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function recent(req, res) {
|
function recent(req, res) {
|
||||||
return res.render('stories/index', {
|
return res.render('stories/index', {
|
||||||
title: 'Recently submitted stories on Camper News',
|
title: 'Recently submitted stories on Camper News',
|
||||||
page: 'recent'
|
page: 'recent'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function preSubmit(req, res) {
|
function preSubmit(req, res) {
|
||||||
|
|
||||||
var data = req.query;
|
var data = req.query;
|
||||||
var cleanData = sanitizeHtml(data.url, {
|
var cleanData = sanitizeHtml(data.url, {
|
||||||
@ -131,10 +132,10 @@ function preSubmit(req, res) {
|
|||||||
storyImage: image,
|
storyImage: image,
|
||||||
storyMetaDescription: description
|
storyMetaDescription: description
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function returnIndividualStory(req, res, next) {
|
function returnIndividualStory(req, res, next) {
|
||||||
var dashedName = req.params.storyName;
|
var dashedName = req.params.storyName;
|
||||||
|
|
||||||
var storyName = dashedName.replace(/\-/g, ' ').trim();
|
var storyName = dashedName.replace(/\-/g, ' ').trim();
|
||||||
@ -191,9 +192,9 @@ function returnIndividualStory(req, res, next) {
|
|||||||
hasUserVoted: userVoted
|
hasUserVoted: userVoted
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStories(req, res, next) {
|
function getStories(req, res, next) {
|
||||||
MongoClient.connect(secrets.db, function(err, database) {
|
MongoClient.connect(secrets.db, function(err, database) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
@ -233,9 +234,9 @@ function getStories(req, res, next) {
|
|||||||
return res.sendStatus(404);
|
return res.sendStatus(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function upvote(req, res, next) {
|
function upvote(req, res, next) {
|
||||||
var data = req.body.data;
|
var data = req.body.data;
|
||||||
Story.find({'_id': data.id}, function(err, story) {
|
Story.find({'_id': data.id}, function(err, story) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -252,9 +253,12 @@ function upvote(req, res, next) {
|
|||||||
story.markModified('rank');
|
story.markModified('rank');
|
||||||
story.save();
|
story.save();
|
||||||
// NOTE(Berks): This logic is full of wholes and race conditions
|
// NOTE(Berks): This logic is full of wholes and race conditions
|
||||||
// this could be the source of many 'can't set headers after they are sent'
|
// this could be the source of many 'can't set headers after
|
||||||
|
// they are sent'
|
||||||
// errors. This needs cleaning
|
// errors. This needs cleaning
|
||||||
User.findOne({'_id': story.author.userId}, function(err, user) {
|
User.findOne(
|
||||||
|
{ where: { id: story.author.userId } },
|
||||||
|
function(err, user) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
|
|
||||||
user.progressTimestamps.push(Date.now() || 0);
|
user.progressTimestamps.push(Date.now() || 0);
|
||||||
@ -267,23 +271,26 @@ function upvote(req, res, next) {
|
|||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
);
|
||||||
return res.send(story);
|
return res.send(story);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function comments(req, res, next) {
|
function comments(req, res, next) {
|
||||||
var data = req.params.id;
|
var data = req.params.id;
|
||||||
Comment.find({'_id': data}, function(err, comment) {
|
Comment.find(
|
||||||
|
{ where: {'_id': data } },
|
||||||
|
function(err, comment) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
comment = comment.pop();
|
comment = comment.pop();
|
||||||
return res.send(comment);
|
return res.send(comment);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function newStory(req, res, next) {
|
function newStory(req, res, next) {
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return next(new Error('Must be logged in'));
|
return next(new Error('Must be logged in'));
|
||||||
}
|
}
|
||||||
@ -305,7 +312,9 @@ function newStory(req, res, next) {
|
|||||||
if (url.search(/^https?:\/\//g) === -1) {
|
if (url.search(/^https?:\/\//g) === -1) {
|
||||||
url = 'http://' + url;
|
url = 'http://' + url;
|
||||||
}
|
}
|
||||||
Story.find({'link': url}, function(err, story) {
|
Story.find(
|
||||||
|
{ where: {'link': url} },
|
||||||
|
function(err, story) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
@ -318,8 +327,9 @@ function newStory(req, res, next) {
|
|||||||
storyURL: '/news/' + story.pop().storyLink
|
storyURL: '/news/' + story.pop().storyLink
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
resources.getURLTitle(url, processResponse);
|
utils.getURLTitle(url, processResponse);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function processResponse(err, story) {
|
function processResponse(err, story) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -340,9 +350,9 @@ function newStory(req, res, next) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function storySubmission(req, res, next) {
|
function storySubmission(req, res, next) {
|
||||||
var data = req.body.data;
|
var data = req.body.data;
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return next(new Error('Not authorized'));
|
return next(new Error('Not authorized'));
|
||||||
@ -360,7 +370,7 @@ function storySubmission(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Story.count({
|
Story.count({
|
||||||
storyLink: new RegExp('^' + storyLink + '(?: [0-9]+)?$', 'i')
|
storyLink: { like: new RegExp('^' + storyLink + '(?: [0-9]+)?$', 'i') }
|
||||||
}, function (err, storyCount) {
|
}, function (err, storyCount) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
@ -416,9 +426,9 @@ function storySubmission(req, res, next) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentSubmit(req, res, next) {
|
function commentSubmit(req, res, next) {
|
||||||
var data = req.body.data;
|
var data = req.body.data;
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return next(new Error('Not authorized'));
|
return next(new Error('Not authorized'));
|
||||||
@ -453,25 +463,29 @@ function commentSubmit(req, res, next) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
commentSave(comment, Story, res, next);
|
commentSave(comment, Story, res, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentOnCommentSubmit(req, res, next) {
|
function commentOnCommentSubmit(req, res, next) {
|
||||||
var data = req.body.data;
|
var data = req.body.data;
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return next(new Error('Not authorized'));
|
return next(new Error('Not authorized'));
|
||||||
}
|
}
|
||||||
|
|
||||||
var sanitizedBody = sanitizeHtml(data.body,
|
var sanitizedBody = sanitizeHtml(
|
||||||
|
data.body,
|
||||||
{
|
{
|
||||||
allowedTags: [],
|
allowedTags: [],
|
||||||
allowedAttributes: []
|
allowedAttributes: []
|
||||||
}).replace(/"/g, '"');
|
}
|
||||||
|
).replace(/"/g, '"');
|
||||||
|
|
||||||
if (data.body !== sanitizedBody) {
|
if (data.body !== sanitizedBody) {
|
||||||
req.flash('errors', {
|
req.flash('errors', {
|
||||||
msg: 'HTML is not allowed'
|
msg: 'HTML is not allowed'
|
||||||
});
|
});
|
||||||
return res.send(true);
|
return res.send(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var comment = new Comment({
|
var comment = new Comment({
|
||||||
associatedPost: data.associatedPost,
|
associatedPost: data.associatedPost,
|
||||||
body: sanitizedBody,
|
body: sanitizedBody,
|
||||||
@ -490,11 +504,11 @@ function commentOnCommentSubmit(req, res, next) {
|
|||||||
commentOn: Date.now()
|
commentOn: Date.now()
|
||||||
});
|
});
|
||||||
commentSave(comment, Comment, res, next);
|
commentSave(comment, Comment, res, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentEdit(req, res, next) {
|
function commentEdit(req, res, next) {
|
||||||
|
|
||||||
Comment.find({'_id': req.params.id}, function(err, cmt) {
|
Comment.find({ id: req.params.id }, function(err, cmt) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
@ -504,7 +518,6 @@ function commentEdit(req, res, next) {
|
|||||||
return next(new Error('Not authorized'));
|
return next(new Error('Not authorized'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var sanitizedBody = sanitizeHtml(req.body.body, {
|
var sanitizedBody = sanitizeHtml(req.body.body, {
|
||||||
allowedTags: [],
|
allowedTags: [],
|
||||||
allowedAttributes: []
|
allowedAttributes: []
|
||||||
@ -518,7 +531,7 @@ function commentEdit(req, res, next) {
|
|||||||
|
|
||||||
cmt.body = sanitizedBody;
|
cmt.body = sanitizedBody;
|
||||||
cmt.commentOn = Date.now();
|
cmt.commentOn = Date.now();
|
||||||
cmt.save(function (err) {
|
cmt.save(function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
@ -527,9 +540,9 @@ function commentEdit(req, res, next) {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentSave(comment, Context, res, next) {
|
function commentSave(comment, Context, res, next) {
|
||||||
comment.save(function(err, data) {
|
comment.save(function(err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
@ -538,7 +551,7 @@ function commentSave(comment, Context, res, next) {
|
|||||||
// Based on the context retrieve the parent
|
// Based on the context retrieve the parent
|
||||||
// object of the comment (Story/Comment)
|
// object of the comment (Story/Comment)
|
||||||
Context.find({
|
Context.find({
|
||||||
'_id': data.associatedPost
|
id: data.associatedPost
|
||||||
}, function (err, associatedContext) {
|
}, function (err, associatedContext) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
@ -604,6 +617,5 @@ function commentSave(comment, Context, res, next) {
|
|||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
module.exports = router;
|
|
||||||
|
@ -3,65 +3,70 @@ var _ = require('lodash'),
|
|||||||
async = require('async'),
|
async = require('async'),
|
||||||
crypto = require('crypto'),
|
crypto = require('crypto'),
|
||||||
nodemailer = require('nodemailer'),
|
nodemailer = require('nodemailer'),
|
||||||
passport = require('passport'),
|
|
||||||
moment = require('moment'),
|
moment = require('moment'),
|
||||||
express = require('express'),
|
|
||||||
debug = require('debug')('freecc:cntr:userController'),
|
debug = require('debug')('freecc:cntr:userController'),
|
||||||
|
|
||||||
User = require('../../common/models/User'),
|
secrets = require('../../config/secrets');
|
||||||
secrets = require('../../config/secrets'),
|
|
||||||
resources = require('./../resources/resources');
|
|
||||||
|
|
||||||
var router = express.Router();
|
module.exports = function(app) {
|
||||||
router.get('/login', function(req, res) {
|
var router = app.loopback.Router();
|
||||||
|
var User = app.models.User;
|
||||||
|
var Story = app.models.Story;
|
||||||
|
var Comment = app.models.Comment;
|
||||||
|
|
||||||
|
router.get('/login', function(req, res) {
|
||||||
res.redirect(301, '/signin');
|
res.redirect(301, '/signin');
|
||||||
});
|
});
|
||||||
router.get('/logout', function(req, res) {
|
router.get('/logout', function(req, res) {
|
||||||
res.redirect(301, '/signout');
|
res.redirect(301, '/signout');
|
||||||
});
|
});
|
||||||
router.get('/signin', getSignin);
|
router.get('/signin', getSignin);
|
||||||
router.post('/signin', postSignin);
|
// router.post('/signin', postSignin);
|
||||||
router.get('/signout', signout);
|
router.get('/signout', signout);
|
||||||
router.get('/forgot', getForgot);
|
router.get('/forgot', getForgot);
|
||||||
router.post('/forgot', postForgot);
|
router.post('/forgot', postForgot);
|
||||||
router.get('/reset/:token', getReset);
|
router.get('/reset/:token', getReset);
|
||||||
router.post('/reset/:token', postReset);
|
router.post('/reset/:token', postReset);
|
||||||
router.get('/email-signup', getEmailSignup);
|
router.get('/email-signup', getEmailSignup);
|
||||||
router.get('/email-signin', getEmailSignin);
|
router.get('/email-signin', getEmailSignin);
|
||||||
router.post('/email-signup', postEmailSignup);
|
router.post('/email-signup', postEmailSignup);
|
||||||
router.post('/email-signin', postSignin);
|
// router.post('/email-signin', postSignin);
|
||||||
router.get('/account/api', getAccountAngular);
|
router.get('/account/api', getAccountAngular);
|
||||||
router.get('/api/checkUniqueUsername/:username', checkUniqueUsername);
|
router.get('/api/checkUniqueUsername/:username', checkUniqueUsername);
|
||||||
router.get('/api/checkExistingUsername/:username', checkExistingUsername);
|
router.get('/api/checkExistingUsername/:username', checkExistingUsername);
|
||||||
router.get('/api/checkUniqueEmail/:email', checkUniqueEmail);
|
router.get('/api/checkUniqueEmail/:email', checkUniqueEmail);
|
||||||
router.post('/account/profile', postUpdateProfile);
|
router.post('/account/profile', postUpdateProfile);
|
||||||
router.post('/account/password', postUpdatePassword);
|
router.post('/account/password', postUpdatePassword);
|
||||||
router.post('/account/delete', postDeleteAccount);
|
router.post('/account/delete', postDeleteAccount);
|
||||||
router.get('/account/unlink/:provider', getOauthUnlink);
|
router.get('/account/unlink/:provider', getOauthUnlink);
|
||||||
router.get('/account', getAccount);
|
router.get('/account', getAccount);
|
||||||
// Ensure this is the last route!
|
// Ensure this is the last route!
|
||||||
router.get('/:username', returnUser);
|
router.get('/:username', returnUser);
|
||||||
|
|
||||||
/**
|
app.use(router);
|
||||||
|
|
||||||
|
/**
|
||||||
* GET /signin
|
* GET /signin
|
||||||
* Siginin page.
|
* Siginin page.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getSignin (req, res) {
|
function getSignin (req, res) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
}
|
}
|
||||||
res.render('account/signin', {
|
res.render('account/signin', {
|
||||||
title: 'Free Code Camp Login'
|
title: 'Free Code Camp Login'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /signin
|
* POST /signin
|
||||||
* Sign in using email and password.
|
* Sign in using email and password.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function postSignin (req, res, next) {
|
/*
|
||||||
|
* TODO(berks): this should be done using loopback
|
||||||
|
function postSignin (req, res, next) {
|
||||||
req.assert('email', 'Email is not valid').isEmail();
|
req.assert('email', 'Email is not valid').isEmail();
|
||||||
req.assert('password', 'Password cannot be blank').notEmpty();
|
req.assert('password', 'Password cannot be blank').notEmpty();
|
||||||
|
|
||||||
@ -94,52 +99,53 @@ function postSignin (req, res, next) {
|
|||||||
return res.redirect(req.session.returnTo || '/');
|
return res.redirect(req.session.returnTo || '/');
|
||||||
});
|
});
|
||||||
})(req, res, next);
|
})(req, res, next);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /signout
|
* GET /signout
|
||||||
* Log out.
|
* Log out.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function signout (req, res) {
|
function signout (req, res) {
|
||||||
req.logout();
|
req.logout();
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /email-signup
|
* GET /email-signup
|
||||||
* Signup page.
|
* Signup page.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getEmailSignin (req, res) {
|
function getEmailSignin (req, res) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
}
|
}
|
||||||
res.render('account/email-signin', {
|
res.render('account/email-signin', {
|
||||||
title: 'Sign in to your Free Code Camp Account'
|
title: 'Sign in to your Free Code Camp Account'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /signin
|
* GET /signin
|
||||||
* Signup page.
|
* Signup page.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getEmailSignup (req, res) {
|
function getEmailSignup (req, res) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
}
|
}
|
||||||
res.render('account/email-signup', {
|
res.render('account/email-signup', {
|
||||||
title: 'Create Your Free Code Camp Account'
|
title: 'Create Your Free Code Camp Account'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /email-signup
|
* POST /email-signup
|
||||||
* Create a new local account.
|
* Create a new local account.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function postEmailSignup (req, res, next) {
|
function postEmailSignup (req, res, next) {
|
||||||
req.assert('email', 'valid email required').isEmail();
|
req.assert('email', 'valid email required').isEmail();
|
||||||
var errors = req.validationErrors();
|
var errors = req.validationErrors();
|
||||||
|
|
||||||
@ -233,34 +239,34 @@ function postEmailSignup (req, res, next) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /account
|
* GET /account
|
||||||
* Profile page.
|
* Profile page.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getAccount (req, res) {
|
function getAccount (req, res) {
|
||||||
res.render('account/account', {
|
res.render('account/account', {
|
||||||
title: 'Manage your Free Code Camp Account'
|
title: 'Manage your Free Code Camp Account'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Angular API Call
|
* Angular API Call
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getAccountAngular (req, res) {
|
function getAccountAngular (req, res) {
|
||||||
res.json({
|
res.json({
|
||||||
user: req.user
|
user: req.user
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique username check API Call
|
* Unique username check API Call
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function checkUniqueUsername (req, res, next) {
|
function checkUniqueUsername (req, res, next) {
|
||||||
User.count(
|
User.count(
|
||||||
{ 'profile.username': req.params.username.toLowerCase() },
|
{ 'profile.username': req.params.username.toLowerCase() },
|
||||||
function (err, data) {
|
function (err, data) {
|
||||||
@ -271,13 +277,13 @@ function checkUniqueUsername (req, res, next) {
|
|||||||
return res.send(false);
|
return res.send(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Existing username check
|
* Existing username check
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function checkExistingUsername (req, res, next) {
|
function checkExistingUsername (req, res, next) {
|
||||||
User.count(
|
User.count(
|
||||||
{ 'profile.username': req.params.username.toLowerCase() },
|
{ 'profile.username': req.params.username.toLowerCase() },
|
||||||
function (err, data) {
|
function (err, data) {
|
||||||
@ -289,13 +295,13 @@ function checkExistingUsername (req, res, next) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique email check API Call
|
* Unique email check API Call
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function checkUniqueEmail (req, res, next) {
|
function checkUniqueEmail (req, res, next) {
|
||||||
User.count(
|
User.count(
|
||||||
{ email: decodeURIComponent(req.params.email).toLowerCase() },
|
{ email: decodeURIComponent(req.params.email).toLowerCase() },
|
||||||
function (err, data) {
|
function (err, data) {
|
||||||
@ -307,15 +313,15 @@ function checkUniqueEmail (req, res, next) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /campers/:username
|
* GET /campers/:username
|
||||||
* Public Profile page.
|
* Public Profile page.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function returnUser (req, res, next) {
|
function returnUser (req, res, next) {
|
||||||
User.find(
|
User.find(
|
||||||
{ 'profile.username': req.params.username.toLowerCase() },
|
{ 'profile.username': req.params.username.toLowerCase() },
|
||||||
function(err, user) {
|
function(err, user) {
|
||||||
@ -325,8 +331,8 @@ function returnUser (req, res, next) {
|
|||||||
}
|
}
|
||||||
if (user[0]) {
|
if (user[0]) {
|
||||||
user = user[0];
|
user = user[0];
|
||||||
|
user.progressTimestamps =
|
||||||
user.progressTimestamps = user.progressTimestamps.sort(function(a, b) {
|
user.progressTimestamps.sort(function(a, b) {
|
||||||
return a - b;
|
return a - b;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -442,14 +448,14 @@ function returnUser (req, res, next) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /account/profile
|
* POST /account/profile
|
||||||
* Update profile information.
|
* Update profile information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function postUpdateProfile (req, res, next) {
|
function postUpdateProfile (req, res, next) {
|
||||||
|
|
||||||
User.findById(req.user.id, function(err) {
|
User.findById(req.user.id, function(err) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
@ -486,36 +492,41 @@ function postUpdateProfile (req, res, next) {
|
|||||||
});
|
});
|
||||||
return res.redirect('/account');
|
return res.redirect('/account');
|
||||||
}
|
}
|
||||||
user.email = req.body.email.trim() || '';
|
var body = req.body || {};
|
||||||
user.profile.name = req.body.name.trim() || '';
|
user.email = body.email.trim() || '';
|
||||||
user.profile.username = req.body.username.trim() || '';
|
user.profile.name = body.name.trim() || '';
|
||||||
user.profile.location = req.body.location.trim() || '';
|
user.profile.username = body.username.trim() || '';
|
||||||
user.profile.githubProfile = req.body.githubProfile.trim() || '';
|
user.profile.location = body.location.trim() || '';
|
||||||
user.profile.facebookProfile = req.body.facebookProfile.trim() || '';
|
|
||||||
user.profile.linkedinProfile = req.body.linkedinProfile.trim() || '';
|
|
||||||
user.profile.codepenProfile = req.body.codepenProfile.trim() || '';
|
|
||||||
user.profile.twitterHandle = req.body.twitterHandle.trim() || '';
|
|
||||||
user.profile.bio = req.body.bio.trim() || '';
|
|
||||||
|
|
||||||
user.profile.picture = req.body.picture.trim() ||
|
user.profile.githubProfile = body.githubProfile.trim() || '';
|
||||||
|
user.profile.facebookProfile = body.facebookProfile.trim() || '';
|
||||||
|
user.profile.linkedinProfile = body.linkedinProfile.trim() || '';
|
||||||
|
|
||||||
|
user.profile.codepenProfile = body.codepenProfile.trim() || '';
|
||||||
|
user.profile.twitterHandle = body.twitterHandle.trim() || '';
|
||||||
|
user.profile.bio = body.bio.trim() || '';
|
||||||
|
|
||||||
|
user.profile.picture = body.picture.trim() ||
|
||||||
'https://s3.amazonaws.com/freecodecamp/' +
|
'https://s3.amazonaws.com/freecodecamp/' +
|
||||||
'camper-image-placeholder.png';
|
'camper-image-placeholder.png';
|
||||||
user.portfolio.website1Title = req.body.website1Title.trim() || '';
|
user.portfolio.website1Title = body.website1Title.trim() || '';
|
||||||
user.portfolio.website1Link = req.body.website1Link.trim() || '';
|
user.portfolio.website1Link = body.website1Link.trim() || '';
|
||||||
user.portfolio.website1Image = req.body.website1Image.trim() || '';
|
user.portfolio.website1Image = body.website1Image.trim() || '';
|
||||||
user.portfolio.website2Title = req.body.website2Title.trim() || '';
|
|
||||||
user.portfolio.website2Link = req.body.website2Link.trim() || '';
|
user.portfolio.website2Title = body.website2Title.trim() || '';
|
||||||
user.portfolio.website2Image = req.body.website2Image.trim() || '';
|
user.portfolio.website2Link = body.website2Link.trim() || '';
|
||||||
user.portfolio.website3Title = req.body.website3Title.trim() || '';
|
user.portfolio.website2Image = body.website2Image.trim() || '';
|
||||||
user.portfolio.website3Link = req.body.website3Link.trim() || '';
|
|
||||||
user.portfolio.website3Image = req.body.website3Image.trim() || '';
|
user.portfolio.website3Title = body.website3Title.trim() || '';
|
||||||
|
user.portfolio.website3Link = body.website3Link.trim() || '';
|
||||||
|
user.portfolio.website3Image = body.website3Image.trim() || '';
|
||||||
|
|
||||||
|
|
||||||
user.save(function (err) {
|
user.save(function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
resources.updateUserStoryPictures(
|
updateUserStoryPictures(
|
||||||
user._id.toString(),
|
user._id.toString(),
|
||||||
user.profile.picture,
|
user.profile.picture,
|
||||||
user.profile.username,
|
user.profile.username,
|
||||||
@ -532,15 +543,17 @@ function postUpdateProfile (req, res, next) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /account/password
|
* POST /account/password
|
||||||
* Update current password.
|
* Update current password.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function postUpdatePassword (req, res, next) {
|
function postUpdatePassword (req, res, next) {
|
||||||
req.assert('password', 'Password must be at least 4 characters long').len(4);
|
req.assert('password', 'Password must be at least 4 characters long')
|
||||||
|
.len(4);
|
||||||
|
|
||||||
req.assert('confirmPassword', 'Passwords do not match')
|
req.assert('confirmPassword', 'Passwords do not match')
|
||||||
.equals(req.body.password);
|
.equals(req.body.password);
|
||||||
|
|
||||||
@ -563,28 +576,28 @@ function postUpdatePassword (req, res, next) {
|
|||||||
res.redirect('/account');
|
res.redirect('/account');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /account/delete
|
* POST /account/delete
|
||||||
* Delete user account.
|
* Delete user account.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function postDeleteAccount (req, res, next) {
|
function postDeleteAccount (req, res, next) {
|
||||||
User.remove({ _id: req.user.id }, function(err) {
|
User.destroyById(req.user.id, function(err) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
req.logout();
|
req.logout();
|
||||||
req.flash('info', { msg: 'Your account has been deleted.' });
|
req.flash('info', { msg: 'Your account has been deleted.' });
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /account/unlink/:provider
|
* GET /account/unlink/:provider
|
||||||
* Unlink OAuth provider.
|
* Unlink OAuth provider.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getOauthUnlink (req, res, next) {
|
function getOauthUnlink (req, res, next) {
|
||||||
var provider = req.params.provider;
|
var provider = req.params.provider;
|
||||||
User.findById(req.user.id, function(err, user) {
|
User.findById(req.user.id, function(err, user) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
@ -601,21 +614,25 @@ function getOauthUnlink (req, res, next) {
|
|||||||
res.redirect('/account');
|
res.redirect('/account');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /reset/:token
|
* GET /reset/:token
|
||||||
* Reset Password page.
|
* Reset Password page.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getReset (req, res, next) {
|
function getReset (req, res, next) {
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
}
|
}
|
||||||
User
|
User.findOne(
|
||||||
.findOne({ resetPasswordToken: req.params.token })
|
{
|
||||||
.where('resetPasswordExpires').gt(Date.now())
|
where: {
|
||||||
.exec(function(err, user) {
|
resetPasswordToken: req.params.token,
|
||||||
|
resetPasswordExpires: Date.now()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(err, user) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
if (!user) {
|
if (!user) {
|
||||||
req.flash('errors', {
|
req.flash('errors', {
|
||||||
@ -628,14 +645,14 @@ function getReset (req, res, next) {
|
|||||||
token: req.params.token
|
token: req.params.token
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /reset/:token
|
* POST /reset/:token
|
||||||
* Process the reset password request.
|
* Process the reset password request.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function postReset (req, res, next) {
|
function postReset (req, res, next) {
|
||||||
var errors = req.validationErrors();
|
var errors = req.validationErrors();
|
||||||
|
|
||||||
if (errors) {
|
if (errors) {
|
||||||
@ -645,10 +662,14 @@ function postReset (req, res, next) {
|
|||||||
|
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
function(done) {
|
function(done) {
|
||||||
User
|
User.findOne(
|
||||||
.findOne({ resetPasswordToken: req.params.token })
|
{
|
||||||
.where('resetPasswordExpires').gt(Date.now())
|
where: {
|
||||||
.exec(function(err, user) {
|
resetPasswordToken: req.params.token,
|
||||||
|
resetPasswordExpires: Date.now()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(err, user) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
if (!user) {
|
if (!user) {
|
||||||
req.flash('errors', {
|
req.flash('errors', {
|
||||||
@ -702,28 +723,28 @@ function postReset (req, res, next) {
|
|||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /forgot
|
* GET /forgot
|
||||||
* Forgot Password page.
|
* Forgot Password page.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getForgot (req, res) {
|
function getForgot (req, res) {
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
}
|
}
|
||||||
res.render('account/forgot', {
|
res.render('account/forgot', {
|
||||||
title: 'Forgot Password'
|
title: 'Forgot Password'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /forgot
|
* POST /forgot
|
||||||
* Create a random token, then the send user an email with a reset link.
|
* Create a random token, then the send user an email with a reset link.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function postForgot (req, res, next) {
|
function postForgot (req, res, next) {
|
||||||
var errors = req.validationErrors();
|
var errors = req.validationErrors();
|
||||||
|
|
||||||
if (errors) {
|
if (errors) {
|
||||||
@ -801,6 +822,60 @@ function postForgot (req, res, next) {
|
|||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
res.redirect('/forgot');
|
res.redirect('/forgot');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = router;
|
function updateUserStoryPictures(userId, picture, username, cb) {
|
||||||
|
|
||||||
|
var counter = 0,
|
||||||
|
foundStories,
|
||||||
|
foundComments;
|
||||||
|
|
||||||
|
Story.find({ 'author.userId': userId }, function (err, stories) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
foundStories = stories;
|
||||||
|
counter++;
|
||||||
|
saveStoriesAndComments();
|
||||||
|
});
|
||||||
|
|
||||||
|
Comment.find({ 'author.userId': userId }, function (err, comments) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
foundComments = comments;
|
||||||
|
counter++;
|
||||||
|
saveStoriesAndComments();
|
||||||
|
});
|
||||||
|
|
||||||
|
function saveStoriesAndComments() {
|
||||||
|
if (counter !== 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tasks = [];
|
||||||
|
R.forEach(function (comment) {
|
||||||
|
comment.author.picture = picture;
|
||||||
|
comment.author.username = username;
|
||||||
|
comment.markModified('author');
|
||||||
|
tasks.push(function (cb) {
|
||||||
|
comment.save(cb);
|
||||||
|
});
|
||||||
|
}, foundComments);
|
||||||
|
|
||||||
|
R.forEach(function (story) {
|
||||||
|
story.author.picture = picture;
|
||||||
|
story.author.username = username;
|
||||||
|
story.markModified('author');
|
||||||
|
tasks.push(function (cb) {
|
||||||
|
story.save(cb);
|
||||||
|
});
|
||||||
|
}, foundStories);
|
||||||
|
async.parallel(tasks, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -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;
|
|
18
server/config.development.js
Normal file
18
server/config.development.js
Normal 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
29
server/config.json
Normal 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
5
server/config.local.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
var globalConfig = require('../common/config.global');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
restApiRoot: globalConfig.restApi
|
||||||
|
};
|
6
server/datasources.development.js
Normal file
6
server/datasources.development.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
db: {
|
||||||
|
connector: 'mongodb',
|
||||||
|
url: process.env.MONGOHQ_URL
|
||||||
|
}
|
||||||
|
};
|
9
server/datasources.json
Normal file
9
server/datasources.json
Normal 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
27
server/middleware.json
Normal 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
69
server/model-config.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
122
server/passport-providers.js
Normal file
122
server/passport-providers.js
Normal 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
|
||||||
|
}
|
||||||
|
};
|
@ -9,7 +9,9 @@ process.on('uncaughtException', function (err) {
|
|||||||
process.exit(1); // eslint-disable-line
|
process.exit(1); // eslint-disable-line
|
||||||
});
|
});
|
||||||
|
|
||||||
var express = require('express'),
|
var R = require('ramda'),
|
||||||
|
loopback = require('loopback'),
|
||||||
|
boot = require('loopback-boot'),
|
||||||
accepts = require('accepts'),
|
accepts = require('accepts'),
|
||||||
cookieParser = require('cookie-parser'),
|
cookieParser = require('cookie-parser'),
|
||||||
compress = require('compression'),
|
compress = require('compression'),
|
||||||
@ -22,27 +24,11 @@ var express = require('express'),
|
|||||||
MongoStore = require('connect-mongo')(session),
|
MongoStore = require('connect-mongo')(session),
|
||||||
flash = require('express-flash'),
|
flash = require('express-flash'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
mongoose = require('mongoose'),
|
|
||||||
passport = require('passport'),
|
|
||||||
expressValidator = require('express-validator'),
|
expressValidator = require('express-validator'),
|
||||||
// request = require('request'),
|
|
||||||
forceDomain = require('forcedomain'),
|
forceDomain = require('forcedomain'),
|
||||||
lessMiddleware = require('less-middleware'),
|
lessMiddleware = require('less-middleware'),
|
||||||
|
|
||||||
/**
|
passportProviders = require('./passport-providers'),
|
||||||
* routers.
|
|
||||||
*/
|
|
||||||
homeRouter = require('./boot/home'),
|
|
||||||
userRouter = require('./boot/user'),
|
|
||||||
fieldGuideRouter = require('./boot/fieldGuide'),
|
|
||||||
challengeMapRouter = require('./boot/challengeMap'),
|
|
||||||
challengeRouter = require('./boot/challenge'),
|
|
||||||
jobsRouter = require('./boot/jobs'),
|
|
||||||
redirectsRouter = require('./boot/redirects'),
|
|
||||||
utilityRouter = require('./boot/utility'),
|
|
||||||
storyRouter = require('./boot/story'),
|
|
||||||
passportRouter = require('./boot/passport'),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API keys and Passport configuration.
|
* API keys and Passport configuration.
|
||||||
*/
|
*/
|
||||||
@ -51,22 +37,10 @@ var express = require('express'),
|
|||||||
/**
|
/**
|
||||||
* Create Express server.
|
* Create Express server.
|
||||||
*/
|
*/
|
||||||
var app = express();
|
var app = loopback();
|
||||||
|
var PassportConfigurator =
|
||||||
/**
|
require('loopback-component-passport').PassportConfigurator;
|
||||||
* Connect to MongoDB.
|
var passportConfigurator = new PassportConfigurator(app);
|
||||||
*/
|
|
||||||
mongoose.connect(secrets.db);
|
|
||||||
mongoose.connection.on('error', function () {
|
|
||||||
console.error(
|
|
||||||
'MongoDB Connection Error. Please make sure that MongoDB is running.'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Express configuration.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
app.set('port', process.env.PORT || 3000);
|
app.set('port', process.env.PORT || 3000);
|
||||||
app.set('views', path.join(__dirname, 'views'));
|
app.set('views', path.join(__dirname, 'views'));
|
||||||
@ -101,8 +75,7 @@ app.use(session({
|
|||||||
'autoReconnect': true
|
'autoReconnect': true
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
app.use(passport.initialize());
|
|
||||||
app.use(passport.session());
|
|
||||||
app.use(flash());
|
app.use(flash());
|
||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by');
|
||||||
|
|
||||||
@ -191,6 +164,8 @@ app.use(helmet.csp({
|
|||||||
safari5: false
|
safari5: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
passportConfigurator.init();
|
||||||
|
|
||||||
app.use(function (req, res, next) {
|
app.use(function (req, res, next) {
|
||||||
// Make user object available in templates.
|
// Make user object available in templates.
|
||||||
res.locals.user = req.user;
|
res.locals.user = req.user;
|
||||||
@ -198,9 +173,14 @@ app.use(function (req, res, next) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
express.static(path.join(__dirname, '../public'), { maxAge: 86400000 })
|
loopback.static(path.join(__dirname, '../public'), { maxAge: 86400000 })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
boot(app, {
|
||||||
|
appRootDir: __dirname,
|
||||||
|
dev: process.env.NODE_ENV
|
||||||
|
});
|
||||||
|
|
||||||
app.use(function (req, res, next) {
|
app.use(function (req, res, next) {
|
||||||
// Remember original destination before login.
|
// Remember original destination before login.
|
||||||
var path = req.path.split('/')[1];
|
var path = req.path.split('/')[1];
|
||||||
@ -213,17 +193,17 @@ app.use(function (req, res, next) {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
// add sub routers
|
passportConfigurator.setupModels({
|
||||||
app.use(fieldGuideRouter);
|
userModel: app.models.user,
|
||||||
app.use(challengeMapRouter);
|
userIdentityModel: app.models.userIdentity,
|
||||||
app.use(challengeRouter);
|
userCredentialModel: app.models.userCredential
|
||||||
app.use(jobsRouter);
|
});
|
||||||
app.use(redirectsRouter);
|
|
||||||
app.use(utilityRouter);
|
R.keys(passportProviders).map(function(strategy) {
|
||||||
app.use(storyRouter);
|
var config = passportProviders[strategy];
|
||||||
app.use(passportRouter);
|
config.session = config.session !== false;
|
||||||
app.use(homeRouter);
|
passportConfigurator.configureProvider(strategy, config);
|
||||||
app.use(userRouter);
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OAuth sign-in routes.
|
* OAuth sign-in routes.
|
||||||
@ -273,12 +253,19 @@ if (process.env.NODE_ENV === 'development') {
|
|||||||
* Start Express server.
|
* Start Express server.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
app.listen(app.get('port'), function () {
|
app.start = function() {
|
||||||
|
app.listen(app.get('port'), function () {
|
||||||
console.log(
|
console.log(
|
||||||
'FreeCodeCamp server listening on port %d in %s mode',
|
'FreeCodeCamp server listening on port %d in %s mode',
|
||||||
app.get('port'),
|
app.get('port'),
|
||||||
app.get('env')
|
app.get('env')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// start the server if `$ node server.js`
|
||||||
|
if (require.main === module) {
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var async = require('async'),
|
var path = require('path'),
|
||||||
path = require('path'),
|
|
||||||
// debug = require('debug')('freecc:cntr:resources'),
|
// debug = require('debug')('freecc:cntr:resources'),
|
||||||
cheerio = require('cheerio'),
|
cheerio = require('cheerio'),
|
||||||
request = require('request'),
|
request = require('request'),
|
||||||
@ -8,11 +7,9 @@ var async = require('async'),
|
|||||||
fs = require('fs'),
|
fs = require('fs'),
|
||||||
|
|
||||||
|
|
||||||
Story = require('../../common/models/Story'),
|
|
||||||
Comment = require('../../common/models/Comment'),
|
|
||||||
resources = require('./resources.json'),
|
resources = require('./resources.json'),
|
||||||
nonprofits = require('../../seed_data/nonprofits.json'),
|
nonprofits = require('../../seed/nonprofits.json'),
|
||||||
fieldGuides = require('../../seed_data/field-guides.json');
|
fieldGuides = require('../../seed/field-guides.json');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cached values
|
* Cached values
|
||||||
@ -41,12 +38,12 @@ Array.zip = function(left, right, combinerFunction) {
|
|||||||
if (!challengeMap) {
|
if (!challengeMap) {
|
||||||
var localChallengeMap = {};
|
var localChallengeMap = {};
|
||||||
var files = fs.readdirSync(
|
var files = fs.readdirSync(
|
||||||
path.join(__dirname, '../../seed_data/challenges')
|
path.join(__dirname, '../../seed/challenges')
|
||||||
);
|
);
|
||||||
var keyCounter = 0;
|
var keyCounter = 0;
|
||||||
files = files.map(function (file) {
|
files = files.map(function (file) {
|
||||||
return require(
|
return require(
|
||||||
path.join(__dirname, '../../seed_data/challenges/' + file)
|
path.join(__dirname, '../../seed/challenges/' + file)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
files = files.sort(function (a, b) {
|
files = files.sort(function (a, b) {
|
||||||
@ -215,59 +212,5 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
},
|
|
||||||
|
|
||||||
updateUserStoryPictures: function (userId, picture, username, cb) {
|
|
||||||
|
|
||||||
var counter = 0,
|
|
||||||
foundStories,
|
|
||||||
foundComments;
|
|
||||||
|
|
||||||
Story.find({'author.userId': userId}, function (err, stories) {
|
|
||||||
if (err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
foundStories = stories;
|
|
||||||
counter++;
|
|
||||||
saveStoriesAndComments();
|
|
||||||
});
|
|
||||||
Comment.find({'author.userId': userId}, function (err, comments) {
|
|
||||||
if (err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
foundComments = comments;
|
|
||||||
counter++;
|
|
||||||
saveStoriesAndComments();
|
|
||||||
});
|
|
||||||
|
|
||||||
function saveStoriesAndComments() {
|
|
||||||
if (counter !== 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var tasks = [];
|
|
||||||
R.forEach(function (comment) {
|
|
||||||
comment.author.picture = picture;
|
|
||||||
comment.author.username = username;
|
|
||||||
comment.markModified('author');
|
|
||||||
tasks.push(function (cb) {
|
|
||||||
comment.save(cb);
|
|
||||||
});
|
|
||||||
}, foundComments);
|
|
||||||
|
|
||||||
R.forEach(function (story) {
|
|
||||||
story.author.picture = picture;
|
|
||||||
story.author.username = username;
|
|
||||||
story.markModified('author');
|
|
||||||
tasks.push(function (cb) {
|
|
||||||
story.save(cb);
|
|
||||||
});
|
|
||||||
}, foundStories);
|
|
||||||
async.parallel(tasks, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
Reference in New Issue
Block a user