Skip to content

Commit

Permalink
Merge pull request #673 from peerlibrary/middleware
Browse files Browse the repository at this point in the history
Using publish middleware
  • Loading branch information
mitar committed Sep 28, 2014
2 parents b2b46ed + e9c816b commit 9f66481
Show file tree
Hide file tree
Showing 25 changed files with 663 additions and 129 deletions.
1 change: 1 addition & 0 deletions .meteor/packages
Expand Up @@ -48,3 +48,4 @@ directcollection
jobCollection
async
srp
middleware
2 changes: 0 additions & 2 deletions client/library.coffee
Expand Up @@ -4,8 +4,6 @@ Deps.autorun ->
Meteor.subscribe 'my-person-library'

Meteor.subscribe 'my-publications'
# So that users can see their own filename of the imported file, before a publication has metadata
Meteor.subscribe 'my-publications-importing'

Meteor.subscribe 'my-collections'

Expand Down
16 changes: 8 additions & 8 deletions client/publication.coffee
Expand Up @@ -6,7 +6,7 @@ SCALE = 1.25
draggingViewport = false
currentPublication = null
publicationHandle = null
publicationCacheHandle = null
publicationCachedIdHandle = null
myGroupsHandle = null

# We use our own reactive variable for publicationDOMReady and not Session to
Expand All @@ -16,7 +16,7 @@ myGroupsHandle = null
# a reactive value so that we can combine code logic easy.
publicationDOMReady = new Variable false

# Mostly used just to force reevaluation of publicationHandle and publicationCacheHandle
# Mostly used just to force reevaluation of publicationHandle and publicationCachedIdHandle
publicationSubscribing = new Variable false

# To be able to limit shown annotations only to those with highlights in the current viewport
Expand Down Expand Up @@ -547,7 +547,7 @@ Deps.autorun ->
if Session.get 'currentPublicationId'
publicationSubscribing.set true
publicationHandle = Meteor.subscribe 'publication-by-id', Session.get 'currentPublicationId'
publicationCacheHandle = Meteor.subscribe 'publications-cached-by-id', Session.get 'currentPublicationId'
publicationCachedIdHandle = Meteor.subscribe 'publication-cachedid-by-id', Session.get 'currentPublicationId'
Meteor.subscribe 'highlights-by-publication', Session.get 'currentPublicationId'
Meteor.subscribe 'annotations-by-publication', Session.get 'currentPublicationId'
Meteor.subscribe 'comments-by-publication', Session.get 'currentPublicationId'
Expand All @@ -557,11 +557,11 @@ Deps.autorun ->
else
publicationSubscribing.set false
publicationHandle = null
publicationCacheHandle = null
publicationCachedIdHandle = null
myGroupsHandle = null

Deps.autorun ->
if publicationSubscribing() and publicationHandle?.ready() and publicationCacheHandle?.ready()
if publicationSubscribing() and publicationHandle?.ready() and publicationCachedIdHandle?.ready()
publicationSubscribing.set false

Deps.autorun ->
Expand Down Expand Up @@ -662,11 +662,11 @@ Deps.autorun ->

Template.publication.loading = ->
publicationSubscribing() # To register dependency
not publicationHandle?.ready() or not publicationCacheHandle?.ready()
not publicationHandle?.ready() or not publicationCachedIdHandle?.ready()

Template.publication.notFound = ->
publicationSubscribing() # To register dependency
publicationHandle?.ready() and publicationCacheHandle?.ready() and not Publication.documents.findOne Session.get('currentPublicationId'), fields: _id: 1
publicationHandle?.ready() and publicationCachedIdHandle?.ready() and not Publication.documents.findOne Session.get('currentPublicationId'), fields: _id: 1

Template.publication.publication = ->
Publication.documents.findOne Session.get 'currentPublicationId'
Expand Down Expand Up @@ -862,7 +862,7 @@ Template.publicationLibraryMenuCollectionListing.countDescription = Template.col

Template.publicationDisplay.cachedAvailable = ->
publicationSubscribing() # To register dependency
publicationHandle?.ready() and publicationCacheHandle?.ready() and Publication.documents.findOne(Session.get('currentPublicationId'), fields: cachedId: 1)?.cachedId
publicationHandle?.ready() and publicationCachedIdHandle?.ready() and Publication.documents.findOne(Session.get('currentPublicationId'), fields: cachedId: 1)?.cachedId

Template.publicationDisplay.created = ->
@_displayHandle = null
Expand Down
5 changes: 4 additions & 1 deletion client/publications.coffee
Expand Up @@ -53,7 +53,10 @@ Template.publicationCatalogItem.annotationsCountDescription = ->
Annotation.verboseNameWithCount @annotationsCount

