Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Big refactoring

  • Loading branch information...
commit cabf3d33c3fe7f039f28e2d725bf478228cac8bb 1 parent cdf80bd
Aldo Nievas authored

Showing 42 changed files with 1,543 additions and 184 deletions. Show diff stats Hide diff stats

  1. 1  Procfile
  2. 52  app.coffee
  3. 178  config/admin_rest.coffee
  4. 49  config/app.coffee
  5. 136  config/blogpost_rest.coffee
  6. 68  config/config.coffee
  7. 33  config/environment.coffee
  8. 26  config/errors.coffee
  9. 79  config/helpers.coffee
  10. 14  config/hooks.coffee
  11. 6  config/models.coffee
  12. 34  config/routes.coffee
  13. 12  hooks/events.coffee
  14. 5  install.sh
  15. 13  lib/exceptions.coffee
  16. 139  models/blogpost.coffee
  17. 8  models/comment.coffee
  18. 26  models/logintoken.coffee
  19. 13  models/post.coffee
  20. 57  models/user.coffee
  21. 11  package.json
  22. 23  public/css/import/_forms.styl
  23. 49  public/css/import/_globals.styl
  24. 32  public/css/import/_mixins.styl
  25. 254  public/css/main.css
  26. 62  public/css/main.styl
  27. 3  public/index.html
  28. 10  public/js/collections/blogposts.coffee
  29. 10  public/js/collections/posts.coffee
  30. 1  public/js/main.js
  31. 8  public/js/models/{post.coffee → blogpost.coffee}
  32. 27  routes/admin.coffee
  33. 37  routes/posts.coffee
  34. 17  server.js
  35. 1  views/404.jade
  36. 2  views/500.jade
  37. 11  views/admin/_post.jade
  38. 35  views/admin/home.jade
  39. 63  views/admin/index.html
  40. 20  views/admin/login.html
  41. 63  views/admin/posts/index.html
  42. 39  views/layout.html
1  Procfile
... ...
@@ -0,0 +1 @@
  1
+web: node server.js
52  app.coffee
... ...
@@ -1,52 +0,0 @@
1  
-express = require 'express'
2  
-mongoose = require 'mongoose'
3  
-require 'express-namespace'
4  
-
5  
-postsRoutes = require './routes/posts'
6  
-admin = require './routes/admin'
7  
-postModel = require './models/post'
8  
-
9  
-app = module.exports = express.createServer();
10  
-
11  
-Db = require('mongodb').Db
12  
-Server = require('mongodb').Server
13  
-server_config = new Server('localhost', 27017, {auto_reconnect: true, native_parser: true})
14  
-db = new Db('my_database', server_config, {})
15  
-mongoStore = require('connect-mongodb');
16  
-
17  
-# Mongo connection
18  
-mongoose.connect 'mongodb://localhost/my_database'
19  
-mongoose.connection.on "open", ->
20  
-  console.log "mongodb is connected!!"
21  
-
22  
-# Configuration
23  
-app.configure () ->
24  
-    app.use express.logger format: ':method :url'
25  
-    app.use express.bodyParser()
26  
-    app.use express.methodOverride()
27  
-    app.use express.cookieParser()
28  
-    app.use express.session
29  
-      secret: '076ee61d63aa10a125ea872411e433b9'
30  
-      maxAge: new Date(Date.now() + 3600000)
31  
-      store: new mongoStore { db: db }
32  
-    app.use app.router
33  
-    app.use express.static "#{__dirname}/public"
34  
-    app.set 'views', __dirname + '/views'
35  
-    app.register '.coffee', require 'coffeekup'
36  
-    app.set 'view engine', 'coffee'
37  
-    app.set 'view options', layout: false
38  
-
39  
-
40  
-app.configure 'development', () ->
41  
-  app.use express.errorHandler dumpExceptions: true, showStack: true
42  
-
43  
-app.configure 'production', () ->
44  
-  app.use express.errorHandler()
45  
-
46  
-#routes
47  
-postsRoutes(app, postModel.Post)
48  
-admin(app, postModel.Post, postModel.User)
49  
-
50  
-# Starting app
51  
-app.listen process.env.PORT || 3000
52  
-console.log "Express server listening on port #{app.address().port}"
178  config/admin_rest.coffee
... ...
@@ -0,0 +1,178 @@
  1
+#  Set all global variables
  2
+pusher = require 'pusher'
  3
+controller = {}
  4
+app = {}
  5
+db = {}
  6
+
  7
+# Constructor
  8
+
  9
+module.exports = (_app) ->
  10
+  app = _app
  11
+  db  = app.set 'db'
  12
+  return controller
  13
+
  14
+########################################################################
  15
+# Login routes
  16
+# render login form
  17
+controller.login = (req, res) ->
  18
+  User = db.main.model('User')
  19
+  res.render 'admin/login', { user: new User(), title: 'Ingresar' }
  20
+
  21
+# login user
  22
+controller.session = (req, res) ->
  23
+  User = db.main.model('User')
  24
+  User.findOne({ email: req.body.user.email }, (err, user) ->
  25
+    if (user && user.authenticate(req.body.user.password))
  26
+      req.session.user_id = user.id
  27
+
  28
+      if (req.body.remember_me)
  29
+        loginToken = new LoginToken({ email: user.email })
  30
+        loginToken.save( ->
  31
+          res.cookie('logintoken', loginToken.cookieValue, { expires: new Date(Date.now() + 2 * 604800000), path: '/' })
  32
+        )
  33
+      res.redirect '/'
  34
+    else
  35
+      req.flash 'error', 'Login failed, try again'
  36
+      res.redirect '/admin/login'
  37
+  )
  38
+
  39
+# logout user
  40
