Skip to content

Commit 75f6263

Browse files
authored
feat(State): Merges xapi-state service. (#465)
1 parent 2f4f084 commit 75f6263

File tree

277 files changed

+6963
-88
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

277 files changed

+6963
-88
lines changed

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"scripts": {
1818
"start": "node dist/server.js",
1919
"build": "tsc",
20-
"test": "SERVICE_AWAIT_UPDATES=true mocha $(find dist -name '*.test.js') --timeout 6000 --exit",
20+
"test": "SERVICE_AWAIT_UPDATES=true AUTH_REPO=test mocha $(find dist -name '*.test.js') --timeout 6000 --exit",
2121
"test-s3": "MODELS_REPO=memory STORAGE_REPO=s3 npm run test",
2222
"test-google": "MODELS_REPO=memory STORAGE_REPO=google npm run test",
2323
"test-azure": "MODELS_REPO=memory STORAGE_REPO=azure npm run test",
@@ -40,7 +40,6 @@
4040
"dependencies": {
4141
"@learninglocker/xapi-activities": "4.4.4",
4242
"@learninglocker/xapi-agents": "4.4.3",
43-
"@learninglocker/xapi-state": "^4.4.0",
4443
"accept-language-parser": "^1.5.0",
4544
"boolean": "^0.2.0",
4645
"dotenv": "^5.0.0",
@@ -77,6 +76,7 @@
7776
"@types/object-hash": "1.3.0",
7877
"@types/redis": "2.8.6",
7978
"@types/source-map-support": "0.5.0",
79+
"@types/supertest": "2.0.8",
8080
"@types/uuid": "3.4.5",
8181
"@types/winston": "2.4.4",
8282
"colors": "1.3.3",
@@ -86,6 +86,7 @@
8686
"rimraf": "2.7.1",
8787
"shelljs": "0.8.3",
8888
"simple-git": "1.126.0",
89+
"supertest": "3.4.2",
8990
"tslint": "5.19.0",
9091
"tslint-consistent-codestyle": "1.15.1",
9192
"tslint-immutable": "4.9.1",
@@ -94,4 +95,4 @@
9495
"publishConfig": {
9596
"access": "public"
9697
}
97-
}
98+
}

src/apps/states/app.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/* tslint:disable:max-file-line-count */
2-
import presenterFactory from '@learninglocker/xapi-state/dist/expressPresenter';
3-
import serviceFactory from '@learninglocker/xapi-state/dist/service';
4-
import enTranslator from '@learninglocker/xapi-state/dist/translatorFactory/en';
52
import { Router } from 'express';
63
import AppConfig from './AppConfig';
4+
import presenterFactory from './expressPresenter';
75
import repoFactory from './repo/factory';
6+
import serviceFactory from './service';
7+
import enTranslator from './translatorFactory/en';
88

99
export default (appConfig: AppConfig): Router => {
1010
const translator = enTranslator;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ContainerURL } from '@azure/storage-blob';
2+
3+
export default interface Config {
4+
readonly containerUrl: ContainerURL;
5+
readonly subFolder: string;
6+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
Aborter,
3+
BlobURL,
4+
Models,
5+
} from '@azure/storage-blob';
6+
import Config from './Config';
7+
8+
export default (config: Config) => {
9+
return async (): Promise<void> => {
10+
// tslint:disable-next-line:no-let
11+
let marker;
12+
do {
13+
const listBlobsResponse: Models.ContainerListBlobFlatSegmentResponse =
14+
await config.containerUrl.listBlobFlatSegment(Aborter.none, marker);
15+
marker = listBlobsResponse.nextMarker;
16+
const deletePromises = listBlobsResponse.segment.blobItems.map(
17+
async (blobItem: Models.BlobItem) => {
18+
const blobUrl = BlobURL.fromContainerURL(config.containerUrl, blobItem.name);
19+
if (blobItem.name.startsWith(config.subFolder)) {
20+
await blobUrl.delete(Aborter.none);
21+
}
22+
},
23+
);
24+
await Promise.all(deletePromises);
25+
} while (marker !== '');
26+
};
27+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
import {
3+
Aborter,
4+
BlobURL,
5+
} from '@azure/storage-blob';
6+
import DeleteProfileContentOptions from '../repoFactory/options/DeleteStateContentOptions';
7+
import getStorageDir from '../utils/getStorageDir';
8+
import Config from './Config';
9+
10+
export default (config: Config) => {
11+
return async (opts: DeleteProfileContentOptions): Promise<void> => {
12+
const profileDir = getStorageDir({ subfolder: config.subFolder, lrs_id: opts.lrs_id });
13+
const filePath = `${profileDir}/${opts.key}`;
14+
15+
const blobUrl = BlobURL.fromContainerURL(config.containerUrl, filePath);
16+
17+
await blobUrl.delete(Aborter.none);
18+
};
19+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {
2+
Aborter,
3+
BlobURL,
4+
} from '@azure/storage-blob';
5+
import DeleteStatesContentOptions from '../repoFactory/options/DeleteStatesContentOptions';
6+
import getStorageDir from '../utils/getStorageDir';
7+
import Config from './Config';
8+
9+
export default (config: Config) => {
10+
return async (opts: DeleteStatesContentOptions): Promise<void> => {
11+
const dir = getStorageDir({ subfolder: config.subFolder, lrs_id: opts.lrs_id });
12+
13+
if (opts.keys.length === 0) {
14+
return;
15+
}
16+
17+
const promises = opts.keys.map(async (key) => {
18+
const filePath = `${dir}/${key}`;
19+
const blobUrl = BlobURL.fromContainerURL(config.containerUrl, filePath);
20+
await blobUrl.delete(Aborter.none);
21+
});
22+
23+
await Promise.all(promises);
24+
};
25+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Aborter, BlobURL } from '@azure/storage-blob';
2+
import GetProfileContentOptions from '../repoFactory/options/GetStateContentOptions';
3+
import GetProfileContentResult from '../repoFactory/results/GetStateContentResult';
4+
import getStorageDir from '../utils/getStorageDir';
5+
import Config from './Config';
6+
7+
export default (config: Config) => {
8+
return async (opts: GetProfileContentOptions): Promise<GetProfileContentResult> => {
9+
const profileDir = getStorageDir({ subfolder: config.subFolder, lrs_id: opts.lrs_id });
10+
const filePath = `${profileDir}/${opts.key}`;
11+
12+
const blobUrl = BlobURL.fromContainerURL(config.containerUrl, filePath);
13+
const content = (await blobUrl.download(Aborter.none, 0)).readableStreamBody;
14+
if (content === undefined) {
15+
throw new Error('Blob not found');
16+
}
17+
18+
return { content };
19+
};
20+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import StorageRepo from '../repoFactory/StorageRepo';
2+
import clearRepo from './clearRepo';
3+
import Config from './Config';
4+
import deleteStateContent from './deleteStateContent';
5+
import deleteStatesContent from './deleteStatesContent';
6+
import getStateContent from './getStateContent';
7+
import storeStateContent from './storeStateContent';
8+
9+
export default (config: Config): StorageRepo => {
10+
return {
11+
clearRepo: clearRepo(config),
12+
deleteStateContent: deleteStateContent(config),
13+
deleteStatesContent: deleteStatesContent(config),
14+
getStateContent: getStateContent(config),
15+
migrate: async () => Promise.resolve(),
16+
rollback: async () => Promise.resolve(),
17+
storeStateContent: storeStateContent(config),
18+
};
19+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {
2+
Aborter,
3+
BlobURL,
4+
BlockBlobURL,
5+
uploadStreamToBlockBlob,
6+
} from '@azure/storage-blob';
7+
import { Readable } from 'stream';
8+
import StoreStateContentOptions from '../repoFactory/options/StoreStateContentOptions';
9+
import getStorageDir from '../utils/getStorageDir';
10+
import Config from './Config';
11+
12+
const BYTES_IN_KILOBYTES = 1024;
13+
const KILOBYTES_IN_MEGABYTES = 1024;
14+
const FOUR = 4;
15+
16+
// https://github.com/Azure/azure-storage-js/blob/master/blob/samples/highlevel.sample.js
17+
const BUFFER_SIZE = FOUR * KILOBYTES_IN_MEGABYTES * BYTES_IN_KILOBYTES; // 4MB
18+
const MAX_BUFFERS = 20;
19+
20+
export default (config: Config) => {
21+
return async (opts: StoreStateContentOptions): Promise<void> => {
22+
return new Promise<void>(async (resolve, reject) => {
23+
const profileDir = getStorageDir({
24+
subfolder: config.subFolder,
25+
lrs_id: opts.lrs_id,
26+
});
27+
const filePath = `${profileDir}/${opts.key}`;
28+
29+
const blobUrl = BlobURL.fromContainerURL(config.containerUrl, filePath);
30+
const blockBlobUrl = BlockBlobURL.fromBlobURL(blobUrl);
31+
32+
opts.content.on('error', reject);
33+
34+
try {
35+
await uploadStreamToBlockBlob(
36+
Aborter.none,
37+
opts.content as Readable,
38+
blockBlobUrl,
39+
BUFFER_SIZE,
40+
MAX_BUFFERS,
41+
);
42+
} catch (err) {
43+
reject(err);
44+
}
45+
resolve();
46+
});
47+
};
48+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* tslint:disable:no-class */
2+
import BaseError from 'jscommons/dist/errors/BaseError';
3+
4+
export default class extends BaseError {
5+
constructor() {
6+
super();
7+
}
8+
}

0 commit comments

Comments
 (0)