Skip to content
This repository was archived by the owner on Jun 5, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ node_modules
tmp
docs/api
builds

# ide
.idea
2 changes: 1 addition & 1 deletion Gruntfile.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ module.exports = (grunt) ->

shell:
mocha:
command: 'NODE_ENV=test ./node_modules/mocha/bin/mocha --compilers coffee:coffee-script/register --recursive test/server -b'
command: 'NODE_ENV=test ./node_modules/mocha/bin/mocha --compilers coffee:coffee-script/register --recursive test/server -b --timeout 5000' # 5000ms timeout to prevent timeout on older/slow? machines
cov:
command: 'NODE_ENV=test ./node_modules/mocha/bin/mocha --compilers coffee:coffee-script/register --recursive test/server --require blanket --reporter html-cov > coverage.html'
publish:
Expand Down
8 changes: 7 additions & 1 deletion client/source/controllers/buckets_controller.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ DashboardView = require 'views/buckets/dashboard'
EntriesBrowser = require 'views/entries/browser'
EntryEditView = require 'views/entries/edit'

Activity = require 'models/activity'
Activities = require 'models/activities'
Bucket = require 'models/bucket'
Buckets = require 'models/buckets'
Fields = require 'models/fields'
Expand All @@ -20,7 +22,11 @@ mediator = require('chaplin').mediator
module.exports = class BucketsController extends Controller

dashboard: ->
@view = new DashboardView
@activities = new Activities

@activities.fetch().done =>
@view = new DashboardView
collection: @activities

add: ->
@adjustTitle 'New Bucket'
Expand Down
6 changes: 6 additions & 0 deletions client/source/models/activities.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Collection = require 'lib/collection'
Activity = require 'models/activity'

module.exports = class Activities extends Collection
url: '/api/activities'
model: Activity
7 changes: 7 additions & 0 deletions client/source/models/activity.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Model = require 'lib/model'

module.exports = class Activity extends Model
urlRoot: '/api/activities'

hello: ->
'test'
30 changes: 28 additions & 2 deletions client/source/templates/buckets/dashboard.hbs
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
<div class="row">
<h1 class="page-title color-yellow">Buckets</h1>

<div class="col-md-4 col-sm-6">
<div class="col-md-12 col-sm-6">
<div class="panel">
<div class="panel-body">
<p class="lead">Buckets is an open-source CMS, built in Node.js, which is being actively developed by <a href="https://assembly.com/buckets" target="_blank">the community at Assembly</a>.</p>
<p class="lead">Buckets is an open-source CMS, built in Node.js, which is being actively developed by <a
href="https://assembly.com/buckets" target="_blank">the community at Assembly</a>.</p>
</div>
</div>
</div>

<h1 class="page-title">Recent Activity</h1>

