Page MenuHomeSealhub

D214.id721.diff
No OneTemporary

D214.id721.diff

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,73 @@
+"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.getRestrictingQuery(collection),
+ await when_true.getRestrictingQuery(context)
+ ),
+ new Query.And(
+ new Query.Not(await special_filter.getRestrictingQuery(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
+ ) {
+ try {
+ 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");
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ },
+ 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,68 @@
+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");
+const matches = require("./../special_filters/matches.js");
+
+describe("when", () => {
+ async function create_resources(app) {
+ app.createChip(app.Sealious.Collection, {
+ name: "numbers",
+ fields: [{ name: "number", type: "int" }],
+ named_filters: {
+ positive: matches({ number: { ">": 0 } }),
+ negative: 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
@@ -3,6 +3,7 @@
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 {
@@ -23,24 +24,19 @@
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",
- },
+ async getRestrictingQuery() {
+ 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() {
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
@@ -4,6 +4,7 @@
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 {
@@ -13,19 +14,17 @@
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 getRestrictingQuery(collection) {
+ let stages = [];
+ for (let field_name in this.filter) {
+ const field_stages = await collection.fields[
+ field_name
+ ].get_aggregation_stages(new SuperContext(), {
+ filter: this.filter,
+ });
+ stages = stages.concat(field_stages);
+ }
+ return Query.fromStages(stages);
}
getNopassReason() {
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].getRestrictingQuery(
+ collection
+ )).toPipeline()
),
])
.map(Promise.all)
@@ -552,6 +550,9 @@
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];
+ },
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
@@ -13,7 +13,7 @@
const documents = await app.Datastore.aggregate(collection.name, [
{ $match: { sealious_id: resource_id } },
- ...this.getAggregationStages(),
+ ...this.getRestrictingQuery(),
]);
return documents.length
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,68 @@
}
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.fromStages([{ $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] } });
+ }
+ });
+ }
+};
+
+Query.fromStages = function(stages) {
+ const query = new Query();
+ for (let stage of stages) {
+ query.pipeline = query._pushToPipeline(query.stages, stage);
+ }
+ return query;
};
module.exports = Query;

File Metadata

Mime Type
text/plain
Expires
Wed, Jan 22, 20:23 (2 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
601517
Default Alt Text
D214.id721.diff (13 KB)

Event Timeline