+controller.logout = (req, res) ->
  41
+  LoginToken = db.main.model('LoginToken')
  42
+  if (req.session)
  43
+    LoginToken.remove({ email: req.currentUser.email }, -> {} )
  44
+    res.clearCookie('logintoken')
  45
+    req.session.destroy(-> {})
  46
+  res.redirect '/admin/login'
  47
+
  48
+##########################################################################
  49
+# Administrative Blog routes
  50
+#
  51
+
  52
+##########################################################################
  53
+# USER
  54
+#
  55
+
  56
+# render user create form
  57
+controller.newUser  = (req, res) ->
  58
+  res.render 'users/create', { user: new User() }
  59
+
  60
+# save new user
  61
+controller.createUser = (req, res) ->
  62
+  User = db.main.model('User')  
  63
+  user = new User(req.body.user)
  64
+
  65
+  userSaveFailed = ->
  66
+    req.flash('error', 'Saving user failed')
  67
+    res.render 'users/create', { user: user }
  68
+
  69
+  user.save( (err) ->
  70
+    if (err) 
  71
+      userSaveFailed()
  72
+    req.flash('info', 'User has been saved!')
  73
+    res.redirect '/'
  74
+  )
  75
+
  76
+##########################################################################
  77
+# POSTS
  78
+#
  79
+
  80
+controller.index = (req, res, next) ->
  81
+  #  expose pusher key
  82
+  res.expose 
  83
+      app_key   : req.app.set('pusher_key') 
  84
+      channel   : 'blog_post'
  85
+      events    : 'post'
  86
+    , 'PUSHER'
  87
+
  88
+  # render template
  89
+  res.render 'home', { posts: db.posts.posts() }
  90
+
  91
+##########################################################################
  92
+# NEW POST
  93
+#
  94
+
  95
+# GET render post creation form
  96
+controller.newPost = (req, res) ->
  97
+  BlogPost = db.main.model('BlogPost')
  98
+  res.render 'blogpost/create', { post: new BlogPost() }
  99
+
  100
+##########################################################################
  101
+# CREATE POST
  102
+#
  103
+# POST save new blog post
  104
+ controller.createPost = (req, res) ->
  105
+  BlogPost = db.main.model('BlogPost')
  106
+  post = new BlogPost()
  107
+  post.title = req.body.blogpost.title
  108
+  post.rsstext = req.body.blogpost.rsstext
  109
+  post.preview = req.body.blogpost.preview
  110
+  post.body = req.body.blogpost.body
  111
+  post.created = new Date()
  112
+  post.modified = new Date()
  113
+  post.tags = req.body.blogpost.tags.split(',')
  114
+
  115
+  postCreationFailed = ->
  116
+    req.flash('error', 'Creating post failed')
  117
+    res.render 'blogpost/create', { post: post }
  118
+
  119
+  post.save (err) ->
  120
+    if (err)
  121
+      return postCreationFailed()
  122
+
  123
+    req.flash('info', 'Post created')
  124
+    res.redirect '/'
  125
+
  126
+##########################################################################
  127
+# DELETE POST
  128
+#
  129
+# delete blog post
  130
+controller.delete = (req, res, next) ->
  131
+  BlogPost = db.main.model('BlogPost')
  132
+  BlogPost.findById(req.params.id, (err, bp) ->
  133
+    if (!bp)
  134
+      return next(new NotFound('Blogpost not found'))
  135
+    else
  136
+      bp.remove (err) ->
  137
+        if (err)
  138
+          return next(new Error('Blogpost failed to remove'))
  139
+        else
  140
+          req.flash('info', 'Post was removed')
  141
+          res.redirect '/'
  142
+  )
  143
+
  144
+# render update form
  145
+controller.edit = (req, res, next) ->
  146
+  BlogPost = db.main.model('BlogPost')
  147
+  BlogPost.findById(req.params.id, (err, bp) ->
  148
+    if (!bp)
  149
+      return next(new NotFound('Blogpost not found'))
  150
+    else
  151
+      res.render 'blogpost/edit', { post: bp }
  152
+  )
  153
+
  154
+#  update blog post
  155
+controller.update = (req, res, next) ->
  156
+  BlogPost = db.main.model('BlogPost')
  157
+  BlogPost.findById(req.params.id, (err, bp) ->
  158
+    if (!bp)
  159
+      return next(new NotFound('Blogpost not found'))
  160
+    else 
  161
+      bp.title = req.body.blogpost.title
  162
+      bp.rsstext = req.body.blogpost.rsstext
  163
+      bp.preview = req.body.blogpost.preview
  164
+      bp.body = req.body.blogpost.body
  165
+      bp.tags = req.body.blogpost.tags.split(',')
  166
+      bp.modified = new Date()
  167
+
  168
+      postUpdateFailed = ->
  169
+        req.flash('error', 'Failde to update Post')
  170
+        res.render 'blogpost/edit', { post: bp }
  171
+
  172
+      bp.save (err) ->
  173
+        if (err)
  174
+          return postUpdateFailed()
  175
+
  176
+        req.flash('info', 'Post updated')
  177
+        res.redirect '/'
  178
+  )
49  config/app.coffee
... ...
@@ -0,0 +1,49 @@
  1
+# Load dependencies
  2
+express       = require 'express'
  3
+stylus        = require 'stylus'
  4
+mongoose      = require 'mongoose'
  5
+nib           = require 'nib'
  6
+models        = require './models'
  7
+config        = require './config'
  8
+routes        = require './routes'
  9
+environment   = require './environment'
  10
+errors        = require './errors'
  11
+hooks         = require './hooks'
  12
+
  13
+# util        = require 'util'
  14
