diff --git a/app/imports/api/creature/Creatures.js b/app/imports/api/creature/Creatures.js
index 8ca02be9..27ce6859 100644
--- a/app/imports/api/creature/Creatures.js
+++ b/app/imports/api/creature/Creatures.js
@@ -4,6 +4,7 @@ import deathSaveSchema from '/imports/api/properties/subSchemas/DeathSavesSchema
import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js';
import SharingSchema from '/imports/api/sharing/SharingSchema.js';
import {assertEditPermission} from '/imports/api/sharing/sharingPermissions.js';
+import { getUserTier } from '/imports/api/users/patreon/tiers.js';
import '/imports/api/creature/removeCreature.js';
@@ -108,6 +109,11 @@ const insertCreature = new ValidatedMethod({
throw new Meteor.Error('Creatures.methods.insert.denied',
'You need to be logged in to insert a creature');
}
+ let tier = getUserTier(this.userId);
+ if (!tier.paidBenefits){
+ throw new Meteor.Error('Creatures.methods.insert.denied',
+ `The ${tier.name} tier does not allow you to insert a creature`);
+ }
// Create the creature document
let charId = Creatures.insert({
diff --git a/app/imports/api/library/Libraries.js b/app/imports/api/library/Libraries.js
index 5be82d49..dbbf013c 100644
--- a/app/imports/api/library/Libraries.js
+++ b/app/imports/api/library/Libraries.js
@@ -1,8 +1,10 @@
+import { ValidatedMethod } from 'meteor/mdg:validated-method';
import SimpleSchema from 'simpl-schema';
import SharingSchema from '/imports/api/sharing/SharingSchema.js';
import simpleSchemaMixin from '/imports/api/creature/mixins/simpleSchemaMixin.js';
import { assertEditPermission, assertOwnership } from '/imports/api/sharing/sharingPermissions.js';
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
+import { getUserTier } from '/imports/api/users/patreon/tiers.js'
/**
* Libraries are trees of library nodes where each node represents a character
@@ -38,6 +40,15 @@ const insertLibrary = new ValidatedMethod({
],
schema: LibrarySchema.omit('owner', 'isDefault'),
run(library) {
+ if (!this.userId) {
+ throw new Meteor.Error('Libraries.methods.insert.denied',
+ 'You need to be logged in to insert a library');
+ }
+ let tier = getUserTier(this.userId);
+ if (!tier.paidBenefits){
+ throw new Meteor.Error('Libraries.methods.insert.denied',
+ `The ${tier.name} tier does not allow you to insert a library`);
+ }
library.owner = this.userId;
return Libraries.insert(library);
},
diff --git a/app/imports/api/sharing/sharingPermissions.js b/app/imports/api/sharing/sharingPermissions.js
index c65596e6..00deca12 100644
--- a/app/imports/api/sharing/sharingPermissions.js
+++ b/app/imports/api/sharing/sharingPermissions.js
@@ -1,17 +1,18 @@
import { _ } from 'meteor/underscore';
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
+import { getUserTier } from '/imports/api/users/patreon/tiers.js';
function assertIdValid(userId){
if (!userId || typeof userId !== 'string'){
- throw new Meteor.Error("Permission denied",
- "No user ID given for edit permission check");
+ throw new Meteor.Error('Permission denied',
+ 'No user ID given for edit permission check');
}
}
function assertdocExists(doc){
if (!doc){
- throw new Meteor.Error("Permission denied",
- `No such document exists`);
+ throw new Meteor.Error('Permission denied',
+ 'No such document exists');
}
}
@@ -21,8 +22,8 @@ export function assertOwnership(doc, userId){
if (doc.owner === userId ){
return true;
} else {
- throw new Meteor.Error("Permission denied",
- `You are not the owner of this document`);
+ throw new Meteor.Error('Permission denied',
+ 'You are not the owner of this document');
}
}
@@ -35,11 +36,34 @@ export function assertOwnership(doc, userId){
export function assertEditPermission(doc, userId) {
assertIdValid(userId);
assertdocExists(doc);
- if (doc.owner === userId || _.contains(doc.writers, userId)){
+ let user = Meteor.users.findOne(userId, {
+ fields: {
+ 'services.patreon': 1,
+ 'roles': 1,
+ }
+ });
+
+ // Admin override
+ if (user.roles && user.roles.includes('admin')){
+ return true;
+ }
+
+ // Ensure the user is of a tier with paid benefits
+ let tier = getUserTier(user);
+ if (!tier.paidBenefits){
+ throw new Meteor.Error('Edit permission denied',
+ `The ${tier.name} tier does not allow you to edit this document`);
+ }
+
+ // Ensure the user is authorized for this specific document
+ if (
+ doc.owner === userId ||
+ _.contains(doc.writers, userId)
+ ){
return true;
} else {
- throw new Meteor.Error("Edit permission denied",
- `You do not have permission to edit this document`);
+ throw new Meteor.Error('Edit permission denied',
+ 'You do not have permission to edit this document');
}
}
@@ -74,8 +98,8 @@ export function assertViewPermission(doc, userId) {
){
return true;
} else {
- throw new Meteor.Error("View permission denied",
- `You do not have permission to view this character`);
+ throw new Meteor.Error('View permission denied',
+ 'You do not have permission to view this character');
}
}
diff --git a/app/imports/api/users/patreon/tiers.js b/app/imports/api/users/patreon/tiers.js
index 3b58984a..ccb3df1c 100644
--- a/app/imports/api/users/patreon/tiers.js
+++ b/app/imports/api/users/patreon/tiers.js
@@ -59,7 +59,11 @@ export function getTierByEntitledCents(entitledCents = 0){
export function getUserTier(user){
if (!user) throw 'user must be provided';
if (typeof user === 'string'){
- user = Meteor.users.findOne(user);
+ user = Meteor.users.findOne(user, {
+ fields: {
+ 'services.patreon': 1,
+ }
+ });
if (!user) throw 'User not found';
}
const entitledCents = getEntitledCents(user);
diff --git a/app/imports/ui/creature/character/CharacterSheetToolbarItems.vue b/app/imports/ui/creature/character/CharacterSheetToolbarItems.vue
index 979a1d22..99e7357f 100644
--- a/app/imports/ui/creature/character/CharacterSheetToolbarItems.vue
+++ b/app/imports/ui/creature/character/CharacterSheetToolbarItems.vue
@@ -1,6 +1,7 @@
-
-
-
-
- {{library.name}}
-
-
- $emit('selected', e)"
- :selected-node-id="selectedNodeId"
- />
-
-
- add
- New property
-
-
-
- create
-
-
-
-
-
-
- add
- New library
-
-
+
+
+
+
+
+ {{ library.name }}
+
+
+
+ $emit('selected', e)"
+ />
+
+
+ add
+ New property
+
+
+
+ create
+
+
+
+
+
+
+ add
+ New library
+
+
diff --git a/app/imports/ui/pages/CharacterList.vue b/app/imports/ui/pages/CharacterList.vue
index ef6af7ca..33b63365 100644
--- a/app/imports/ui/pages/CharacterList.vue
+++ b/app/imports/ui/pages/CharacterList.vue
@@ -53,6 +53,18 @@
+
+ add
+
+
@@ -86,6 +100,7 @@
import Creatures, {insertCreature} from '/imports/api/creature/Creatures.js';
import Parties from '/imports/api/campaign/Parties.js';
import LabeledFab from '/imports/ui/components/LabeledFab.vue';
+ import { getUserTier } from '/imports/api/users/patreon/tiers.js';
const characterTransform = function(char){
char.url = `/character/${char._id}/${char.urlName || '-'}`;
@@ -136,26 +151,21 @@
},
methods: {
insertCharacter(){
- insertCreature.call((error, result) => {
- if (error){
- console.error(error);
- } else {
- this.$router.push({ path: `/character/${result}`})
- }
- });
-
- /*
- store.commit("pushDialogStack", {
- component: CharacterCreationDialog,
- data: {},
- element: undefined,
- returnElement: undefined,
- callback(result){
- if (!result) return;
- insertCreature.call(result);
- },
- });
- */
+ let tier = getUserTier(Meteor.userId());
+ if (tier.paidBenefits){
+ insertCreature.call((error, result) => {
+ if (error){
+ console.error(error);
+ } else {
+ this.$router.push({ path: `/character/${result}`})
+ }
+ });
+ } else {
+ this.$store.commit('pushDialogStack', {
+ component: 'tier-too-low-dialog',
+ elementId: 'new-character-button',
+ });
+ }
},
}
};
diff --git a/app/imports/ui/pages/PatreonLevelTooLow.vue b/app/imports/ui/pages/PatreonLevelTooLow.vue
index ef0de5c2..b81026dc 100644
--- a/app/imports/ui/pages/PatreonLevelTooLow.vue
+++ b/app/imports/ui/pages/PatreonLevelTooLow.vue
@@ -12,6 +12,12 @@
You need to be at least Adventurer tier (or be invited by a Patron of
a higher tier) to access this beta
+
+ Join now
+
diff --git a/app/imports/ui/properties/forms/ResourcesForm.vue b/app/imports/ui/properties/forms/ResourcesForm.vue
index f82b5450..60337f0e 100644
--- a/app/imports/ui/properties/forms/ResourcesForm.vue
+++ b/app/imports/ui/properties/forms/ResourcesForm.vue
@@ -28,7 +28,7 @@
{
- if (userSubscription.ready()){
- computation.stop();
- const user = Meteor.user();
- if (!user){
- next({ name: 'signIn', query: { redirect: to.path} });
- return;
- }
- let tier = getUserTier(user);
- if (tier && tier.paidBenefits){
- next();
- } else {
- next('/patreon-level-too-low');
- }
- }
- });
-}
-
function claimInvite(to, from, next){
Tracker.autorun((computation) => {
if (userSubscription.ready()){
@@ -118,7 +98,7 @@ RouterFactory.configure(factory => {
meta: {
title: 'Character List',
},
- beforeEnter: ensurePaidFeatures,
+ beforeEnter: ensureLoggedIn,
},{
path: '/library',
components: {
@@ -127,7 +107,7 @@ RouterFactory.configure(factory => {
meta: {
title: 'Library',
},
- beforeEnter: ensurePaidFeatures,
+ beforeEnter: ensureLoggedIn,
},{
name: 'singleLibrary',
path: '/library/:id',
@@ -138,7 +118,6 @@ RouterFactory.configure(factory => {
meta: {
title: 'Library',
},
- beforeEnter: ensurePaidFeatures,
},{
path: '/character/:id/:urlName',
components: {
@@ -149,7 +128,6 @@ RouterFactory.configure(factory => {
meta: {
title: 'Character Sheet',
},
- beforeEnter: ensurePaidFeatures,
},{
path: '/character/:id',
components: {
@@ -160,7 +138,6 @@ RouterFactory.configure(factory => {
meta: {
title: 'Character Sheet',
},
- beforeEnter: ensurePaidFeatures,
},{
path: '/friends',
components: {
diff --git a/app/imports/ui/user/TierTooLowDialog.vue b/app/imports/ui/user/TierTooLowDialog.vue
new file mode 100644
index 00000000..69d9e34d
--- /dev/null
+++ b/app/imports/ui/user/TierTooLowDialog.vue
@@ -0,0 +1,49 @@
+
+
+
+
+ Your current Patreon tier is {{ tier.name }}
+
+
+ You need to be at least Adventurer tier (or be invited by a Patron of
+ a higher tier) to perform this action
+
+
+ Join now
+
+
+
+
+ Cancel
+
+
+
+
+