<div class="col-md-12 col-sm-4">
<div class="activities">
{{#if activities}}
{{#each activities}}
<div class="activity">
<a class="link-with-avatar" href="/{{adminSegment}}/users/{{actor.email}}">{{gravatar actor.email_hash}} <span class="actor-name">{{actor.name}}</span></a>
{{action}} {{resource.kind}}
{{#if resource.path}}
<a href="/{{adminSegment}}{{resource.path}}">{{resource.name}}</a>
{{else}}
{{resource.name}}
{{/if}}
{{timeAgo publishDate}}
</div>
{{/each}}
{{else}}
<div class="activity">
There has been no activity yet.
</div>
{{/if}}
</div>
</div>
</div>
5 changes: 5 additions & 0 deletions client/source/views/buckets/dashboard.coffee
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
_ = require 'underscore'
PageView = require 'views/base/page'

tpl = require 'templates/buckets/dashboard'

module.exports = class DashboardView extends PageView
template: tpl

getTemplateData: ->
_.extend super,
activities: @collection.toJSON()
1 change: 1 addition & 0 deletions client/source/views/entries/browser.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Chaplin = require 'chaplin'

PageView = require 'views/base/page'
EntriesList = require 'views/entries/list'
Activity = require 'models/activity'
Entry = require 'models/entry'

EntryEditView = require 'views/entries/edit'
Expand Down
13 changes: 13 additions & 0 deletions client/style/_views.styl
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@
.handle
cursor move

// Activities

.activities
.activity
padding 15px

a.link-with-avatar:hover
text-decoration: none

span
text-decoration: underline


// Entries

.entry-publish
Expand Down
24 changes: 24 additions & 0 deletions docs/user-docs/activities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Activities

Activities allow you to keep track of the changes that have been done to your site.
The admin dashboard displays the 20 most recent activities.

Find below a list of actions that currently create activities:


## For Entries

When a user adds, updates or deletes an entry, the following activity will be created:
`[USER NAME] [added|updated|deleted] [SINGULAR BUCKET NAME] [ENTRY TITLE]`

## For Buckets

When a user adds, updates or deletes a bucket, the following activity will be created:
`[USER NAME] [added|updated|deleted] bucket [BUCKET NAME]`

## For Users

When a user adds, updates or deletes a user, the following activity will be created:
`[USER NAME] [added|updated|deleted] user [USER NAME]`


68 changes: 52 additions & 16 deletions server/models/activity.coffee
Original file line number Diff line number Diff line change
@@ -1,33 +1,69 @@
mongoose = require 'mongoose'
db = require '../lib/database'
logger = require '../lib/logger'

# Conforms, at least somewhat, to the activity stream spec outlined at
# http://activitystrea.ms/specs/json/1.0
activitySchema = new mongoose.Schema
published:
publishDate:
type: Date
default: Date.now
actor:
id:
type: mongoose.Schema.Types.ObjectId
ref: 'User'
required: true
verb:
name:
type: mongoose.Schema.Types.ObjectId
ref: 'User'
required: true
action:
type: String
enum: ['created', 'updated', 'deleted']
required: true
resource:
kind:
type: String
enum: ['post', 'update']
required: true
object:
objectType:
name:
type: String
required: true
enum: ['entry', 'bucket', 'user']
id:
entry:
type: mongoose.Schema.Types.ObjectId
required: true
ref: 'Entry'
bucket:
type: mongoose.Schema.Types.ObjectId
ref: 'Bucket'
user:
type: mongoose.Schema.Types.ObjectId
ref: 'User'
,
autoIndex: no
toJSON:
virtuals: yes
transform: (doc, ret, options) ->
delete ret._id
delete ret.__v
ret

activitySchema.virtual 'resource.path'
.get ->
if @resource.entry or @resource.bucket or @resource.user
switch @resource.kind
when 'bucket' then "/buckets/#{@resource.bucket.slug}"
when 'user' then "/users/#{@resource.user.email}"
else "/buckets/#{@resource.bucket.slug}/#{@resource.entry.id}"

activitySchema.statics.createForResource = (resource, action, actor, callback) ->
@create { resource, action, actor }, (err, activity) ->
if err
logger.error 'Error creating Activity', activity, err
else
callback(action) if callback

activitySchema.set 'toJSON', virtuals: true
activitySchema.statics.unlinkActivities = (conditions) ->
@update conditions,
{
$set:
'resource.entry': null
'resource.bucket': null
'resource.user': null
},
{ multi: true },
(err) ->
logger.error 'Error unlinking Activities', resource, err if err

module.exports = db.model 'Activity', activitySchema
8 changes: 8 additions & 0 deletions server/models/bucket.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ inflection = require 'inflection'
mongoose = require 'mongoose'
uniqueValidator = require 'mongoose-unique-validator'

Activity = require '../models/activity'
Route = require '../models/route'
db = require '../lib/database'
{Sortable} = require '../lib/mongoose-plugins'
Expand Down Expand Up @@ -114,4 +115,11 @@ bucketSchema.methods.getMembers = (callback) ->
resourceId: @_id
, callback

bucketSchema.methods.createActivity = (action, actor, callback) ->
Activity.createForResource
kind: 'bucket'
name: @name
bucket: @
, action, actor, callback

module.exports = db.model 'Bucket', bucketSchema
11 changes: 10 additions & 1 deletion server/models/entry.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ getSlug = require 'speakingurl'

db = require '../lib/database'

Activity = require '../models/activity'

# Add a parser to Chrono to understand "now"
# A bit hacky because Chrono doesn't support ms yet
chrono.parsers.NowParser = (text, ref, opt) ->
Expand Down Expand Up @@ -97,7 +99,6 @@ entrySchema.path('publishDate').set (val='') ->
parsed = chrono.parse(val)?[0]?.startDate
parsed || Date.now()


entrySchema.path('description').validate (val) ->
val?.length < 140
, 'Descriptions must be less than 140 characters.'
Expand All @@ -107,6 +108,14 @@ entrySchema.path 'keywords'
return unless _.isString val
_.compact _.map val.split(','), (val) -> val.trim()

entrySchema.methods.createActivity = (action, actor, callback) ->
Activity.createForResource
kind: @bucket.singular.toLowerCase()
name: @title
entry: @
bucket: @bucket
, action, actor, callback

entrySchema.statics.findByParams = (params, callback) ->

settings = _.defaults params,
Expand Down
8 changes: 8 additions & 0 deletions server/models/user.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ async = require 'async'
fs = require 'fs-extra'
_ = require 'underscore'
db = require '../lib/database'
Activity = require '../models/activity'

if process.env.DROPBOX_APP_KEY and process.env.DROPBOX_APP_SECRET
dbox_app = dbox.app
Expand Down Expand Up @@ -265,6 +266,13 @@ userSchema.methods.syncDropbox = (host='', reset, callback) ->
callback e, written
console.log "Saved new Dropbox cursor for User."

userSchema.methods.createActivity = (action, actor, callback) ->
Activity.createForResource
kind: 'user'
name: @name
user: @
, action, actor, callback

userSchema.virtual 'email_hash'
.get ->
crypto.createHash('md5').update(@email).digest('hex') if @email
Expand Down
23 changes: 23 additions & 0 deletions server/routes/api/activities.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
express = require 'express'

Activity = require '../../models/activity'

module.exports = app = express()


app.route('/activities')
.get (req, res) ->
return res.status(401).end() unless req.user

Activity
.find {}
.sort '-publishDate'
.limit 20
.populate 'actor resource.user', 'name email'
.populate 'resource.entry', 'id'
.populate 'resource.bucket', 'slug'
.exec (err, activities) ->
if err
res.send err, 400
else if activities
res.send activities
9 changes: 7 additions & 2 deletions server/routes/api/buckets.coffee
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
express = require 'express'

Activity = require '../../models/activity'
Bucket = require '../../models/bucket'
User = require '../../models/user'

Expand Down Expand Up @@ -159,6 +160,7 @@ app.route('/buckets')
if err
res.status(400).send err
else if bucket
bucket.createActivity 'created', req.user
res.status(200).send bucket

.get (req, res) ->
Expand Down Expand Up @@ -197,14 +199,16 @@ app.route('/buckets/:bucketID')
.delete (req, res) ->
return res.status(401).end() unless req.user?.hasRole ['administrator']

Bucket.findById req.params.bucketID, (err, bkt) ->
Bucket.findById req.params.bucketID, (err, bucket) ->
if err
res.send 400, err
else
bkt.remove (err) ->
bucket.remove (err) ->
if err
res.status(400).send err
else
bucket.createActivity 'deleted', req.user, ->
Activity.unlinkActivities { 'resource.bucket': bucket }
res.status(204).end()

.put (req, res) ->
Expand All @@ -215,6 +219,7 @@ app.route('/buckets/:bucketID')
return res.status(400).send e: err if err
bucket.set(req.body).save (err, bucket) ->
return res.status(400).send err if err
bucket.createActivity 'updated', req.user
res.status(200).send bucket

###
Expand Down
Loading