+mongoStore  = require 'connect-mongodb'
  15
+helpers     = require './helpers'
  16
+
  17
+#
  18
+# Exports
  19
+#
  20
+module.exports = ->
  21
+  # Create Server
  22
+  app = express.createServer()
  23
+
  24
+  helpers(app)
  25
+  # Load Mongoose Models
  26
+
  27
+  models(app)
  28
+
  29
+  # Load Expressjs config
  30
+
  31
+  config(app)
  32
+
  33
+  # Load Environmental Settings
  34
+
  35
+  environment(app)
  36
+
  37
+  # Load routes config
  38
+
  39
+  routes(app)
  40
+
  41
+  # Load error routes + pages
  42
+
  43
+  # errors(app)
  44
+
  45
+  # Load hooks
  46
+
  47
+  hooks(app)
  48
+
  49
+  return app
136  config/blogpost_rest.coffee
... ...
@@ -0,0 +1,136 @@
  1
+#  Set all global variables
  2
+helpers = require './helpers'
  3
+controller = {}
  4
+app = {}
  5
+db = {}
  6
+
  7
+# Constructor
  8
+
  9
+module.exports = (_app) ->
  10
+  app = _app
  11
+  db  = app.set 'db'
  12
+  return controller
  13
+
  14
+
  15
+#######################################################################
  16
+# RESTful service routes
  17
+# fetch latest entries
  18
+controller.latest = (req, res) ->
  19
+  BlogPost = db.main.model('BlogPost')
  20
+  BlogPost.find({}, ['title', 'created', 'slug'], { limit: 3 })
  21
+          .sort('created', -1)
  22
+          .execFind( (err, posts) ->
  23
+            docs = []
  24
+            for post in posts
  25
+              doc = post.doc
  26
+              doc.url = post.url
  27
+              docs.push(doc)
  28
+            res.json(docs)
  29
+          )
  30
+
  31
+# rss feed route
  32
+controller.rss = (req, res, next) ->
  33
+  BlogPost = db.main.model('BlogPost')
  34
+  BlogPost.find().limit(50)
  35
+          .sort('created', -1)
  36
+          .execFind( (err, posts) ->
  37
+            if (err)
  38
+              return next(new Error('There are no Posts'))
  39
+            else
  40
+              # render rss template using posts
  41
+              res.contentType('.rss')
  42
+              res.render('xml/rss', {
  43
+                layout: false,
  44
+                selfclosetags: false,
  45
+                posts: posts
  46
+              })
  47
+            )
  48
+
  49
+##########################################################################
  50
+# Public Blog routes
  51
+# index route, load page 1 of blog
  52
+controller.posts = (req, res) ->
  53
+  BlogPost = db.main.model('BlogPost')
  54
+  BlogPost.find().limit(10)
  55
+    .sort('created', -1)
  56
+    .execFind(
  57
+      (err, posts) ->
  58
+        res.json posts
  59
+    )
  60
+
  61
+# paging route, load requested page from database
  62
+# app.get '/page/:page/', (req, res) ->
  63
+#   # find blogposts for page
  64
+#   res.render 'index', { title: 'Paged Indexpage'}
  65
+
  66
+# about route
  67
+# app.get '/about', (req, res) ->
  68
+#   res.render 'about'
  69
+
  70
+# detail route
  71
+controller.postBySlug =  (req, res, next) ->
  72
+  BlogPost = db.main.model('BlogPost')
  73
+  # parse params as integers
  74
+  dateparts = helpers.parseDateInts(req.params)
  75
+  if (!dateparts)
  76
+    return next(new NotFound('Blogpost not found for given date'))
  77
+
  78
+  whereClause = helpers.preparePostWhereclause(dateparts, req.params.slug)
  79
+  BlogPost.findOne().where(whereClause).execFind((err, post) ->
  80
+    if (!post)
  81
+      return next(new NotFound('Blogpost not found'))
  82
+    else
  83
+      res.json post
  84
+  )
  85
+
  86
+# save comment
  87
+controller.saveComment = (req, res) ->
  88
+  BlogPost = db.main.model('BlogPost')
  89
+  dateparts = helpers.parseDateInts(req.params)
  90
+  if (!dateparts)
  91
+    return next(new NotFound('Blogpost not found'))
  92
+
  93
+  whereClause = helpers.preparePostWhereclause(dateparts, req.params.slug)
  94
+  BlogPost.findOne().where(whereClause).execFind((err, post) ->
  95
+    if (!post)
  96
+      return next(new NotFound('Blogpost not found'))
  97
+    else
  98
+      # append comment
  99
+      comment =
  100
+        author: req.body.comment.author
  101
+        body: req.body.comment.body
  102
+        title: req.body.comment.title
  103
+        date: new Date()
  104
+      post.comments.$push(comment)
  105
+
  106
+      commentCreationFailed = ->
  107
+        req.flash('error', 'Comment not saved')
  108
+        # res.render 'blogpost/detail', { post: post }
  109
+
  110
+      post.save((err) ->
  111
+        if (err)
  112
+          return commentCreationFailed()
  113
+        req.flash('info', 'Thanks. Your comment has been saved!')
  114
+        res.redirect('/' + req.params.year + '/' + req.params.month + '/' + req.params.day + '/' + req.params.slug + '/')
  115
+      )
  116
+  )
  117
+
  118
+# tag search route
  119
+# app.get '/tags/:tag', (req, res) ->
  120
+#   # find blogposts matching requested tag
  121
+#   res.render 'index', { title: 'Tagsearch Indexpage' }
  122
+
  123
+# tag list route
  124
+# app.get '/tags', (req, res) ->
  125
+#   # find all tags using mapreduce
  126
+#   map = ->
  127
