Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F970754
D214.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
19 KB
Referenced Files
None
Subscribers
None
D214.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D214: 'when' access strategy - Ref T805
Attached
Detach File
Event Timeline
Log In to Comment