2014-01-26 17:46:25 -05:00
|
|
|
storage = require 'lib/storage'
|
2014-04-09 22:07:44 -04:00
|
|
|
deltasLib = require 'lib/deltas'
|
2014-04-10 16:09:44 -04:00
|
|
|
auth = require 'lib/auth'
|
2014-01-26 17:46:25 -05:00
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
class CocoModel extends Backbone.Model
|
|
|
|
idAttribute: "_id"
|
|
|
|
loaded: false
|
|
|
|
loading: false
|
2014-01-26 17:46:25 -05:00
|
|
|
saveBackups: false
|
2014-01-03 13:32:13 -05:00
|
|
|
@schema: null
|
|
|
|
|
|
|
|
initialize: ->
|
|
|
|
super()
|
|
|
|
if not @constructor.className
|
|
|
|
console.error("#{@} needs a className set.")
|
|
|
|
@markToRevert()
|
2014-04-12 13:51:02 -04:00
|
|
|
@addSchemaDefaults()
|
2014-01-26 17:46:25 -05:00
|
|
|
@once 'sync', @onLoaded, @
|
|
|
|
@saveBackup = _.debounce(@saveBackup, 500)
|
2014-02-12 15:41:41 -05:00
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
type: ->
|
|
|
|
@constructor.className
|
2014-04-13 17:48:36 -04:00
|
|
|
|
2014-04-12 00:11:52 -04:00
|
|
|
clone: (withChanges=true) ->
|
|
|
|
# Backbone does not support nested documents
|
|
|
|
clone = super()
|
|
|
|
clone.set($.extend(true, {}, if withChanges then @attributes else @_revertAttributes))
|
|
|
|
clone
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-01-26 17:46:25 -05:00
|
|
|
onLoaded: ->
|
2014-01-03 13:32:13 -05:00
|
|
|
@loaded = true
|
|
|
|
@loading = false
|
2014-04-12 13:51:02 -04:00
|
|
|
@markToRevert()
|
|
|
|
@loadFromBackup()
|
2014-02-12 15:41:41 -05:00
|
|
|
|
2014-01-26 17:46:25 -05:00
|
|
|
set: ->
|
|
|
|
res = super(arguments...)
|
2014-02-05 14:03:32 -05:00
|
|
|
@saveBackup() if @saveBackups and @loaded and @hasLocalChanges()
|
2014-01-26 17:46:25 -05:00
|
|
|
res
|
2014-02-12 15:41:41 -05:00
|
|
|
|
2014-03-23 13:29:08 -04:00
|
|
|
loadFromBackup: ->
|
|
|
|
return unless @saveBackups
|
|
|
|
existing = storage.load @id
|
|
|
|
if existing
|
|
|
|
@set(existing, {silent:true})
|
|
|
|
CocoModel.backedUp[@id] = @
|
|
|
|
|
2014-01-26 17:46:25 -05:00
|
|
|
saveBackup: ->
|
|
|
|
storage.save(@id, @attributes)
|
|
|
|
CocoModel.backedUp[@id] = @
|
2014-02-12 15:41:41 -05:00
|
|
|
|
2014-01-26 17:46:25 -05:00
|
|
|
@backedUp = {}
|
2014-01-03 13:32:13 -05:00
|
|
|
schema: -> return @constructor.schema
|
|
|
|
|
|
|
|
validate: ->
|
2014-04-12 04:35:56 -04:00
|
|
|
result = tv4.validateMultiple(@attributes, @constructor.schema? or {})
|
2014-01-03 13:32:13 -05:00
|
|
|
if result.errors?.length
|
|
|
|
console.log @, "got validate result with errors:", result
|
|
|
|
return result.errors unless result.valid
|
|
|
|
|
|
|
|
save: (attrs, options) ->
|
2014-04-17 20:30:55 -04:00
|
|
|
@set 'editPath', document.location.pathname
|
2014-01-03 13:32:13 -05:00
|
|
|
options ?= {}
|
|
|
|
success = options.success
|
|
|
|
options.success = (resp) =>
|
|
|
|
@trigger "save:success", @
|
|
|
|
success(@, resp) if success
|
|
|
|
@markToRevert()
|
2014-01-26 17:46:25 -05:00
|
|
|
@clearBackup()
|
2014-01-03 13:32:13 -05:00
|
|
|
@trigger "save", @
|
|
|
|
return super attrs, options
|
|
|
|
|
|
|
|
fetch: ->
|
2014-03-12 01:01:38 -04:00
|
|
|
res = super(arguments...)
|
2014-01-03 13:32:13 -05:00
|
|
|
@loading = true
|
2014-03-12 01:01:38 -04:00
|
|
|
res
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
markToRevert: ->
|
2014-03-23 13:00:16 -04:00
|
|
|
if @type() is 'ThangType'
|
|
|
|
@_revertAttributes = _.clone @attributes # No deep clones for these!
|
|
|
|
else
|
2014-03-21 22:21:23 -04:00
|
|
|
@_revertAttributes = $.extend(true, {}, @attributes)
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
revert: ->
|
|
|
|
@set(@_revertAttributes, {silent: true}) if @_revertAttributes
|
2014-01-26 17:46:25 -05:00
|
|
|
@clearBackup()
|
2014-02-12 15:41:41 -05:00
|
|
|
|
2014-01-26 17:46:25 -05:00
|
|
|
clearBackup: ->
|
|
|
|
storage.remove @id
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
hasLocalChanges: ->
|
|
|
|
not _.isEqual @attributes, @_revertAttributes
|
|
|
|
|
|
|
|
cloneNewMinorVersion: ->
|
|
|
|
newData = $.extend(null, {}, @attributes)
|
2014-04-12 01:33:09 -04:00
|
|
|
clone = new @constructor(newData)
|
|
|
|
clone
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
cloneNewMajorVersion: ->
|
|
|
|
clone = @cloneNewMinorVersion()
|
|
|
|
clone.unset('version')
|
|
|
|
clone
|
|
|
|
|
|
|
|
isPublished: ->
|
|
|
|
for permission in @get('permissions') or []
|
|
|
|
return true if permission.target is 'public' and permission.access is 'read'
|
|
|
|
false
|
|
|
|
|
|
|
|
publish: ->
|
|
|
|
if @isPublished() then throw new Error("Can't publish what's already-published. Can't kill what's already dead.")
|
|
|
|
@set "permissions", (@get("permissions") or []).concat({access: 'read', target: 'public'})
|
|
|
|
|
|
|
|
addSchemaDefaults: ->
|
2014-04-12 13:51:02 -04:00
|
|
|
return if @addedSchemaDefaults
|
2014-01-03 13:32:13 -05:00
|
|
|
@addedSchemaDefaults = true
|
2014-04-12 04:35:56 -04:00
|
|
|
for prop, defaultValue of @constructor.schema.default or {}
|
2014-01-03 13:32:13 -05:00
|
|
|
continue if @get(prop)?
|
|
|
|
#console.log "setting", prop, "to", defaultValue, "from attributes.default"
|
|
|
|
@set prop, defaultValue
|
2014-04-12 04:35:56 -04:00
|
|
|
for prop, sch of @constructor.schema.properties or {}
|
2014-01-03 13:32:13 -05:00
|
|
|
continue if @get(prop)?
|
2014-04-21 19:15:23 -04:00
|
|
|
continue if prop is 'emails' # hack, defaults are handled through User.coffee's email-specific methods.
|
2014-01-03 13:32:13 -05:00
|
|
|
#console.log "setting", prop, "to", sch.default, "from sch.default" if sch.default?
|
|
|
|
@set prop, sch.default if sch.default?
|
2014-03-23 13:29:08 -04:00
|
|
|
if @loaded
|
|
|
|
@markToRevert()
|
|
|
|
@loadFromBackup()
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-02-15 20:29:54 -05:00
|
|
|
getReferencedModels: (data, schema, path='/', shouldLoadProjection=null) ->
|
2014-01-03 13:32:13 -05:00
|
|
|
# returns unfetched model shells for every referenced doc in this model
|
|
|
|
# OPTIMIZE so that when loading models, it doesn't cause the site to stutter
|
|
|
|
data ?= @attributes
|
2014-04-12 04:35:56 -04:00
|
|
|
schema ?= @schema()
|
2014-01-03 13:32:13 -05:00
|
|
|
models = []
|
|
|
|
|
|
|
|
if $.isArray(data) and schema.items?
|
|
|
|
for subData, i in data
|
2014-02-15 20:29:54 -05:00
|
|
|
models = models.concat(@getReferencedModels(subData, schema.items, path+i+'/', shouldLoadProjection))
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
if $.isPlainObject(data) and schema.properties?
|
|
|
|
for key, subData of data
|
|
|
|
continue unless schema.properties[key]
|
2014-02-15 20:29:54 -05:00
|
|
|
models = models.concat(@getReferencedModels(subData, schema.properties[key], path+key+'/', shouldLoadProjection))
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-02-15 20:29:54 -05:00
|
|
|
model = CocoModel.getReferencedModel data, schema, shouldLoadProjection
|
2014-01-03 13:32:13 -05:00
|
|
|
models.push model if model
|
|
|
|
return models
|
|
|
|
|
2014-02-15 20:29:54 -05:00
|
|
|
@getReferencedModel: (data, schema, shouldLoadProjection=null) ->
|
2014-01-03 13:32:13 -05:00
|
|
|
return null unless schema.links?
|
|
|
|
linkObject = _.find schema.links, rel: "db"
|
|
|
|
return null unless linkObject
|
2014-03-11 21:30:25 -04:00
|
|
|
return null if linkObject.href.match("thang.type") and not @isObjectID(data) # Skip loading hardcoded Thang Types for now (TODO)
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
# not fully extensible, but we can worry about that later
|
|
|
|
link = linkObject.href
|
|
|
|
link = link.replace('{(original)}', data.original)
|
|
|
|
link = link.replace('{(majorVersion)}', '' + (data.majorVersion ? 0))
|
|
|
|
link = link.replace('{($)}', data)
|
2014-02-15 20:29:54 -05:00
|
|
|
@getOrMakeModelFromLink(link, shouldLoadProjection)
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-02-15 20:29:54 -05:00
|
|
|
@getOrMakeModelFromLink: (link, shouldLoadProjection=null) ->
|
2014-01-03 13:32:13 -05:00
|
|
|
makeUrlFunc = (url) -> -> url
|
|
|
|
modelUrl = link.split('/')[2]
|
|
|
|
modelModule = _.string.classify(modelUrl)
|
|
|
|
modulePath = "models/#{modelModule}"
|
|
|
|
window.loadedModels ?= {}
|
|
|
|
|
|
|
|
try
|
|
|
|
Model = require modulePath
|
|
|
|
window.loadedModels[modulePath] = Model
|
|
|
|
catch e
|
|
|
|
console.error 'could not load model from link path', link, 'using path', modulePath
|
|
|
|
return
|
|
|
|
|
|
|
|
model = new Model()
|
2014-02-15 20:29:54 -05:00
|
|
|
if shouldLoadProjection? model
|
|
|
|
sep = if link.search(/\?/) is -1 then "?" else "&"
|
|
|
|
link += sep + "project=true"
|
2014-01-03 13:32:13 -05:00
|
|
|
model.url = makeUrlFunc(link)
|
|
|
|
return model
|
|
|
|
|
|
|
|
@isObjectID: (s) ->
|
2014-04-11 23:38:34 -04:00
|
|
|
s.length is 24 and s.match(/[a-f0-9]/gi)?.length is 24
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-03-03 14:41:35 -05:00
|
|
|
hasReadAccess: (actor) ->
|
|
|
|
# actor is a User object
|
|
|
|
|
2014-04-10 16:09:44 -04:00
|
|
|
actor ?= auth.me
|
|
|
|
return true if actor.isAdmin()
|
2014-03-03 14:41:35 -05:00
|
|
|
if @get('permissions')?
|
|
|
|
for permission in @get('permissions')
|
|
|
|
if permission.target is 'public' or actor.get('_id') is permission.target
|
|
|
|
return true if permission.access in ['owner', 'read']
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
2014-03-03 15:13:02 -05:00
|
|
|
hasWriteAccess: (actor) ->
|
|
|
|
# actor is a User object
|
2014-03-03 14:41:35 -05:00
|
|
|
|
2014-04-10 16:09:44 -04:00
|
|
|
actor ?= auth.me
|
|
|
|
return true if actor.isAdmin()
|
2014-03-03 14:41:35 -05:00
|
|
|
if @get('permissions')?
|
|
|
|
for permission in @get('permissions')
|
|
|
|
if permission.target is 'public' or actor.get('_id') is permission.target
|
|
|
|
return true if permission.access in ['owner', 'write']
|
|
|
|
|
|
|
|
return false
|
2014-04-13 17:48:36 -04:00
|
|
|
|
2014-04-09 19:09:35 -04:00
|
|
|
getDelta: ->
|
2014-04-12 00:11:52 -04:00
|
|
|
differ = deltasLib.makeJSONDiffer()
|
|
|
|
differ.diff @_revertAttributes, @attributes
|
2014-04-13 17:48:36 -04:00
|
|
|
|
2014-04-12 00:11:52 -04:00
|
|
|
applyDelta: (delta) ->
|
|
|
|
newAttributes = $.extend(true, {}, @attributes)
|
|
|
|
jsondiffpatch.patch newAttributes, delta
|
|
|
|
@set newAttributes
|
2014-04-13 17:48:36 -04:00
|
|
|
|
2014-04-09 22:07:44 -04:00
|
|
|
getExpandedDelta: ->
|
|
|
|
delta = @getDelta()
|
2014-04-12 05:46:42 -04:00
|
|
|
deltasLib.expandDelta(delta, @_revertAttributes, @schema())
|
2014-04-13 17:48:36 -04:00
|
|
|
|
2014-04-15 18:09:36 -04:00
|
|
|
watch: (doWatch=true) ->
|
|
|
|
$.ajax("#{@urlRoot}/#{@id}/watch", {type:'PUT', data:{on:doWatch}})
|
2014-04-16 14:02:40 -04:00
|
|
|
@watching = -> doWatch
|
2014-04-22 14:11:08 -04:00
|
|
|
|
2014-04-15 18:09:36 -04:00
|
|
|
watching: ->
|
2014-04-16 14:02:40 -04:00
|
|
|
return me.id in (@get('watchers') or [])
|
2014-03-03 14:41:35 -05:00
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
module.exports = CocoModel
|