+#     if (!this.tags)
  128
+#       return
  129
+#     for (idx in this.tags)
  130
+#       emit(this.tags[idx], 1)
  131
+
  132
+#   reduce = (prev, curr) ->
  133
+#     count = 0
  134
+#     for (idx in curr)
  135
+#       count += curr[idx]
  136
+#     return count
68  config/config.coffee
... ...
@@ -0,0 +1,68 @@
  1
+#
  2
+#  Load dependencies
  3
+#
  4
+
  5
+express   = require 'express'
  6
+stylus    = require 'stylus'
  7
+expose    = require 'express-expose'
  8
+mongoose  = require 'mongoose'
  9
+nib       = require 'nib'
  10
+mongoStore  = require 'connect-mongodb'
  11
+mongodb     = require 'mongodb'
  12
+
  13
+# Exports
  14
+
  15
+module.exports = (app) ->
  16
+
  17
+  # Setup DB connection for Sessions Store
  18
+  Db = mongodb.Db
  19
+  Server = mongodb.Server
  20
+  server_config = new Server('localhost', 27017, {auto_reconnect: true, native_parser: true})
  21
+  db_store = new Db('satio-blog', server_config, {})
  22
+
  23
+  # Setup DB Connection
  24
+  dblink = process.env.MONGOHQ_URL || 'mongodb://localhost/satio-blog'
  25
+  db  = mongoose.createConnection dblink
  26
+
  27
+  # Compile Hack for Stylus
  28
+  #  Replaced by connect-assets
  29
+  # function compile(str, path) {
  30
+  #   return stylus(str)
  31
+  #     .set('filename', path)
  32
+  #     .include(nib.path);
  33
+  # }
  34
+
  35
+  # Configure expressjs
  36
+
  37
+  app.configure ->
  38
+    app.use express.logger('\033[90m:method\033[0m \033[36m:url\033[0m \033[90m:response-time ms\033[0m')
  39
+    app.use express.cookieParser()
  40
+    app.use express.bodyParser()
  41
+    app.use express.errorHandler({dumpException: true, showStack: true})
  42
+    app.use express.session
  43
+          secret: '076ee61d63aa10a125ea872411e433b9'
  44
+          maxAge: new Date(Date.now() + 3600000)
  45
+          store: new mongoStore { db: db_store }
  46
+    # app.use app.router
  47
+    app.register '.html', require('ejs')
  48
+    app.set 'views', __dirname + '/../views'
  49
+    app.set 'view engine', 'html'
  50
+    app.use express.static __dirname + '/../public'
  51
+      # .use(stylus.middleware(
  52
+      # {
  53
+      #   src: __dirname + '/../public',
  54
+      #   compile: compile
  55
+      # }))
  56
+
  57
+  # Save reference to database connection
  58
+  app.configure ->
  59
+    app.set 'db', {
  60
+        'main': db
  61
+        # 'posts': db.model 'BlogPost'
  62
+        # 'users': db.model 'Users'
  63
+        # 'logintoken' : db.model 'LoginToken'
  64
+    }
  65
+
  66
+    app.set 'version', '0.1.0'
  67
+
  68
+  return app
33  config/environment.coffee
... ...
@@ -0,0 +1,33 @@
  1
+Pusher   = require 'pusher'
  2
+push = {}
  3
+
  4
+module.exports = (app) ->
  5
+  port = process.env.PORT || 3000
  6
+
  7
+  app.configure 'local', ->
  8
+    # Setup pusher
  9
+    push = new Pusher
  10
+        appId  : '16815'
  11
+        appKey : '191d9e7fccb0ce60ebec'
  12
+        secret : 'ce1b66eaf7ed8faf5a02'
  13
+
  14
+    app.set('host', 'localhost')
  15
+    app.set('port', port)
  16
+    app.set('ENV','local')
  17
+
  18
+  app.configure 'production', ->
  19
+    # Setup pusher
  20
+    push = new Pusher
  21
+      appId  : 'YOUR_PUSHER_APP_ID'
  22
+      appKey : 'YOUR_PUSHER_APP_KEY'
  23
+      secret : 'YOUR_PUSHER_SECRET_KEY'
  24
+
  25
+    app.set('host', 'satio.no.de')
  26
+    app.set('port', port)
  27
+    app.set('ENV','production')
  28
+
  29
+    # Set pusher
  30
+  app.set 'pusher', { 'blog_post': push.channel('blog_post') }
  31
+  app.set 'pusher_key', push.options.appKey
  32
+
  33
+  return app
26  config/errors.coffee
... ...
@@ -0,0 +1,26 @@
  1
+module.exports = (app) ->
  2
+  NotFound = (msg) ->
  3
+    @name = 'NotFound'
  4
+    Error.call(@, msg)
  5
+    Error.captureStackTrace(@, arguments.callee)
  6
+
  7
+  NotFound.prototype.__proto__ = Error.prototype
  8
+
  9
+  # Catch all
  10
+
  11
+  # app.all '*', notFound = (req, res, next) ->
  12
+  #    throw new NotFound
  13
+
  14
+  # Load 404 page
  15
+  app.error (err, req, res, next) ->
  16
+      if (err instanceof NotFound)
  17
+          res.render('404')
  18
+      else
  19
+          next(err)
  20
+
  21
+  # Load 500 page
  22
+  app.error (err, req, res) ->
  23
+    console.log(err)
  24
+    res.render('500', { error: err })
  25
+
  26
+  return app
79  config/helpers.coffee
... ...
@@ -0,0 +1,79 @@
  1
+# Global variables
  2
+controller = {}
  3
+app = {}
  4
+db = {}
  5
+
  6
+module.exports = (_app) ->
  7
