Replace Filter class with expression structure
This is in order to be able to support UPPER() and LOWER() functions in queries which are needed for case-insensitive search.
This commit is contained in:
1 parent
7194fcab44
commit
c2d13e8364
25 files changed
+716
-578
No files matched your search
+46
-37
@@ -7,20 +7,22 @@ const querystring = require("querystring");
|
||||
|
||||
const config = require("./config");
|
||||
const device = require("./device");
|
||||
const filterParser = require("../common/filter-parser");
|
||||
const Filter = require("../common/filter");
|
||||
const expressionParser = require("../common/expression-parser");
|
||||
const expression = require("../common/expression");
|
||||
|
||||
const commaAscii = ",".charCodeAt(0);
|
||||
const newLineAscii = "\n".charCodeAt(0);
|
||||
|
||||
function unpackTimestamps(filter) {
|
||||
return filterParser.map(filter, exp => {
|
||||
if (!Array.isArray(filter)) return filter;
|
||||
return expressionParser.map(filter, exp => {
|
||||
if (["=", "<>", ">=", "<=", "<", ">"].includes(exp[0]))
|
||||
if (typeof exp[2] === "number") {
|
||||
if (Array.isArray(exp) && typeof exp[2] === "number") {
|
||||
let alt = exp.slice();
|
||||
alt[2] = new Date(alt[2]).toJSON();
|
||||
return ["OR", exp, alt];
|
||||
}
|
||||
return exp;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,37 +41,42 @@ function filterToMongoQuery(filter, negate = false, res = {}) {
|
||||
} else if (op === "NOT") {
|
||||
filterToMongoQuery(filter[1], !negate, res);
|
||||
} else if (op === "=") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
const param = filter[1][1];
|
||||
let p = (res[param] = res[param] || {});
|
||||
if (negate) p["$ne"] = filter[2];
|
||||
else p["$eq"] = filter[2];
|
||||
} else if (op === "<>") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
if (negate) {
|
||||
p["$eq"] = filter[2];
|
||||
} else {
|
||||
p["$ne"] = filter[2];
|
||||
p["$exists"] = true;
|
||||
}
|
||||
const param = filter[1][1];
|
||||
let p = (res[param] = res[param] || {});
|
||||
if (negate) p = p["$not"] = p["$not"] || {};
|
||||
p["$ne"] = filter[2];
|
||||
p["$exists"] = true;
|
||||
} else if (op === ">") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
const param = filter[1][1];
|
||||
let p = (res[param] = res[param] || {});
|
||||
if (negate) p = p["$not"] = p["$not"] || {};
|
||||
p["$gt"] = filter[2];
|
||||
} else if (op === ">=") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
const param = filter[1][1];
|
||||
let p = (res[param] = res[param] || {});
|
||||
if (negate) p = p["$not"] = p["$not"] || {};
|
||||
p["$gte"] = filter[2];
|
||||
} else if (op === "<") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
const param = filter[1][1];
|
||||
let p = (res[param] = res[param] || {});
|
||||
if (negate) p = p["$not"] = p["$not"] || {};
|
||||
p["$lt"] = filter[2];
|
||||
} else if (op === "<=") {
|
||||
let p = (res[filter[1]] = res[filter[1]] || {});
|
||||
const param = filter[1][1];
|
||||
let p = (res[param] = res[param] || {});
|
||||
if (negate) p = p["$not"] = p["$not"] || {};
|
||||
p["$lte"] = filter[2];
|
||||
} else if (op === "IS NULL") {
|
||||
res[filter[1]] = { $exists: negate };
|
||||
const param = filter[1][1];
|
||||
res[param] = { $exists: negate };
|
||||
} else if (op === "IS NOT NULL") {
|
||||
res[filter[1]] = { $exists: !negate };
|
||||
const param = filter[1][1];
|
||||
res[param] = { $exists: !negate };
|
||||
} else {
|
||||
throw new Error(`Unrecognized operator ${op}`);
|
||||
}
|
||||
@@ -80,14 +87,15 @@ function filterToMongoQuery(filter, negate = false, res = {}) {
|
||||
function count(resource, filter, limit) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let qs = {};
|
||||
if (filter && filter.ast) {
|
||||
filter = filter.evaluateExpressions();
|
||||
let ast = unpackTimestamps(filter.ast);
|
||||
filter = expression.evaluate(filter, null, Date.now());
|
||||
filter = unpackTimestamps(filter);
|
||||
|
||||
let q = filterToMongoQuery(ast);
|
||||
if (resource === "devices") q = device.transposeQuery(q);
|
||||
qs.query = JSON.stringify(q);
|
||||
}
|
||||
let q = {};
|
||||
if (Array.isArray(filter)) q = filterToMongoQuery(filter);
|
||||
else if (filter != null && !filter) return resolve(0);
|
||||
|
||||
if (resource === "devices") q = device.transposeQuery(q);
|
||||
qs.query = JSON.stringify(q);
|
||||
|
||||
if (limit) qs.limit = limit;
|
||||
|
||||
@@ -117,15 +125,15 @@ function query(resource, filter, limit, skip, projection, callback) {
|
||||
let ret;
|
||||
if (!callback) ret = [];
|
||||
let qs = {};
|
||||
if (filter && filter.ast) {
|
||||
filter = filter.evaluateExpressions();
|
||||
let ast = unpackTimestamps(filter.ast);
|
||||
filter = expression.evaluate(filter, null, Date.now());
|
||||
filter = unpackTimestamps(filter);
|
||||
|
||||
let q = filterToMongoQuery(ast);
|
||||
if (resource === "devices") q = device.transposeQuery(q);
|
||||
let q = {};
|
||||
if (Array.isArray(filter)) q = filterToMongoQuery(filter);
|
||||
else if (filter != null && !filter) return resolve(ret);
|
||||
|
||||
qs.query = JSON.stringify(q);
|
||||
}
|
||||
if (resource === "devices") q = device.transposeQuery(q);
|
||||
qs.query = JSON.stringify(q);
|
||||
|
||||
if (limit) qs.limit = limit;
|
||||
if (skip) qs.skip = skip;
|
||||
@@ -308,12 +316,13 @@ function postTasks(deviceId, tasks) {
|
||||
|
||||
let promises2 = [];
|
||||
for (let s of statuses) {
|
||||
promises2.push(query("tasks", new Filter(["=", "_id", s._id])));
|
||||
promises2.push(query("tasks", ["=", ["PARAM", "_id"], s._id]));
|
||||
promises2.push(
|
||||
query(
|
||||
"faults",
|
||||
new Filter(["=", "_id", `${deviceId}:task_${s._id}`])
|
||||
)
|
||||
query("faults", [
|
||||
"=",
|
||||
["PARAM", "_id"],
|
||||
`${deviceId}:task_${s._id}`
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+24
-18
@@ -4,7 +4,7 @@ const Router = require("koa-router");
|
||||
|
||||
const db = require("./db");
|
||||
const apiFunctions = require("./api-functions");
|
||||
const Filter = require("../common/filter");
|
||||
const expression = require("../common/expression");
|
||||
|
||||
const router = new Router();
|
||||
|
||||
@@ -33,17 +33,20 @@ const resources = {
|
||||
|
||||
for (let [resource, flags] of Object.entries(resources)) {
|
||||
router.head(`/${resource}`, async (ctx, next) => {
|
||||
let filter, limit;
|
||||
if (ctx.request.query.filter) filter = new Filter(ctx.request.query.filter);
|
||||
let filter = true;
|
||||
let limit;
|
||||
if (ctx.request.query.filter)
|
||||
filter = expression.parse(ctx.request.query.filter);
|
||||
if (ctx.request.query.limit) limit = +ctx.request.query.limit;
|
||||
|
||||
if (!ctx.state.authorizer.hasAccess(resource, 1)) return next();
|
||||
|
||||
// Exclude temporary tasks and faults
|
||||
if (resource === "tasks" || resource === "faults") {
|
||||
let f = new Filter(["NOT", ["<", "expiry", Date.now() + 60000]]);
|
||||
filter = f.and(filter);
|
||||
}
|
||||
if (resource === "tasks" || resource === "faults")
|
||||
filter = expression.and(filter, [
|
||||
"NOT",
|
||||
["<", ["PARAM", "expiry"], Date.now() + 60000]
|
||||
]);
|
||||
|
||||
let count;
|
||||
if (flags & RESOURCE_DB) count = await db.count(resource, filter, limit);
|
||||
@@ -54,8 +57,10 @@ for (let [resource, flags] of Object.entries(resources)) {
|
||||
});
|
||||
|
||||
router.get(`/${resource}`, async (ctx, next) => {
|
||||
let filter, limit, skip, projection;
|
||||
if (ctx.request.query.filter) filter = new Filter(ctx.request.query.filter);
|
||||
let filter = true;
|
||||
let limit, skip, projection;
|
||||
if (ctx.request.query.filter)
|
||||
filter = expression.parse(ctx.request.query.filter);
|
||||
if (ctx.request.query.limit) limit = +ctx.request.query.limit;
|
||||
if (ctx.request.query.skip) skip = +ctx.request.query.skip;
|
||||
if (ctx.request.query.projection) projection = ctx.request.query.projection;
|
||||
@@ -63,10 +68,11 @@ for (let [resource, flags] of Object.entries(resources)) {
|
||||
if (!ctx.state.authorizer.hasAccess(resource, 2)) return next();
|
||||
|
||||
// Exclude temporary tasks and faults
|
||||
if (resource === "tasks" || resource === "faults") {
|
||||
let f = new Filter(["NOT", ["<", "expiry", Date.now() + 60000]]);
|
||||
filter = f.and(filter);
|
||||
}
|
||||
if (resource === "tasks" || resource === "faults")
|
||||
filter = expression.and(filter, [
|
||||
"NOT",
|
||||
["<", ["PARAM", "expiry"], Date.now() + 60000]
|
||||
]);
|
||||
|
||||
let res;
|
||||
if (flags & RESOURCE_DB)
|
||||
@@ -78,7 +84,7 @@ for (let [resource, flags] of Object.entries(resources)) {
|
||||
});
|
||||
|
||||
router.head(`/${resource}/:id`, async (ctx, next) => {
|
||||
let filter = new Filter(`${RESOURCE_IDS[resource]} = "${ctx.params.id}"`);
|
||||
let filter = ["=", ["PARAM", RESOURCE_IDS[resource]], ctx.params.id];
|
||||
if (!ctx.state.authorizer.hasAccess(resource, 2)) return next();
|
||||
|
||||
let res;
|
||||
@@ -90,7 +96,7 @@ for (let [resource, flags] of Object.entries(resources)) {
|
||||
});
|
||||
|
||||
router.get(`/${resource}/:id`, async (ctx, next) => {
|
||||
let filter = new Filter(`${RESOURCE_IDS[resource]} = "${ctx.params.id}"`);
|
||||
let filter = ["=", ["PARAM", RESOURCE_IDS[resource]], ctx.params.id];
|
||||
if (!ctx.state.authorizer.hasAccess(resource, 2)) return next();
|
||||
|
||||
let res;
|
||||
@@ -104,7 +110,7 @@ for (let [resource, flags] of Object.entries(resources)) {
|
||||
if (flags & RESOURCE_DELETE && !(flags & RESOURCE_DB))
|
||||
router.delete(`/${resource}/:id`, async (ctx, next) => {
|
||||
const authorizer = ctx.state.authorizer;
|
||||
let filter = new Filter(`${RESOURCE_IDS[resource]} = "${ctx.params.id}"`);
|
||||
let filter = ["=", ["PARAM", RESOURCE_IDS[resource]], ctx.params.id];
|
||||
if (!authorizer.hasAccess(resource, 2)) return next();
|
||||
let res = await apiFunctions.query(resource, filter);
|
||||
if (!res.length) return next();
|
||||
@@ -119,7 +125,7 @@ for (let [resource, flags] of Object.entries(resources)) {
|
||||
|
||||
router.post("/devices/:id/tasks", async (ctx, next) => {
|
||||
const authorizer = ctx.state.authorizer;
|
||||
let filter = new Filter(`DeviceID.ID = "${ctx.params.id}"`);
|
||||
let filter = ["=", ["PARAM", "DeviceID.ID"], ctx.params.id];
|
||||
if (!authorizer.hasAccess("devices", 2)) return next();
|
||||
let devices = await apiFunctions.query("devices", filter);
|
||||
if (!devices.length) return next();
|
||||
@@ -135,7 +141,7 @@ router.post("/devices/:id/tasks", async (ctx, next) => {
|
||||
|
||||
router.post("/devices/:id/tags", async (ctx, next) => {
|
||||
const authorizer = ctx.state.authorizer;
|
||||
let filter = new Filter(`DeviceID.ID = "${ctx.params.id}"`);
|
||||
let filter = ["=", ["PARAM", "DeviceID.ID"], ctx.params.id];
|
||||
if (!authorizer.hasAccess("devices", 2)) return next();
|
||||
let res = await apiFunctions.query("devices", filter);
|
||||
if (!res.length) return next();
|
||||
|
||||
+4
-2
@@ -20,7 +20,8 @@ function getClient() {
|
||||
|
||||
function query(resource, filter, limit, skip, callback) {
|
||||
let q;
|
||||
if (filter && filter.ast) q = apiFunctions.filterToMongoQuery(filter.ast);
|
||||
if (Array.isArray(filter)) q = apiFunctions.filterToMongoQuery(filter);
|
||||
else if (filter != null && !filter) return Promise.resolve([]);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
getClient().then(client => {
|
||||
@@ -50,7 +51,8 @@ function query(resource, filter, limit, skip, callback) {
|
||||
|
||||
function count(resource, filter) {
|
||||
let q;
|
||||
if (filter && filter.ast) q = apiFunctions.filterToMongoQuery(filter.ast);
|
||||
if (Array.isArray(filter)) q = apiFunctions.filterToMongoQuery(filter);
|
||||
else if (filter != null && !filter) return Promise.resolve([]);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
getClient().then(client => {
|
||||
|
||||
+15
-1
@@ -11,6 +11,7 @@ const jwt = require("jsonwebtoken");
|
||||
const config = require("./config");
|
||||
const api = require("./api");
|
||||
const Authorizer = require("../common/authorizer");
|
||||
const expression = require("../common/expression");
|
||||
|
||||
const koa = new Koa();
|
||||
const router = new Router();
|
||||
@@ -20,7 +21,20 @@ const JWT_COOKIE = "genieacs-ui-jwt";
|
||||
|
||||
function getPermissionSets(roles) {
|
||||
const allPermissions = config.permissions;
|
||||
const permissionSets = roles.map(r => Object.values(allPermissions[r] || {}));
|
||||
const permissionSets = roles.map(role => {
|
||||
return Object.values(allPermissions[role] || {}).map(p => {
|
||||
p = Object.assign({}, p);
|
||||
for (let [k, v] of Object.entries(p)) {
|
||||
p[k] = v = Object.assign({}, v);
|
||||
if (["devices", "faults", "files"].includes(k))
|
||||
if (v.filter == null || v.filter === "") v.filter = true;
|
||||
else {
|
||||
v.filter = expression.parse(v.filter);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
});
|
||||
});
|
||||
return permissionSets;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user