├── .gitignore ├── README.md ├── package.json ├── public ├── admin │ ├── css │ │ └── main.css │ ├── js │ │ ├── app.js │ │ ├── controllers.js │ │ └── lib │ │ │ └── markdown.js │ └── templates │ │ ├── addPost.html │ │ ├── allPosts.html │ │ └── nav.html ├── common │ └── modules │ │ └── posts.js └── home │ ├── css │ └── main.css │ ├── img │ └── icons │ │ ├── close.svg │ │ ├── close_white.svg │ │ └── menu.svg │ ├── js │ ├── app.js │ └── controllers │ │ └── main.js │ └── templates │ └── main.html ├── server.js ├── server ├── api │ └── posts.js ├── env.js ├── models │ ├── post.js │ └── user.js ├── passport.js └── routes.js └── views ├── 404.ejs ├── admin ├── dashboard.ejs ├── login.ejs └── register.ejs └── index.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Build a blog with javascript and mongodb 2 | ===== 3 | 4 | A blog made out of the javascripts. Uses mongoose, angular, express and the node.js and the mongodbs for realz 5 | 6 | 7 | #### Getting started 8 | ``` 9 | $ git clone 10 | $ npm install 11 | $ node server 12 | ``` 13 | 14 | **Tutorial part 1:** https://connorleech.ghost.io/build-a-blog-with-the-mean-stack-part-1/ 15 | 16 | **Questions?** [Hit me up on twitter](https://twitter.com/cleechtech) or better yet [create an issue](https://github.com/jasonshark/mean-blog/issues) 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ghost-clone", 3 | "version": "0.0.1", 4 | "description": "a starting point for express, angular, mongodb apps", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "keywords": [ 11 | "nodejs", 12 | "express", 13 | "angularjs", 14 | "mean", 15 | "angular", 16 | "mongodb", 17 | "mongoose" 18 | ], 19 | "author": "Connor Leech", 20 | "license": "MIT", 21 | "dependencies": { 22 | "body-parser": "^1.13.1", 23 | "cookie-parser": "^1.3.5", 24 | "cors": "^2.7.1", 25 | "ejs": "^2.3.2", 26 | "express": "^4.12.4", 27 | "express-session": "^1.11.3", 28 | "method-override": "^2.3.3", 29 | "mongoose": "^4.0.6", 30 | "passport": "^0.2.2", 31 | "passport-local": "^1.0.0", 32 | "passport-local-mongoose": "^1.0.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/admin/css/main.css: -------------------------------------------------------------------------------- 1 | html, body, section, .full-height { 2 | height: 100%; 3 | } 4 | 5 | #pad{ 6 | font-family: Menlo,Monaco,Consolas,"Courier New",monospace; 7 | 8 | border: none; 9 | overflow: auto; 10 | outline: none; 11 | resize: none; 12 | 13 | -webkit-box-shadow: none; 14 | -moz-box-shadow: none; 15 | box-shadow: none; 16 | } 17 | 18 | #markdown { 19 | overflow: auto; 20 | border-left: 1px solid black; 21 | } 22 | 23 | #main-content { 24 | margin-top:70px; 25 | } -------------------------------------------------------------------------------- /public/admin/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | var adminApp = angular.module('mean-blog.admin', [ 3 | 'ui.router', 4 | 'btford.markdown', 5 | 'mean-blog.posts' 6 | ]); 7 | 8 | adminApp.config(function($stateProvider, $urlRouterProvider){ 9 | 10 | $urlRouterProvider.otherwise('/'); 11 | 12 | $stateProvider 13 | .state('allPosts', { 14 | url: '/', 15 | templateUrl: '/admin/templates/allPosts.html', 16 | resolve: { 17 | postList: function(Posts){ 18 | return Posts.all().then(function(data){ 19 | return data; 20 | }); 21 | } 22 | }, 23 | controller: 'AllPostsCtrl' 24 | }) 25 | .state('addPost', { 26 | url: '/addPost', 27 | templateUrl: '/admin/templates/addPost.html', 28 | controller: 'AddPostCtrl' 29 | }); 30 | }); 31 | 32 | -------------------------------------------------------------------------------- /public/admin/js/controllers.js: -------------------------------------------------------------------------------- 1 | 2 | adminApp.controller('NavCtrl', function($scope, $state){ 3 | $scope.active = $state; 4 | $scope.isActive = function(viewLocation){ 5 | var active = (viewLocation === $state.current.name); 6 | return active; 7 | }; 8 | }); 9 | 10 | adminApp.controller('AllPostsCtrl', function($scope, postList){ 11 | $scope.posts = postList; 12 | $scope.activePost = false; 13 | $scope.setActive = function(post){ 14 | $scope.activePost = post; 15 | } 16 | }); 17 | 18 | adminApp.controller('AddPostCtrl', function($scope, Posts){ 19 | $scope.post = {}; 20 | $scope.addPost = function(newPost){ 21 | Posts.add(newPost).then(function(res){ 22 | console.log(res); 23 | }); 24 | }; 25 | 26 | 27 | }); -------------------------------------------------------------------------------- /public/admin/js/lib/markdown.js: -------------------------------------------------------------------------------- 1 | /* 2 | * angular-markdown-directive v0.3.1 3 | * (c) 2013-2014 Brian Ford http://briantford.com 4 | * License: MIT 5 | */ 6 | 7 | 'use strict'; 8 | 9 | angular.module('btford.markdown', ['ngSanitize']). 10 | provider('markdownConverter', function () { 11 | var opts = {}; 12 | return { 13 | config: function (newOpts) { 14 | opts = newOpts; 15 | }, 16 | $get: function () { 17 | return new showdown.Converter(opts); 18 | } 19 | }; 20 | }). 21 | directive('btfMarkdown', ['$sanitize', 'markdownConverter', function ($sanitize, markdownConverter) { 22 | return { 23 | restrict: 'AE', 24 | link: function (scope, element, attrs) { 25 | if (attrs.btfMarkdown) { 26 | scope.$watch(attrs.btfMarkdown, function (newVal) { 27 | var html = newVal ? $sanitize(markdownConverter.makeHtml(newVal)) : ''; 28 | element.html(html); 29 | }); 30 | } else { 31 | var html = $sanitize(markdownConverter.makeHtml(element.text())); 32 | element.html(html); 33 | } 34 | } 35 | }; 36 | }]); -------------------------------------------------------------------------------- /public/admin/templates/addPost.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Publish Post
4 |
5 | 6 |
7 |
8 |
9 | 10 |
11 |
12 |
13 |
14 |
-------------------------------------------------------------------------------- /public/admin/templates/allPosts.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

