Merge branch 'staging' of github.com:FreeCodeCamp/freecodecamp into staging
This commit is contained in:
@ -95,7 +95,7 @@ DEBUG=true
|
||||
mongod
|
||||
|
||||
# Seed your database with the challenges
|
||||
node seed_data/seed.js
|
||||
node seed/
|
||||
|
||||
# start the application
|
||||
gulp
|
||||
|
7
common/config.global.js
Normal file
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",
|
||||
"logoUrl": "string",
|
||||
"imageUrl": "string",
|
||||
"estimatedHours": 0,
|
||||
"estimatedHours": "number",
|
||||
"interestedCampers": [],
|
||||
"confirmedCampers": [],
|
||||
"currentStatus": "string"
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "bonfire",
|
||||
"name": "story",
|
||||
"base": "PersistedModel",
|
||||
"trackChanges": false,
|
||||
"idInjection": true,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bonfire",
|
||||
"base": "PersistedModel",
|
||||
"name": "user",
|
||||
"base": "User",
|
||||
"trackChanges": false,
|
||||
"idInjection": true,
|
||||
"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-middleware": "~2.0.1",
|
||||
"lodash": "~2.4.1",
|
||||
"loopback": "^2.18.0",
|
||||
"loopback-boot": "^2.8.0",
|
||||
"loopback-component-passport": "^1.3.1",
|
||||
"loopback-connector-mongodb": "^1.10.0",
|
||||
"lusca": "~1.0.2",
|
||||
"method-override": "~2.3.0",
|
||||
"moment": "~2.10.2",
|
||||
"mongodb": "~1.4.33",
|
||||
"mongoose": "~4.0.1",
|
||||
"mongoose-long": "0.0.2",
|
||||
"mongodb": "^2.0.33",
|
||||
"morgan": "~1.5.0",
|
||||
"node-slack": "0.0.7",
|
||||
"nodemailer": "~1.3.0",
|
||||
"passport": "~0.2.1",
|
||||
"passport-facebook": "~1.0.3",
|
||||
"passport-github": "~0.1.5",
|
||||
"passport-google-oauth": "~0.1.5",
|
||||
"passport-linkedin-oauth2": "~1.2.1",
|
||||
"passport-local": "~1.0.0",
|
||||
"passport-oauth": "~1.0.0",
|
||||
"passport-twitter": "~1.0.2",
|
||||
"passport-facebook": "^2.0.0",
|
||||
"passport-google-oauth": "^0.2.0",
|
||||
"passport-google-oauth2": "^0.1.6",
|
||||
"passport-linkedin-oauth2": "^1.2.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"passport-oauth": "^1.0.0",
|
||||
"passport-twitter": "^1.0.3",
|
||||
"ramda": "~0.10.0",
|
||||
"request": "~2.53.0",
|
||||
"rx": "^2.5.3",
|
||||
"sanitize-html": "~1.6.1",
|
||||
"sitemap": "~0.7.4",
|
||||
"twit": "~1.1.20",
|
||||
"uglify-js": "~2.4.15",
|
||||
"validator": "~3.22.1",
|
||||
@ -94,7 +95,7 @@
|
||||
"gulp": "~3.8.8",
|
||||
"gulp-eslint": "~0.9.0",
|
||||
"gulp-inject": "~1.0.2",
|
||||
"gulp-nodemon": "~1.0.4",
|
||||
"gulp-nodemon": "^2.0.3",
|
||||
"mocha": "~2.0.1",
|
||||
"multiline": "~1.0.1",
|
||||
"supertest": "~0.15.0"
|
||||
|
@ -237,7 +237,7 @@
|
||||
"name": "Waypoint: Fill in the Blank with Placeholder Text",
|
||||
"difficulty": 0.015,
|
||||
"description": [
|
||||
"Replace the text inside your <code>p</code> element with the first few words of the provided \"Ktty Ipsum\" text.",
|
||||
"Replace the text inside your <code>p</code> element with the first few words of the provided \"Kitty Ipsum\" text.",
|
||||
"Web developers traditionally use \"Lorem Ipsum\" text as placeholder text. It's called \"Lorem Ipsum\" text because those are the first two words of a famous passage by Cicero of Ancient Rome.",
|
||||
"\"Lorem Ipsum\" text has been used as placeholder text by typesetters since the 16th century, and this tradition continues on the web.",
|
||||
"Well, 5 centuries is long enough. Since we're building a CatPhotoApp, let's use something called \"Kitty Ipsum\"!",
|
@ -683,7 +683,7 @@
|
||||
" <li>Fork the Free Code Camp repository and <code>open seed_data/bonfires.json</code> to become familiar with the format of our bonfires.</li>",
|
||||
" <li>Regardless of your bonfire's difficulty, put it as the last bonfire in the JSON file. Change one of the numbers in the ID to ensure that your bonfire has a unique ID.</li>",
|
||||
" <li>In the terminal, run <code>node seed_data/seed.js</code>. Run <code>gulp</code>. You should be able to navigate to your new bonfire in the challenge map. Whenever you make a change to bonfire.json, you'll need to reseed in order to see these changes in the browser.</li>",
|
||||
" <li>Solved your own Bonfire. Confirmed that your tests work as expected and that your instructions are sufficiently clear.</li>",
|
||||
" <li>Solve your own Bonfire. Confirm that your tests work as expected and that your instructions are sufficiently clear.</li>",
|
||||
" <li>Submit a pull request to Free Code Camp's Staging branch and in the pull request body, link to a gist that has your algorithmic solution.</li>",
|
||||
" </ol>",
|
||||
" </p>",
|
99
seed/index.js
Normal file
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,16 +31,32 @@
|
||||
*/
|
||||
|
||||
var R = require('ramda'),
|
||||
express = require('express'),
|
||||
Challenge = require('../../common/models/Challenge'),
|
||||
User = require('../../common/models/User'),
|
||||
resources = require('../resources/resources'),
|
||||
userMigration = require('../resources/middleware').userMigration,
|
||||
MDNlinks = require('../../seed_data/bonfireMDNlinks');
|
||||
utils = require('../utils'),
|
||||
userMigration = require('../utils/middleware').userMigration,
|
||||
MDNlinks = require('../../seed/bonfireMDNlinks');
|
||||
|
||||
var router = express.Router();
|
||||
var challengeMapWithNames = resources.getChallengeMapWithNames();
|
||||
var challengeMapWithIds = resources.getChallengeMapWithIds();
|
||||
var challengeMapWithNames = utils.getChallengeMapWithNames();
|
||||
var challengeMapWithIds = utils.getChallengeMapWithIds();
|
||||
|
||||
|
||||
function getMDNlinks(links) {
|
||||
// takes in an array of links, which are strings
|
||||
var populatedLinks = [];
|
||||
|
||||
// for each key value, push the corresponding link
|
||||
// from the MDNlinks object into a new array
|
||||
if (links) {
|
||||
links.forEach(function (value) {
|
||||
populatedLinks.push(MDNlinks[value]);
|
||||
});
|
||||
}
|
||||
return populatedLinks;
|
||||
}
|
||||
|
||||
module.exports = function(app) {
|
||||
var router = app.loopback.Router();
|
||||
var Challenge = app.models.Challenge;
|
||||
var User = app.models.User;
|
||||
|
||||
router.get(
|
||||
'/challenges/next-challenge',
|
||||
@ -59,19 +75,7 @@ router.post('/completed-challenge/', completedChallenge);
|
||||
router.post('/completed-zipline-or-basejump', completedZiplineOrBasejump);
|
||||
router.post('/completed-bonfire', completedBonfire);
|
||||
|
||||
function getMDNlinks(links) {
|
||||
// takes in an array of links, which are strings
|
||||
var populatedLinks = [];
|
||||
|
||||
// for each key value, push the corresponding link
|
||||
// from the MDNlinks object into a new array
|
||||
if (links) {
|
||||
links.forEach(function (value) {
|
||||
populatedLinks.push(MDNlinks[value]);
|
||||
});
|
||||
}
|
||||
return populatedLinks;
|
||||
}
|
||||
app.use(router);
|
||||
|
||||
function returnNextChallenge(req, res, next) {
|
||||
if (!req.user) {
|
||||
@ -81,7 +85,7 @@ function returnNextChallenge(req, res, next) {
|
||||
return elem._id;
|
||||
});
|
||||
|
||||
req.user.uncompletedChallenges = resources.allChallengeIds()
|
||||
req.user.uncompletedChallenges = utils.allChallengeIds()
|
||||
.filter(function (elem) {
|
||||
if (completed.indexOf(elem) === -1) {
|
||||
return elem;
|
||||
@ -135,7 +139,7 @@ function returnCurrentChallenge(req, res, next) {
|
||||
return elem._id;
|
||||
});
|
||||
|
||||
req.user.uncompletedChallenges = resources.allChallengeIds()
|
||||
req.user.uncompletedChallenges = utils.allChallengeIds()
|
||||
.filter(function (elem) {
|
||||
if (completed.indexOf(elem) === -1) {
|
||||
return elem;
|
||||
@ -176,11 +180,11 @@ function returnIndividualChallenge(req, res, next) {
|
||||
.join(' ') :
|
||||
dashedName.replace(/\-/g, ' ');
|
||||
|
||||
Challenge.find({'name': new RegExp(challengeName, 'i')},
|
||||
Challenge.find(
|
||||
{ where: { name: new RegExp(challengeName, 'i') } },
|
||||
function(err, challengeFromMongo) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
if (err) { return next(err); }
|
||||
|
||||
// Handle not found
|
||||
if (challengeFromMongo.length < 1) {
|
||||
req.flash('errors', {
|
||||
@ -224,11 +228,11 @@ function returnIndividualChallenge(req, res, next) {
|
||||
details: challenge.description.slice(1),
|
||||
tests: challenge.tests,
|
||||
challengeSeed: challenge.challengeSeed,
|
||||
verb: resources.randomVerb(),
|
||||
phrase: resources.randomPhrase(),
|
||||
compliment: resources.randomCompliment(),
|
||||
verb: utils.randomVerb(),
|
||||
phrase: utils.randomPhrase(),
|
||||
compliment: utils.randomCompliment(),
|
||||
challengeId: challenge._id,
|
||||
environment: resources.whichEnvironment(),
|
||||
environment: utils.whichEnvironment(),
|
||||
challengeType: challenge.challengeType
|
||||
});
|
||||
},
|
||||
@ -242,9 +246,9 @@ function returnIndividualChallenge(req, res, next) {
|
||||
details: challenge.description.slice(1),
|
||||
tests: challenge.tests,
|
||||
challengeSeed: challenge.challengeSeed,
|
||||
verb: resources.randomVerb(),
|
||||
phrase: resources.randomPhrase(),
|
||||
compliment: resources.randomCompliment(),
|
||||
verb: utils.randomVerb(),
|
||||
phrase: utils.randomPhrase(),
|
||||
compliment: utils.randomCompliment(),
|
||||
challengeId: challenge._id,
|
||||
challengeType: challenge.challengeType
|
||||
});
|
||||
@ -258,9 +262,9 @@ function returnIndividualChallenge(req, res, next) {
|
||||
details: challenge.description,
|
||||
tests: challenge.tests,
|
||||
video: challenge.challengeSeed[0],
|
||||
verb: resources.randomVerb(),
|
||||
phrase: resources.randomPhrase(),
|
||||
compliment: resources.randomCompliment(),
|
||||
verb: utils.randomVerb(),
|
||||
phrase: utils.randomPhrase(),
|
||||
compliment: utils.randomCompliment(),
|
||||
challengeId: challenge._id,
|
||||
challengeType: challenge.challengeType
|
||||
});
|
||||
@ -273,9 +277,9 @@ function returnIndividualChallenge(req, res, next) {
|
||||
name: challenge.name,
|
||||
details: challenge.description,
|
||||
video: challenge.challengeSeed[0],
|
||||
verb: resources.randomVerb(),
|
||||
phrase: resources.randomPhrase(),
|
||||
compliment: resources.randomCompliment(),
|
||||
verb: utils.randomVerb(),
|
||||
phrase: utils.randomPhrase(),
|
||||
compliment: utils.randomCompliment(),
|
||||
challengeId: challenge._id,
|
||||
challengeType: challenge.challengeType
|
||||
});
|
||||
@ -288,9 +292,9 @@ function returnIndividualChallenge(req, res, next) {
|
||||
name: challenge.name,
|
||||
details: challenge.description,
|
||||
video: challenge.challengeSeed[0],
|
||||
verb: resources.randomVerb(),
|
||||
phrase: resources.randomPhrase(),
|
||||
compliment: resources.randomCompliment(),
|
||||
verb: utils.randomVerb(),
|
||||
phrase: utils.randomPhrase(),
|
||||
compliment: utils.randomCompliment(),
|
||||
challengeId: challenge._id,
|
||||
challengeType: challenge.challengeType
|
||||
});
|
||||
@ -307,9 +311,9 @@ function returnIndividualChallenge(req, res, next) {
|
||||
details: challenge.description,
|
||||
tests: challenge.tests,
|
||||
challengeSeed: challenge.challengeSeed,
|
||||
verb: resources.randomVerb(),
|
||||
phrase: resources.randomPhrase(),
|
||||
compliment: resources.randomCompliment(),
|
||||
verb: utils.randomVerb(),
|
||||
phrase: utils.randomPhrase(),
|
||||
compliment: utils.randomCompliment(),
|
||||
bonfires: challenge,
|
||||
challengeId: challenge._id,
|
||||
MDNkeys: challenge.MDNlinks,
|
||||
@ -339,12 +343,12 @@ function completedBonfire(req, res, next) {
|
||||
var challengeName = req.body.challengeInfo.challengeName;
|
||||
|
||||
if (isCompletedWith) {
|
||||
var paired = User.find({'profile.username': isCompletedWith.toLowerCase()})
|
||||
.limit(1);
|
||||
paired.exec(function (err, pairedWith) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
} else {
|
||||
User.find({
|
||||
where: { 'profile.username': isCompletedWith.toLowerCase() },
|
||||
limit: 1
|
||||
}, function (err, pairedWith) {
|
||||
if (err) { return next(err); }
|
||||
|
||||
var index = req.user.uncompletedChallenges.indexOf(challengeId);
|
||||
if (index > -1) {
|
||||
req.user.progressTimestamps.push(Date.now() || 0);
|
||||
@ -388,11 +392,9 @@ function completedBonfire(req, res, next) {
|
||||
challengeType: 5
|
||||
});
|
||||
|
||||
|
||||
req.user.save(function (err, user) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
if (err) { return next(err); }
|
||||
|
||||
if (pairedWith) {
|
||||
pairedWith.save(function (err, paired) {
|
||||
if (err) {
|
||||
@ -406,7 +408,6 @@ function completedBonfire(req, res, next) {
|
||||
res.send(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
req.user.completedChallenges.push({
|
||||
@ -425,14 +426,9 @@ function completedBonfire(req, res, next) {
|
||||
req.user.uncompletedChallenges.splice(index, 1);
|
||||
}
|
||||
|
||||
req.user.save(function (err, user) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
// NOTE(berks): Under certain conditions the res is never ended
|
||||
if (user) {
|
||||
req.user.save(function (err) {
|
||||
if (err) { return next(err); }
|
||||
res.send(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -486,13 +482,11 @@ function completedZiplineOrBasejump(req, res, next) {
|
||||
}
|
||||
|
||||
if (isCompletedWith) {
|
||||
var paired = User.find({'profile.username': isCompletedWith.toLowerCase()})
|
||||
.limit(1);
|
||||
|
||||
paired.exec(function (err, pairedWithFromMongo) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
} else {
|
||||
User.find({
|
||||
where: { 'profile.username': isCompletedWith.toLowerCase() },
|
||||
limit: 1
|
||||
}, function (err, pairedWithFromMongo) {
|
||||
if (err) { return next(err); }
|
||||
var index = req.user.uncompletedChallenges.indexOf(challengeId);
|
||||
if (index > -1) {
|
||||
req.user.progressTimestamps.push(Date.now() || 0);
|
||||
@ -512,9 +506,7 @@ function completedZiplineOrBasejump(req, res, next) {
|
||||
});
|
||||
|
||||
req.user.save(function (err, user) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
if (err) { return next(err); }
|
||||
|
||||
if (req.user._id.toString() === pairedWith._id.toString()) {
|
||||
return res.sendStatus(200);
|
||||
@ -545,7 +537,6 @@ function completedZiplineOrBasejump(req, res, next) {
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
@ -570,12 +561,12 @@ function completedZiplineOrBasejump(req, res, next) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
// NOTE(berks): under certain conditions this will not close the response.
|
||||
// NOTE(berks): under certain conditions this will not close
|
||||
// the response.
|
||||
if (user) {
|
||||
return res.sendStatus(200);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
};
|
||||
|
@ -1,21 +1,23 @@
|
||||
var R = require('ramda'),
|
||||
express = require('express'),
|
||||
// debug = require('debug')('freecc:cntr:challengeMap'),
|
||||
User = require('../../common/models/User'),
|
||||
resources = require('./../resources/resources'),
|
||||
middleware = require('../resources/middleware'),
|
||||
router = express.Router();
|
||||
utils = require('./../utils'),
|
||||
middleware = require('../utils/middleware');
|
||||
|
||||
|
||||
module.exports = function(app) {
|
||||
var User = app.models.User;
|
||||
var router = app.loopback.Router();
|
||||
|
||||
router.get('/map', middleware.userMigration, challengeMap);
|
||||
|
||||
router.get('/learn-to-code', function(req, res) {
|
||||
res.redirect(301, '/map');
|
||||
});
|
||||
|
||||
router.get('/about', function(req, res) {
|
||||
res.redirect(301, '/map');
|
||||
});
|
||||
|
||||
app.use(router);
|
||||
|
||||
function challengeMap(req, res, next) {
|
||||
var completedList = [];
|
||||
|
||||
@ -29,7 +31,7 @@ function challengeMap(req, res, next) {
|
||||
.map(function(challenge) {
|
||||
return challenge._id;
|
||||
});
|
||||
var challengeList = resources.
|
||||
var challengeList = utils.
|
||||
getChallengeMapForDisplay(completedChallengeList);
|
||||
|
||||
Object.keys(challengeList).forEach(function(key) {
|
||||
@ -48,10 +50,9 @@ function challengeMap(req, res, next) {
|
||||
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
|
||||
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
|
||||
|
||||
User.count({}, function (err, camperCount) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
User.count(function(err, camperCount) {
|
||||
if (err) { return next(err); }
|
||||
|
||||
res.render('challengeMap/show', {
|
||||
daysRunning: daysRunning,
|
||||
camperCount: numberWithCommas(camperCount),
|
||||
@ -61,5 +62,4 @@ function challengeMap(req, res, next) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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'),
|
||||
express = require('express'),
|
||||
// Rx = require('rx'),
|
||||
// debug = require('debug')('freecc:fieldguides'),
|
||||
FieldGuide = require('../../common/models/FieldGuide'),
|
||||
resources = require('../resources/resources');
|
||||
utils = require('../utils');
|
||||
|
||||
var router = express.Router();
|
||||
module.exports = function(app) {
|
||||
var router = app.loopback.Router();
|
||||
var FieldGuide = app.models.FieldGuide;
|
||||
|
||||
router.get('/field-guide/all-articles', showAllFieldGuides);
|
||||
router.get('/field-guide/:fieldGuideName', returnIndividualFieldGuide);
|
||||
router.get('/field-guide/', returnNextFieldGuide);
|
||||
router.post('/completed-field-guide/', completedFieldGuide);
|
||||
|
||||
app.use(router);
|
||||
|
||||
function returnIndividualFieldGuide(req, res, next) {
|
||||
var dashedName = req.params.fieldGuideName;
|
||||
if (req.user) {
|
||||
var completed = req.user.completedFieldGuides;
|
||||
|
||||
var uncompletedFieldGuides = resources.allFieldGuideIds()
|
||||
var uncompletedFieldGuides = utils.allFieldGuideIds()
|
||||
.filter(function (elem) {
|
||||
if (completed.indexOf(elem) === -1) {
|
||||
return elem;
|
||||
@ -24,9 +27,12 @@ function returnIndividualFieldGuide(req, res, next) {
|
||||
});
|
||||
req.user.uncompletedFieldGuides = uncompletedFieldGuides;
|
||||
// TODO(berks): handle callback properly
|
||||
req.user.save();
|
||||
req.user.save(function(err) {
|
||||
if (err) { return next(err); }
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE(berks): loopback might have issue with regex here.
|
||||
FieldGuide.find(
|
||||
{ dashedName: new RegExp(dashedName, 'i') },
|
||||
function(err, fieldGuideFromMongo) {
|
||||
@ -59,7 +65,7 @@ function returnIndividualFieldGuide(req, res, next) {
|
||||
}
|
||||
|
||||
function showAllFieldGuides(req, res) {
|
||||
var allFieldGuideNamesAndIds = resources.allFieldGuideNamesAndIds();
|
||||
var allFieldGuideNamesAndIds = utils.allFieldGuideNamesAndIds();
|
||||
|
||||
var completedFieldGuides = [];
|
||||
if (req.user && req.user.completedFieldGuides) {
|
||||
@ -90,7 +96,7 @@ function returnNextFieldGuide(req, res, next) {
|
||||
"You've read all our current Field Guide entries. You can ",
|
||||
'contribute to our Field Guide ',
|
||||
"<a href='https://github.com/FreeCodeCamp/freecodecamp/blob/",
|
||||
"staging/seed_data/field-guides.json'>here</a>."
|
||||
"staging/seed/field-guides.json'>here</a>."
|
||||
].join('')
|
||||
});
|
||||
}
|
||||
@ -119,5 +125,4 @@ function completedFieldGuide(req, res, next) {
|
||||
res.send(true);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
};
|
||||
|
@ -1,10 +1,12 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var message =
|
||||
'Learn to Code JavaScript and get a Coding Job by Helping Nonprofits';
|
||||
|
||||
module.exports = function(app) {
|
||||
var router = app.loopback.Router();
|
||||
router.get('/', index);
|
||||
|
||||
app.use(router);
|
||||
|
||||
function index(req, res, next) {
|
||||
if (req.user && !req.user.profile.picture) {
|
||||
req.user.profile.picture =
|
||||
@ -18,5 +20,4 @@ function index(req, res, next) {
|
||||
res.render('home', { title: message });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
};
|
||||
|
@ -1,8 +1,9 @@
|
||||
var express = require('express');
|
||||
var Job = require('../../common/models/Job');
|
||||
var router = express.Router();
|
||||
module.exports = function(app) {
|
||||
var Job = app.models.Job;
|
||||
var router = app.loopback.Router();
|
||||
|
||||
router.get('/jobs', jobsDirectory);
|
||||
app.use(router);
|
||||
|
||||
function jobsDirectory(req, res, next) {
|
||||
Job.find({}, function(err, jobs) {
|
||||
@ -14,5 +15,4 @@ function jobsDirectory(req, res, next) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
};
|
||||
|
0
server/boot/middlewares.js
Normal file
0
server/boot/middlewares.js
Normal file
@ -1,20 +1,25 @@
|
||||
var express = require('express'),
|
||||
Nonprofit = require('../../common/models/Nonprofit');
|
||||
|
||||
var router = express.Router();
|
||||
module.exports = function(app) {
|
||||
var router = app.loopback.Router();
|
||||
var Nonprofit = app.models.Nonprofit;
|
||||
|
||||
router.get('/nonprofits/directory', nonprofitsDirectory);
|
||||
router.get('/nonprofits/:nonprofitName', returnIndividualNonprofit);
|
||||
|
||||
app.use(router);
|
||||
|
||||
function nonprofitsDirectory(req, res, next) {
|
||||
Nonprofit.find({ estimatedHours: { $gt: 0 } }, function(err, nonprofits) {
|
||||
Nonprofit.find(
|
||||
{ where: { estimatedHours: { $gt: 0 } } },
|
||||
function(err, nonprofits) {
|
||||
if (err) { return next(err); }
|
||||
|
||||
res.render('nonprofits/directory', {
|
||||
title: 'Nonprofits we help',
|
||||
nonprofits: nonprofits
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function returnIndividualNonprofit(req, res, next) {
|
||||
@ -22,7 +27,7 @@ function returnIndividualNonprofit(req, res, next) {
|
||||
var nonprofitName = dashedName.replace(/\-/g, ' ');
|
||||
|
||||
Nonprofit.find(
|
||||
{ name: new RegExp(nonprofitName, 'i') },
|
||||
{ where: { name: new RegExp(nonprofitName, 'i') } },
|
||||
function(err, nonprofit) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
@ -122,5 +127,4 @@ function interestedInNonprofit(req, res, next) {
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
module.exports = router;
|
||||
};
|
||||
|
@ -1,8 +1,9 @@
|
||||
var express = require('express'),
|
||||
passport = require('passport'),
|
||||
/*
|
||||
var passport = require('passport'),
|
||||
passportConf = require('../../config/passport');
|
||||
|
||||
var router = express.Router();
|
||||
module.exports = function(app) {
|
||||
var router = app.loopback.Router();
|
||||
var passportOptions = {
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/login'
|
||||
@ -65,4 +66,6 @@ router.get(
|
||||
}
|
||||
);
|
||||
|
||||
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,5 +1,5 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
module.exports = function(app) {
|
||||
var router = app.loopback.Router();
|
||||
|
||||
router.get('/nonprofit-project-instructions', function(req, res) {
|
||||
res.redirect(
|
||||
@ -41,7 +41,10 @@ router.get('/nodeschool-challenges', 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,16 +1,16 @@
|
||||
var nodemailer = require('nodemailer'),
|
||||
sanitizeHtml = require('sanitize-html'),
|
||||
express = require('express'),
|
||||
moment = require('moment'),
|
||||
mongodb = require('mongodb'),
|
||||
// debug = require('debug')('freecc:cntr:story'),
|
||||
Story = require('../../common/models/Story'),
|
||||
Comment = require('../../common/models/Comment'),
|
||||
User = require('../../common/models/User'),
|
||||
resources = require('../resources/resources'),
|
||||
utils = require('../utils'),
|
||||
MongoClient = mongodb.MongoClient,
|
||||
secrets = require('../../config/secrets'),
|
||||
router = express.Router();
|
||||
secrets = require('../../config/secrets');
|
||||
|
||||
module.exports = function(app) {
|
||||
var router = app.loopback.Router();
|
||||
var User = app.models.User;
|
||||
var Story = app.models.Story;
|
||||
|
||||
router.get('/stories/hotStories', hotJSON);
|
||||
router.get('/stories/recentStories', recentJSON);
|
||||
@ -27,6 +27,8 @@ 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
|
||||
@ -39,7 +41,6 @@ function hotRank(timeValue, rank) {
|
||||
var z = Math.log(rank) / Math.log(10);
|
||||
hotness = z + (timeValue / time48Hours);
|
||||
return hotness;
|
||||
|
||||
}
|
||||
|
||||
function hotJSON(req, res, next) {
|
||||
@ -252,9 +253,12 @@ function upvote(req, res, next) {
|
||||
story.markModified('rank');
|
||||
story.save();
|
||||
// NOTE(Berks): This logic is full of wholes and race conditions
|
||||
// this could be the source of many 'can't set headers after they are sent'
|
||||
// this could be the source of many 'can't set headers after
|
||||
// they are sent'
|
||||
// errors. This needs cleaning
|
||||
User.findOne({'_id': story.author.userId}, function(err, user) {
|
||||
User.findOne(
|
||||
{ where: { id: story.author.userId } },
|
||||
function(err, user) {
|
||||
if (err) { return next(err); }
|
||||
|
||||
user.progressTimestamps.push(Date.now() || 0);
|
||||
@ -267,14 +271,17 @@ function upvote(req, res, next) {
|
||||
return next(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
return res.send(story);
|
||||
});
|
||||
}
|
||||
|
||||
function comments(req, res, next) {
|
||||
var data = req.params.id;
|
||||
Comment.find({'_id': data}, function(err, comment) {
|
||||
Comment.find(
|
||||
{ where: {'_id': data } },
|
||||
function(err, comment) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@ -305,7 +312,9 @@ function newStory(req, res, next) {
|
||||
if (url.search(/^https?:\/\//g) === -1) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
Story.find({'link': url}, function(err, story) {
|
||||
Story.find(
|
||||
{ where: {'link': url} },
|
||||
function(err, story) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@ -318,8 +327,9 @@ function newStory(req, res, next) {
|
||||
storyURL: '/news/' + story.pop().storyLink
|
||||
});
|
||||
}
|
||||
resources.getURLTitle(url, processResponse);
|
||||
});
|
||||
utils.getURLTitle(url, processResponse);
|
||||
}
|
||||
);
|
||||
|
||||
function processResponse(err, story) {
|
||||
if (err) {
|
||||
@ -360,7 +370,7 @@ function storySubmission(req, res, next) {
|
||||
}
|
||||
|
||||
Story.count({
|
||||
storyLink: new RegExp('^' + storyLink + '(?: [0-9]+)?$', 'i')
|
||||
storyLink: { like: new RegExp('^' + storyLink + '(?: [0-9]+)?$', 'i') }
|
||||
}, function (err, storyCount) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
@ -461,17 +471,21 @@ function commentOnCommentSubmit(req, res, next) {
|
||||
return next(new Error('Not authorized'));
|
||||
}
|
||||
|
||||
var sanitizedBody = sanitizeHtml(data.body,
|
||||
var sanitizedBody = sanitizeHtml(
|
||||
data.body,
|
||||
{
|
||||
allowedTags: [],
|
||||
allowedAttributes: []
|
||||
}).replace(/"/g, '"');
|
||||
}
|
||||
).replace(/"/g, '"');
|
||||
|
||||
if (data.body !== sanitizedBody) {
|
||||
req.flash('errors', {
|
||||
msg: 'HTML is not allowed'
|
||||
});
|
||||
return res.send(true);
|
||||
}
|
||||
|
||||
var comment = new Comment({
|
||||
associatedPost: data.associatedPost,
|
||||
body: sanitizedBody,
|
||||
@ -494,7 +508,7 @@ function commentOnCommentSubmit(req, res, next) {
|
||||
|
||||
function commentEdit(req, res, next) {
|
||||
|
||||
Comment.find({'_id': req.params.id}, function(err, cmt) {
|
||||
Comment.find({ id: req.params.id }, function(err, cmt) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@ -504,7 +518,6 @@ function commentEdit(req, res, next) {
|
||||
return next(new Error('Not authorized'));
|
||||
}
|
||||
|
||||
|
||||
var sanitizedBody = sanitizeHtml(req.body.body, {
|
||||
allowedTags: [],
|
||||
allowedAttributes: []
|
||||
@ -538,7 +551,7 @@ function commentSave(comment, Context, res, next) {
|
||||
// Based on the context retrieve the parent
|
||||
// object of the comment (Story/Comment)
|
||||
Context.find({
|
||||
'_id': data.associatedPost
|
||||
id: data.associatedPost
|
||||
}, function (err, associatedContext) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
@ -605,5 +618,4 @@ function commentSave(comment, Context, res, next) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
};
|
||||
|
@ -3,16 +3,17 @@ var _ = require('lodash'),
|
||||
async = require('async'),
|
||||
crypto = require('crypto'),
|
||||
nodemailer = require('nodemailer'),
|
||||
passport = require('passport'),
|
||||
moment = require('moment'),
|
||||
express = require('express'),
|
||||
debug = require('debug')('freecc:cntr:userController'),
|
||||
|
||||
User = require('../../common/models/User'),
|
||||
secrets = require('../../config/secrets'),
|
||||
resources = require('./../resources/resources');
|
||||
secrets = require('../../config/secrets');
|
||||
|
||||
module.exports = function(app) {
|
||||
var router = app.loopback.Router();
|
||||
var User = app.models.User;
|
||||
var Story = app.models.Story;
|
||||
var Comment = app.models.Comment;
|
||||
|
||||
var router = express.Router();
|
||||
router.get('/login', function(req, res) {
|
||||
res.redirect(301, '/signin');
|
||||
});
|
||||
@ -20,7 +21,7 @@ router.get('/logout', function(req, res) {
|
||||
res.redirect(301, '/signout');
|
||||
});
|
||||
router.get('/signin', getSignin);
|
||||
router.post('/signin', postSignin);
|
||||
// router.post('/signin', postSignin);
|
||||
router.get('/signout', signout);
|
||||
router.get('/forgot', getForgot);
|
||||
router.post('/forgot', postForgot);
|
||||
@ -29,7 +30,7 @@ router.post('/reset/:token', postReset);
|
||||
router.get('/email-signup', getEmailSignup);
|
||||
router.get('/email-signin', getEmailSignin);
|
||||
router.post('/email-signup', postEmailSignup);
|
||||
router.post('/email-signin', postSignin);
|
||||
// router.post('/email-signin', postSignin);
|
||||
router.get('/account/api', getAccountAngular);
|
||||
router.get('/api/checkUniqueUsername/:username', checkUniqueUsername);
|
||||
router.get('/api/checkExistingUsername/:username', checkExistingUsername);
|
||||
@ -42,6 +43,8 @@ router.get('/account', getAccount);
|
||||
// Ensure this is the last route!
|
||||
router.get('/:username', returnUser);
|
||||
|
||||
app.use(router);
|
||||
|
||||
/**
|
||||
* GET /signin
|
||||
* Siginin page.
|
||||
@ -61,6 +64,8 @@ function getSignin (req, res) {
|
||||
* Sign in using email and password.
|
||||
*/
|
||||
|
||||
/*
|
||||
* TODO(berks): this should be done using loopback
|
||||
function postSignin (req, res, next) {
|
||||
req.assert('email', 'Email is not valid').isEmail();
|
||||
req.assert('password', 'Password cannot be blank').notEmpty();
|
||||
@ -95,6 +100,7 @@ function postSignin (req, res, next) {
|
||||
});
|
||||
})(req, res, next);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* GET /signout
|
||||
@ -325,8 +331,8 @@ function returnUser (req, res, next) {
|
||||
}
|
||||
if (user[0]) {
|
||||
user = user[0];
|
||||
|
||||
user.progressTimestamps = user.progressTimestamps.sort(function(a, b) {
|
||||
user.progressTimestamps =
|
||||
user.progressTimestamps.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
@ -486,36 +492,41 @@ function postUpdateProfile (req, res, next) {
|
||||
});
|
||||
return res.redirect('/account');
|
||||
}
|
||||
user.email = req.body.email.trim() || '';
|
||||
user.profile.name = req.body.name.trim() || '';
|
||||
user.profile.username = req.body.username.trim() || '';
|
||||
user.profile.location = req.body.location.trim() || '';
|
||||
user.profile.githubProfile = req.body.githubProfile.trim() || '';
|
||||
user.profile.facebookProfile = req.body.facebookProfile.trim() || '';
|
||||
user.profile.linkedinProfile = req.body.linkedinProfile.trim() || '';
|
||||
user.profile.codepenProfile = req.body.codepenProfile.trim() || '';
|
||||
user.profile.twitterHandle = req.body.twitterHandle.trim() || '';
|
||||
user.profile.bio = req.body.bio.trim() || '';
|
||||
var body = req.body || {};
|
||||
user.email = body.email.trim() || '';
|
||||
user.profile.name = body.name.trim() || '';
|
||||
user.profile.username = body.username.trim() || '';
|
||||
user.profile.location = body.location.trim() || '';
|
||||
|
||||
user.profile.picture = req.body.picture.trim() ||
|
||||
user.profile.githubProfile = body.githubProfile.trim() || '';
|
||||
user.profile.facebookProfile = body.facebookProfile.trim() || '';
|
||||
user.profile.linkedinProfile = body.linkedinProfile.trim() || '';
|
||||
|
||||
user.profile.codepenProfile = body.codepenProfile.trim() || '';
|
||||
user.profile.twitterHandle = body.twitterHandle.trim() || '';
|
||||
user.profile.bio = body.bio.trim() || '';
|
||||
|
||||
user.profile.picture = body.picture.trim() ||
|
||||
'https://s3.amazonaws.com/freecodecamp/' +
|
||||
'camper-image-placeholder.png';
|
||||
user.portfolio.website1Title = req.body.website1Title.trim() || '';
|
||||
user.portfolio.website1Link = req.body.website1Link.trim() || '';
|
||||
user.portfolio.website1Image = req.body.website1Image.trim() || '';
|
||||
user.portfolio.website2Title = req.body.website2Title.trim() || '';
|
||||
user.portfolio.website2Link = req.body.website2Link.trim() || '';
|
||||
user.portfolio.website2Image = req.body.website2Image.trim() || '';
|
||||
user.portfolio.website3Title = req.body.website3Title.trim() || '';
|
||||
user.portfolio.website3Link = req.body.website3Link.trim() || '';
|
||||
user.portfolio.website3Image = req.body.website3Image.trim() || '';
|
||||
user.portfolio.website1Title = body.website1Title.trim() || '';
|
||||
user.portfolio.website1Link = body.website1Link.trim() || '';
|
||||
user.portfolio.website1Image = body.website1Image.trim() || '';
|
||||
|
||||
user.portfolio.website2Title = body.website2Title.trim() || '';
|
||||
user.portfolio.website2Link = body.website2Link.trim() || '';
|
||||
user.portfolio.website2Image = body.website2Image.trim() || '';
|
||||
|
||||
user.portfolio.website3Title = body.website3Title.trim() || '';
|
||||
user.portfolio.website3Link = body.website3Link.trim() || '';
|
||||
user.portfolio.website3Image = body.website3Image.trim() || '';
|
||||
|
||||
|
||||
user.save(function (err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
resources.updateUserStoryPictures(
|
||||
updateUserStoryPictures(
|
||||
user._id.toString(),
|
||||
user.profile.picture,
|
||||
user.profile.username,
|
||||
@ -540,7 +551,9 @@ function postUpdateProfile (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')
|
||||
.equals(req.body.password);
|
||||
|
||||
@ -571,7 +584,7 @@ function postUpdatePassword (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); }
|
||||
req.logout();
|
||||
req.flash('info', { msg: 'Your account has been deleted.' });
|
||||
@ -612,10 +625,14 @@ function getReset (req, res, next) {
|
||||
if (req.isAuthenticated()) {
|
||||
return res.redirect('/');
|
||||
}
|
||||
User
|
||||
.findOne({ resetPasswordToken: req.params.token })
|
||||
.where('resetPasswordExpires').gt(Date.now())
|
||||
.exec(function(err, user) {
|
||||
User.findOne(
|
||||
{
|
||||
where: {
|
||||
resetPasswordToken: req.params.token,
|
||||
resetPasswordExpires: Date.now()
|
||||
}
|
||||
},
|
||||
function(err, user) {
|
||||
if (err) { return next(err); }
|
||||
if (!user) {
|
||||
req.flash('errors', {
|
||||
@ -645,10 +662,14 @@ function postReset (req, res, next) {
|
||||
|
||||
async.waterfall([
|
||||
function(done) {
|
||||
User
|
||||
.findOne({ resetPasswordToken: req.params.token })
|
||||
.where('resetPasswordExpires').gt(Date.now())
|
||||
.exec(function(err, user) {
|
||||
User.findOne(
|
||||
{
|
||||
where: {
|
||||
resetPasswordToken: req.params.token,
|
||||
resetPasswordExpires: Date.now()
|
||||
}
|
||||
},
|
||||
function(err, user) {
|
||||
if (err) { return next(err); }
|
||||
if (!user) {
|
||||
req.flash('errors', {
|
||||
@ -803,4 +824,58 @@ function postForgot (req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
var express = require('express'),
|
||||
var R = require('ramda'),
|
||||
loopback = require('loopback'),
|
||||
boot = require('loopback-boot'),
|
||||
accepts = require('accepts'),
|
||||
cookieParser = require('cookie-parser'),
|
||||
compress = require('compression'),
|
||||
@ -22,27 +24,11 @@ var express = require('express'),
|
||||
MongoStore = require('connect-mongo')(session),
|
||||
flash = require('express-flash'),
|
||||
path = require('path'),
|
||||
mongoose = require('mongoose'),
|
||||
passport = require('passport'),
|
||||
expressValidator = require('express-validator'),
|
||||
// request = require('request'),
|
||||
forceDomain = require('forcedomain'),
|
||||
lessMiddleware = require('less-middleware'),
|
||||
|
||||
/**
|
||||
* routers.
|
||||
*/
|
||||
homeRouter = require('./boot/home'),
|
||||
userRouter = require('./boot/user'),
|
||||
fieldGuideRouter = require('./boot/fieldGuide'),
|
||||
challengeMapRouter = require('./boot/challengeMap'),
|
||||
challengeRouter = require('./boot/challenge'),
|
||||
jobsRouter = require('./boot/jobs'),
|
||||
redirectsRouter = require('./boot/redirects'),
|
||||
utilityRouter = require('./boot/utility'),
|
||||
storyRouter = require('./boot/story'),
|
||||
passportRouter = require('./boot/passport'),
|
||||
|
||||
passportProviders = require('./passport-providers'),
|
||||
/**
|
||||
* API keys and Passport configuration.
|
||||
*/
|
||||
@ -51,22 +37,10 @@ var express = require('express'),
|
||||
/**
|
||||
* Create Express server.
|
||||
*/
|
||||
var app = express();
|
||||
|
||||
/**
|
||||
* Connect to MongoDB.
|
||||
*/
|
||||
mongoose.connect(secrets.db);
|
||||
mongoose.connection.on('error', function () {
|
||||
console.error(
|
||||
'MongoDB Connection Error. Please make sure that MongoDB is running.'
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Express configuration.
|
||||
*/
|
||||
|
||||
var app = loopback();
|
||||
var PassportConfigurator =
|
||||
require('loopback-component-passport').PassportConfigurator;
|
||||
var passportConfigurator = new PassportConfigurator(app);
|
||||
|
||||
app.set('port', process.env.PORT || 3000);
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
@ -101,8 +75,7 @@ app.use(session({
|
||||
'autoReconnect': true
|
||||
})
|
||||
}));
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
app.use(flash());
|
||||
app.disable('x-powered-by');
|
||||
|
||||
@ -191,6 +164,8 @@ app.use(helmet.csp({
|
||||
safari5: false
|
||||
}));
|
||||
|
||||
passportConfigurator.init();
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
// Make user object available in templates.
|
||||
res.locals.user = req.user;
|
||||
@ -198,9 +173,14 @@ app.use(function (req, res, next) {
|
||||
});
|
||||
|
||||
app.use(
|
||||
express.static(path.join(__dirname, '../public'), { maxAge: 86400000 })
|
||||
loopback.static(path.join(__dirname, '../public'), { maxAge: 86400000 })
|
||||
);
|
||||
|
||||
boot(app, {
|
||||
appRootDir: __dirname,
|
||||
dev: process.env.NODE_ENV
|
||||
});
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
// Remember original destination before login.
|
||||
var path = req.path.split('/')[1];
|
||||
@ -213,17 +193,17 @@ app.use(function (req, res, next) {
|
||||
next();
|
||||
});
|
||||
|
||||
// add sub routers
|
||||
app.use(fieldGuideRouter);
|
||||
app.use(challengeMapRouter);
|
||||
app.use(challengeRouter);
|
||||
app.use(jobsRouter);
|
||||
app.use(redirectsRouter);
|
||||
app.use(utilityRouter);
|
||||
app.use(storyRouter);
|
||||
app.use(passportRouter);
|
||||
app.use(homeRouter);
|
||||
app.use(userRouter);
|
||||
passportConfigurator.setupModels({
|
||||
userModel: app.models.user,
|
||||
userIdentityModel: app.models.userIdentity,
|
||||
userCredentialModel: app.models.userCredential
|
||||
});
|
||||
|
||||
R.keys(passportProviders).map(function(strategy) {
|
||||
var config = passportProviders[strategy];
|
||||
config.session = config.session !== false;
|
||||
passportConfigurator.configureProvider(strategy, config);
|
||||
});
|
||||
|
||||
/**
|
||||
* OAuth sign-in routes.
|
||||
@ -273,6 +253,7 @@ if (process.env.NODE_ENV === 'development') {
|
||||
* Start Express server.
|
||||
*/
|
||||
|
||||
app.start = function() {
|
||||
app.listen(app.get('port'), function () {
|
||||
console.log(
|
||||
'FreeCodeCamp server listening on port %d in %s mode',
|
||||
@ -280,5 +261,11 @@ app.listen(app.get('port'), function () {
|
||||
app.get('env')
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// start the server if `$ node server.js`
|
||||
if (require.main === module) {
|
||||
app.start();
|
||||
}
|
||||
|
||||
module.exports = app;
|
||||
|
@ -1,5 +1,4 @@
|
||||
var async = require('async'),
|
||||
path = require('path'),
|
||||
var path = require('path'),
|
||||
// debug = require('debug')('freecc:cntr:resources'),
|
||||
cheerio = require('cheerio'),
|
||||
request = require('request'),
|
||||
@ -8,11 +7,9 @@ var async = require('async'),
|
||||
fs = require('fs'),
|
||||
|
||||
|
||||
Story = require('../../common/models/Story'),
|
||||
Comment = require('../../common/models/Comment'),
|
||||
resources = require('./resources.json'),
|
||||
nonprofits = require('../../seed_data/nonprofits.json'),
|
||||
fieldGuides = require('../../seed_data/field-guides.json');
|
||||
nonprofits = require('../../seed/nonprofits.json'),
|
||||
fieldGuides = require('../../seed/field-guides.json');
|
||||
|
||||
/**
|
||||
* Cached values
|
||||
@ -41,12 +38,12 @@ Array.zip = function(left, right, combinerFunction) {
|
||||
if (!challengeMap) {
|
||||
var localChallengeMap = {};
|
||||
var files = fs.readdirSync(
|
||||
path.join(__dirname, '../../seed_data/challenges')
|
||||
path.join(__dirname, '../../seed/challenges')
|
||||
);
|
||||
var keyCounter = 0;
|
||||
files = files.map(function (file) {
|
||||
return require(
|
||||
path.join(__dirname, '../../seed_data/challenges/' + file)
|
||||
path.join(__dirname, '../../seed/challenges/' + file)
|
||||
);
|
||||
});
|
||||
files = files.sort(function (a, b) {
|
||||
@ -215,59 +212,5 @@ module.exports = {
|
||||
}
|
||||
});
|
||||
})();
|
||||
},
|
||||
|
||||
updateUserStoryPictures: function (userId, picture, username, cb) {
|
||||
|
||||
var counter = 0,
|
||||
foundStories,
|
||||
foundComments;
|
||||
|
||||
Story.find({'author.userId': userId}, function (err, stories) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
foundStories = stories;
|
||||
counter++;
|
||||
saveStoriesAndComments();
|
||||
});
|
||||
Comment.find({'author.userId': userId}, function (err, comments) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
foundComments = comments;
|
||||
counter++;
|
||||
saveStoriesAndComments();
|
||||
});
|
||||
|
||||
function saveStoriesAndComments() {
|
||||
if (counter !== 2) {
|
||||
return;
|
||||
}
|
||||
var tasks = [];
|
||||
R.forEach(function (comment) {
|
||||
comment.author.picture = picture;
|
||||
comment.author.username = username;
|
||||
comment.markModified('author');
|
||||
tasks.push(function (cb) {
|
||||
comment.save(cb);
|
||||
});
|
||||
}, foundComments);
|
||||
|
||||
R.forEach(function (story) {
|
||||
story.author.picture = picture;
|
||||
story.author.username = username;
|
||||
story.markModified('author');
|
||||
tasks.push(function (cb) {
|
||||
story.save(cb);
|
||||
});
|
||||
}, foundStories);
|
||||
async.parallel(tasks, function (err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
cb();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user