Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 66 additions & 239 deletions acs-data-access/lib/api-v1.js

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions acs-data-access/lib/base-structure-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export class BaseStructureHandler {
constructor(api) {
this.api = api;
this.auth = api.auth;
this.log = api.log;
this.cdb = api.cdb;
}

validate_config(config) {}
check_source_permission(principal, config) {}
resolve(ctx) {}
create_subclass_relationships(datasetUuid, config) {}
remove_subclass_relationships(datasetUuid, config) {}
}
84 changes: 84 additions & 0 deletions acs-data-access/lib/session-limits-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as rx from "rxjs";

import { retryBackoff } from "@amrc-factoryplus/rx-util";

import {BaseStructureHandler} from './base-structure-handler.js';
import { fail } from './utils.js';
import { valid_uuid, valid_datetime } from "./validate.js";
import { DataAccess as Constants } from "./constants.js";



export class SessionLimitsHandler extends BaseStructureHandler {
constructor(api){
super(api);

this.structure = Constants.App.SessionLimits;
this.sourcePermission = Constants.Perm.UseForSession;
}

validate_config(config) {
if(!config)
return fail(this.log, 422, `config not provided`);

const {to, from, source} = config;
if(!to || !from || !source)
return fail(this.log, 422, `req.body.config for SessionLimits type must contain "source", "to" and "from" fields.`);

if(!valid_uuid(source))
return fail(this.log, 422, `req.body.config.source uuid is invalid.`);

if(!valid_datetime(to))
return fail(this.log, 422, `req.body.config.to datetime format is invalid.`);

if(!valid_datetime(from))
return fail(this.log, 422, `req.body.config.from datetime format is invalid.`);
}

async check_sources_permissions(principal, config) {
const target = config.source;
if(!target) return fail(this.log, 422, `Dataset definition does not contain source.`);

if(!valid_uuid(target)) return fail(this.log, 422, `Source uuid ${target} is invalid.`);

const ok = await this.auth.check_acl(
principal,
this.sourcePermission,
target,
true
);

return ok;
}

async resolve({ config, inherited_range, visited }) {
const session_range = {
from: config.from ?? null,
to: config.to ?? null,
};

const merged_range = this.api.intersectRanges(
inherited_range,
session_range,
);

return this.api.resolve_dataset(
config.source,
merged_range,
visited,
);
}

async create_subclass_relationships(dataset_uuid, config) {
await rx.lastValueFrom(
rx.defer(() =>
this.cdb.class_add_subclass(config.source, dataset_uuid)
).pipe(retryBackoff(500, e => this.log(e)))
);
}

async remove_subclass_relationships(dataset_uuid, config) {
await this.cdb.class_remove_subclass(config.source, dataset_uuid);
this.log(`Removed ${dataset_uuid} from ${config.source} subclasses.`)
}
}
58 changes: 58 additions & 0 deletions acs-data-access/lib/sparkplug-sources-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {BaseStructureHandler} from './base-structure-handler.js';
import { fail } from './utils.js';
import { valid_uuid } from "./validate.js";
import { DataAccess as Constants } from "./constants.js";

export class SparkplugSourcesHandler extends BaseStructureHandler {
constructor(api){
super(api);

this.structure = Constants.App.SparkplugSrc;
this.sourcePermission = Constants.Perm.UseSparkplug;
}

validate_config(config) {
if(!config)
return fail(this.log, 422, `config not provided`);

const source = config.source;
if(!source)
return fail(this.log, 422, `config for SparkplugSrc structure type ${this.structure} must contain "source" field.`);

if(!valid_uuid(source))
return fail(this.log, 422, `config.source uuid is invalid.`);
}

async check_sources_permissions(principal, config) {
const target = config.source;
if(!target) return fail(this.log, 422, `Dataset definition does not contain source.`);

if(!valid_uuid(target)) return fail(this.log, 422, `Source uuid ${target} is invalid.`);

const ok = await this.auth.check_acl(
principal,
this.sourcePermission,
target,
true
);

return ok;
}

async resolve({ dataset_uuid, config, inherited_range }) {
return [{
dataset_uuid,
device_uuid: config.source,
from: inherited_range.from,
to: inherited_range.to,
}];
}

async create_subclass_relationships() {
return;
}

async remove_subclass_relationships() {
return;
}
}
85 changes: 85 additions & 0 deletions acs-data-access/lib/unions-components-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as rx from "rxjs";

import { retryBackoff } from "@amrc-factoryplus/rx-util";

import {BaseStructureHandler} from './base-structure-handler.js';
import { fail } from './utils.js';
import { valid_uuid } from "./validate.js";
import { DataAccess as Constants } from "./constants.js";

