Skip to content

Commit 89a8d65

Browse files
committed
Start Implementation of Collectors and App Templates
1 parent 68985c2 commit 89a8d65

10 files changed

Lines changed: 402 additions & 5 deletions

File tree

package-lock.json

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@pryv/monitor": "^2.4.0",
4343
"@pryv/socket.io": "^2.4.0",
4444
"c8": "^10.1.3",
45-
"pryv": "^2.4.0"
45+
"pryv": "^2.4.0",
46+
"short-unique-id": "^5.3.2"
4647
}
4748
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
const { HDSLibError } = require('../errors');
2+
const ShortUniqueId = require('short-unique-id');
3+
const collectorIdGenerator = new ShortUniqueId({ dictionary: 'alphanum_lower', length: 7 });
4+
5+
/**
6+
* App which manages Collectors
7+
* A "Collector" can be see as a "Request" and set of "Responses"
8+
* - Responses are authorization tokens from individuals
9+
*
10+
* The App can create multiple "collectors e.g. Questionnaries"
11+
*
12+
* Stream structure
13+
* - [baseStreamId] "Root" stream for this app
14+
* - [baseStreamId]-[collectorsId] Each "questionnary" or "request for a set of data" has it's own stream
15+
* - [baseStreamId]-[collectorsId]-settings Contains events with the current settings of this app (this stream will be shared in "read" with the request)
16+
* - [baseStreamId]-[collectorsId]-pending Contains events with "pending" requests
17+
* - [baseStreamId]-[collectorsId]-active Contains events with "active" users
18+
* - [baseStreamId]-[scollectorsId]-errors Contains events with "revoked" or "erroneous" users
19+
*/
20+
class AppManagingAccount {
21+
/** @type {Pryv.Connection} */
22+
connection;
23+
/** @type {string} */
24+
baseStreamId;
25+
/** @type {string} */
26+
appName;
27+
28+
#cache;
29+
30+
/**
31+
* use AppManagingAccount.newFromConnection() to create new AppManagingAccount
32+
* @param {string} appName
33+
* @param {string} baseStreamId
34+
* @param {Pryv.Connection} connection
35+
*/
36+
constructor (appName, baseStreamId, connection) {
37+
this.baseStreamId = baseStreamId;
38+
this.appName = appName;
39+
this.connection = connection;
40+
this.#cache = { };
41+
}
42+
43+
/**
44+
* Return an initialized AppManagingAccount instance
45+
* @param {Pryv.Connection} connection
46+
* @returns {AppManagingAccount}
47+
*/
48+
static async newFromConnection (connection, baseStreamId) {
49+
const appManagingAccount = await newAppManagingAccountFromConnection(connection, baseStreamId);
50+
await appManagingAccount.init();
51+
return appManagingAccount;
52+
}
53+
54+
async init () {
55+
// -- check if stream structure exists
56+
await this.getCollectors();
57+
return this;
58+
}
59+
60+
async getCollectors (forceRefresh) {
61+
if (!forceRefresh && this.#cache.collectorsMap) return Object.values(this.#cache.collectorsMap);
62+
// Collectors are materialized by streams
63+
const apiCalls = [{
64+
method: 'streams.get',
65+
params: {
66+
parentId: this.baseStreamId
67+
}
68+
}];
69+
const result = (await this.connection.api(apiCalls))[0];
70+
if (result.error) throw new HDSLibError('Failed getting collectors', result.error);
71+
if (!result.streams || !Array.isArray(result.streams)) throw new HDSLibError('Failed getting collectors, invalid result', result);
72+
const collectorsMap = {};
73+
for (const stream of result.streams) {
74+
const collector = new Collector(this, stream);
75+
collectorsMap[collector.id] = collector;
76+
}
77+
this.#cache.collectorsMap = collectorsMap;
78+
return Object.values(this.#cache.collectorsMap);
79+
}
80+
81+
async createCollector (name) {
82+
const streamId = this.baseStreamId + '-' + collectorIdGenerator.rnd();
83+
const apiCalls = [{
84+
method: 'streams.create',
85+
params: {
86+
id: streamId,
87+
name,
88+
parentId: this.baseStreamId
89+
}
90+
}];
91+
const result = (await this.connection.api(apiCalls))[0];
92+
if (result.error) throw new HDSLibError('Failed creating collector', result.error);
93+
if (!result.stream?.name) throw new HDSLibError('Failed creating collector, invalid result', result);
94+
const collector = new Collector(this, result.stream);
95+
this.#cache.collectorsMap[collector.id] = collector;
96+
return collector;
97+
}
98+
}
99+
100+
class Collector {
101+
appManaging;
102+
id;
103+
name;
104+
/**
105+
* @param {AppManagingAccount} appManaging
106+
* @param {Pryv.Stream} stream
107+
*/
108+
constructor (appManaging, stream) {
109+
this.id = stream.id;
110+
this.name = stream.name;
111+
this.appManaging = appManaging;
112+
}
113+
}
114+
115+
module.exports = AppManagingAccount;
116+
117+
async function newAppManagingAccountFromConnection (connection, baseStreamId) {
118+
const accessInfo = (await connection.api([{ method: 'getAccessInfo', params: {} }]))[0];
119+
if (!accessInfo.type === 'app') throw new Error('Failed createing new AppManagingAccount, invalid coonection: ' + JSON.stringify(accessInfo));
120+
// check if baseStreamId is in pemission set
121+
const found = accessInfo.permissions.find(p => (p.streamId === baseStreamId || p.streamId === '*'));
122+
if (found && found.level !== 'manage') { // check if level 'manage'
123+
throw new Error(`Failed creating new AppManagingAccount, Not sufficient permissions on stream: ${baseStreamId}`);
124+
}
125+
if (!found) {
126+
// here we may check if we can create or manage baseStreamId as it might be covered by permissions
127+
throw new Error(`Failed creating new AppManagingAccount, cannot find "${baseStreamId}" in permission list`);
128+
}
129+
const appManagingAccount = new AppManagingAccount(accessInfo.name, baseStreamId, connection);
130+
return appManagingAccount;
131+
}

src/appTemplates/appTemplates.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const AppManagingAccount = require('./AppManagingAccount');
2+
3+
module.exports = {
4+
AppManagingAccount
5+
};

src/errors.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class HDSLibError extends Error {
2+
constructor (message, innerObject = { }) {
3+
const msg = (innerObject.message !== null) ? message + ' >> ' + innerObject.message : message;
4+
super(msg);
5+
this.innerObject = innerObject;
6+
}
7+
8+
toString () {
9+
const res = super.toString();
10+
return res + '\nInner Object:\n' + JSON.stringify(this.innerObject, null, 2);
11+
}
12+
}
13+
14+
module.exports = {
15+
HDSLibError
16+
};

src/index-webpack.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import HDSModel from './HDSModel/HDSModel';
2-
2+
import appTemplates from './appTemplates';
33
/**
44
* Export for webpack build
55
*/
66
export const HDSLib = {
7-
HDSModel
7+
HDSModel,
8+
appTemplates
89
};

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
module.exports = {
2-
HDSModel: require('./HDSModel/HDSModel')
2+
HDSModel: require('./HDSModel/HDSModel'),
3+
appTemplates: require('./appTemplates/appTemplates')
34
};

tests/apptemplates.test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* eslint-env mocha */
2+
const assert = require('node:assert/strict');
3+
const { createUserAndPermissions } = require('./test-utils/pryvService');
4+
const AppManagingAccount = require('../src/appTemplates/AppManagingAccount');
5+
const pryv = require('pryv');
6+
7+
describe('[APTX] appTemplates', function () {
8+
this.timeout(10000);
9+
let user, appManaging;
10+
const baseStreamId = 'test-app-template';
11+
const appName = 'Test HDSLib.appTemplates';
12+
13+
before(async () => {
14+
const permissions = [{ streamId: baseStreamId, level: 'manage' }];
15+
user = await createUserAndPermissions(null, permissions, appName);
16+
const connection = new pryv.Connection(user.appApiEndpoint);
17+
appManaging = await AppManagingAccount.newFromConnection(connection, baseStreamId);
18+
});
19+
20+
it('[APTA] Initial and create One collector', async () => {
21+
assert.equal(appManaging.appName, appName);
22+
assert.equal(appManaging.baseStreamId, baseStreamId);
23+
24+
const collectorEmpty = await appManaging.getCollectors();
25+
assert.ok(Array.isArray(collectorEmpty), 'Collectors should be an array');
26+
assert.equal(collectorEmpty.length, 0, 'Collectors should be an empty array');
27+
28+
const collectorName = 'Test';
29+
// create a Collector
30+
const newCollector = await appManaging.createCollector(collectorName);
31+
assert.ok(newCollector.id.startsWith(baseStreamId), 'Collectors id should start with baseStreamId');
32+
assert.ok(newCollector.name, collectorName);
33+
34+
// Create a Collector with the same name should fail
35+
try {
36+
await appManaging.createCollector(collectorName);
37+
throw new Error('Creating a Collector with the same name should fail');
38+
} catch (e) {
39+
assert.equal(e.message, 'Failed creating collector >> A stream with name "Test" already exists');
40+
assert.equal(e.innerObject?.id, 'item-already-exists');
41+
}
42+
43+
// check if collector is in the list
44+
const collectors = await appManaging.getCollectors();
45+
const found = collectors.find(c => c.name === collectorName);
46+
if (!found) throw new Error('Should find collector with name: ' + collectorName);
47+
assert.equal(found, newCollector);
48+
});
49+
});

tests/test-utils/config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
serviceInfoURL: 'https://demo.datasafe.dev/reg/service/info',
3+
appId: 'hds-lib-tests',
4+
modelURL: 'https://model.datasafe.dev/pack.json'
5+
};

0 commit comments

Comments
 (0)