├── .gitignore ├── LICENSE.md ├── README.md ├── app ├── common │ └── ajax-functions.js ├── config │ ├── auth.js │ └── passport.js ├── controllers │ ├── clickController.client.js │ ├── clickHandler.server.js │ └── userController.client.js ├── models │ └── users.js └── routes │ └── index.js ├── package.json ├── public ├── css │ └── main.css ├── fonts │ ├── Roboto-Medium.ttf │ └── Roboto-Regular.ttf ├── img │ ├── clementine_150.png │ ├── gh-mark-32px.png │ └── github_32px.png ├── index.html ├── login.html └── profile.html └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .eslintrc 3 | .env -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Clementine.js 2 | Copyright (c) 2015 Blake Johnston. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This Project Is No Longer Actively Maintained 2 | 3 | Heya! :wave: This project is being archived and is no longer maintained. More information can be found at https://www.clementinejs.com. 4 | 5 | # Clementine.js FCC Boilerplate 6 | 7 | [](https://gitter.im/johnstonbl01/clementinejs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 8 | 9 | ## Overview 10 | 11 | Clementine.js is a lightweight boilerplate for fullstack JavaScript development which utilizes MongoDB, Express and Node.js. The boilerplate errs on the side of transparency and simplicity, making it an ideal starting point for beginner and seasoned developers alike. 12 | 13 | The [Free Code Camp](http://www.freecodecamp.com) version of Clementine.js is meant for use when completing projects as part of the FCC curriculum. This version includes GitHub authentication using [Passport](http://passportjs.org/). 14 | 15 | ## Versions 16 | 17 | There are 3 versions of Clementine.js: 18 | 19 | - [**Standard**](https://github.com/johnstonbl01/clementinejs): the simplest version of Clementine.js. Intended for those who wish for the smallest and least intrusive footprint OR to implement features on their own. 20 | - [**Angular**](https://github.com/johnstonbl01/clementinejs-angular): a slightly more complex version of the same application. This version employs the use of AngularJS as the front-end framework. 21 | - **Free Code Camp (FCC)** (this version): A modified version of the standard boilerplate that is intended for use with the [Free Code Camp](http://freecodecamp.com/) curriculum. 22 | 23 | # Quick Start Guide 24 | 25 | ### Prerequisites 26 | 27 | In order to use Clementine.js, you must have the following installed: 28 | 29 | - [Node.js](https://nodejs.org/) 30 | - [NPM](https://nodejs.org/) 31 | - [MongoDB](http://www.mongodb.org/) 32 | - [Git](https://git-scm.com/) 33 | 34 | ### Installation & Startup 35 | 36 | To install Clementine.js, simply enter the below in the terminal window: 37 | 38 | ```bash 39 | $ git clone https://github.com/johnstonbl01/clementinejs-fcc.git your-project 40 | ``` 41 | 42 | To install the dependencies, enter the following in your terminal: 43 | 44 | ``` 45 | $ cd your-project 46 | $ npm install 47 | ``` 48 | 49 | This will install the Clementine.js components into the `your-project` directory. 50 | 51 | ### Setup GitHub Authentication 52 | 53 | Please follow [this guide](http://www.clementinejs.com/tutorials/tutorial-passport.html#GitHubAppSetup) to register the application with GitHub and get API keys / secrets. 54 | 55 | ### Local Environment Variables 56 | 57 | Create a file named `.env` in the root directory. This file should contain: 58 | 59 | ``` 60 | GITHUB_KEY=your-client-id-here 61 | GITHUB_SECRET=your-client-secret-here 62 | MONGO_URI=mongodb://localhost:27017/clementinejs 63 | PORT=8080 64 | APP_URL=http://localhost:8080/ 65 | ``` 66 | 67 | ### Starting the App 68 | 69 | To start the app, make sure you're in the project directory and type `node server.js` into the terminal. This will start the Node server and connect to MongoDB. 70 | 71 | You should the following messages within the terminal window: 72 | 73 | ``` 74 | Node.js listening on port 8080... 75 | ``` 76 | 77 | Next, open your browser and enter `http://localhost:8080/`. Congrats, you're up and running! 78 | 79 | ### c9.io Setup 80 | 81 | If you're using c9.io, please [reference the documentation](http://www.clementinejs.com/versions/fcc.html#c9.ioSetup) for instructions to get Clementine.js working in the c9 environment. 82 | 83 | ## Contributing 84 | 85 | This is an open-source project, and contributions are always welcome! To see ways to contribute, please review the [contribution guidelines](http://www.clementinejs.com/developers/contributing.html). 86 | 87 | ## Documentation 88 | 89 | Complete documentation can be [found here](http://www.clementinejs.com). 90 | 91 | ### Tutorial 92 | 93 | You can find a complete step-by-step tutorial on how to create this app from the ground up [here](http://www.clementinejs.com/tutorials/tutorial-passport.html). 94 | 95 | ## Features 96 | 97 | | Features | Standard | Angular | FCC | 98 | |:--------- |:--------: |:--------: |:---------:| 99 | | MongoDB | _Yes_ | _Yes_ | _Yes_ | 100 | | Express | _Yes_ | _Yes_ | _Yes_ | 101 | | AngularJS (1.x) | _No_ | _Yes_ | _No_ | 102 | | Node.js | _Yes_ | _Yes_ | _Yes_ | 103 | | Passport | _No_ | _No_ | _Yes_ | 104 | | Mongoose | _No_ | _No_ | _Yes_ | 105 | 106 | ## License 107 | 108 | MIT License. [Click here for more information.](LICENSE.md) 109 | -------------------------------------------------------------------------------- /app/common/ajax-functions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var appUrl = window.location.origin; 4 | var ajaxFunctions = { 5 | ready: function ready (fn) { 6 | if (typeof fn !== 'function') { 7 | return; 8 | } 9 | 10 | if (document.readyState === 'complete') { 11 | return fn(); 12 | } 13 | 14 | document.addEventListener('DOMContentLoaded', fn, false); 15 | }, 16 | ajaxRequest: function ajaxRequest (method, url, callback) { 17 | var xmlhttp = new XMLHttpRequest(); 18 | 19 | xmlhttp.onreadystatechange = function () { 20 | if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { 21 | callback(xmlhttp.response); 22 | } 23 | }; 24 | 25 | xmlhttp.open(method, url, true); 26 | xmlhttp.send(); 27 | } 28 | }; -------------------------------------------------------------------------------- /app/config/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 'githubAuth': { 5 | 'clientID': process.env.GITHUB_KEY, 6 | 'clientSecret': process.env.GITHUB_SECRET, 7 | 'callbackURL': process.env.APP_URL + 'auth/github/callback' 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /app/config/passport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var GitHubStrategy = require('passport-github').Strategy; 4 | var User = require('../models/users'); 5 | var configAuth = require('./auth'); 6 | 7 | module.exports = function (passport) { 8 | passport.serializeUser(function (user, done) { 9 | done(null, user.id); 10 | }); 11 | 12 | passport.deserializeUser(function (id, done) { 13 | User.findById(id, function (err, user) { 14 | done(err, user); 15 | }); 16 | }); 17 | 18 | passport.use(new GitHubStrategy({ 19 | clientID: configAuth.githubAuth.clientID, 20 | clientSecret: configAuth.githubAuth.clientSecret, 21 | callbackURL: configAuth.githubAuth.callbackURL 22 | }, 23 | function (token, refreshToken, profile, done) { 24 | process.nextTick(function () { 25 | User.findOne({ 'github.id': profile.id }, function (err, user) { 26 | if (err) { 27 | return done(err); 28 | } 29 | 30 | if (user) { 31 | return done(null, user); 32 | } else { 33 | var newUser = new User(); 34 | 35 | newUser.github.id = profile.id; 36 | newUser.github.username = profile.username; 37 | newUser.github.displayName = profile.displayName; 38 | newUser.github.publicRepos = profile._json.public_repos; 39 | newUser.nbrClicks.clicks = 0; 40 | 41 | newUser.save(function (err) { 42 | if (err) { 43 | throw err; 44 | } 45 | 46 | return done(null, newUser); 47 | }); 48 | } 49 | }); 50 | }); 51 | })); 52 | }; 53 | -------------------------------------------------------------------------------- /app/controllers/clickController.client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function () { 4 | 5 | var addButton = document.querySelector('.btn-add'); 6 | var deleteButton = document.querySelector('.btn-delete'); 7 | var clickNbr = document.querySelector('#click-nbr'); 8 | var apiUrl = appUrl + '/api/:id/clicks'; 9 | 10 | function updateClickCount (data) { 11 | var clicksObject = JSON.parse(data); 12 | clickNbr.innerHTML = clicksObject.clicks; 13 | } 14 | 15 | ajaxFunctions.ready(ajaxFunctions.ajaxRequest('GET', apiUrl, updateClickCount)); 16 | 17 | addButton.addEventListener('click', function () { 18 | 19 | ajaxFunctions.ajaxRequest('POST', apiUrl, function () { 20 | ajaxFunctions.ajaxRequest('GET', apiUrl, updateClickCount); 21 | }); 22 | 23 | }, false); 24 | 25 | deleteButton.addEventListener('click', function () { 26 | 27 | ajaxFunctions.ajaxRequest('DELETE', apiUrl, function () { 28 | ajaxFunctions.ajaxRequest('GET', apiUrl, updateClickCount); 29 | }); 30 | 31 | }, false); 32 | 33 | })(); 34 | -------------------------------------------------------------------------------- /app/controllers/clickHandler.server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Users = require('../models/users.js'); 4 | 5 | function ClickHandler () { 6 | 7 | this.getClicks = function (req, res) { 8 | Users 9 | .findOne({ 'github.id': req.user.github.id }, { '_id': false }) 10 | .exec(function (err, result) { 11 | if (err) { throw err; } 12 | 13 | res.json(result.nbrClicks); 14 | }); 15 | }; 16 | 17 | this.addClick = function (req, res) { 18 | Users 19 | .findOneAndUpdate({ 'github.id': req.user.github.id }, { $inc: { 'nbrClicks.clicks': 1 } }) 20 | .exec(function (err, result) { 21 | if (err) { throw err; } 22 | 23 | res.json(result.nbrClicks); 24 | } 25 | ); 26 | }; 27 | 28 | this.resetClicks = function (req, res) { 29 | Users 30 | .findOneAndUpdate({ 'github.id': req.user.github.id }, { 'nbrClicks.clicks': 0 }) 31 | .exec(function (err, result) { 32 | if (err) { throw err; } 33 | 34 | res.json(result.nbrClicks); 35 | } 36 | ); 37 | }; 38 | 39 | } 40 | 41 | module.exports = ClickHandler; 42 | -------------------------------------------------------------------------------- /app/controllers/userController.client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function () { 4 | 5 | var profileId = document.querySelector('#profile-id') || null; 6 | var profileUsername = document.querySelector('#profile-username') || null; 7 | var profileRepos = document.querySelector('#profile-repos') || null; 8 | var displayName = document.querySelector('#display-name'); 9 | var apiUrl = appUrl + '/api/:id'; 10 | 11 | function updateHtmlElement (data, element, userProperty) { 12 | element.innerHTML = data[userProperty]; 13 | } 14 | 15 | ajaxFunctions.ready(ajaxFunctions.ajaxRequest('GET', apiUrl, function (data) { 16 | var userObject = JSON.parse(data); 17 | 18 | if (userObject.displayName !== null) { 19 | updateHtmlElement(userObject, displayName, 'displayName'); 20 | } else { 21 | updateHtmlElement(userObject, displayName, 'username'); 22 | } 23 | 24 | if (profileId !== null) { 25 | updateHtmlElement(userObject, profileId, 'id'); 26 | } 27 | 28 | if (profileUsername !== null) { 29 | updateHtmlElement(userObject, profileUsername, 'username'); 30 | } 31 | 32 | if (profileRepos !== null) { 33 | updateHtmlElement(userObject, profileRepos, 'publicRepos'); 34 | } 35 | 36 | })); 37 | })(); 38 | -------------------------------------------------------------------------------- /app/models/users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | 6 | var User = new Schema({ 7 | github: { 8 | id: String, 9 | displayName: String, 10 | username: String, 11 | publicRepos: Number 12 | }, 13 | nbrClicks: { 14 | clicks: Number 15 | } 16 | }); 17 | 18 | module.exports = mongoose.model('User', User); 19 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = process.cwd(); 4 | var ClickHandler = require(path + '/app/controllers/clickHandler.server.js'); 5 | 6 | module.exports = function (app, passport) { 7 | 8 | function isLoggedIn (req, res, next) { 9 | if (req.isAuthenticated()) { 10 | return next(); 11 | } else { 12 | res.redirect('/login'); 13 | } 14 | } 15 | 16 | var clickHandler = new ClickHandler(); 17 | 18 | app.route('/') 19 | .get(isLoggedIn, function (req, res) { 20 | res.sendFile(path + '/public/index.html'); 21 | }); 22 | 23 | app.route('/login') 24 | .get(function (req, res) { 25 | res.sendFile(path + '/public/login.html'); 26 | }); 27 | 28 | app.route('/logout') 29 | .get(function (req, res) { 30 | req.logout(); 31 | res.redirect('/login'); 32 | }); 33 | 34 | app.route('/profile') 35 | .get(isLoggedIn, function (req, res) { 36 | res.sendFile(path + '/public/profile.html'); 37 | }); 38 | 39 | app.route('/api/:id') 40 | .get(isLoggedIn, function (req, res) { 41 | res.json(req.user.github); 42 | }); 43 | 44 | app.route('/auth/github') 45 | .get(passport.authenticate('github')); 46 | 47 | app.route('/auth/github/callback') 48 | .get(passport.authenticate('github', { 49 | successRedirect: '/', 50 | failureRedirect: '/login' 51 | })); 52 | 53 | app.route('/api/:id/clicks') 54 | .get(isLoggedIn, clickHandler.getClicks) 55 | .post(isLoggedIn, clickHandler.addClick) 56 | .delete(isLoggedIn, clickHandler.resetClicks); 57 | }; 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clementinejs-fcc", 3 | "version": "1.0.1", 4 | "description": "The elegant and lightweight boilerplate for full stack JavaScript.", 5 | "main": "server.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/johnstonbl01/clementinejs-fcc" 9 | }, 10 | "homepage": "http://www.clementinejs.com/", 11 | "bugs": "https://github.com/johnstonbl01/clementinejs-fcc/issues", 12 | "keywords": [ 13 | "MongoDB", 14 | "Mongoose", 15 | "full stack", 16 | "Express", 17 | "Node", 18 | "javascript", 19 | "boilerplate" 20 | ], 21 | "scripts": { 22 | "start": "node server.js" 23 | }, 24 | "author": "Blake Johnston", 25 | "license": "MIT", 26 | "dependencies": { 27 | "dotenv": "^1.2.0", 28 | "express": "^4.12.4", 29 | "express-session": "^1.11.3", 30 | "mongoose": "^4.1.0", 31 | "passport": "^0.3.0", 32 | "passport-github": "^1.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/css/main.css: -------------------------------------------------------------------------------- 1 | /********* Fonts **********/ 2 | 3 | @font-face { 4 | font-family: 'Roboto'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('Roboto'), local('Roboto-Regular'), url('../fonts/Roboto-Regular.ttf') format('truetype'); 8 | } 9 | 10 | @font-face { 11 | font-family: 'Roboto'; 12 | font-style: normal; 13 | font-weight: 500; 14 | src: local('Roboto Medium'), local('Roboto-Medium'), url('../fonts/Roboto-Medium.ttf') format('truetype'); 15 | } 16 | 17 | /****** Main Styling ******/ 18 | 19 | body { 20 | font-family: 'Roboto', sans-serif; 21 | font-size: 16px; 22 | margin: 0; 23 | padding: 0; 24 | } 25 | 26 | header { 27 | color: #00BCD4; 28 | height: 56px; 29 | margin: 0 0 14px 0; 30 | text-align: center; 31 | } 32 | 33 | p { 34 | margin: 8px 0 0 0; 35 | } 36 | 37 | .container p { 38 | text-align: center; 39 | padding: 0; 40 | } 41 | 42 | /****** Header Styling ******/ 43 | 44 | header p { 45 | margin: 16px 0 5px 0; 46 | } 47 | 48 | .menu { 49 | text-decoration: none; 50 | padding: 6px; 51 | margin: 0; 52 | color: #727272; 53 | } 54 | 55 | .menu:visited { 56 | color: #727272; 57 | } 58 | 59 | .menu:hover { 60 | color: #FFA000; 61 | } 62 | 63 | .menu:active { 64 | color: #FF630D; 65 | } 66 | 67 | header a ~ p { 68 | margin: 0; 69 | padding: 0; 70 | display: inline; 71 | color: #ECEFF1; 72 | } 73 | 74 | #display-name { 75 | font-weight: 400; 76 | } 77 | 78 | /****** Login Styling ******/ 79 | 80 | .login { 81 | margin: 86px auto 0 auto; 82 | text-align: center; 83 | } 84 | 85 | #login-btn { 86 | width: 225px; 87 | padding: 7px 5px; 88 | } 89 | 90 | .btn p { 91 | margin: 8px 0 0 0; 92 | padding: 0; 93 | } 94 | 95 | .btn > img { 96 | float: left; 97 | margin-left: 10px 98 | } 99 | 100 | /****** Logo Div Styling ******/ 101 | 102 | img { 103 | margin: 0 auto; 104 | display: block; 105 | } 106 | 107 | .clementine-text { /* Styling for the Clementine.js text */ 108 | padding: 0; 109 | margin: -25px 0 0 0; 110 | font-weight: 500; 111 | font-size: 60px; 112 | color: #FFA000; 113 | } 114 | 115 | /****** Click Styling ******/ 116 | 117 | .btn-container { /* Styling for the div that contains the buttons */ 118 | margin: -10px auto 0 auto; 119 | text-align: center; 120 | } 121 | 122 | .btn { /* Styling for buttons */ 123 | margin: 0 8px; 124 | color: white; 125 | background-color: #00BCD4; 126 | display: inline-block; 127 | border: 0; 128 | font-size: 14px; 129 | border-radius: 3px; 130 | padding: 10px 5px; 131 | width: 100px; 132 | font-weight: 500; 133 | } 134 | 135 | .btn:focus { /* Remove outline when hovering over button */ 136 | outline: none; 137 | } 138 | 139 | .btn:active { /* Scale the button down by 10% when clicking on button */ 140 | transform: scale(0.9, 0.9); 141 | -webkit-transform: scale(0.9, 0.9); 142 | -moz-transform: scale(0.9, 0.9); 143 | } 144 | 145 | .btn-delete { /* Styling for delete button */ 146 | background-color: #ECEFF1; 147 | color: #212121; 148 | } 149 | 150 | #click-nbr { 151 | font-weight: 400; 152 | } 153 | 154 | /****** Profile Styling ******/ 155 | 156 | .github-profile { 157 | width: 350px; 158 | height: 200px; 159 | border-radius: 3px; 160 | margin: 86px auto 0 auto; 161 | background-color: #EEE; 162 | text-align: center; 163 | color: #000; 164 | } 165 | 166 | .github-profile p:first-child { 167 | padding-top: 16px; 168 | } 169 | 170 | .github-profile p:nth-child(5) { 171 | margin-bottom: 16px 172 | } 173 | 174 | .github-profile p { 175 | margin: 0 0 0 16px; 176 | text-align: left; 177 | } 178 | 179 | .profile-value { 180 | font-weight: 400; 181 | } 182 | 183 | span { 184 | font-weight: 500; 185 | } 186 | 187 | .github-profile > img { 188 | padding-top: 16px; 189 | margin-bottom: 16px; 190 | } 191 | 192 | .github-profile .menu:hover { 193 | color: #FFA000; 194 | } 195 | 196 | .github-profile .menu:active { 197 | color: #FF630D; 198 | } 199 | 200 | #menu-divide { 201 | color: #FFFFFF; 202 | display: inline; 203 | margin: 0; 204 | } -------------------------------------------------------------------------------- /public/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnstonbl01/clementinejs-fcc/57f8ce2649d83dfd5a23c35f169a891b5385d15a/public/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /public/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnstonbl01/clementinejs-fcc/57f8ce2649d83dfd5a23c35f169a891b5385d15a/public/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /public/img/clementine_150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnstonbl01/clementinejs-fcc/57f8ce2649d83dfd5a23c35f169a891b5385d15a/public/img/clementine_150.png -------------------------------------------------------------------------------- /public/img/gh-mark-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnstonbl01/clementinejs-fcc/57f8ce2649d83dfd5a23c35f169a891b5385d15a/public/img/gh-mark-32px.png -------------------------------------------------------------------------------- /public/img/github_32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnstonbl01/clementinejs-fcc/57f8ce2649d83dfd5a23c35f169a891b5385d15a/public/img/github_32px.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |Welcome, !
13 | Profile 14 ||
15 | Logout 16 |Clementine.js
23 |You have clicked the button times.
27 |