|
@@ -18,6 +18,94 @@ import documentVersionPlugin from "../schemas/plugins/documentVersion";
|
|
|
import getDataPlugin from "../schemas/plugins/getData";
|
|
|
import Migration from "../Migration";
|
|
|
|
|
|
+/**
|
|
|
+ * Experimental: function to get all nested keys from a MongoDB query object
|
|
|
+ */
|
|
|
+function getAllKeys(obj: object) {
|
|
|
+ const keys: string[] = [];
|
|
|
+
|
|
|
+ function processObject(obj: object, parentKey = "") {
|
|
|
+ let returnChanged = false;
|
|
|
+
|
|
|
+ // eslint-disable-next-line
|
|
|
+ for (let key in obj) {
|
|
|
+ // eslint-disable-next-line
|
|
|
+ if (obj.hasOwnProperty(key)) {
|
|
|
+ if (key.startsWith("$")) {
|
|
|
+ // eslint-disable-next-line
|
|
|
+ // @ts-ignore
|
|
|
+ // eslint-disable-next-line
|
|
|
+ processNestedObject(obj[key], parentKey); // Process nested keys without including the current key
|
|
|
+ // eslint-disable-next-line
|
|
|
+ continue; // Skip the current key
|
|
|
+ }
|
|
|
+
|
|
|
+ const currentKey = parentKey ? `${parentKey}.${key}` : key;
|
|
|
+
|
|
|
+ // eslint-disable-next-line
|
|
|
+ // @ts-ignore
|
|
|
+ if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
|
+ // eslint-disable-next-line
|
|
|
+ // @ts-ignore
|
|
|
+ if (Array.isArray(obj[key])) {
|
|
|
+ // eslint-disable-next-line
|
|
|
+ // @ts-ignore
|
|
|
+ // eslint-disable-next-line
|
|
|
+ if (processArray(obj[key], currentKey)) {
|
|
|
+ returnChanged = true;
|
|
|
+ // eslint-disable-next-line
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // eslint-disable-next-line
|
|
|
+ // @ts-ignore
|
|
|
+ else if (processObject(obj[key], currentKey)) {
|
|
|
+ returnChanged = true;
|
|
|
+ // eslint-disable-next-line
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ keys.push(currentKey);
|
|
|
+
|
|
|
+ returnChanged = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return returnChanged;
|
|
|
+ }
|
|
|
+
|
|
|
+ function processArray(arr: Array<any>, parentKey: string) {
|
|
|
+ let returnChanged = false;
|
|
|
+
|
|
|
+ for (let i = 0; i < arr.length; i += 1) {
|
|
|
+ const currentKey = parentKey;
|
|
|
+
|
|
|
+ if (typeof arr[i] === "object" && arr[i] !== null) {
|
|
|
+ if (Array.isArray(arr[i])) {
|
|
|
+ if (processArray(arr[i], currentKey)) returnChanged = true;
|
|
|
+ } else if (processObject(arr[i], currentKey))
|
|
|
+ returnChanged = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return returnChanged;
|
|
|
+ }
|
|
|
+
|
|
|
+ function processNestedObject(obj: object, parentKey: string) {
|
|
|
+ if (typeof obj === "object" && obj !== null) {
|
|
|
+ if (Array.isArray(obj)) {
|
|
|
+ processArray(obj, parentKey);
|
|
|
+ } else {
|
|
|
+ processObject(obj, parentKey);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ processObject(obj);
|
|
|
+ return keys;
|
|
|
+}
|
|
|
+
|
|
|
export default class DataModule extends BaseModule {
|
|
|
private models?: Models;
|
|
|
|
|
@@ -117,36 +205,94 @@ export default class DataModule extends BaseModule {
|
|
|
ModelName extends keyof Models,
|
|
|
SchemaType extends Schemas[keyof ModelName]
|
|
|
>(modelName: ModelName, schema: SchemaType) {
|
|
|
- // const preMethods: string[] = [
|
|
|
- // "aggregate",
|
|
|
- // "count",
|
|
|
- // "countDocuments",
|
|
|
- // "deleteOne",
|
|
|
- // "deleteMany",
|
|
|
- // "estimatedDocumentCount",
|
|
|
- // "find",
|
|
|
- // "findOne",
|
|
|
- // "findOneAndDelete",
|
|
|
- // "findOneAndRemove",
|
|
|
- // "findOneAndReplace",
|
|
|
- // "findOneAndUpdate",
|
|
|
- // "init",
|
|
|
- // "insertMany",
|
|
|
- // "remove",
|
|
|
- // "replaceOne",
|
|
|
- // "save",
|
|
|
- // "update",
|
|
|
- // "updateOne",
|
|
|
- // "updateMany",
|
|
|
- // "validate"
|
|
|
- // ];
|
|
|
-
|
|
|
- // preMethods.forEach(preMethod => {
|
|
|
- // // @ts-ignore
|
|
|
- // schema.pre(preMethods, () => {
|
|
|
- // console.log(`Pre-${preMethod}!`);
|
|
|
- // });
|
|
|
- // });
|
|
|
+ const methods: string[] = [
|
|
|
+ "aggregate",
|
|
|
+ "count",
|
|
|
+ "countDocuments",
|
|
|
+ "deleteOne",
|
|
|
+ "deleteMany",
|
|
|
+ "estimatedDocumentCount",
|
|
|
+ "find",
|
|
|
+ "findOne",
|
|
|
+ "findOneAndDelete",
|
|
|
+ "findOneAndRemove",
|
|
|
+ "findOneAndReplace",
|
|
|
+ "findOneAndUpdate",
|
|
|
+ // "init",
|
|
|
+ "insertMany",
|
|
|
+ "remove",
|
|
|
+ "replaceOne",
|
|
|
+ "save",
|
|
|
+ "update",
|
|
|
+ "updateOne",
|
|
|
+ "updateMany"
|
|
|
+ // "validate"
|
|
|
+ ];
|
|
|
+
|
|
|
+ methods.forEach(method => {
|
|
|
+ // NOTE: some Mongo selectors may also search through linked documents. Prevent that
|
|
|
+ schema.pre(method, async function () {
|
|
|
+ console.log(`Pre-${method}! START`);
|
|
|
+
|
|
|
+ if (
|
|
|
+ this.options?.userContext &&
|
|
|
+ ["find", "update", "deleteOne", "save"].indexOf(method) ===
|
|
|
+ -1
|
|
|
+ )
|
|
|
+ throw new Error("Method not allowed");
|
|
|
+
|
|
|
+ console.log(`Pre-${method}!`, this.options?.userContext);
|
|
|
+
|
|
|
+ if (["find", "update", "deleteOne"].indexOf(method) !== -1) {
|
|
|
+ const filter = this.getFilter();
|
|
|
+ const filterKeys = getAllKeys(filter);
|
|
|
+
|
|
|
+ filterKeys.forEach(filterKey => {
|
|
|
+ const splitFilterKeys = filterKey
|
|
|
+ .split(".")
|
|
|
+ .reduce(
|
|
|
+ (keys: string[], key: string) =>
|
|
|
+ keys.length > 0
|
|
|
+ ? [
|
|
|
+ ...keys,
|
|
|
+ `${
|
|
|
+ keys[keys.length - 1]
|
|
|
+ }.${key}`
|
|
|
+ ]
|
|
|
+ : [key],
|
|
|
+ []
|
|
|
+ );
|
|
|
+ splitFilterKeys.forEach(splitFilterKey => {
|
|
|
+ const path = this.schema.path(splitFilterKey);
|
|
|
+ if (!path) {
|
|
|
+ throw new Error(
|
|
|
+ "Attempted to query with non-existant property"
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (path.options.restricted) {
|
|
|
+ throw new Error(
|
|
|
+ "Attempted to query with restricted property"
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log(`Pre-${method}!`, filterKeys);
|
|
|
+
|
|
|
+ // Here we want to always exclude some properties depending on the model, like passwords/tokens
|
|
|
+ this.projection({ restrictedName: 0 });
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`Pre-${method}! END`);
|
|
|
+ });
|
|
|
+
|
|
|
+ schema.post(method, async function (docOrDocs) {
|
|
|
+ console.log(`Post-${method} START!`);
|
|
|
+ console.log(`Post-${method}!`, docOrDocs);
|
|
|
+ console.log(`Post-${method}!`, this);
|
|
|
+ console.log(`Post-${method} END!`);
|
|
|
+ });
|
|
|
+ });
|
|
|
|
|
|
const { enabled, eventCreated, eventUpdated, eventDeleted } =
|
|
|
schema.get("patchHistory") ?? {};
|