export class UnionComponentsHandler extends BaseStructureHandler {

constructor(api){
super(api);

this.structure = Constants.App.UnionComponents;
this.sourcePermission = Constants.Perm.IncludeInUnion;
}

validate_config(config) {
if(!config)
return fail(this.log, 422, `config not provided`);

if(!Array.isArray(config))
return fail(this.log, 422, `config must be array [] for UnionComponents structure`);

for(let src of config){
if (!valid_uuid(src))
return fail(this.log, 422, `${src} is invalid UUID`);
}
}

async check_sources_permissions(principal, config) {
if(!Array.isArray(config)) return fail(this.log, 422, `Dataset def of structure ${this.structure} must be an Array.`);

if(config.length == 0) return true;

for(let target of config){
const ok = await this.auth.check_acl(
principal,
this.sourcePermission,
target,
true
);

if(!ok) return false;
}

return true;
}

async resolve({ config, inherited_range, visited }) {
const results = [];

for (const src of config) {
const src_results = await this.api.resolve_dataset(
src,
inherited_range,
new Set(visited),
);

results.push(...src_results);
}

return results;
}

async create_subclass_relationships(datasetUuid, config) {
for (const source of config) {
await rx.lastValueFrom(
rx.defer(() =>
this.cdb.class_add_subclass(datasetUuid, source)
).pipe(retryBackoff(500, e => this.log(e)))
);
}
}

async remove_subclass_relationships(dataset_uuid, config) {
if(config.length <= 0) return;

for (const src of config){
await this.cdb.class_remove_subclass(dataset_uuid, src);
this.log(`Removed ${dataset_uuid} from ${src} subclasses.`);
}
}
}
10 changes: 9 additions & 1 deletion acs-data-access/lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import { APIError } from "@amrc-factoryplus/service-api";
export function fail(log, status, message) {
log(message);
throw new APIError(status);
}


export function csv_escape(value) {
if (value == null) return "";

Expand Down Expand Up @@ -35,4 +42,5 @@ export function minDate(a, b) {
return new Date(a) < new Date(b)
? a
: b
}
}

26 changes: 14 additions & 12 deletions acs-data-access/tests/http/delete_dataset.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ describe('GET /delete/:uuid', () => {
let admin_fplus;
let ALL_GRANTS = [];

let dataset_to_be_deleted;

beforeEach(async () => {
admin_fplus = new ServiceClient({
Expand All @@ -43,11 +42,6 @@ describe('GET /delete/:uuid', () => {
password: process.env.ADMIN_PASSWORD,
verbose: 'ALL'
});


dataset_to_be_deleted = await admin_fplus.DataAccess.create_dataset(Constants.App.SparkplugSrc, Temp_Data.Valid_Body.SRC.config);

console.log(`Created new dataset ${dataset_to_be_deleted}`);
});

// Clean up grants if not removed in case of failure.
Expand Down Expand Up @@ -83,24 +77,32 @@ describe('GET /delete/:uuid', () => {


test('Successful deletion', async () => {
const dataset_uuid = await admin_fplus.DataAccess.create_dataset(Constants.App.SparkplugSrc, Temp_Data.Valid_Body.SRC.config);

await sleep(4000);

const grant = await addGrant(
TEST_PRINCIPAL,
Constants.Perm.DeleteDataset,
dataset_to_be_deleted,
dataset_uuid,
false
);

await sleep(10000);
await sleep(4000);

const deleted = await test_fplus.DataAccess.delete_dataset(dataset_to_be_deleted);
const deleted = await test_fplus.DataAccess.delete_dataset(dataset_uuid);

await sleep(4000);

await deleteGrant(grant);

expect(deleted).toBe(dataset_to_be_deleted);
expect(deleted).toBe(dataset_uuid);

}, 30000);

test('Irrelevant permission', async () => {
const dataset_to_be_deleted = Temp_Data.Test_Uuids.Src_Datasets.Node2_TestDoubleDeviceDataset;

const grant = await addGrant(
TEST_PRINCIPAL,
Constants.Perm.EditDataset,
Expand All @@ -116,7 +118,7 @@ describe('GET /delete/:uuid', () => {
expect.fail('Expected to throw');
}
catch (err) {
expect(err.status).not.toBe(200);
expect(err.status).toBe(403);
}

await deleteGrant(grant);
Expand All @@ -131,7 +133,7 @@ describe('GET /delete/:uuid', () => {
expect.fail('Expected to throw');
}
catch (err) {
expect(err.status).not.toBe(200);
expect(err.status).toBe(422);
}
}, 30000);
});
2 changes: 1 addition & 1 deletion acs-data-access/tests/http/metadata_list.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {ServiceClient} from "@amrc-factoryplus/service-client";
import process from "node:process";
import {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest';
import { DataAccess as Constants } from "../../lib/constants.js";
import * as Temp_Data from '../temp_data.js';
import {Test_Uuids} from '../temp_data.js';

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
Expand Down
Loading
Loading