+  app = _app
  8
+  db  = app.set('db')
  9
+  return controller
  10
+
  11
+
  12
+controller.parseDateInts = (params) ->
  13
+  y = parseInt(params.year)
  14
+  m = parseInt(params.month.trimLeft('0'))
  15
+  d = parseInt(params.day.trimLeft('0'))
  16
+  if (y == NaN || m == NaN || d == NaN)
  17
+    return null
  18
+  return {
  19
+    y: y
  20
+    m: m
  21
+    d: d
  22
+  }
  23
+
  24
+controller.preparePostWhereclause = (date, slug) ->
  25
+  # // build search dates
  26
+  searchstart = new Date(Date.UTC(date.y, date.m - 1, date.d))
  27
+  searchend = new Date(Date.UTC(date.y, date.m - 1, date.d, 23, 59, 59))
  28
+  # return where clause structure
  29
+  return {
  30
+    slug: slug
  31
+    created: { $gte: searchstart }
  32
+    created: { $lte: searchend }
  33
+  }
  34
+
  35
+controller.loadUser = (req, res, next) ->
  36
+  if (app.set('disableAuthentication') == true)
  37
+    next
  38
+  else
  39
+    if (req.session.user_id)
  40
+      User = db.main.model('User')
  41
+      User.findById(req.session.user_id, (err, user) ->
  42
+        if (user)
  43
+          req.currentUser = user
  44
+          next()
  45
+        else
  46
+          res.redirect '/admin/login'
  47
+      )
  48
+    else if (req.cookies.logintoken)
  49
+      authFromLoginToken(req, res, next)
  50
+    else
  51
+      res.redirect '/admin/login'
  52
+
  53
+########################################################################
  54
+# authentication methods
  55
+controller.authFromLoginToken = (req, res, next) ->
  56
+  cookie = JSON.parse(req.cookies.logintoken)
  57
+  LoginToken  = db.main.model('LoginToken')
  58
+  User        = db.main.model('User')
  59
+  LoginToken.findOne({ email: cookie.email, token: cookie.token, series: cookie.series }, (err, token) ->
  60
+    if (!token)
  61
+      res.redirect '/admin/login'
  62
+      return
  63
+    User.findOne({ email: token.email }, (err, user) ->
  64
+      if (user)
  65
+        req.session.user_id = user.id
  66
+        req.currentUser = user
  67
+
  68
+        token.token = token.randomToken()
  69
+        token.save( ->
  70
+          res.cookie 'logintoken',
  71
+                      token.cookieValue,
  72
+                      { expires: new Date(Date.now() + 2 * 604800000), path: '/' }
  73
+        )
  74
+        next()
  75
+      else
  76
+        res.redirect '/admin/login'
  77
+    )
  78
+  )
  79
+
14  config/hooks.coffee
... ...
@@ -0,0 +1,14 @@
  1
+# /**
  2
+#  * Load hooks
  3
+#  */
  4
+
  5
+ev = require('../hooks/events')
  6
+
  7
+# /**
  8
+#  * Exports
  9
+#  */
  10
+module.exports = (app) ->
  11
+  # Event hooks
  12
+  app.on('event:create_blog_post', ev.create_blog_post)
  13
+  app.on('event:update_blog_post', ev.delete_blog_post)
  14
+  app.on('event:delete_blog_post', ev.delete_blog_post)
6  config/models.coffee
... ...
@@ -0,0 +1,6 @@
  1
+mongoose  = require 'mongoose'
  2
+
  3
+module.exports = ->
  4
+  mongoose.model 'BlogPost', require('../models/blogpost')
  5
+  mongoose.model 'User', require('../models/user')
  6
+  mongoose.model 'LoginToken', require('../models/logintoken')
34  config/routes.coffee
... ...
@@ -0,0 +1,34 @@
  1
+module.exports = (app) ->
  2
+
  3
+  blog    = require('./blogpost_rest')(app)
  4
+  admin   = require('./admin_rest')(app)
  5
+  helpers  = require('./helpers')(app)
  6
+
  7
+  # Load database and pass it down to the controllers
  8
+  db = app.set('db')
  9
+
  10
+  # Load Root
  11
+
  12
+  app.get '/admin/', blog.index
  13
+
  14
+  # Load Admin Controller + Routes
  15
+  app.get '/admin/login', admin.login
  16
+  app.post '/admin/session', admin.session
  17
+  app.get '/admin/logout', helpers.loadUser, admin.logout
  18
+  app.get '/admin/user/new', helpers.loadUser, admin.newUser
  19
+  app.post '/admin/user/create', helpers.loadUser,  admin.createUser
  20
+
  21
+  app.get  '/admin/posts', helpers.loadUser, admin.index
  22
+  app.get  '/admin/post/new', helpers.loadUser, admin.newPost
  23
+  app.post '/admin/post/create', helpers.loadUser, admin.createPost
  24
+  app.get '/admin/post/edit/:id', helpers.loadUser, admin.edit
  25
+  app.put '/admin/post/edit/:id', helpers.loadUser, admin.update
  26
+  app.del '/admin/post/:id', helpers.loadUser, admin.delete
  27
+
  28
+  # Public routes
  29
+  app.get '/:year/:month/:day/:slug', blog.postBySlug
  30
+  app.get '/posts' , blog.posts
  31
+  app.get '/posts/latest' , blog.latest
  32
+  app.get '/rss', blog.rss
  33
+  app.post '/:year/:month/:day/:slug/comment', blog.saveComment
  34
+
12  hooks/events.coffee
... ...
@@ -0,0 +1,12 @@
  1
+# // Events
  2
+exports.create_blog_post = (data, req) ->
  3
+  pusher = req.app.set('pusher')
  4
