Added Soft remove and a character asset parenting system
This commit is contained in:
@@ -18,4 +18,4 @@ aldeed:autoform
|
||||
conielo:autoform-polymer-paper
|
||||
msavin:mongol
|
||||
matb33:collection-hooks
|
||||
sewdn:collection-behaviours
|
||||
zimme:collection-softremovable
|
||||
|
||||
@@ -15,6 +15,7 @@ blaze-tools@1.0.3
|
||||
boilerplate-generator@1.0.3
|
||||
callback-hook@1.0.3
|
||||
check@1.0.5
|
||||
coffeescript@1.0.6
|
||||
conielo:autoform-polymer-paper@0.1.1
|
||||
dburles:collection-helpers@1.0.2
|
||||
dburles:mongo-collection-instances@0.3.3
|
||||
@@ -67,7 +68,6 @@ retry@1.0.3
|
||||
routepolicy@1.0.5
|
||||
service-configuration@1.0.4
|
||||
session@1.1.0
|
||||
sewdn:collection-behaviours@0.2.0
|
||||
sha@1.0.3
|
||||
spacebars@1.0.6
|
||||
spacebars-compiler@1.0.5
|
||||
@@ -79,3 +79,5 @@ underscore@1.0.3
|
||||
url@1.0.4
|
||||
webapp@1.2.0
|
||||
webapp-hashing@1.0.3
|
||||
zimme:collection-behaviours@1.0.3
|
||||
zimme:collection-softremovable@1.0.3
|
||||
|
||||
@@ -27,5 +27,8 @@ Schemas.Action = new SimpleSchema({
|
||||
|
||||
Actions.attachSchema(Schemas.Action);
|
||||
|
||||
Actions.attachBehaviour('softRemovable');
|
||||
makeChild(Actions);
|
||||
|
||||
Actions.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Actions.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -49,10 +49,8 @@ Schemas.Attack = new SimpleSchema({
|
||||
allowedValues: ["editable", "feature", "class", "buff", "equipment", "racial", "inate"]
|
||||
},
|
||||
//the id of the feature, buff or item that created this effect
|
||||
sourceId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
optional: true
|
||||
parent: {
|
||||
type: Schemas.Parent
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
@@ -67,5 +65,8 @@ Schemas.Attack = new SimpleSchema({
|
||||
|
||||
Attacks.attachSchema(Schemas.Attack);
|
||||
|
||||
Attacks.attachBehaviour('softRemovable');
|
||||
makeChild(Attacks); //children of lots of things
|
||||
|
||||
Attacks.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Attacks.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -20,11 +20,8 @@ Schemas.Buff = new SimpleSchema({
|
||||
|
||||
Buffs.attachSchema(Schemas.Buff);
|
||||
|
||||
Buffs.before.remove(function (userId, buff) {
|
||||
Effects.find({sourceId: buff._id, type: "buff"}).forEach(function(effect){
|
||||
Effects.remove(effect._id);
|
||||
});
|
||||
});
|
||||
Buffs.attachBehaviour('softRemovable');
|
||||
makeParent(Buffs); //parents of effects and attacks
|
||||
|
||||
Buffs.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Buffs.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -458,28 +458,3 @@ Characters.deny({
|
||||
return _.contains(fields, 'owner');
|
||||
}
|
||||
});
|
||||
|
||||
CHARACTER_SUBSCHEMA_ALLOW = {
|
||||
// the user must be logged in, and the user must be a writer of the character
|
||||
insert: function (userId, doc) {
|
||||
var char = Characters.findOne( doc.charId, { fields: {owner: 1, writers: 1} } );
|
||||
return ( userId && char.owner === userId || _.contains(char.writers, userId) );
|
||||
},
|
||||
update: function (userId, doc, fields, modifier) {
|
||||
var char = Characters.findOne( doc.charId, { fields: {owner: 1, writers: 1} } );
|
||||
return ( userId && char.owner === userId || _.contains(char.writers, userId) );
|
||||
},
|
||||
remove: function (userId, doc) {
|
||||
var char = Characters.findOne( doc.charId, { fields: {owner: 1, writers: 1} } );
|
||||
return ( userId && char.owner === userId || _.contains(char.writers, userId) );
|
||||
},
|
||||
fetch: ["charId"]
|
||||
};
|
||||
|
||||
CHARACTER_SUBSCHEMA_DENY = {
|
||||
update: function (userId, docs, fields, modifier) {
|
||||
// can't change character
|
||||
return _.contains(fields, 'charId');
|
||||
},
|
||||
fetch: ["charId"]
|
||||
};
|
||||
@@ -21,5 +21,8 @@ Schemas.Class = new SimpleSchema({
|
||||
|
||||
Classes.attachSchema(Schemas.Class);
|
||||
|
||||
Classes.attachBehaviour('softRemovable');
|
||||
makeParent(Classes); //parents of effects and attacks
|
||||
|
||||
Classes.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Classes.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -35,11 +35,9 @@ Schemas.Effect = new SimpleSchema({
|
||||
defaultValue: "editable",
|
||||
allowedValues: ["editable", "feature", "class", "buff", "equipment", "racial", "inate"]
|
||||
},
|
||||
//the id of the feature, buff or item that created this effect
|
||||
sourceId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
optional: true
|
||||
//the thing that created this effect
|
||||
parent: {
|
||||
type: Schemas.Parent
|
||||
},
|
||||
//which stat the effect is applied to
|
||||
stat: {
|
||||
@@ -91,5 +89,8 @@ Characters.after.insert(function (userId, char) {
|
||||
}
|
||||
});
|
||||
|
||||
Effects.attachBehaviour('softRemovable');
|
||||
makeChild(Effects); //children of lots of things
|
||||
|
||||
Effects.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Effects.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -21,5 +21,7 @@ Schemas.Experience = new SimpleSchema({
|
||||
|
||||
Experiences.attachSchema(Schemas.Experience);
|
||||
|
||||
Experiences.attachBehaviour('softRemovable');
|
||||
|
||||
Experiences.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Experiences.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -22,20 +22,8 @@ Features.helpers({
|
||||
}
|
||||
});
|
||||
|
||||
//Delete effects where this the removed feature is source
|
||||
Features.before.remove(function (userId, feature) {
|
||||
Effects.find({sourceId: feature._id, type: "feature"}).forEach(function(effect){
|
||||
Effects.remove(effect._id);
|
||||
});
|
||||
});
|
||||
|
||||
//keep the effects up to date with enabled state
|
||||
Features.after.update(function (userId, feature, fieldNames, modifier, options) {
|
||||
var enabled = feature.enabled !== "disabled";
|
||||
Effects.find({sourceId: feature._id, type: "feature"}).forEach(function(effect){
|
||||
Effects.update(effect._id, { $set: {charId: feature.charId, enabled: enabled, name: feature.name} });
|
||||
});
|
||||
}, {fetchPrevious: false});
|
||||
Features.attachBehaviour('softRemovable');
|
||||
makeParent(Features); //parents of effects and attacks
|
||||
|
||||
Features.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Features.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -9,5 +9,7 @@ Schemas.Note = new SimpleSchema({
|
||||
|
||||
Notes.attachSchema(Schemas.Note);
|
||||
|
||||
Notes.attachBehaviour('softRemovable');
|
||||
|
||||
Notes.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Notes.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -15,15 +15,12 @@ Schemas.Proficiency = new SimpleSchema({
|
||||
defaultValue: "editable",
|
||||
allowedValues: ["editable", "feature", "buff", "equipment", "inate"]
|
||||
},
|
||||
//the id of the feature, buff or item that created this effect
|
||||
sourceId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
optional: true
|
||||
},
|
||||
});
|
||||
|
||||
Proficiencies.attachSchema(Schemas.Proficiency);
|
||||
|
||||
Proficiencies.attachBehaviour('softRemovable');
|
||||
makeChild(Proficiencies);
|
||||
|
||||
Proficiencies.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Proficiencies.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -23,15 +23,8 @@ SpellLists.helpers({
|
||||
}
|
||||
});
|
||||
|
||||
SpellLists.before.remove(function (userId, list) {
|
||||
if(Meteor.isServer){
|
||||
Spells.remove({listId: list._id});
|
||||
} else {
|
||||
Spells.find({listId: list._id}).forEach(function(spell){
|
||||
Spells.remove(spell._id);
|
||||
});
|
||||
}
|
||||
});
|
||||
SpellLists.attachBehaviour('softRemovable');
|
||||
makeParent(SpellLists); //parents of spells
|
||||
|
||||
SpellLists.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
SpellLists.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -2,7 +2,6 @@ Spells = new Mongo.Collection("spells");
|
||||
|
||||
Schemas.Spell = new SimpleSchema({
|
||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id},
|
||||
listId: {type: String, regEx: SimpleSchema.RegEx.Id},
|
||||
prepared: {type: String, defaultValue: "unprepared", allowedValues: ["prepared","unprepared","always"]},
|
||||
name: {type: String, trim: false},
|
||||
description: {type: String, optional: true, trim: false},
|
||||
@@ -21,5 +20,8 @@ Schemas.Spell = new SimpleSchema({
|
||||
|
||||
Spells.attachSchema(Schemas.Spell);
|
||||
|
||||
Spells.attachBehaviour('softRemovable');
|
||||
makeChild(Spells); //children of spell lists
|
||||
|
||||
Spells.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Spells.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -19,4 +19,4 @@ Schemas.DeathSave = new SimpleSchema({
|
||||
type: Boolean,
|
||||
defaultValue: false
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -30,14 +30,7 @@ Containers.helpers({
|
||||
}
|
||||
});
|
||||
|
||||
Containers.before.remove(function (userId, container) {
|
||||
if(Meteor.isServer){
|
||||
Items.remove({container: container._id});
|
||||
} else {
|
||||
Items.find({container: container._id}).forEach(function(item){
|
||||
Items.remove(item._id);
|
||||
});
|
||||
}
|
||||
});
|
||||
Containers.attachBehaviour('softRemovable');
|
||||
makeParent(Containers); //parents of items
|
||||
|
||||
Containers.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
|
||||
@@ -4,7 +4,6 @@ Schemas.Item = new SimpleSchema({
|
||||
name: {type: String, defaultValue: "New Item", trim: false},
|
||||
plural: {type: String, optional: true, trim: false},
|
||||
description:{type: String, optional: true, trim: false},
|
||||
container: {type: String, regEx: SimpleSchema.RegEx.Id}, //id of container it is normally stowed in
|
||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id}, //id of owner
|
||||
quantity: {type: Number, min: 0, defaultValue: 1},
|
||||
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
|
||||
@@ -14,8 +13,8 @@ Schemas.Item = new SimpleSchema({
|
||||
defaultValue: "none",
|
||||
allowedValues: ["none", "head", "armor", "arms", "hands", "held", "feet"]
|
||||
},
|
||||
equipped: {type: Boolean, defaultValue: false},
|
||||
color: {type: String, allowedValues: _.pluck(colorOptions, "key"), defaultValue: "q"}
|
||||
enabled: {type: Boolean, defaultValue: false},
|
||||
color: {type: String, allowedValues: _.pluck(colorOptions, "key"), defaultValue: "q"}
|
||||
});
|
||||
|
||||
Items.attachSchema(Schemas.Item);
|
||||
@@ -36,24 +35,8 @@ Items.helpers({
|
||||
}
|
||||
});
|
||||
|
||||
//remove effects and attacks if their item source is removed
|
||||
Items.before.remove(function (userId, item) {
|
||||
Effects.find({sourceId: item._id, type: "equipment"}).forEach(function(effect){
|
||||
Effects.remove(effect._id);
|
||||
});
|
||||
Attacks.find({sourceId: item._id, type: "equipment"}).forEach(function(attack){
|
||||
Attacks.remove(attack._id);
|
||||
});
|
||||
});
|
||||
|
||||
//keep the effects and attacks on the correct character and enabled when equipped
|
||||
Items.after.update(function (userId, item, fieldNames, modifier, options) {
|
||||
Effects.find({sourceId: item._id, type: "equipment"}).forEach(function(effect){
|
||||
Effects.update(effect._id, { $set: {charId: item.charId, enabled: item.equipped, name: item.name} });
|
||||
});
|
||||
Attacks.find({sourceId: item._id, type: "equipment"}).forEach(function(attack){
|
||||
Attacks.update(attack._id, { $set: {charId: item.charId, enabled: item.equipped, name: item.name} });
|
||||
});
|
||||
}, {fetchPrevious: false});
|
||||
Items.attachBehaviour('softRemovable');
|
||||
makeChild(Items); //children of containers
|
||||
makeParent(Items); //parents of effects and attacks
|
||||
|
||||
Items.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
|
||||
24
rpg-docs/lib/constants/characterAssetAllowDeny.js
Normal file
24
rpg-docs/lib/constants/characterAssetAllowDeny.js
Normal file
@@ -0,0 +1,24 @@
|
||||
CHARACTER_SUBSCHEMA_ALLOW = {
|
||||
// the user must be logged in, and the user must be a writer of the character
|
||||
insert: function (userId, doc) {
|
||||
var char = Characters.findOne( doc.charId, { fields: {owner: 1, writers: 1} } );
|
||||
return ( userId && char.owner === userId || _.contains(char.writers, userId) );
|
||||
},
|
||||
update: function (userId, doc, fields, modifier) {
|
||||
var char = Characters.findOne( doc.charId, { fields: {owner: 1, writers: 1} } );
|
||||
return ( userId && char.owner === userId || _.contains(char.writers, userId) );
|
||||
},
|
||||
remove: function (userId, doc) {
|
||||
var char = Characters.findOne( doc.charId, { fields: {owner: 1, writers: 1} } );
|
||||
return ( userId && char.owner === userId || _.contains(char.writers, userId) );
|
||||
},
|
||||
fetch: ["charId"]
|
||||
};
|
||||
|
||||
CHARACTER_SUBSCHEMA_DENY = {
|
||||
update: function (userId, docs, fields, modifier) {
|
||||
// can't change character
|
||||
return _.contains(fields, 'charId');
|
||||
},
|
||||
fetch: ["charId"]
|
||||
};
|
||||
112
rpg-docs/lib/functions/parenting.js
Normal file
112
rpg-docs/lib/functions/parenting.js
Normal file
@@ -0,0 +1,112 @@
|
||||
var childSchema = new SimpleSchema({
|
||||
parent: { type: Object },
|
||||
'parent.collection': { type: String },
|
||||
'parent.id': { type: String, regEx: SimpleSchema.RegEx.Id }
|
||||
});
|
||||
|
||||
var joinWithDefaultKeys = function(keys){
|
||||
var defaultKeys = [
|
||||
'charId',
|
||||
'enabled',
|
||||
'removed',
|
||||
'removedAt',
|
||||
'removedBy',
|
||||
'restoredAt',
|
||||
'restoredBy'
|
||||
];
|
||||
return _.union(keys, defaultKeys);
|
||||
}
|
||||
|
||||
var childCollections = [];
|
||||
|
||||
makeChild = function(collection, inheritedKeys){
|
||||
collection.inheritedKeys = joinWithDefaultKeys(inheritedKeys);
|
||||
|
||||
collection.helpers({
|
||||
//returns the parent even if it's removed
|
||||
getParent: function(){
|
||||
var parentCol = Meteor.isClient?
|
||||
window[this.parent.collection] : global[this.parent.collection];
|
||||
if (parentCol)
|
||||
return parentCol.findOne(this.parent.id, {removed: true});
|
||||
},
|
||||
getParentCollection: function(){
|
||||
return Meteor.isClient?
|
||||
window[this.parent.collection] : global[this.parent.collection];
|
||||
}
|
||||
});
|
||||
|
||||
//when we change parents, inherit its properties
|
||||
collection.after.update(function (userId, doc, fieldNames, modifier, options) {
|
||||
if(modifier && modifier.$set && modifier.$set.parent){
|
||||
var parent = doc.getParent();
|
||||
if(!parent) throw new Meteor.Error('Parenting Error',
|
||||
'Document\'s parent does not exist');
|
||||
var handMeDowns = _.pick(parent, collection.inheritedKeys);
|
||||
collection.update(doc._id, {$set: handMeDowns});
|
||||
}
|
||||
});
|
||||
|
||||
collection.attachSchema(childSchema);
|
||||
|
||||
childCollections.push(collection);
|
||||
};
|
||||
|
||||
makeParent = function(collection, donatedKeys){
|
||||
collection.donatedKeys = joinWithDefaultKeys(donatedKeys);
|
||||
|
||||
//after changing, push the changes to all children
|
||||
collection.after.update(function (userId, doc, fieldNames, modifier, options) {
|
||||
if(!modifier) return;
|
||||
modifier = _.pick(modifier, ['$set', '$unset']);
|
||||
modifier.$set = _.pick(modifier.$set, donatedKeys);
|
||||
modifier.$unset = _.pick(modifier.$unset, donatedKeys);
|
||||
doc = _.pick(doc, ['_id','charId']);
|
||||
Meteor.call('updateChildren', doc, modifier);
|
||||
});
|
||||
|
||||
collection.after.remove(function (userId, doc) {
|
||||
doc = _.pick(doc, ['_id','charId']);
|
||||
Meteor.call('removeChildren', doc);
|
||||
});
|
||||
};
|
||||
|
||||
var checkPermission = function(userId, charId){
|
||||
var char = Characters.findOne( charId, { fields: {owner: 1, writers: 1} } );
|
||||
if(!char)
|
||||
throw new Meteor.Error('Access Denied',
|
||||
'Character '+charId+' does not exist');
|
||||
if (!userId)
|
||||
throw new Meteor.Error('Access Denied',
|
||||
'No UserId set when trying to update character asset.');
|
||||
if (char.owner !== userId && !_.contains(char.writers, userId))
|
||||
throw new Meteor.Error('Access Denied',
|
||||
'Not permitted to update assets of this character.');
|
||||
return true;
|
||||
};
|
||||
|
||||
Meteor.methods({
|
||||
updateChildren: function (parent, modifier) {
|
||||
check(parent, {_id: String, charId: String});
|
||||
check(modifier, Object);
|
||||
checkPermission(this.userId, parent.charId);
|
||||
|
||||
_.each(childCollections, function(collection){
|
||||
collection.update(
|
||||
{charId: parent.charId, 'parent.id': parent._id},
|
||||
modifier,
|
||||
{multi: true}
|
||||
);
|
||||
});
|
||||
},
|
||||
removeChildren: function (parent) {
|
||||
check(parent, {_id: String, charId: String});
|
||||
checkPermission(this.userId, parent.charId);
|
||||
|
||||
_.each(childCollections, function(collection){
|
||||
collection.remove(
|
||||
{charId: parent.charId, 'parent.id': parent._id}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,4 +1,10 @@
|
||||
Meteor.publish("characterList",function(userId){
|
||||
if(!userId) return;
|
||||
return Characters.find({$or: [ {readers: userId}, {writers: userId}, {owner: userId} ] });
|
||||
return Characters.find({
|
||||
$or: [
|
||||
{readers: userId},
|
||||
{writers: userId},
|
||||
{owner: userId}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
Meteor.publish("singleCharacter", function(characterId, userId){
|
||||
//TODO check if this characer can be viewed by this user
|
||||
return [
|
||||
Characters.find({_id: characterId}),
|
||||
|
||||
Actions.find({charId: characterId}),
|
||||
Attacks.find({charId: characterId}),
|
||||
Classes.find({charId: characterId}),
|
||||
Containers.find({charId: characterId}),
|
||||
Effects.find({charId: characterId}),
|
||||
Experiences.find({charId: characterId}),
|
||||
Features.find({charId: characterId}),
|
||||
Items.find({charId: characterId}),
|
||||
Notes.find({charId: characterId}),
|
||||
Spells.find({charId: characterId}),
|
||||
SpellLists.find({charId: characterId}),
|
||||
TemporaryHitPoints.find({charId: characterId}),
|
||||
];
|
||||
if(
|
||||
Characters.findOne({
|
||||
_id: characterId,
|
||||
$or: [
|
||||
{readers: userId},
|
||||
{writers: userId},
|
||||
{owner: userId}
|
||||
]
|
||||
})
|
||||
){
|
||||
return [
|
||||
Characters.find({_id: characterId}),
|
||||
//get all the assets for this character including soft deleted ones
|
||||
Actions.find ({charId: characterId}, {removed: true}),
|
||||
Attacks.find ({charId: characterId}, {removed: true}),
|
||||
Classes.find ({charId: characterId}, {removed: true}),
|
||||
Containers.find ({charId: characterId}, {removed: true}),
|
||||
Effects.find ({charId: characterId}, {removed: true}),
|
||||
Experiences.find ({charId: characterId}, {removed: true}),
|
||||
Features.find ({charId: characterId}, {removed: true}),
|
||||
Items.find ({charId: characterId}, {removed: true}),
|
||||
Notes.find ({charId: characterId}, {removed: true}),
|
||||
Spells.find ({charId: characterId}, {removed: true}),
|
||||
SpellLists.find ({charId: characterId}, {removed: true}),
|
||||
TemporaryHitPoints.find({charId: characterId}, {removed: true}),
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user