Published posts

5 |
    6 |
  • No posts have been published
  • 7 | 8 |
  • 12 | 13 |

    {{post.title}}

    14 |

    {{post.body.substr(0, 140)}}

    15 |
  • 16 |
17 |
18 | 19 | 20 | 21 |
22 |

{{activePost.title}}

23 |
24 |
25 |
-------------------------------------------------------------------------------- /public/admin/templates/nav.html: -------------------------------------------------------------------------------- 1 | 2 | 24 | -------------------------------------------------------------------------------- /public/common/modules/posts.js: -------------------------------------------------------------------------------- 1 | 2 | var postsModule = angular.module('mean-blog.posts', []); 3 | 4 | postsModule.service('Posts', function($http){ 5 | 6 | return { 7 | all: function(){ 8 | return $http.get('/api/posts').then(function(postList){ 9 | return postList.data; 10 | }); 11 | }, 12 | add: function(newPost){ 13 | return $http({ 14 | method: 'post', 15 | url: '/api/posts', 16 | data: newPost 17 | }).then(function(res){ 18 | // return the new post 19 | return res.data; 20 | }).catch(function(err){ 21 | console.error('Something went wrong adding the post!'); 22 | console.error(err); 23 | return err; 24 | }); 25 | }, 26 | remove: function(){ 27 | 28 | }, 29 | update: function(){ 30 | 31 | } 32 | }; 33 | }); -------------------------------------------------------------------------------- /public/home/css/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/connor11528/mean-blog/430d46eaf887e4fbc2c547cbcb2893fe571fc1ed/public/home/css/main.css -------------------------------------------------------------------------------- /public/home/img/icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/home/img/icons/close_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/home/img/icons/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/home/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | var app = angular.module('mean-blog.home', [ 3 | 'ui.router', 4 | 'ngMaterial' 5 | ]); 6 | 7 | app.config(function($stateProvider, $urlRouterProvider){ 8 | $stateProvider 9 | .state('home', { 10 | url: "/", 11 | templateUrl: "/home/templates/main.html", 12 | controller: 'MainCtrl' 13 | }); 14 | 15 | $urlRouterProvider.otherwise("/"); 16 | }); -------------------------------------------------------------------------------- /public/home/js/controllers/main.js: -------------------------------------------------------------------------------- 1 | 2 | app.controller('MainCtrl', function ($scope, $mdSidenav, $mdUtil, $log) { 3 | 4 | $scope.toggleRight = buildToggler('right'); 5 | 6 | /** 7 | * Build handler to open/close a SideNav; when animation finishes 8 | * report completion in console 9 | */ 10 | function buildToggler(navID) { 11 | var debounceFn = $mdUtil.debounce(function(){ 12 | $mdSidenav(navID) 13 | .toggle() 14 | .then(function () { 15 | $log.debug("toggle " + navID + " is done"); 16 | }); 17 | },300); 18 | 19 | return debounceFn; 20 | } 21 | 22 | }) 23 | .controller('SidenavCtrl', function ($scope, $mdSidenav, $log) { 24 | $scope.close = function () { 25 | $mdSidenav('right').close() 26 | .then(function () { 27 | $log.debug("close RIGHT is done"); 28 | }); 29 | }; 30 | }); -------------------------------------------------------------------------------- /public/home/templates/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 |

MEAN Blog

19 |
20 | 21 |
22 | 23 | 24 | 25 | 26 |
27 | 28 | 33 |
34 | 35 |
-------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 2 | var express = require('express'), 3 | mongoose = require('mongoose'), 4 | bodyParser = require('body-parser'), 5 | passport = require('passport'), 6 | cookieParser = require('cookie-parser'), 7 | methodOverride = require('method-override'), 8 | cors = require('cors'), 9 | app = express(); 10 | 11 | // ENVIRONMENT CONFIG 12 | var env = process.env.NODE_ENV = process.env.NODE_ENV || 'development', 13 | envConfig = require('./server/env')[env]; 14 | 15 | mongoose.connect(envConfig.db); 16 | 17 | // PASSPORT CONFIG 18 | require('./server/passport')(passport); 19 | 20 | // EXPRESS CONFIG 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cors()); 24 | app.use(methodOverride()); 25 | app.use(cookieParser()); 26 | app.set('view engine', 'ejs'); 27 | app.use(require('express-session')({ 28 | secret: 'keyboard cat', 29 | resave: false, 30 | saveUninitialized: false 31 | })); 32 | app.use(passport.initialize()); 33 | app.use(passport.session()); 34 | app.use(express.static(__dirname + '/public')); 35 | 36 | 37 | // ROUTES 38 | require('./server/routes')(app, passport); 39 | 40 | // Start server 41 | app.listen(envConfig.port, function(){ 42 | console.log('Server listening on port ' + envConfig.port) 43 | }); -------------------------------------------------------------------------------- /server/api/posts.js: -------------------------------------------------------------------------------- 1 | var Post = require('../models/post'); 2 | 3 | // Posts API 4 | module.exports = function(apiRouter){ 5 | 6 | // get all posts 7 | apiRouter.get('/posts', function(req, res){ 8 | Post.find({}, function(err, posts){ 9 | if (err) res.send(err); 10 | 11 | res.json(posts); 12 | }); 13 | }); 14 | 15 | // add a post 16 | apiRouter.post('/posts', function(req, res){ 17 | 18 | var post = new Post(); 19 | post.title = req.body.title; 20 | post.body = req.body.body; 21 | 22 | post.save(function(err, post){ 23 | if(err) res.send(err); 24 | 25 | res.json(post); 26 | }) 27 | }); 28 | 29 | // get a single post 30 | apiRouter.get('/posts/:id', function(req, res){ 31 | Post.findById(req.params.id, function(err, post){ 32 | if (err) res.send(err); 33 | 34 | res.json(post); 35 | }); 36 | }); 37 | 38 | // update a post 39 | apiRouter.put('/posts/:id', function(req, res){ 40 | Post.findById(req.params.id, function(err, post){ 41 | if(err) res.send(err); 42 | 43 | post.title = req.body.title; 44 | post.body = req.body.body; 45 | 46 | post.save(function(err){ 47 | if(err) res.send(err); 48 | 49 | res.json({ message: 'Post updated!' }); 50 | }) 51 | }); 52 | }); 53 | 54 | // delete a post 55 | apiRouter.delete('/posts/:id', function(req, res){ 56 | Post.remove({ 57 | _id: req.params.id 58 | }, function(err, post){ 59 | if(err) res.send(err); 60 | 61 | res.json({ message: 'Post deleted!' }); 62 | }) 63 | }); 64 | }; -------------------------------------------------------------------------------- /server/env.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | rootPath = path.normalize(__dirname + '/../../'); 3 | 4 | module.exports = { 5 | development: { 6 | rootPath: rootPath, 7 | db: 'mongodb://localhost/ghost-clone', 8 | port: process.env.PORT || 3000 9 | }, 10 | production: { 11 | rootPath: rootPath, 12 | db: process.env.MONGOLAB_URI || 'you can add a mongolab uri here ($ heroku config | grep MONGOLAB_URI)', 13 | port: process.env.PORT || 80 14 | } 15 | }; -------------------------------------------------------------------------------- /server/models/post.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | var postSchema = new mongoose.Schema({ 4 | title: { type: String, required: '{PATH} is required!'}, 5 | body: { type: String, required: '{PATH} is required!'}, 6 | created_at: { type: Date, default: Date.now }, 7 | updated_at: { type: Date, default: Date.now } 8 | }); 9 | 10 | postSchema.pre('save', function(next){ 11 | now = new Date(); 12 | this.updated_at = now; 13 | next(); 14 | }); 15 | 16 | module.exports = mongoose.model('Post', postSchema); -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'), 2 | passportLocalMongoose = require('passport-local-mongoose'); 3 | 4 | var User = new mongoose.Schema({ 5 | email: { 6 | type: String, 7 | required: '{PATH} is required!' 8 | } 9 | }); 10 | 11 | // Passport-Local-Mongoose will add a username, 12 | // hash and salt field to store the username, 13 | // the hashed password and the salt value 14 | 15 | // configure to use email for username field 16 | User.plugin(passportLocalMongoose, { usernameField: 'email' }); 17 | 18 | module.exports = mongoose.model('User', User); -------------------------------------------------------------------------------- /server/passport.js: -------------------------------------------------------------------------------- 1 | // requires the model with Passport-Local Mongoose plugged in 2 | var User = require('./models/user'), 3 | LocalStrategy = require('passport-local').Strategy; 4 | 5 | module.exports = function(passport){ 6 | // use static authenticate method of model in LocalStrategy 7 | passport.use(User.createStrategy()); 8 | 9 | // use static serialize and deserialize of model for passport session support 10 | passport.serializeUser(User.serializeUser()); 11 | passport.deserializeUser(User.deserializeUser()); 12 | }; 13 | -------------------------------------------------------------------------------- /server/routes.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | path = require('path'), 3 | User = require('./models/user'), 4 | rootPath = path.normalize(__dirname + '/../'), 5 | apiRouter = express.Router(), 6 | router = express.Router(); 7 | 8 | module.exports = function(app, passport){ 9 | app.use('/api', apiRouter); 10 | app.use('/', router); 11 | 12 | // API routes 13 | require('./api/posts')(apiRouter); 14 | 15 | // home route 16 | router.get('/', function(req, res) { 17 | res.render('index'); 18 | }); 19 | 20 | // admin route 21 | router.get('/admin', function(req, res) { 22 | res.render('admin/login'); 23 | }); 24 | 25 | router.get('/admin/register', function(req, res) { 26 | res.render('admin/register'); 27 | }); 28 | 29 | router.get('/admin/dashboard', isAdmin, function(req, res){ 30 | res.render('admin/dashboard', {user: req.user}); 31 | }); 32 | 33 | router.post('/register', function(req, res){ 34 | 35 | // passport-local-mongoose: Convenience method to register a new user instance with a given password. Checks if username is unique 36 | User.register(new User({ 37 | email: req.body.email 38 | }), req.body.password, function(err, user) { 39 | if (err) { 40 | console.error(err); 41 | return; 42 | } 43 | 44 | // log the user in after it is created 45 | passport.authenticate('local')(req, res, function(){ 46 | console.log('authenticated by passport'); 47 | res.redirect('/admin/dashboard'); 48 | }); 49 | }); 50 | }); 51 | 52 | router.post('/login', passport.authenticate('local'), function(req, res){ 53 | res.redirect('/admin/dashboard'); 54 | }); 55 | 56 | app.use(function(req, res, next){ 57 | res.status(404); 58 | 59 | res.render('404'); 60 | return; 61 | }); 62 | 63 | }; 64 | 65 | function isAdmin(req, res, next){ 66 | if(req.isAuthenticated() && req.user.email === 'connorleech@gmail.com'){ 67 | console.log('cool you are an admin, carry on your way'); 68 | next(); 69 | } else { 70 | console.log('You are not an admin'); 71 | res.redirect('/admin'); 72 | } 73 | } -------------------------------------------------------------------------------- /views/404.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 404 - Page not found 7 | 8 | 9 | 10 | 11 |
12 |

404

13 |

You are making things up

14 |
15 | 16 | -------------------------------------------------------------------------------- /views/admin/dashboard.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ghost clone - admin 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 38 | -------------------------------------------------------------------------------- /views/admin/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ghost clone - admin 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 |
18 |
19 |
20 |

Admin Login

21 |
22 | 23 | 24 | 25 | 26 |
27 | Register a new account 28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /views/admin/register.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ghost clone - admin 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 |
18 |
19 |
20 |

New user registration

21 |
22 | 23 | 24 | 25 | 26 |
27 | Login with an existing account 28 |
29 |
30 |
31 | 32 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MEAN Blog 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | --------------------------------------------------------------------------------