+  pusher.blog_post.trigger('post', data)
  5
+
  6
+exports.update_blog_post = (data, req) ->
  7
+  pusher = req.app.set('pusher')
  8
+  pusher.blog_post.trigger('post', data)
  9
+
  10
+exports.delete_blog_post = (data, req) ->
  11
+  pusher = req.app.set('pusher')
  12
+  pusher.blog_post.trigger('post', data)
5  install.sh
... ...
@@ -1,5 +0,0 @@
1  
-#!/bin/bash
2  
-npm install express -g
3  
-npm install mongodb --mongodb:native -g
4  
-npm install mongoose -g
5  
-npm install jade -g
13  lib/exceptions.coffee
... ...
@@ -0,0 +1,13 @@
  1
+process.on 'uncaughtException', (err) ->
  2
+  console.log "ERROR:"
  3
+  if (err.message)
  4
+    console.log '\nMessage: ' + err.message
  5
+
  6
+  if (err.stack)
  7
+    console.log '\nStacktrace:'
  8
+    console.log '===================='
  9
+    console.log err.stack
  10
+
  11
+process.addListener 'uncaughtException', (err) ->
  12
+  myError 'uncaughtException: ' + err, err
  13
+  # //process.exit(1); -- not exiting for now.
139  models/blogpost.coffee
... ...
@@ -0,0 +1,139 @@
  1
+mongoose  = require 'mongoose'
  2
+Schema   = mongoose.Schema
  3
+
  4
+#  Comment model
  5
+#  Used for persisting user comments
  6
+Comment = module.exports = new Schema
  7
+      author: String
  8
+      title: String
  9
+      date: Date
  10
+      body: String
  11
+
  12
+# register virtual members
  13
+Comment.virtual('readableday')
  14
+  .get ->
  15
+    day = @date.getDate()
  16
+    return (day < 10 ? '0' + day : day)
  17
+
  18
+Comment.virtual('readablemonth')
  19
+  .get ->
  20
+    return monthNamesShort[@date.getMonth()]
  21
+
  22
+Comment.virtual('readabletime')
  23
+  .get ->
  24
+    hour = @date.getHours()
  25
+    minute = @date.getMinutes()
  26
+    return (hour < 10 ? '0' +  hour : hour) + ':' + (minute < 10 ? '0' +  minute : minute);
  27
+
  28
+Comment.virtual('bodyParsed')
  29
+  .get ->
  30
+    return convertBasicMarkup(@body, false)
  31
+
  32
+# register validators
  33
+Comment.path('author').validate( 
  34
+    (val) ->
  35
+        val.length > 0
  36
+    'AUTHOR_MISSING' )
  37
+
  38
+Comment.path('body').validate( 
  39
+    (val) ->
  40
+        val.length > 0 
  41
+    'BODY_MISSING' )
  42
+
  43
+
  44
+# Blogpost model
  45
+# Used for persisting blog posts
  46
+
  47
+BlogPost = module.exports = new Schema
  48
+    title: String
  49
+    preview: String
  50
+    body: String
  51
+    rsstext: String
  52
+    slug: String
  53
+    created: Date
  54
+    modified: Date
  55
+    tags: [String]
  56
+    comments: [Comment]
  57
+
  58
+monthNames = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July',
  59
+                   'August', 'September', 'October', 'November', 'December' ]
  60
+monthNamesShort = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
  61
+                        'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
  62
+
  63
+# define virtual getter method for id (readable string)
  64
+BlogPost.virtual('id')
  65
+  .get ->
  66
+    return @_id.toHexString()
  67
+
  68
+BlogPost.virtual('url')
  69
+  .get ->
  70
+    # build url for current post
  71
+    year = @created.getFullYear()
  72
+    month = @created.getMonth() + 1
  73
+    day = @created.getDate()
  74
+    return '/' + year + '/' + (month < 10 ? '0' + month : month) + '/' + (day < 10 ? '0' + day : day) + '/' + @slug + '/'
  75
+
  76
+BlogPost.virtual('rfc822created')
  77
+  .get ->
  78
+    return @created.toGMTString()
  79
+
  80
+BlogPost.virtual('readabledate')
  81
+  .get ->
  82
+    year = @created.getFullYear()
  83
+    month = monthNames[@created.getMonth()]
  84
+    day = @created.getDate()
  85
+    return (day < 10 ? '0' + day : day) + '. ' + month + ' ' + year
  86
+
  87
+
  88
+BlogPost.virtual('readableday')
  89
+  .get ->
  90
+    day = @created.getDate()
  91
+    return (day < 10 ? '0' + day : day)
  92
+
  93
+
  94
+BlogPost.virtual('readablemonth')
  95
+  .get ->
  96
+    return monthNamesShort[@created.getMonth()]
  97
+
  98
+
  99
+BlogPost.virtual('previewParsed')
  100
+  .get ->
  101
+    return convertBasicMarkup(@preview, true)
  102
+
  103
+BlogPost.virtual('bodyParsed')
  104
+  .get ->
  105
+    return convertBasicMarkup(@body, true)
  106
+
  107
+# register validators
  108
+BlogPost.path('title').validate(
  109
+      (val) ->
  110
+        val.length > 0
  111
+      'TITLE_MISSING' )
  112
+
  113
+BlogPost.path('preview').validate(
  114
+      (val) ->
  115
+        val.length > 0
  116
+      'PREVIEW_MISSING' )
  117
+
  118
+BlogPost.path('rsstext').validate(
  119
+      (val) ->
  120
+        val.length > 0
  121
+      'RSSTEXT_MISSING' )
  122
+
  123
+BlogPost.path('body').validate(
  124
+      (val) ->
  125
+        val.length > 0
  126
+      'BODY_MISSING' )
  127
