Fixed library nodes not having a default schema
This commit is contained in:
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@@ -14,3 +14,4 @@ private/oldClient
|
|||||||
nohup.out
|
nohup.out
|
||||||
node_modules
|
node_modules
|
||||||
dump
|
dump
|
||||||
|
*.crt
|
||||||
|
|||||||
3
app/imports/@types/ddp-rate-limiter-mixin.d.ts
vendored
Normal file
3
app/imports/@types/ddp-rate-limiter-mixin.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
declare module 'ddp-rate-limiter-mixin' {
|
||||||
|
export const RateLimiterMixin: <T>(options: T) => T;
|
||||||
|
}
|
||||||
8
app/imports/@types/meteor.d.ts
vendored
8
app/imports/@types/meteor.d.ts
vendored
@@ -1,7 +1,5 @@
|
|||||||
declare module 'meteor/meteor' {
|
declare namespace Meteor {
|
||||||
namespace Meteor {
|
interface User {
|
||||||
interface User {
|
roles?: string[];
|
||||||
roles?: string[];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
13
app/imports/@types/mongo.d.ts
vendored
13
app/imports/@types/mongo.d.ts
vendored
@@ -32,5 +32,18 @@ declare namespace Mongo {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
attachSchema(ss: SimpleSchema | TypedSimpleSchema<T>, options?: SchemaOptions): void;
|
attachSchema(ss: SimpleSchema | TypedSimpleSchema<T>, options?: SchemaOptions): void;
|
||||||
|
update(
|
||||||
|
selector: Selector<T> | ObjectID | string,
|
||||||
|
modifier: Modifier<T>,
|
||||||
|
options?: {
|
||||||
|
multi?: boolean | undefined;
|
||||||
|
upsert?: boolean | undefined;
|
||||||
|
arrayFilters?: Array<{ [identifier: string]: any }> | undefined;
|
||||||
|
// Add Collection2 options
|
||||||
|
selector?: Record<string, any>;
|
||||||
|
getAutoValues?: boolean;
|
||||||
|
},
|
||||||
|
callback?: FunctionConstructor,
|
||||||
|
): number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export type CreaturePropertyTypes = {
|
|||||||
>
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CreatureProperty = ConvertToUnion<CreaturePropertyTypes>;
|
export type CreatureProperty = Simplify<ConvertToUnion<CreaturePropertyTypes>>;
|
||||||
|
|
||||||
const CreatureProperties = new Mongo.Collection<CreatureProperty>('creatureProperties');
|
const CreatureProperties = new Mongo.Collection<CreatureProperty>('creatureProperties');
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { Mongo } from 'meteor/mongo';
|
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
@@ -7,7 +6,7 @@ import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema';
|
|||||||
import ChildSchema, { RefSchema } from '/imports/api/parenting/ChildSchema';
|
import ChildSchema, { RefSchema } from '/imports/api/parenting/ChildSchema';
|
||||||
import propertySchemasIndex from '/imports/api/properties/propertySchemasIndex';
|
import propertySchemasIndex from '/imports/api/properties/propertySchemasIndex';
|
||||||
import Libraries from '/imports/api/library/Libraries';
|
import Libraries from '/imports/api/library/Libraries';
|
||||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions';
|
import { assertDocEditPermission, assertEditPermission } from '/imports/api/sharing/sharingPermissions';
|
||||||
import { softRemove } from '/imports/api/parenting/softRemove';
|
import { softRemove } from '/imports/api/parenting/softRemove';
|
||||||
import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema';
|
import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema';
|
||||||
import { storedIconsSchema } from '/imports/api/icons/Icons';
|
import { storedIconsSchema } from '/imports/api/icons/Icons';
|
||||||
@@ -15,12 +14,13 @@ import '/imports/api/library/methods/index';
|
|||||||
import { updateReferenceNodeWork } from '/imports/api/library/methods/updateReferenceNode';
|
import { updateReferenceNodeWork } from '/imports/api/library/methods/updateReferenceNode';
|
||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
||||||
import { restore } from '/imports/api/parenting/softRemove';
|
import { restore } from '/imports/api/parenting/softRemove';
|
||||||
import { fetchDocByRef, getAncestry } from '/imports/api/parenting/parentingFunctions';
|
import { fetchDocByRef } from '/imports/api/parenting/parentingFunctions';
|
||||||
import { rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
|
import { rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
|
||||||
|
import { ConvertToUnion, InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
|
||||||
|
import type { PropertyType } from '/imports/api/properties/PropertyType.type';
|
||||||
|
import { Simplify } from 'type-fest';
|
||||||
|
|
||||||
let LibraryNodes = new Mongo.Collection('libraryNodes');
|
const LibraryNodeSchema = TypedSimpleSchema.from({
|
||||||
|
|
||||||
let LibraryNodeSchema = new SimpleSchema({
|
|
||||||
_id: {
|
_id: {
|
||||||
type: String,
|
type: String,
|
||||||
max: 32,
|
max: 32,
|
||||||
@@ -51,13 +51,11 @@ let LibraryNodeSchema = new SimpleSchema({
|
|||||||
fillSlots: {
|
fillSlots: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
|
||||||
},
|
},
|
||||||
// Will this property show up in the insert-from-library dialog
|
// Will this property show up in the insert-from-library dialog
|
||||||
searchable: {
|
searchable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
|
||||||
},
|
},
|
||||||
libraryTags: {
|
libraryTags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -100,39 +98,50 @@ let LibraryNodeSchema = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type LibraryNodeTypes = {
|
||||||
|
[T in PropertyType]: Simplify<
|
||||||
|
{ type: T }
|
||||||
|
& InferType<typeof propertySchemasIndex[T]>
|
||||||
|
> & Simplify<
|
||||||
|
Exclude<InferType<typeof LibraryNodeSchema>, 'type'>
|
||||||
|
& InferType<typeof ColorSchema>
|
||||||
|
& InferType<typeof ChildSchema>
|
||||||
|
& InferType<typeof SoftRemovableSchema>
|
||||||
|
>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LibraryNode = ConvertToUnion<LibraryNodeTypes>;
|
||||||
|
|
||||||
|
const LibraryNodes = new Mongo.Collection<LibraryNode>('libraryNodes');
|
||||||
|
|
||||||
// Set up server side search index
|
// Set up server side search index
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
LibraryNodes._ensureIndex({
|
LibraryNodes.createIndexAsync({
|
||||||
'name': 'text',
|
'name': 'text',
|
||||||
'tags': 'text',
|
'tags': 'text',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let key in propertySchemasIndex) {
|
const genericLibraryNodeSchema = TypedSimpleSchema.from({})
|
||||||
let schema = new SimpleSchema({});
|
.extend(LibraryNodeSchema)
|
||||||
schema.extend(LibraryNodeSchema);
|
.extend(ColorSchema)
|
||||||
schema.extend(ColorSchema);
|
.extend(ChildSchema)
|
||||||
schema.extend(propertySchemasIndex[key]);
|
.extend(SoftRemovableSchema);
|
||||||
schema.extend(ChildSchema);
|
|
||||||
schema.extend(SoftRemovableSchema);
|
// Attach the default schema
|
||||||
// @ts-expect-error don't have types for .attachSchema
|
LibraryNodes.attachSchema(genericLibraryNodeSchema);
|
||||||
|
|
||||||
|
// Attach the schemas for each type
|
||||||
|
let key: keyof typeof propertySchemasIndex;
|
||||||
|
for (key in propertySchemasIndex) {
|
||||||
|
const schema = TypedSimpleSchema.from({})
|
||||||
|
.extend(propertySchemasIndex[key])
|
||||||
|
.extend(genericLibraryNodeSchema);
|
||||||
LibraryNodes.attachSchema(schema, {
|
LibraryNodes.attachSchema(schema, {
|
||||||
selector: { type: key }
|
selector: { type: key }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLibrary(node) {
|
|
||||||
if (!node) throw new Meteor.Error('No node provided');
|
|
||||||
let library = Libraries.findOne(node.root.id);
|
|
||||||
if (!library) throw new Meteor.Error('Library does not exist');
|
|
||||||
return library;
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertNodeEditPermission(node, userId) {
|
|
||||||
let lib = getLibrary(node);
|
|
||||||
return assertEditPermission(lib, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertNode = new ValidatedMethod({
|
const insertNode = new ValidatedMethod({
|
||||||
name: 'libraryNodes.insert',
|
name: 'libraryNodes.insert',
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
@@ -209,8 +218,8 @@ const updateLibraryNode = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
run({ _id, path, value }) {
|
run({ _id, path, value }) {
|
||||||
let node = LibraryNodes.findOne(_id);
|
let node = LibraryNodes.findOne(_id);
|
||||||
assertNodeEditPermission(node, this.userId);
|
assertDocEditPermission(node, this.userId);
|
||||||
let pathString = path.join('.');
|
const pathString = path.join('.');
|
||||||
let modifier;
|
let modifier;
|
||||||
// unset empty values
|
// unset empty values
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
@@ -218,7 +227,7 @@ const updateLibraryNode = new ValidatedMethod({
|
|||||||
} else {
|
} else {
|
||||||
modifier = { $set: { [pathString]: value } };
|
modifier = { $set: { [pathString]: value } };
|
||||||
}
|
}
|
||||||
let numUpdated = LibraryNodes.update(_id, modifier, {
|
const numUpdated = LibraryNodes.update(_id, modifier, {
|
||||||
selector: { type: node.type },
|
selector: { type: node.type },
|
||||||
});
|
});
|
||||||
if (node.type == 'reference') {
|
if (node.type == 'reference') {
|
||||||
@@ -238,8 +247,8 @@ const pushToLibraryNode = new ValidatedMethod({
|
|||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({ _id, path, value }) {
|
run({ _id, path, value }) {
|
||||||
let node = LibraryNodes.findOne(_id);
|
const node = LibraryNodes.findOne(_id);
|
||||||
assertNodeEditPermission(node, this.userId);
|
assertDocEditPermission(node, this.userId);
|
||||||
return LibraryNodes.update(_id, {
|
return LibraryNodes.update(_id, {
|
||||||
$push: { [path.join('.')]: value },
|
$push: { [path.join('.')]: value },
|
||||||
}, {
|
}, {
|
||||||
@@ -257,8 +266,8 @@ const pullFromLibraryNode = new ValidatedMethod({
|
|||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({ _id, path, itemId }) {
|
run({ _id, path, itemId }) {
|
||||||
let node = LibraryNodes.findOne(_id);
|
const node = LibraryNodes.findOne(_id);
|
||||||
assertNodeEditPermission(node, this.userId);
|
assertDocEditPermission(node, this.userId);
|
||||||
return LibraryNodes.update(_id, {
|
return LibraryNodes.update(_id, {
|
||||||
$pull: { [path.join('.')]: { _id: itemId } },
|
$pull: { [path.join('.')]: { _id: itemId } },
|
||||||
}, {
|
}, {
|
||||||
@@ -279,8 +288,8 @@ const softRemoveLibraryNode = new ValidatedMethod({
|
|||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({ _id }) {
|
run({ _id }) {
|
||||||
let node = LibraryNodes.findOne(_id);
|
const node = LibraryNodes.findOne(_id);
|
||||||
assertNodeEditPermission(node, this.userId);
|
assertDocEditPermission(node, this.userId);
|
||||||
softRemove(LibraryNodes, node);
|
softRemove(LibraryNodes, node);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -297,8 +306,9 @@ const restoreLibraryNode = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
run({ _id }) {
|
run({ _id }) {
|
||||||
// Permissions
|
// Permissions
|
||||||
let node = LibraryNodes.findOne(_id);
|
const node = LibraryNodes.findOne(_id);
|
||||||
assertNodeEditPermission(node, this.userId);
|
if (!node) return;
|
||||||
|
assertDocEditPermission(node, this.userId);
|
||||||
// Do work
|
// Do work
|
||||||
restore(LibraryNodes, node);
|
restore(LibraryNodes, node);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
||||||
import { InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
|
import { InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
|
||||||
import type { Simplify } from 'type-fest';
|
import type { Simplify } from 'type-fest';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import SimpleSchema from 'simpl-schema';
|
|||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import { RefSchema } from '/imports/api/parenting/ChildSchema';
|
import { RefSchema } from '/imports/api/parenting/ChildSchema';
|
||||||
import { assertDocEditPermission, assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
import { assertDocEditPermission, assertEditPermission } from '/imports/api/sharing/sharingPermissions';
|
||||||
import { compact } from 'lodash';
|
import { compact } from 'lodash';
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||||
import { fetchDocByRefAsync, getCollectionByName, moveDocBetweenRoots, moveDocWithinRoot } from '/imports/api/parenting/parentingFunctions';
|
import { fetchDocByRefAsync, getCollectionByName, moveDocBetweenRoots, moveDocWithinRoot } from '/imports/api/parenting/parentingFunctions';
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { TreeDoc, treeDocFields, Reference } from '/imports/api/parenting/ChildS
|
|||||||
import { getProperties } from '/imports/api/engine/loadCreatures';
|
import { getProperties } from '/imports/api/engine/loadCreatures';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||||
|
|
||||||
export function getCollectionByName(name: string): Mongo.Collection<TreeDoc> {
|
export function getCollectionByName<T = TreeDoc>(name: string): Mongo.Collection<T> {
|
||||||
const collection = Mongo.Collection.get<TreeDoc>(name)
|
const collection = Mongo.Collection.get<T>(name)
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new Meteor.Error('bad-collection-reference',
|
throw new Meteor.Error('bad-collection-reference',
|
||||||
`Parent references collection ${name}, which does not exist`
|
`Parent references collection ${name}, which does not exist`
|
||||||
@@ -27,8 +27,8 @@ export function fetchDocByRefAsync(ref: Reference, options?: Mongo.Options<objec
|
|||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchDocByRef(ref: Reference, options?: Mongo.Options<object>): TreeDoc {
|
export function fetchDocByRef<T extends object = TreeDoc>(ref: Reference, options?: Mongo.Options<object>): T {
|
||||||
const doc: TreeDoc = getCollectionByName(ref.collection).findOne(ref.id, options);
|
const doc: T = getCollectionByName<T>(ref.collection).findOne(ref.id, options);
|
||||||
assertDocFound(doc, ref);
|
assertDocFound(doc, ref);
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
import { includes } from 'lodash';
|
import { includes } from 'lodash';
|
||||||
import { fetchDocByRef } from '/imports/api/parenting/parentingFunctions';
|
import { fetchDocByRef } from '/imports/api/parenting/parentingFunctions';
|
||||||
|
import type { Shared } from '/imports/api/sharing/SharingSchema';
|
||||||
|
import type { TreeDoc } from '/imports/api/parenting/ChildSchema';
|
||||||
|
|
||||||
function assertIdValid(userId) {
|
function assertIdValid(userId: string | undefined | null): asserts userId {
|
||||||
if (!userId || typeof userId !== 'string') {
|
if (!userId || typeof userId !== 'string') {
|
||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
'No user ID. Are you logged in?');
|
'No user ID. Are you logged in?');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertdocExists(doc) {
|
function assertDocExists(doc: Record<string, any> | undefined): asserts doc {
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
'Permission denied: No such document exists');
|
'Permission denied: No such document exists');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertOwnership(doc, userId) {
|
export function assertOwnership(doc: Shared, userId: string): asserts doc {
|
||||||
assertIdValid(userId);
|
assertIdValid(userId);
|
||||||
assertdocExists(doc);
|
assertDocExists(doc);
|
||||||
|
|
||||||
if (doc.owner === userId) {
|
if (doc.owner === userId) {
|
||||||
return true;
|
return;
|
||||||
} else {
|
} else {
|
||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
'You are not the owner of this document');
|
'You are not the owner of this document');
|
||||||
@@ -33,18 +35,22 @@ export function assertOwnership(doc, userId) {
|
|||||||
*
|
*
|
||||||
* Warning: the doc and userId must be set by a trusted source
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
*/
|
*/
|
||||||
export function assertEditPermission(doc, userId) {
|
export function assertEditPermission(doc: Shared | undefined, userId: string | undefined | null): asserts doc {
|
||||||
assertIdValid(userId);
|
assertIdValid(userId);
|
||||||
assertdocExists(doc);
|
assertDocExists(doc);
|
||||||
const user = Meteor.users.findOne(userId, {
|
const user = Meteor.users.findOne(userId, {
|
||||||
fields: {
|
fields: {
|
||||||
'roles': 1,
|
'roles': 1,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (!user) {
|
||||||
|
throw new Meteor.Error('Edit permission denied',
|
||||||
|
'No such user exists');
|
||||||
|
}
|
||||||
|
|
||||||
// Admin override
|
// Admin override
|
||||||
if (user.roles && user.roles.includes('admin')) {
|
if (user.roles && user.roles.includes('admin')) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the user is authorized for this specific document
|
// Ensure the user is authorized for this specific document
|
||||||
@@ -52,7 +58,7 @@ export function assertEditPermission(doc, userId) {
|
|||||||
doc.owner === userId ||
|
doc.owner === userId ||
|
||||||
includes(doc.writers, userId)
|
includes(doc.writers, userId)
|
||||||
) {
|
) {
|
||||||
return true;
|
return;
|
||||||
} else {
|
} else {
|
||||||
throw new Meteor.Error('Edit permission denied',
|
throw new Meteor.Error('Edit permission denied',
|
||||||
'You do not have permission to edit this document');
|
'You do not have permission to edit this document');
|
||||||
@@ -65,18 +71,23 @@ export function assertEditPermission(doc, userId) {
|
|||||||
*
|
*
|
||||||
* Warning: the doc and userId must be set by a trusted source
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
*/
|
*/
|
||||||
export function assertCopyPermission(doc, userId) {
|
export function assertCopyPermission(doc: Shared, userId): asserts doc {
|
||||||
assertIdValid(userId);
|
assertIdValid(userId);
|
||||||
assertdocExists(doc);
|
assertDocExists(doc);
|
||||||
const user = Meteor.users.findOne(userId, {
|
const user = Meteor.users.findOne(userId, {
|
||||||
fields: {
|
fields: {
|
||||||
'roles': 1,
|
'roles': 1,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Meteor.Error('Edit permission denied',
|
||||||
|
'No such user exists');
|
||||||
|
}
|
||||||
|
|
||||||
// Admin override
|
// Admin override
|
||||||
if (user.roles && user.roles.includes('admin')) {
|
if (user.roles && user.roles.includes('admin')) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the user is authorized for this specific document
|
// Ensure the user is authorized for this specific document
|
||||||
@@ -84,22 +95,22 @@ export function assertCopyPermission(doc, userId) {
|
|||||||
doc.owner === userId ||
|
doc.owner === userId ||
|
||||||
includes(doc.writers, userId)
|
includes(doc.writers, userId)
|
||||||
) {
|
) {
|
||||||
return true;
|
return;
|
||||||
} else if (
|
} else if (
|
||||||
(includes(doc.readers, userId) || doc.public) &&
|
(includes(doc.readers, userId) || doc.public) &&
|
||||||
doc.readersCanCopy
|
doc.readersCanCopy
|
||||||
) {
|
) {
|
||||||
return true;
|
return;
|
||||||
} else {
|
} else {
|
||||||
throw new Meteor.Error('Copy permission denied',
|
throw new Meteor.Error('Copy permission denied',
|
||||||
'You do not have permission to copy this document');
|
'You do not have permission to copy this document');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRoot(doc) {
|
function getRoot(doc: TreeDoc | Shared | undefined) {
|
||||||
assertdocExists(doc);
|
assertDocExists(doc);
|
||||||
if (doc.root) {
|
if ('root' in doc) {
|
||||||
return fetchDocByRef(doc.root);
|
return fetchDocByRef<Shared>(doc.root);
|
||||||
} else {
|
} else {
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
@@ -111,8 +122,8 @@ function getRoot(doc) {
|
|||||||
*
|
*
|
||||||
* Warning: the doc and userId must be set by a trusted source
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
*/
|
*/
|
||||||
export function assertDocEditPermission(doc, userId) {
|
export function assertDocEditPermission(doc: TreeDoc | Shared | undefined, userId: string | null): asserts doc {
|
||||||
let root = getRoot(doc);
|
const root = getRoot(doc);
|
||||||
assertEditPermission(root, userId);
|
assertEditPermission(root, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,14 +133,14 @@ export function assertDocEditPermission(doc, userId) {
|
|||||||
*
|
*
|
||||||
* Warning: the doc and userId must be set by a trusted source
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
*/
|
*/
|
||||||
export function assertDocCopyPermission(doc, userId) {
|
export function assertDocCopyPermission(doc, userId): asserts doc {
|
||||||
let root = getRoot(doc);
|
const root = getRoot(doc);
|
||||||
assertCopyPermission(root, userId);
|
assertCopyPermission(root, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertViewPermission(doc, userId) {
|
export function assertViewPermission(doc, userId): asserts doc {
|
||||||
assertdocExists(doc);
|
assertDocExists(doc);
|
||||||
if (doc.public) return true;
|
if (doc.public) return;
|
||||||
assertIdValid(userId);
|
assertIdValid(userId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -137,7 +148,7 @@ export function assertViewPermission(doc, userId) {
|
|||||||
includes(doc.readers, userId) ||
|
includes(doc.readers, userId) ||
|
||||||
includes(doc.writers, userId)
|
includes(doc.writers, userId)
|
||||||
) {
|
) {
|
||||||
return true;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Admin override
|
// Admin override
|
||||||
@@ -146,8 +157,13 @@ export function assertViewPermission(doc, userId) {
|
|||||||
'roles': 1,
|
'roles': 1,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (!user) {
|
||||||
|
throw new Meteor.Error('Edit permission denied',
|
||||||
|
'No such user exists');
|
||||||
|
}
|
||||||
|
|
||||||
if (user.roles && user.roles.includes('admin')) {
|
if (user.roles && user.roles.includes('admin')) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Meteor.Error('View permission denied',
|
throw new Meteor.Error('View permission denied',
|
||||||
@@ -161,19 +177,19 @@ export function assertViewPermission(doc, userId) {
|
|||||||
*
|
*
|
||||||
* Warning: the doc and userId must be set by a trusted source
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
*/
|
*/
|
||||||
export function assertDocViewPermission(doc, userId) {
|
export function assertDocViewPermission(doc, userId): asserts doc {
|
||||||
let root = getRoot(doc);
|
const root = getRoot(doc);
|
||||||
assertViewPermission(root, userId);
|
assertViewPermission(root, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertAdmin(userId) {
|
export function assertAdmin(userId): asserts userId {
|
||||||
assertIdValid(userId);
|
assertIdValid(userId);
|
||||||
let user = Meteor.users.findOne(userId, { fields: { roles: 1 } });
|
const user = Meteor.users.findOne(userId, { fields: { roles: 1 } });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
'UserId does not match any existing user');
|
'UserId does not match any existing user');
|
||||||
}
|
}
|
||||||
let isAdmin = user.roles && user.roles.includes('admin')
|
const isAdmin = user.roles && user.roles.includes('admin')
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
'User does not have the admin role');
|
'User does not have the admin role');
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
ref="searchBox"
|
ref="searchBox"
|
||||||
v-model="filter"
|
v-model="filter"
|
||||||
class="mx-4"
|
class="mx-4"
|
||||||
@extra-fields-changed="val => extraFields = val"
|
|
||||||
:is-library="true"
|
:is-library="true"
|
||||||
|
@extra-fields-changed="val => extraFields = val"
|
||||||
/>
|
/>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-fade-transition>
|
<v-fade-transition>
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ Migrations.add({
|
|||||||
name: 'Changes parenting from array of ancestors to nested sets',
|
name: 'Changes parenting from array of ancestors to nested sets',
|
||||||
up: Meteor.wrapAsync(async (_, next) => {
|
up: Meteor.wrapAsync(async (_, next) => {
|
||||||
console.log('migrating up library nodes 2 -> 3');
|
console.log('migrating up library nodes 2 -> 3');
|
||||||
migrateCollection('libraryNodes');
|
await migrateCollection('libraryNodes');
|
||||||
console.log('migrating up creature props 2 -> 3');
|
console.log('migrating up creature props 2 -> 3');
|
||||||
migrateCollection('creatureProperties');
|
await migrateCollection('creatureProperties');
|
||||||
console.log('migrating up docs 2 -> 3');
|
console.log('migrating up docs 2 -> 3');
|
||||||
migrateCollection('docs');
|
await migrateCollection('docs');
|
||||||
console.log('New parenting schema fields added, if it was done correctly remove the old fields manually');
|
console.log('New parenting schema fields added, if it was done correctly remove the old fields manually');
|
||||||
|
|
||||||
console.log('removing all CreatureVariables, creatures will add them back the next time they recalculate');
|
console.log('removing all CreatureVariables, creatures will add them back the next time they recalculate');
|
||||||
@@ -52,7 +52,9 @@ export function migrateCollection(collectionName: string) {
|
|||||||
// Copy the parent id field and the root ancestor to the new structure
|
// Copy the parent id field and the root ancestor to the new structure
|
||||||
// Using the mongo aggregation API
|
// Using the mongo aggregation API
|
||||||
// Waring: This will destroy parenting data if the old parenting fields are deleted
|
// Waring: This will destroy parenting data if the old parenting fields are deleted
|
||||||
return collection.rawCollection().updateMany({}, [
|
return collection.rawCollection().updateMany({
|
||||||
|
'root.id': undefined,
|
||||||
|
}, [
|
||||||
{
|
{
|
||||||
$addFields: {
|
$addFields: {
|
||||||
'root': { $arrayElemAt: ['$ancestors', 0] },
|
'root': { $arrayElemAt: ['$ancestors', 0] },
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"strictFunctionTypes": true,
|
"strictFunctionTypes": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"preserveSymlinks": true,
|
"preserveSymlinks": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
"outDir": "build",
|
"outDir": "build",
|
||||||
"paths": {
|
"paths": {
|
||||||
"/*": [
|
"/*": [
|
||||||
"./*"
|
"*"
|
||||||
],
|
],
|
||||||
"meteor/*": [
|
"meteor/*": [
|
||||||
"node_modules/@types/meteor/*",
|
"node_modules/@types/meteor/*",
|
||||||
|
|||||||
Reference in New Issue
Block a user