Template.publicationCatalogItem.hasAbstract = ->
@hasAbstract or @abstract
@abstract ? @hasAbstract

Template.publicationCatalogItem.hasCachedId = ->
@cachedId ? @hasCachedId

Template.publicationCatalogItem.open = ->
@access is Publication.ACCESS.OPEN
Expand Down
19 changes: 19 additions & 0 deletions packages/middleware/package.js
@@ -0,0 +1,19 @@
Package.describe({
summary: "Meteor publish middleware support"
});

Package.on_use(function (api) {
api.use(['coffeescript', 'underscore'], 'server');

api.export('PublishEndpoint');
api.export('PublishMiddleware');

api.add_files([
'server.coffee'
], 'server');
});

Package.on_test(function (api) {
api.use(['middleware', 'tinytest', 'test-helpers', 'coffeescript', 'insecure', 'random', 'assert', 'underscore'], ['client', 'server']);
api.add_files('tests.coffee', ['client', 'server']);
});
135 changes: 135 additions & 0 deletions packages/middleware/server.coffee
@@ -0,0 +1,135 @@
Fiber = Npm.require 'fibers'
savedYield = Fiber.yield

globals = @

# When inside Meteor._noYieldsAllowed Fiber.yield is overridden with
# a function which throws an exception, so is not savedYield anymore.
# Afterwards Fiber.yield is restored back to savedYield.
isInsideNoYieldsAllowed = ->
Fiber.yield isnt savedYield

class MiddlewarePublish
constructor: (@publish) ->
# We store those methods at construction time because
# we override them later on publish object.
@_publishAdded = @publish.added.bind @publish
@_publishChanged = @publish.changed.bind @publish
@_publishRemoved = @publish.removed.bind @publish
@_publishReady = @publish.ready.bind @publish
@_publishStop = @publish.stop.bind @publish
@_publishError = @publish.error.bind @publish

# We copy other fields as they are
for own key, value of @publish when key not in ['added', 'changed', 'removed', 'ready', 'stop', 'error']
@[key] = value

added: (args...) =>
@_publishAdded args...

changed: (args...) =>
@_publishChanged args...

removed: (args...) =>
@_publishRemoved args...

ready: (args...) =>
@_publishReady args...

stop: (args...) =>
@_publishStop args...

error: (args...) =>
@_publishError args...

class globals.PublishEndpoint
constructor: (@options, @publishFunction) ->
# To pass null (autopublish) or string directly for name
if @options is null or _.isString @options
@options =
name: @options

@middlewares = []

self = @

Meteor.publish @options.name, ->
publish = @

state = {}

publish.params = ->
@_params

publish.set = (key, value) ->
state[key] = value

publish.get = (key) ->
state[key]

self.publish self.middlewares, publish

publish: (middlewares, publish) =>
if middlewares.length
latestMiddleware = middlewares[middlewares.length - 1]
otherMiddlewares = middlewares[0...middlewares.length - 1]

midlewarePublish = new MiddlewarePublish publish

publish.added = (collection, id, fields) ->
latestMiddleware.added midlewarePublish, collection, id, fields

publish.changed = (collection, id, fields) ->
latestMiddleware.changed midlewarePublish, collection, id, fields

publishRemoved = publish.removed
publish.removed = (collection, id) ->
# When unsubscribing, Meteor removes all documents so this callback is called
# inside Meteor._noYieldsAllowed which means inside the callback no function
# which calls yield can be called. Because this is often not true, in that
# special case we are not going through middlewares but are directly calling
# original removed callback.
if isInsideNoYieldsAllowed()
publishRemoved.call publish, collection, id
else
latestMiddleware.removed midlewarePublish, collection, id

publish.ready = ->
latestMiddleware.onReady midlewarePublish

publish.stop = ->
latestMiddleware.onStop midlewarePublish

publish.error = (error) ->
latestMiddleware.onError midlewarePublish, error

@publish otherMiddlewares, publish
else
@publishFunction.apply publish, publish.params()

use: (middleware) =>
throw new Error "Middleware '#{ middleware }' is not an instance of a PublishMiddleware class" unless middleware instanceof PublishMiddleware

@middlewares.push middleware

class globals.PublishMiddleware
added: (publish, collection, id, fields) =>
publish.added collection, id, fields

changed: (publish, collection, id, fields) =>
publish.changed collection, id, fields

removed: (publish, collection, id) =>
publish.removed collection, id

onReady: (publish) =>
publish.ready()

onStop: (publish) =>
publish.stop()

onError: (publish, error) =>
publish.error error

PublishEndpoint = globals.PublishEndpoint
PublishMiddleware = globals.PublishMiddleware

0 comments on commit 9f66481

Please sign in to comment.