+
  128
+# generate a proper slug value for blogpost
  129
+slugGenerator = (options) ->
  130
+  options = options || {}
  131
+  key = options.key || 'title'
  132
+  return slugGenerator = (schema) ->
  133
+      schema.path(key)
  134
+        .set (v) ->
  135
+          @slug = v.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/\++/g, '')
  136
+          return v
  137
+
  138
+# attach slugGenerator plugin to BlogPost schema
  139
+BlogPost.plugin( slugGenerator() )
8  models/comment.coffee
... ...
@@ -1,8 +0,0 @@
1  
-mongoose 	= require 'mongoose'
2  
-Schema    = mongoose.Schema
3  
-
4  
-#models
5  
-module.exports.Comment = mongoose.model 'Comment', new Schema({
6  
-  body: String,
7  
-  date: { type: Date, default: Date.now }
8  
-}, { collection : 'posts' })
26  models/logintoken.coffee
... ...
@@ -0,0 +1,26 @@
  1
+mongoose  = require 'mongoose'
  2
+Schema   = mongoose.Schema
  3
+
  4
+# LoginToken model
  5
+# Used for persisting session tokens
  6
+
  7
+LoginToken = module.exports = new Schema
  8
+  email: { type: String, index: true }
  9
+  series: { type: String, index: true }
  10
+  token: { type: String, index: true }
  11
+
  12
+
  13
+LoginToken.virtual('id')
  14
+  .get ->
  15
+    return @_id.toHexString()
  16
+
  17
+LoginToken.virtual('cookieValue')
  18
+  .get ->
  19
+    return JSON.stringify({ email: @email, token: @token, series: @series })
  20
+
  21
+LoginToken.method 'randomToken', ->
  22
+  return Math.round((new Date().valueOf() * Math.random())) + ''
  23
+
  24
+LoginToken.pre 'save', (next) ->
  25
+  @token = @randomToken()
  26
+  @series = @randomToken()
13  models/post.coffee
... ...
@@ -1,13 +0,0 @@
1  
-mongoose 	= require 'mongoose'
2  
-Schema    = mongoose.Schema
3  
-Comment = require './comment'
4  
-
5  
-#models
6  
-module.exports.Post = mongoose.model 'Post', new Schema
7  
-  title: String
8  
-  copete: String
9  
-  body: String
10  
-  slug: { type: String, index: { unique: true }}
11  
-  comments: [Comment]
12  
-  created_at: { type: Date, default: Date.now }
13  
-, { collection : 'posts' }
57  models/user.coffee
... ...
@@ -1,23 +1,40 @@
  1
+crypto    = require 'crypto'
1 2
 mongoose  = require 'mongoose'
2 3
 Schema    = mongoose.Schema
3 4
 
4  
-module.exports.Post = mongoose.model 'User', new Schema({
5  
-  email: {
6  
-    type: Email,
7  
-    validate: [required, 'Email is required'],
8  
-    index: { unique: true }
9  
-  },
10  
-  hexdigest: {
11  
-    type: String,
12  
-    validate: [required, 'Password is required'],
13  
-    match: /[A-Za-z0-9]{12}\$[0-9a-f]{32}/
14  
-  },
15  
-  active: {
16  
-    type: Boolean,
17  
-    'default': false
18  
-  },
19  
-  createdAt: {
20  
-    type: Date,
21  
-    'default': Date.now
22  
-  }
23  
-})
  5
+
  6
+validatePresenceOf = (value) ->
  7
+  return value && value.length
  8
+
  9
+User = module.exports = new Schema
  10
+      email:  { type: String, validate: [validatePresenceOf, 'Email Address is required'], index: { unique: true } }
  11
+      name:   String
  12
+      hashed_password: String
  13
+      salt: String
  14
+
  15
+User.virtual('id')
  16
+  .get ->
  17
+    return @._id.toHexString()
  18
+
  19
+User.virtual('password')
  20
+  .set (pw) ->
  21
+    @._password = pw
  22
+    @salt = @createSalt()
  23
+    @hashed_password = @encryptPassword(pw)
  24
+  .get ->
  25
+    return @._password
  26
+
  27
+User.method 'authenticate', (plain) ->
  28
+  return @encryptPassword(plain) == @hashed_password
  29
+
  30
+User.method 'createSalt', ->
  31
+  return Math.round((new Date().valueOf() * Math.random())) + ''
  32
+
  33
+User.method 'encryptPassword', (str) ->
  34
+  return crypto.createHmac('sha1', @salt).update(str).digest('hex')
  35
+
  36
+User.pre 'save', (next) ->
  37
+  if (!validatePresenceOf(@password))
  38
+    next(new Error('Password cannot be blank'))
  39
+  else
  40
+    next
11  package.json
@@ -15,12 +15,19 @@
15 15
   	},
16 16
   "devDependencies": {
17 17
       "coffee-script": "1.2.x",
18  
-      "coffeekup" : "0.3.1",
19 18
       "express" : "2.5.x",
20 19
       "express-namespace" : "0.0.4",
  20
+      "express-expose" : "0.0.2",
  21
+      "connect-assets" : "2.1.8",
  22
+      "stylus" : "0.24.0",
  23
+      "nib" : "0.3.2",
  24
+      "bootstrap-stylus" : "0.2.1",
21 25
       "mongodb" : "0.9.x",
22 26
       "mongoose" : "2.5.x",
23  
-      "connect-mongodb" : "1.1.3"
  27
+      "connect-mongodb" : "1.1.3",
  28
+      "crypto" : "0.0.3",
  29
+      "pusher" : "0.0.2",
  30
+      "ejs" : "0.6.1"
24 31
     },
