Page MenuHomeSealhub

D214.diff
No OneTemporary

D214.diff

diff --git a/lib/app/app.js b/lib/app/app.js
--- a/lib/app/app.js
+++ b/lib/app/app.js
@@ -25,6 +25,10 @@
const EmailTemplates = locreq("lib/email/templates/templates");
const i18nFactory = locreq("lib/i18n/i18n");
const Query = locreq("lib/datastore/query");
+const SpecialFilterFactory = locreq("lib/chip-types/special-filter.js");
+const load_special_filters = locreq(
+ "lib/app/base-chips/special_filters/load-special-filters.js"
+);
class App {
constructor(custom_config, manifest) {
@@ -68,6 +72,9 @@
this
);
+ this.SpecialFilter = SpecialFilterFactory(this);
+ load_special_filters(this);
+
assert(
this.ConfigManager.get("upload_path"),
"'upload_path' not set in config"
diff --git a/lib/app/base-chips/access-strategy-types/access-strategy-types.test.js b/lib/app/base-chips/access-strategy-types/access-strategy-types.test.js
--- a/lib/app/base-chips/access-strategy-types/access-strategy-types.test.js
+++ b/lib/app/base-chips/access-strategy-types/access-strategy-types.test.js
@@ -4,4 +4,5 @@
require("./users-who-can.subtest.js");
require("./user-referenced-in-field.subtest.js");
require("./roles.subtest.js");
+ require("./when.subtest.js");
});
diff --git a/lib/app/base-chips/access-strategy-types/logged_in.js b/lib/app/base-chips/access-strategy-types/logged_in.js
--- a/lib/app/base-chips/access-strategy-types/logged_in.js
+++ b/lib/app/base-chips/access-strategy-types/logged_in.js
@@ -8,15 +8,13 @@
if (context.user_id) {
return new Query.AllowAll();
}
- return new Query.DenyQuery();
+ return new Query.DenyAll();
},
checker_function: function(context) {
if (context.user_id) {
return Promise.resolve();
} else {
- return Promise.reject(
- "Only logged-in users can perform this action."
- );
+ return Promise.reject("Only logged-in users can perform this action.");
}
},
};
diff --git a/lib/app/base-chips/access-strategy-types/or.js b/lib/app/base-chips/access-strategy-types/or.js
--- a/lib/app/base-chips/access-strategy-types/or.js
+++ b/lib/app/base-chips/access-strategy-types/or.js
@@ -20,14 +20,7 @@
if (queries.some(query => query instanceof Query.AllowAll)) {
return new Query.AllowAll();
}
- return Promise.reduce(
- queries,
- async (aggregated, query) => {
- aggregated.addQuery(query);
- return aggregated;
- },
- new Query.Or()
- );
+ return new Query.Or(...queries);
},
item_sensitive: function(params) {
const access_strategies = parse_params(app, params);
@@ -44,23 +37,19 @@
const results = access_strategies.map(function(strategy) {
return strategy.check(context, item);
});
- return Promise.any(results).catch(
- Promise.AggregateError,
- function(aggregated_errors) {
- aggregated_errors.forEach(function(error) {
- if (!(error instanceof Error)) {
- throw error;
- }
- });
- const error_message = aggregated_errors
- .map(
- aggregated_errors =>
- aggregated_errors.message
- )
- .reduce((a, b) => `${a} ${b}`);
- return Promise.reject(error_message);
- }
- );
+ return Promise.any(results).catch(Promise.AggregateError, function(
+ aggregated_errors
+ ) {
+ aggregated_errors.forEach(function(error) {
+ if (!(error instanceof Error)) {
+ throw error;
+ }
+ });
+ const error_message = aggregated_errors
+ .map(aggregated_errors => aggregated_errors.message)
+ .reduce((a, b) => `${a} ${b}`);
+ return Promise.reject(error_message);
+ });
}
});
},
diff --git a/lib/app/base-chips/access-strategy-types/when.js b/lib/app/base-chips/access-strategy-types/when.js
new file mode 100644
--- /dev/null
+++ b/lib/app/base-chips/access-strategy-types/when.js
@@ -0,0 +1,69 @@
+"use strict";
+const Promise = require("bluebird");
+const Query = require("../../../datastore/query.js");
+
+async function construct_query(
+ app,
+ context,
+ collection_name,
+ special_filter_name,
+ when_true_name,
+ when_false_name
+) {
+ const collection = app.ChipManager.get_chip("collection", collection_name);
+ const special_filter = collection.get_named_filter(special_filter_name);
+ const when_true = new app.Sealious.AccessStrategy(app, when_true_name);
+ const when_false = new app.Sealious.AccessStrategy(app, when_false_name);
+ return new Query.Or(
+ new Query.And(
+ await special_filter.getFilteringQuery(collection),
+ await when_true.getRestrictingQuery(context)
+ ),
+ new Query.And(
+ new Query.Not(await special_filter.getFilteringQuery(collection)),
+ await when_false.getRestrictingQuery(context)
+ )
+ );
+}
+
+module.exports = app => ({
+ name: "when",
+ getRestrictingQuery: async function(
+ context,
+ [collection_name, special_filter_name, when_true_name, when_false_name]
+ ) {
+ return construct_query(
+ app,
+ context,
+ collection_name,
+ special_filter_name,
+ when_true_name,
+ when_false_name
+ );
+ },
+ checker_function: async function(
+ context,
+ [collection_name, special_filter_name, when_true_name, when_false_name],
+ item
+ ) {
+ const results = await app.Datastore.aggregate(
+ item.collection_name,
+ (await construct_query(
+ app,
+ context,
+ collection_name,
+ special_filter_name,
+ when_true_name,
+ when_false_name
+ ))
+ .match({ sealious_id: item.id })
+ .toPipeline()
+ );
+ if (results.length) {
+ return Promise.resolve();
+ } else {
+ return Promise.reject("No access");
+ }
+ },
+ item_sensitive: true,
+});
diff --git a/lib/app/base-chips/access-strategy-types/when.subtest.js b/lib/app/base-chips/access-strategy-types/when.subtest.js
new file mode 100644
--- /dev/null
+++ b/lib/app/base-chips/access-strategy-types/when.subtest.js
@@ -0,0 +1,67 @@
+const locreq = require("locreq")(__dirname);
+const assert = require("assert");
+const { with_stopped_app } = locreq("test_utils/with-test-app.js");
+const assert_throws_async = locreq("test_utils/assert_throws_async.js");
+const axios = require("axios");
+
+describe("when", () => {
+ async function create_resources(app) {
+ app.createChip(app.Sealious.Collection, {
+ name: "numbers",
+ fields: [{ name: "number", type: "int" }],
+ named_filters: {
+ positive: app.SpecialFilter.Matches({ number: { ">": 0 } }),
+ negative: app.SpecialFilter.Matches({ number: { "<": 0 } }),
+ },
+ access_strategy: {
+ default: ["when", ["numbers", "negative", "logged_in", "public"]],
+ },
+ });
+
+ await app.start();
+
+ for (let number of [-1, 0, 1]) {
+ await app.run_action(
+ new app.Sealious.SuperContext(),
+ ["collections", "numbers"],
+ "create",
+ { number }
+ );
+ }
+
+ await app.run_action(
+ new app.Sealious.SuperContext(),
+ ["collections", "users"],
+ "create",
+ { username: "user", password: "password", email: "user@example.com" }
+ );
+ }
+
+ it("should only use 'when_true' access strategy when the item passes the filter", async () =>
+ with_stopped_app(async ({ app, base_url, rest_api }) => {
+ await create_resources(app);
+ const session = await rest_api.login({
+ username: "user",
+ password: "password",
+ });
+
+ const resources_when_logged_in = await rest_api.get(
+ "/api/v1/collections/numbers?sort[body.number]=asc",
+ session
+ );
+
+ assert.equal(resources_when_logged_in.length, 3);
+ assert.equal(resources_when_logged_in[0].body.number, -1);
+ }));
+
+ it("should only use 'when_false' access strategy when the item doesn't pass the filter", async () =>
+ with_stopped_app(async ({ app, base_url, rest_api }) => {
+ await create_resources(app);
+
+ const public_resources = await rest_api.get(
+ "/api/v1/collections/numbers?sort[body.number]=asc"
+ );
+
+ assert.equal(public_resources.length, 2);
+ }));
+});
diff --git a/lib/app/base-chips/field-types/single_reference.js b/lib/app/base-chips/field-types/single_reference.js
--- a/lib/app/base-chips/field-types/single_reference.js
+++ b/lib/app/base-chips/field-types/single_reference.js
@@ -22,7 +22,7 @@
}
return collection
- .get_aggregation_stages(context, "show", { filter })
+ .get_aggregation_stages(context, "retrieve", { filter })
.then(stages => [{ $match: { sealious_id: resource_id } }, ...stages])
.then(stages => app.Datastore.aggregate(collection.name, stages))
.then(
diff --git a/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.js b/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.js
--- a/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.js
+++ b/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.js
@@ -1,50 +1,47 @@
const locreq = require("locreq")(__dirname);
-const SpecialFilter = locreq("lib/chip-types/special-filter.js");
-
const assert = require("assert");
const Collection = locreq("lib/chip-types/collection");
+const Query = locreq("lib/datastore/query.js");
-module.exports = SpecialFilter.WithParams(
- class IsReferencedByResourcesMatching extends SpecialFilter {
- constructor(params) {
- super(params);
- const {
- collection,
- referencing_field,
- field_to_check,
- allowed_values,
- nopass_reason,
- } = params;
-
- assert(params);
- assert(collection instanceof Collection);
- assert(collection.fields[referencing_field]);
- assert(collection.fields[field_to_check]);
- assert(Array.isArray(allowed_values));
- assert(typeof nopass_reason === "string");
- }
- getAggregationStages() {
- return [
- {
- $lookup: {
- from: this.params.collection.name,
- localField: "sealious_id",
- foreignField: `body.${this.params.referencing_field}`,
- as: "resource",
+module.exports = app => {
+ const parametrized = app.SpecialFilter.WithParams(
+ class IsReferencedByResourcesMatching extends app.SpecialFilter {
+ constructor(params) {
+ const {
+ collection,
+ referencing_field,
+ field_to_check,
+ allowed_values,
+ nopass_reason,
+ } = params;
+ super(params);
+ assert(params);
+ assert(collection);
+ assert(collection instanceof Collection);
+ assert(collection.fields[referencing_field]);
+ assert(collection.fields[field_to_check]);
+ assert(Array.isArray(allowed_values));
+ assert(typeof nopass_reason === "string");
+ }
+ async getFilteringQuery() {
+ const query = new Query();
+ const lookup_id = query.lookup({
+ from: this.params.collection.name,
+ localField: "sealious_id",
+ foreignField: `body.${this.params.referencing_field}`,
+ });
+ query.match({
+ [`${lookup_id}.body.${this.params.field_to_check}`]: {
+ $in: this.params.allowed_values,
},
- },
- {
- $match: {
- [`resource.body.${this.params.field_to_check}`]: {
- $in: this.params.allowed_values,
- },
- },
- },
- ];
- }
+ });
+ return query;
+ }
- getNopassReason() {
- return this.params.nopass_reason;
+ getNopassReason() {
+ return this.params.nopass_reason;
+ }
}
- }
-);
+ );
+ app.SpecialFilter.IsReferencedByResourcesMatching = parametrized;
+};
diff --git a/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.subtest.js b/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.subtest.js
--- a/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.subtest.js
+++ b/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.subtest.js
@@ -4,7 +4,6 @@
const Promise = require("bluebird");
const { create_resource_as } = locreq("test_utils");
-const IsReferencedByResourcesMatching = require("./IsReferencedByResourcesMatching");
const { with_running_app } = locreq("test_utils/with-test-app.js");
describe("IsReferencedByResourcesMatching", () => {
@@ -37,7 +36,7 @@
});
Users.add_special_filters({
- staff: IsReferencedByResourcesMatching({
+ staff: app.SpecialFilter.IsReferencedByResourcesMatching({
collection: UsersRoles,
referencing_field: "user",
field_to_check: "role",
diff --git a/lib/app/base-chips/special_filters/load-special-filters.js b/lib/app/base-chips/special_filters/load-special-filters.js
new file mode 100644
--- /dev/null
+++ b/lib/app/base-chips/special_filters/load-special-filters.js
@@ -0,0 +1,4 @@
+module.exports = app => {
+ require("./IsReferencedByResourcesMatching.js")(app);
+ require("./matches.js")(app);
+};
diff --git a/lib/app/base-chips/special_filters/matches.js b/lib/app/base-chips/special_filters/matches.js
--- a/lib/app/base-chips/special_filters/matches.js
+++ b/lib/app/base-chips/special_filters/matches.js
@@ -1,35 +1,35 @@
const locreq = require("locreq")(__dirname);
const assert = require("assert");
-const SpecialFilter = locreq("lib/chip-types/special-filter.js");
-const Collection = locreq("lib/chip-types/collection");
-const SuperContext = locreq("lib/super-context.js");
+const Query = locreq("lib/datastore/query.js");
-module.exports = SpecialFilter.WithParams(
- class Matches extends SpecialFilter {
- constructor(params) {
- super(params);
- assert(params);
- this.filter = params;
- }
+module.exports = app => {
+ const matches = app.SpecialFilter.WithParams(
+ class Matches extends app.SpecialFilter {
+ constructor(params) {
+ super(params);
+ assert(params);
+ this.filter = params;
+ }
- async getAggregationStages(collection) {
- return Promise.all(
- Object.keys(this.filter).map(field_name =>
- collection.fields[field_name].get_aggregation_stages(
- new SuperContext(),
- {
- filter: this.filter,
- }
- )
- )
- ).then(stages =>
- stages.reduce((acc, curr) => acc.concat(curr), [])
- );
- }
+ async getFilteringQuery(collection) {
+ let pipeline = [];
+ for (let field_name in this.filter) {
+ const field_pipeline = await collection.fields[
+ field_name
+ ].get_aggregation_stages(new app.Sealious.SuperContext(), {
+ filter: this.filter,
+ });
+ pipeline = pipeline.concat(field_pipeline);
+ }
+ return Query.fromCustomPipeline(pipeline);
+ }
- getNopassReason() {
- return this.params.nopass_reason;
+ getNopassReason() {
+ return this.params.nopass_reason;
+ }
}
- }
-);
+ );
+ app.SpecialFilter.Matches = matches;
+ return matches;
+};
diff --git a/lib/app/base-chips/special_filters/matches.subtest.js b/lib/app/base-chips/special_filters/matches.subtest.js
--- a/lib/app/base-chips/special_filters/matches.subtest.js
+++ b/lib/app/base-chips/special_filters/matches.subtest.js
@@ -4,7 +4,6 @@
const Promise = require("bluebird");
const { create_resource_as } = locreq("test_utils");
-const matches = require("./matches");
const { with_running_app } = locreq("test_utils/with-test-app.js");
describe("Matches", () => {
@@ -19,8 +18,8 @@
},
],
named_filters: {
- positive: matches({ number: { ">": 0 } }),
- negative: matches({ number: { "<": 0 } }),
+ positive: app.SpecialFilter.Matches({ number: { ">": 0 } }),
+ negative: app.SpecialFilter.Matches({ number: { "<": 0 } }),
},
});
diff --git a/lib/app/load-base-chips.js b/lib/app/load-base-chips.js
--- a/lib/app/load-base-chips.js
+++ b/lib/app/load-base-chips.js
@@ -23,6 +23,7 @@
"users-who-can",
"user-referenced-in-field",
"roles",
+ "when",
]);
BaseChips.set(FieldType, [
diff --git a/lib/chip-types/collection.js b/lib/chip-types/collection.js
--- a/lib/chip-types/collection.js
+++ b/lib/chip-types/collection.js
@@ -510,12 +510,10 @@
return Object.keys(a[0] || {})[0] === "$match" ? -1 : 1;
})
),
- Promise.all(
- named_filters.map(named_filter =>
- collection.named_filters[named_filter].getAggregationStages(
- collection
- )
- )
+ Promise.map(named_filters, async named_filter =>
+ (await collection.named_filters[named_filter].getFilteringQuery(
+ collection
+ )).toPipeline()
),
])
.map(Promise.all)
@@ -552,6 +550,12 @@
add_special_filters: function(named_filters = []) {
return pure.add_special_filters(this, named_filters);
},
+ get_named_filter: function(filter_name) {
+ return this.named_filters[filter_name];
+ },
+ add_named_filter: function(filter_name, filter) {
+ this.named_filters[filter_name] = filter;
+ },
validate_field_values(
context,
assume_delete_value_on_missing_key,
diff --git a/lib/chip-types/special-filter.js b/lib/chip-types/special-filter.js
--- a/lib/chip-types/special-filter.js
+++ b/lib/chip-types/special-filter.js
@@ -3,28 +3,30 @@
const Collection = locreq("lib/chip-types/collection");
-const SpecialFilter = class SpecialFilter {
- constructor(params) {
- this.params = params;
+module.exports = app => {
+ class SpecialFilter {
+ constructor(params) {
+ this.params = params;
+ }
+
+ async checkSingleResource(app, collection, resource_id) {
+ assert(collection instanceof Collection);
+
+ const documents = await app.Datastore.aggregate(collection.name, [
+ { $match: { sealious_id: resource_id } },
+ ...this.getFilteringQuery(),
+ ]);
+
+ return documents.length
+ ? SpecialFilter.pass()
+ : SpecialFilter.nopass(this.getNopassReason());
+ }
}
- async checkSingleResource(app, collection, resource_id) {
- assert(collection instanceof Collection);
+ SpecialFilter.WithParams = _class => (...params) => new _class(...params);
- const documents = await app.Datastore.aggregate(collection.name, [
- { $match: { sealious_id: resource_id } },
- ...this.getAggregationStages(),
- ]);
+ SpecialFilter.pass = async () => ({ passed: true });
+ SpecialFilter.nopass = async reason => ({ passed: false, reason });
- return documents.length
- ? SpecialFilter.pass()
- : SpecialFilter.nopass(this.getNopassReason());
- }
+ return SpecialFilter;
};
-
-SpecialFilter.WithParams = _class => (...params) => new _class(...params);
-
-SpecialFilter.pass = async () => ({ passed: true });
-SpecialFilter.nopass = async reason => ({ passed: false, reason });
-
-module.exports = SpecialFilter;
diff --git a/lib/datastore/query.js b/lib/datastore/query.js
--- a/lib/datastore/query.js
+++ b/lib/datastore/query.js
@@ -88,35 +88,60 @@
}
addQuery(query) {
let stages = query.dump();
+ const combined_match = {};
for (let stage of stages) {
if (stage.$lookup) {
this._lookup(stage);
} else if (stage.$match) {
- this._match(stage);
+ Object.assign(combined_match, stage.$match);
} else {
throw new Error("Unsupported query: " + Object.keys(stage));
}
}
+ if (Object.keys(combined_match).length) this.matches.push(combined_match);
}
_lookup(stage) {
const id = stage.$lookup.as;
this.lookups[id] = stage;
}
- _match(stage) {
- this.matches.push(stage);
- }
dump() {
return Object.values(this.lookups).concat({
- $match: { $or: this.matches.map(stage => stage.$match) },
+ $match: { $or: this.matches },
});
}
toPipeline() {
return Object.values(this.lookups)
.reduce((acc, stage) => this._pushToPipeline(acc, stage), [])
.concat({
- $match: { $or: this.matches.map(stage => stage.$match) },
+ $match: { $or: this.matches },
});
}
+ match(body) {
+ return Query.fromCustomPipeline([{ $match: body }, ...this.toPipeline()]);
+ }
+};
+
+Query.And = class extends Query {
+ constructor(...queries) {
+ super();
+ this.stages = queries
+ .map(query => query.stages)
+ .reduce((acc, stages) => acc.concat(stages), []);
+ }
+};
+
+Query.Not = class extends Query {
+ constructor(query) {
+ super();
+ query.toPipeline().map(stage => {
+ if (!stage.$match) {
+ return stage;
+ }
+ for (let field_name in stage.$match) {
+ this.match({ [field_name]: { $not: stage.$match[field_name] } });
+ }
+ });
+ }
};
module.exports = Query;

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 25, 00:10 (5 h, 16 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
548622
Default Alt Text
D214.diff (19 KB)

Event Timeline