25 32
   "optionalDependencies": {}
26 33
 }
23  public/css/import/_forms.styl
... ...
@@ -0,0 +1,23 @@
  1
+// forms
  2
+
  3
+input[type='text']
  4
+  field_style()
  5
+
  6
+textarea
  7
+  field_style()
  8
+
  9
+button,
  10
+input[type='button'],
  11
+input[type='submit']
  12
+  button()
  13
+
  14
+fieldset
  15
+  border 1px solid light
  16
+  border-radius 5px
  17
+  width 350px
  18
+  shade()
  19
+
  20
+.label
  21
+  font 14px 'helvetica neue', helvetica, arial, sans-serif
  22
+  smooth()
  23
+
49  public/css/import/_globals.styl
... ...
@@ -0,0 +1,49 @@
  1
+// globals
  2
+
  3
+body
  4
+  padding 5px 40px 40px 40px
  5
+  font 14px 'helvetica neue', helvetica, arial, sans-serif
  6
+  background url(/images/background.png) 
  7
+  smooth()
  8
+
  9
+.hide
  10
+  display: none
  11
+
  12
+.show
  13
+  display: block
  14
+  
  15
+#header
  16
+  roundedges(white, darkGray)
  17
+  padding 20px
  18
+  width 760px
  19
+  font 18px 'helvetica neue', helvetica, arial, sans-serif
  20
+  color light
  21
+  smooth()
  22
+  shade()
  23
+
  24
+#header a
  25
+  color light
  26
+
  27
+.error 
  28
+  background  #c85269 
  29
+  border  1px solid #9a3c4e 
  30
+  -webkit-border-radius  5px 
  31
+  -moz-border-radius  5px 
  32
+  border-radius  5px 
  33
+  padding  10px 
  34
+  color  #fffeff 
  35
+  font-size  22px
  36
+
  37
+// wrapper
  38
+
  39
+.wrap
  40
+  width 800px
  41
+
  42
+.left
  43
+  float left
  44
+  width 400px
  45
+
  46
+.right
  47
+  float right
  48
+  width 390px
  49
+
32  public/css/import/_mixins.styl
... ...
@@ -0,0 +1,32 @@
  1
+// mixins
  2
+
  3
+field_style()
  4
+  margin: 5px 0
  5
+  padding: 8px 10px
  6
+  outline: none
  7
+  border-radius: 5px
  8
+  border: 1px solid light
  9
+  color: light
  10
+  &:focus
  11
+    box-shadow: 0 1px 1px 0 rgba(blue, 0.6), 0 0 3px 1px rgba(blue, 0.3)
  12
+
  13
+button()
  14
+  bold-button(glow: blue)
  15
+
  16
+smooth()
  17
+  -webkit-font-smoothing: antialiased
  18
+
  19
+borderfy()
  20
+  border: 1px solid lighter
  21
+
  22
+roundedges(bg_color, border_color)
  23
+  if bg_color
  24
+    background-color  bg_color
  25
+  if border_color
  26
+    border  1px solid border_color
  27
+  -webkit-border-radius  5px 
  28
+  -moz-border-radius  5px 
  29
+  border-radius  5px
  30
+
  31
+shade()
  32
+  box-shadow: 0 1px 1px 0 rgba(light, 0.6), 0 0 3px 1px rgba(light, 0.1)
254  public/css/main.css
... ...
@@ -0,0 +1,254 @@
  1
+body {
  2
+  padding: 5px 40px 40px 40px;
  3
+  font: 14px 'helvetica neue', helvetica, arial, sans-serif;
  4
+  background: url("/images/background.png");
  5
+  -webkit-font-smoothing: antialiased;
  6
+}
  7
+.hide {
  8
+  display: none;
  9
+}
  10
+.show {
  11
+  display: block;
  12
+}
  13
+#header {
  14
+  background-color: #fff;
  15
+  border: 1px solid darkGray;
  16
+  -webkit-border-radius: 5px;
  17
+  -moz-border-radius: 5px;
  18
+  -webkit-border-radius: 5px;
  19
+  -moz-border-radius: 5px;
  20
+  border-radius: 5px;
  21
+  padding: 20px;
  22
+  width: 760px;
  23
+  font: 18px 'helvetica neue', helvetica, arial, sans-serif;
  24
+  color: #a9a9a9;
  25
+  -webkit-font-smoothing: antialiased;
  26
+  -webkit-box-shadow: 0 1px 1px 0 rgba(169,169,169,0.60), 0 0 3px 1px rgba(169,169,169,0.10);
  27
+  -moz-box-shadow: 0 1px 1px 0 rgba(169,169,169,0.60), 0 0 3px 1px rgba(169,169,169,0.10);
  28
+  box-shadow: 0 1px 1px 0 rgba(169,169,169,0.60), 0 0 3px 1px rgba(169,169,169,0.10);
  29
+}
  30
+#header a {
  31
+  color: #a9a9a9;
  32
+}
  33
+.error {
  34
+  background: #c85269;
  35
+  border: 1px solid #9a3c4e;
  36
+  -webkit-border-radius: 5px;
  37
+  -moz-border-radius: 5px;
  38
+  -webkit-border-radius: 5px;
  39
+  -moz-border-radius: 5px;
  40
+  border-radius: 5px;
  41
+  padding: 10px;
  42
+  color: #fffeff;
  43
+  font-size: 22px;
  44
+}
  45
+.wrap {
  46
+  width: 800px;
  47
+}
  48
+.left {
  49
+  float: left;
  50
+  width: 400px;
  51
+}
  52
+.right {
  53
+  float: right;
  54
+  width: 390px;
  55
+}
  56
+input[type='text'] {