@nu-art/file-upload-backend 0.401.8 → 0.500.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/module-pack.d.ts +1 -1
- package/core/module-pack.js +3 -3
- package/core/permissions.d.ts +1 -1
- package/core/permissions.js +18 -17
- package/modules/ModuleBE_AssetUploader.d.ts +5 -8
- package/modules/ModuleBE_AssetUploader.js +104 -31
- package/modules/ModuleBE_AssetsAPI.d.ts +4 -5
- package/modules/ModuleBE_AssetsAPI.js +60 -18
- package/modules/ModuleBE_AssetsDB.d.ts +18 -7
- package/modules/ModuleBE_AssetsDB.js +245 -188
- package/modules/ModuleBE_AssetsDeleted.d.ts +3 -3
- package/modules/ModuleBE_AssetsDeleted.js +1 -19
- package/modules/ModuleBE_AssetsStorage.d.ts +4 -1
- package/modules/ModuleBE_AssetsStorage.js +15 -32
- package/modules/ModuleBE_AssetsTemp.d.ts +3 -3
- package/modules/ModuleBE_AssetsTemp.js +1 -19
- package/modules/ModuleBE_BucketListener.d.ts +14 -1
- package/modules/ModuleBE_BucketListener.js +1 -1
- package/package.json +15 -14
package/core/module-pack.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const ModulePackBE_FileUploader: (import("../modules/ModuleBE_AssetsTemp.js").ModuleBE_AssetsTemp_Class | import("../modules/ModuleBE_BucketListener.js").ModuleBE_BucketListener_Class | import("../modules/ModuleBE_AssetsStorage.js").ModuleBE_AssetsStorage_Class | import("../modules/ModuleBE_AssetsDB.js").ModuleBE_AssetsDB_Class | import("@nu-art/push-pub-sub-backend").ModuleBE_PushPubSub_Class | import("@nu-art/push-pub-sub-backend/modules/ModuleBE_PushSessionDB").ModuleBE_PushSessionDB_Class | import("@nu-art/push-pub-sub-backend/modules/ModuleBE_PushSubscriptionDB").ModuleBE_PushSubscriptionDB_Class | import("@nu-art/
|
|
1
|
+
export declare const ModulePackBE_FileUploader: (import("../modules/ModuleBE_AssetsTemp.js").ModuleBE_AssetsTemp_Class | import("../modules/ModuleBE_BucketListener.js").ModuleBE_BucketListener_Class | import("../modules/ModuleBE_AssetsStorage.js").ModuleBE_AssetsStorage_Class | import("../modules/ModuleBE_AssetsDB.js").ModuleBE_AssetsDB_Class | import("@nu-art/push-pub-sub-backend").ModuleBE_PushPubSub_Class | import("@nu-art/push-pub-sub-backend/modules/ModuleBE_PushSessionDB").ModuleBE_PushSessionDB_Class | import("@nu-art/push-pub-sub-backend/modules/ModuleBE_PushSubscriptionDB").ModuleBE_PushSubscriptionDB_Class | import("@nu-art/push-pub-sub-backend/modules/ModuleBE_PushMessagesHistoryDB").ModuleBE_PushMessagesHistoryDB_Class | import("@nu-art/db-api-backend").ModuleBE_BaseApi_Class<import("@nu-art/push-pub-sub-shared/push-subscription/types").DatabaseDef_PushSubscription & import("@nu-art/db-api-shared").DB_Prototype<any, Omit<any, any>, import("@nu-art/ts-common").SubsetObjectByKeys<any, any>>> | import("../modules/ModuleBE_AssetsDeleted.js").ModuleBE_AssetsDeleted_Class | import("../modules/ModuleBE_AssetUploader.js").ModuleBE_AssetUploader_Class | import("../modules/ModuleBE_AssetsAPI.js").ModuleBE_AssetsAPI_Class | import("@nu-art/db-api-backend").ModuleBE_BaseApi_Class<import("@nu-art/file-upload-shared").DatabaseDef_AssetsTemp> | import("@nu-art/db-api-backend").ModuleBE_BaseApi_Class<import("@nu-art/file-upload-shared").DatabaseDef_Assets>)[];
|
package/core/module-pack.js
CHANGED
|
@@ -22,15 +22,15 @@ import { ModuleBE_BucketListener } from '../modules/ModuleBE_BucketListener.js';
|
|
|
22
22
|
import { ModulePackBE_PushPubSub } from '@nu-art/push-pub-sub-backend';
|
|
23
23
|
import { ModuleBE_AssetUploader } from '../modules/ModuleBE_AssetUploader.js';
|
|
24
24
|
import { ModuleBE_AssetsAPI } from '../modules/ModuleBE_AssetsAPI.js';
|
|
25
|
-
import {
|
|
25
|
+
import { createApisForDBModule } from '@nu-art/db-api-backend';
|
|
26
26
|
import { ModuleBE_AssetsStorage } from '../modules/ModuleBE_AssetsStorage.js';
|
|
27
27
|
import { ModuleBE_AssetsDeleted } from '../modules/ModuleBE_AssetsDeleted.js';
|
|
28
28
|
export const ModulePackBE_FileUploader = [
|
|
29
29
|
...ModulePackBE_PushPubSub,
|
|
30
30
|
ModuleBE_AssetUploader,
|
|
31
|
-
ModuleBE_AssetsTemp,
|
|
31
|
+
ModuleBE_AssetsTemp, createApisForDBModule(ModuleBE_AssetsTemp),
|
|
32
32
|
ModuleBE_AssetsDeleted,
|
|
33
33
|
ModuleBE_AssetsStorage,
|
|
34
|
-
ModuleBE_AssetsDB, ModuleBE_AssetsAPI,
|
|
34
|
+
ModuleBE_AssetsDB, createApisForDBModule(ModuleBE_AssetsDB), ModuleBE_AssetsAPI,
|
|
35
35
|
ModuleBE_BucketListener
|
|
36
36
|
];
|
package/core/permissions.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DefaultDef_Group } from '@nu-art/permissions-shared';
|
|
2
|
-
import { DefaultDef_Domain, DefaultDef_Package } from '@nu-art/permissions-backend
|
|
2
|
+
import { DefaultDef_Domain, DefaultDef_Package } from '@nu-art/permissions-backend';
|
|
3
3
|
export declare const PermissionsDomain_AssetsManager: Readonly<DefaultDef_Domain>;
|
|
4
4
|
export declare const PermissionsGroup_AssetsManager: Readonly<DefaultDef_Group>;
|
|
5
5
|
export declare const PermissionsGroup_AssetsViewer: Readonly<DefaultDef_Group>;
|
package/core/permissions.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
import { DefaultAccessLevel_Admin, DefaultAccessLevel_Delete, DefaultAccessLevel_Read, DefaultAccessLevel_Write } from '@nu-art/permissions-shared';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { DefaultAccessLevel_Admin, DefaultAccessLevel_Delete, DefaultAccessLevel_Read, DefaultAccessLevel_Write, toPermissionDomainId, toPermissionGroupId } from '@nu-art/permissions-shared';
|
|
2
|
+
import { CrudApiDef } from '@nu-art/db-api-shared';
|
|
3
|
+
import { ApiDef_Assets, ApiDef_AssetUploader, DBDef_Assets } from '@nu-art/file-upload-shared';
|
|
4
|
+
import { Domain_Developer } from '@nu-art/permissions-backend';
|
|
5
|
+
const AssetsCrudApiDef = CrudApiDef(DBDef_Assets.dbKey);
|
|
5
6
|
const Domain_AssetsManager_ID = '993c496c6aaad9c67723034137d26c42';
|
|
6
7
|
const _PermissionsDomain_AssetsManager = {
|
|
7
|
-
_id: Domain_AssetsManager_ID,
|
|
8
|
+
_id: toPermissionDomainId(Domain_AssetsManager_ID),
|
|
8
9
|
namespace: 'Assets',
|
|
9
10
|
dbNames: [],
|
|
10
11
|
customApis: [
|
|
11
|
-
{ path:
|
|
12
|
-
{ path:
|
|
13
|
-
{ path:
|
|
14
|
-
{ path:
|
|
15
|
-
{ path:
|
|
16
|
-
{ path:
|
|
17
|
-
{ path:
|
|
18
|
-
{ path: ApiDef_Assets.
|
|
19
|
-
{ path: ApiDef_AssetUploader.
|
|
12
|
+
{ path: AssetsCrudApiDef.deleteAll.path, accessLevel: DefaultAccessLevel_Admin.name },
|
|
13
|
+
{ path: AssetsCrudApiDef.deleteQuery.path, accessLevel: DefaultAccessLevel_Admin.name },
|
|
14
|
+
{ path: AssetsCrudApiDef.upsertAll.path, accessLevel: DefaultAccessLevel_Admin.name },
|
|
15
|
+
{ path: AssetsCrudApiDef.upsert.path, accessLevel: DefaultAccessLevel_Admin.name },
|
|
16
|
+
{ path: AssetsCrudApiDef.deleteUnique.path, accessLevel: DefaultAccessLevel_Delete.name },
|
|
17
|
+
{ path: AssetsCrudApiDef.queryUnique.path, accessLevel: DefaultAccessLevel_Read.name },
|
|
18
|
+
{ path: AssetsCrudApiDef.query.path, accessLevel: DefaultAccessLevel_Read.name },
|
|
19
|
+
{ path: ApiDef_Assets.getReadSignedUrl.path, accessLevel: DefaultAccessLevel_Read.name },
|
|
20
|
+
{ path: ApiDef_AssetUploader.getUploadUrl.path, accessLevel: DefaultAccessLevel_Write.name },
|
|
20
21
|
{
|
|
21
|
-
path: ApiDef_AssetUploader.
|
|
22
|
+
path: ApiDef_AssetUploader.processAssetManually.path,
|
|
22
23
|
domainId: Domain_Developer._id,
|
|
23
24
|
accessLevel: DefaultAccessLevel_Write.name
|
|
24
25
|
},
|
|
@@ -28,7 +29,7 @@ export const PermissionsDomain_AssetsManager = Object.freeze(_PermissionsDomain_
|
|
|
28
29
|
const PermissionsGroupId_AssetsViewer = '0773dcf3b9fbe5e595ef6e2a596b8939';
|
|
29
30
|
const PermissionsGroupId_AssetsManager = '3f5037358fba0ae1199047f2fa8add94';
|
|
30
31
|
const _PermissionsGroup_AssetsViewer = {
|
|
31
|
-
_id: PermissionsGroupId_AssetsViewer,
|
|
32
|
+
_id: toPermissionGroupId(PermissionsGroupId_AssetsViewer),
|
|
32
33
|
name: 'Assets Viewer',
|
|
33
34
|
uiLabel: 'Assets Viewer',
|
|
34
35
|
accessLevels: {
|
|
@@ -36,7 +37,7 @@ const _PermissionsGroup_AssetsViewer = {
|
|
|
36
37
|
}
|
|
37
38
|
};
|
|
38
39
|
const _PermissionsGroup_AssetsManager = {
|
|
39
|
-
_id: PermissionsGroupId_AssetsManager,
|
|
40
|
+
_id: toPermissionGroupId(PermissionsGroupId_AssetsManager),
|
|
40
41
|
name: 'Assets Manager',
|
|
41
42
|
uiLabel: 'Assets Manager',
|
|
42
43
|
accessLevels: {
|
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { TempSignedUrl, UI_Asset } from '@nu-art/file-upload-shared';
|
|
4
|
-
import { ApiDef, BaseHttpRequest, TypedApi } from '@nu-art/thunderstorm-shared';
|
|
1
|
+
import type { ApiDef, GeneralApi } from '@nu-art/api-types';
|
|
2
|
+
import { ModuleBase_AssetUploader, TempSignedUrl, UI_Asset, type IAssetUploadRequest, type UploaderConfig } from '@nu-art/file-upload-shared';
|
|
5
3
|
export type ServerFilesToUpload = UI_Asset & {
|
|
6
4
|
file: Buffer;
|
|
7
5
|
};
|
|
8
6
|
type Config = UploaderConfig & {
|
|
9
|
-
|
|
7
|
+
baseUrl?: string;
|
|
10
8
|
};
|
|
11
9
|
export declare class ModuleBE_AssetUploader_Class extends ModuleBase_AssetUploader<Config> {
|
|
12
10
|
constructor();
|
|
13
|
-
|
|
14
|
-
createRequest<API extends TypedApi<any, any, any, any>>(uploadFile: ApiDef<API>): BaseHttpRequest<API>;
|
|
11
|
+
createRequest<API extends GeneralApi>(uploadFile: ApiDef<API>): IAssetUploadRequest<API>;
|
|
15
12
|
upload(files: ServerFilesToUpload[]): UI_Asset[];
|
|
16
|
-
protected subscribeToPush(
|
|
13
|
+
protected subscribeToPush(_toSubscribe: TempSignedUrl[]): Promise<void>;
|
|
17
14
|
}
|
|
18
15
|
export declare const ModuleBE_AssetUploader: ModuleBE_AssetUploader_Class;
|
|
19
16
|
export {};
|
|
@@ -1,45 +1,118 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
import { ApiDef_AssetUploader, ModuleBase_AssetUploader } from '@nu-art/file-upload-shared';
|
|
2
|
+
function createFetchRequest(apiDef, baseUrl) {
|
|
3
|
+
let url = '';
|
|
4
|
+
const headers = {};
|
|
5
|
+
let body;
|
|
6
|
+
return {
|
|
7
|
+
setUrl(u) {
|
|
8
|
+
url = u;
|
|
9
|
+
return this;
|
|
10
|
+
},
|
|
11
|
+
setHeader(key, value) {
|
|
12
|
+
headers[key] = value;
|
|
13
|
+
return this;
|
|
14
|
+
},
|
|
15
|
+
setTimeout(_ms) {
|
|
16
|
+
return this;
|
|
17
|
+
},
|
|
18
|
+
setBody(b) {
|
|
19
|
+
body = b;
|
|
20
|
+
return this;
|
|
21
|
+
},
|
|
22
|
+
setOnProgressListener(_cb) {
|
|
23
|
+
return this;
|
|
24
|
+
},
|
|
25
|
+
executeSync: async () => {
|
|
26
|
+
const fullUrl = url || `${(baseUrl || '').replace(/\/$/, '')}/${apiDef.path}`;
|
|
27
|
+
const res = await fetch(fullUrl, {
|
|
28
|
+
method: apiDef.method ?? 'POST',
|
|
29
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
30
|
+
body: body !== undefined ? (typeof body === 'string' ? body : JSON.stringify(body)) : undefined
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok)
|
|
33
|
+
throw new Error(`Request failed: ${res.status} ${res.statusText}`);
|
|
34
|
+
const text = await res.text();
|
|
35
|
+
return text ? JSON.parse(text) : undefined;
|
|
36
|
+
},
|
|
37
|
+
execute(cb) {
|
|
38
|
+
this.executeSync().then(cb).catch((e) => {
|
|
39
|
+
throw e;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
22
44
|
export class ModuleBE_AssetUploader_Class extends ModuleBase_AssetUploader {
|
|
23
45
|
constructor() {
|
|
24
46
|
super();
|
|
47
|
+
const baseUrl = () => this.config?.baseUrl ?? '';
|
|
25
48
|
this.vv1 = {
|
|
26
|
-
getUploadUrl:
|
|
27
|
-
processAssetManually:
|
|
49
|
+
getUploadUrl: (body) => createFetchRequest(ApiDef_AssetUploader.getUploadUrl, baseUrl()).setBody(body),
|
|
50
|
+
processAssetManually: (params) => {
|
|
51
|
+
const query = {};
|
|
52
|
+
if (params.feId)
|
|
53
|
+
query.feId = params.feId;
|
|
54
|
+
const req = createFetchRequest({ method: 'GET', path: ApiDef_AssetUploader.processAssetManually.path }, baseUrl());
|
|
55
|
+
const path = ApiDef_AssetUploader.processAssetManually.path;
|
|
56
|
+
return {
|
|
57
|
+
...req,
|
|
58
|
+
setUrlParam(key, value) {
|
|
59
|
+
query[key] = value;
|
|
60
|
+
return this;
|
|
61
|
+
},
|
|
62
|
+
execute() {
|
|
63
|
+
const qs = Object.keys(query).length ? '?' + Object.entries(query).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&') : '';
|
|
64
|
+
req.setUrl(`${(baseUrl() || '').replace(/\/$/, '')}/${path}${qs}`);
|
|
65
|
+
void req.executeSync();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
28
69
|
};
|
|
29
70
|
}
|
|
30
|
-
init() {
|
|
31
|
-
super.init();
|
|
32
|
-
AxiosHttpModule.setRequestOption(this.config.requestConfig);
|
|
33
|
-
}
|
|
34
71
|
createRequest(uploadFile) {
|
|
35
|
-
|
|
72
|
+
let url = '';
|
|
73
|
+
const headers = {};
|
|
74
|
+
let body;
|
|
75
|
+
return {
|
|
76
|
+
setUrl(u) {
|
|
77
|
+
url = u;
|
|
78
|
+
return this;
|
|
79
|
+
},
|
|
80
|
+
setHeader(key, value) {
|
|
81
|
+
headers[key] = value;
|
|
82
|
+
return this;
|
|
83
|
+
},
|
|
84
|
+
setTimeout(_ms) {
|
|
85
|
+
return this;
|
|
86
|
+
},
|
|
87
|
+
setBody(b) {
|
|
88
|
+
body = b;
|
|
89
|
+
return this;
|
|
90
|
+
},
|
|
91
|
+
setOnProgressListener(_cb) {
|
|
92
|
+
return this;
|
|
93
|
+
},
|
|
94
|
+
executeSync: async () => {
|
|
95
|
+
const res = await fetch(url, {
|
|
96
|
+
method: uploadFile.method ?? 'PUT',
|
|
97
|
+
headers: { ...headers },
|
|
98
|
+
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
99
|
+
});
|
|
100
|
+
if (!res.ok)
|
|
101
|
+
throw new Error(`Upload failed: ${res.status} ${res.statusText}`);
|
|
102
|
+
return res.json().catch(() => undefined);
|
|
103
|
+
},
|
|
104
|
+
execute(cb) {
|
|
105
|
+
this.executeSync().then(cb).catch((e) => {
|
|
106
|
+
throw e;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
36
110
|
}
|
|
37
111
|
upload(files) {
|
|
38
112
|
return this.uploadImpl(files);
|
|
39
113
|
}
|
|
40
|
-
async subscribeToPush(
|
|
41
|
-
// Not
|
|
42
|
-
// We said timeout
|
|
114
|
+
async subscribeToPush(_toSubscribe) {
|
|
115
|
+
// Not used in backend flow
|
|
43
116
|
}
|
|
44
117
|
}
|
|
45
118
|
export const ModuleBE_AssetUploader = new ModuleBE_AssetUploader_Class();
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export declare class ModuleBE_AssetsAPI_Class extends
|
|
1
|
+
import { Module } from '@nu-art/ts-common';
|
|
2
|
+
import { API_Assets } from '@nu-art/file-upload-shared';
|
|
3
|
+
export declare class ModuleBE_AssetsAPI_Class extends Module {
|
|
4
4
|
constructor();
|
|
5
|
-
|
|
6
|
-
private getReadSignedUrl;
|
|
5
|
+
getReadSignedUrl(body: API_Assets['getReadSignedUrl']['Body']): Promise<API_Assets['getReadSignedUrl']['Response']>;
|
|
7
6
|
}
|
|
8
7
|
export declare const ModuleBE_AssetsAPI: ModuleBE_AssetsAPI_Class;
|
|
@@ -1,22 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export class ModuleBE_AssetsAPI_Class extends ModuleBE_BaseApi_Class {
|
|
6
|
-
constructor() {
|
|
7
|
-
super(ModuleBE_AssetsDB);
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
8
5
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
14
31
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
35
|
+
import { Module } from '@nu-art/ts-common';
|
|
36
|
+
import { ApiHandler } from '@nu-art/http-server';
|
|
37
|
+
import { ModuleBE_AssetsDB } from './ModuleBE_AssetsDB.js';
|
|
38
|
+
import { ModuleBE_AssetsStorage } from './ModuleBE_AssetsStorage.js';
|
|
39
|
+
import { ApiDef_Assets } from '@nu-art/file-upload-shared';
|
|
40
|
+
let ModuleBE_AssetsAPI_Class = (() => {
|
|
41
|
+
let _classSuper = Module;
|
|
42
|
+
let _instanceExtraInitializers = [];
|
|
43
|
+
let _getReadSignedUrl_decorators;
|
|
44
|
+
return class ModuleBE_AssetsAPI_Class extends _classSuper {
|
|
45
|
+
static {
|
|
46
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
47
|
+
_getReadSignedUrl_decorators = [ApiHandler(ApiDef_Assets.getReadSignedUrl)];
|
|
48
|
+
__esDecorate(this, null, _getReadSignedUrl_decorators, { kind: "method", name: "getReadSignedUrl", static: false, private: false, access: { has: obj => "getReadSignedUrl" in obj, get: obj => obj.getReadSignedUrl }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
49
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
50
|
+
}
|
|
51
|
+
constructor() {
|
|
52
|
+
super('AssetsAPI');
|
|
53
|
+
__runInitializers(this, _instanceExtraInitializers);
|
|
54
|
+
}
|
|
55
|
+
async getReadSignedUrl(body) {
|
|
56
|
+
const dbAsset = await ModuleBE_AssetsDB.query.uniqueAssert(body._id);
|
|
57
|
+
return {
|
|
58
|
+
signedUrl: (await (await ModuleBE_AssetsStorage.getFile(dbAsset)).getReadSignedUrl()).signedUrl
|
|
59
|
+
};
|
|
60
|
+
}
|
|
20
61
|
};
|
|
21
|
-
}
|
|
62
|
+
})();
|
|
63
|
+
export { ModuleBE_AssetsAPI_Class };
|
|
22
64
|
export const ModuleBE_AssetsAPI = new ModuleBE_AssetsAPI_Class();
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { TypedMap } from '@nu-art/ts-common';
|
|
2
|
-
import {
|
|
2
|
+
import { ModuleBE_BaseDB, PostWriteProcessingDataShape } from '@nu-art/db-api-backend';
|
|
3
3
|
import { Clause_Where } from '@nu-art/firebase-shared';
|
|
4
4
|
import { OnAssetUploaded } from './ModuleBE_BucketListener.js';
|
|
5
|
-
import { DB_Asset,
|
|
6
|
-
import { CollectionActionType
|
|
5
|
+
import { API_AssetUploader, DB_Asset, DatabaseDef_Assets, TempSignedUrl, UI_Asset } from '@nu-art/file-upload-shared';
|
|
6
|
+
import { CollectionActionType } from '@nu-art/firebase-backend/firestore-v3/FirestoreCollectionV3';
|
|
7
7
|
import { FileMetadata } from '@google-cloud/storage';
|
|
8
8
|
import { FileWrapper } from '@nu-art/firebase-backend';
|
|
9
9
|
import { Transaction } from 'firebase-admin/firestore';
|
|
10
|
-
type
|
|
10
|
+
/** Local type for cleanup scheduler (replaces thunderstorm-backend). */
|
|
11
|
+
export type CleanupDetails = {
|
|
12
|
+
moduleKey: string;
|
|
13
|
+
interval: number;
|
|
14
|
+
cleanup: () => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
export interface OnCleanupSchedulerAct {
|
|
17
|
+
__onCleanupSchedulerAct(): CleanupDetails;
|
|
18
|
+
}
|
|
19
|
+
type MyConfig = {
|
|
11
20
|
authKey: string;
|
|
12
21
|
bucketName?: string;
|
|
13
22
|
storagePath: string;
|
|
@@ -30,11 +39,13 @@ export type FileTypeValidation = {
|
|
|
30
39
|
export declare const DefaultMimetypeValidator: (file: FileWrapper, doc: DB_Asset) => Promise<import("file-type").FileTypeResult>;
|
|
31
40
|
export type FileValidator = (file: FileWrapper, doc: DB_Asset) => Promise<FileTypeResult | undefined>;
|
|
32
41
|
export declare const fileSizeValidator: (file: FileWrapper, metadata: FileMetadata, minSizeInBytes?: number, maxSizeInBytes?: number) => Promise<boolean>;
|
|
33
|
-
export declare class ModuleBE_AssetsDB_Class extends ModuleBE_BaseDB<
|
|
42
|
+
export declare class ModuleBE_AssetsDB_Class extends ModuleBE_BaseDB<DatabaseDef_Assets, MyConfig> implements OnCleanupSchedulerAct, OnAssetUploaded {
|
|
34
43
|
constructor();
|
|
35
44
|
mimeTypeValidator: TypedMap<FileValidator>;
|
|
36
45
|
fileValidator: TypedMap<FileTypeValidation>;
|
|
37
|
-
protected postWriteProcessing(data:
|
|
46
|
+
protected postWriteProcessing(data: PostWriteProcessingDataShape<DB_Asset>, actionType: CollectionActionType, transaction?: Transaction): Promise<void>;
|
|
47
|
+
getUploadUrl(body: API_AssetUploader['getUploadUrl']['Body']): Promise<API_AssetUploader['getUploadUrl']['Response']>;
|
|
48
|
+
processAssetManually(params: API_AssetUploader['processAssetManually']['Params']): Promise<API_AssetUploader['processAssetManually']['Response']>;
|
|
38
49
|
init(): void;
|
|
39
50
|
getAssetsContent(assetIds: string[]): Promise<AssetContent[]>;
|
|
40
51
|
registerTypeValidator(mimeType: string, validator: (file: FileWrapper, doc: DB_Asset) => Promise<void>): void;
|
|
@@ -43,7 +54,7 @@ export declare class ModuleBE_AssetsDB_Class extends ModuleBE_BaseDB<DBProto_Ass
|
|
|
43
54
|
__onCleanupSchedulerAct(): CleanupDetails;
|
|
44
55
|
private cleanup;
|
|
45
56
|
getUrl: (files: UI_Asset[]) => Promise<TempSignedUrl[]>;
|
|
46
|
-
|
|
57
|
+
processAssetImpl: (feId?: string) => Promise<void[]>;
|
|
47
58
|
__processAsset: (filePath?: string) => Promise<void>;
|
|
48
59
|
private notifyFrontend;
|
|
49
60
|
}
|
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
+
}
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
1
35
|
/*
|
|
2
36
|
* Permissions management system, define access level for each of
|
|
3
37
|
* your server apis, and restrict users by giving them access levels
|
|
@@ -16,9 +50,10 @@
|
|
|
16
50
|
* See the License for the specific language governing permissions and
|
|
17
51
|
* limitations under the License.
|
|
18
52
|
*/
|
|
19
|
-
import { ApiException,
|
|
53
|
+
import { ApiException, BadImplementationException, currentTimeMillis, Day, filterInstances, generateHex, Hour, ImplementationMissingException, MB, Minute, MUSTNeverHappenException, ThisShouldNotHappenException } from '@nu-art/ts-common';
|
|
20
54
|
import { ModuleBE_AssetsTemp } from './ModuleBE_AssetsTemp.js';
|
|
21
|
-
import {
|
|
55
|
+
import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
56
|
+
import { ApiHandler } from '@nu-art/http-server';
|
|
22
57
|
import { ModuleBE_AssetsStorage } from './ModuleBE_AssetsStorage.js';
|
|
23
58
|
import { ApiDef_AssetUploader, DBDef_Assets, FileStatus } from '@nu-art/file-upload-shared';
|
|
24
59
|
import { PushMessageBE_FileUploadStatus } from '../core/messages.js';
|
|
@@ -39,205 +74,227 @@ export const fileSizeValidator = async (file, metadata, minSizeInBytes = 0, maxS
|
|
|
39
74
|
throw new ThisShouldNotHappenException(`could not resolve metadata.size for file: ${file.path}`);
|
|
40
75
|
return fileSize >= minSizeInBytes && fileSize <= maxSizeInBytes;
|
|
41
76
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
let ModuleBE_AssetsDB_Class = (() => {
|
|
78
|
+
let _classSuper = ModuleBE_BaseDB;
|
|
79
|
+
let _instanceExtraInitializers = [];
|
|
80
|
+
let _getUploadUrl_decorators;
|
|
81
|
+
let _processAssetManually_decorators;
|
|
82
|
+
return class ModuleBE_AssetsDB_Class extends _classSuper {
|
|
83
|
+
static {
|
|
84
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
85
|
+
_getUploadUrl_decorators = [ApiHandler(ApiDef_AssetUploader.getUploadUrl)];
|
|
86
|
+
_processAssetManually_decorators = [ApiHandler(ApiDef_AssetUploader.processAssetManually)];
|
|
87
|
+
__esDecorate(this, null, _getUploadUrl_decorators, { kind: "method", name: "getUploadUrl", static: false, private: false, access: { has: obj => "getUploadUrl" in obj, get: obj => obj.getUploadUrl }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
88
|
+
__esDecorate(this, null, _processAssetManually_decorators, { kind: "method", name: "processAssetManually", static: false, private: false, access: { has: obj => "processAssetManually" in obj, get: obj => obj.processAssetManually }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
89
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
90
|
+
}
|
|
91
|
+
constructor() {
|
|
92
|
+
super(DBDef_Assets);
|
|
93
|
+
this.setDefaultConfig({
|
|
94
|
+
...this.config,
|
|
95
|
+
storagePath: 'assets',
|
|
96
|
+
pathRegexp: '^assets/.*',
|
|
97
|
+
authKey: 'file-uploader'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
mimeTypeValidator = (__runInitializers(this, _instanceExtraInitializers), {});
|
|
101
|
+
fileValidator = {};
|
|
102
|
+
async postWriteProcessing(data, actionType, transaction) {
|
|
103
|
+
const deleted = data.deleted ? (Array.isArray(data.deleted) ? data.deleted : [data.deleted]) : [];
|
|
104
|
+
if (deleted.length)
|
|
105
|
+
await ModuleBE_AssetsDeleted.set.all(deleted, transaction);
|
|
106
|
+
}
|
|
107
|
+
async getUploadUrl(body) {
|
|
108
|
+
return this.getUrl(body);
|
|
109
|
+
}
|
|
110
|
+
async processAssetManually(params) {
|
|
111
|
+
await this.processAssetImpl(params.feId);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
init() {
|
|
115
|
+
super.init();
|
|
116
|
+
this.registerVersionUpgradeProcessor('1.0.1', async (assets) => {
|
|
117
|
+
assets.forEach(asset => {
|
|
118
|
+
// @ts-ignore
|
|
119
|
+
delete asset._audit;
|
|
120
|
+
});
|
|
68
121
|
});
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
122
|
+
const originalQuery = this.query;
|
|
123
|
+
this.query = {
|
|
124
|
+
...originalQuery,
|
|
125
|
+
unique: async (_id, transaction) => {
|
|
126
|
+
const dbAsset = await originalQuery.uniqueAssert(_id, transaction);
|
|
127
|
+
const signedUrl = (dbAsset.signedUrl?.validUntil || 0) > currentTimeMillis() ? dbAsset.signedUrl : undefined;
|
|
128
|
+
if (!signedUrl) {
|
|
129
|
+
const url = await ModuleBE_AssetsStorage.getReadSignedUrl(dbAsset);
|
|
130
|
+
dbAsset.signedUrl = {
|
|
131
|
+
url,
|
|
132
|
+
validUntil: currentTimeMillis() + Day - Minute
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return dbAsset;
|
|
82
136
|
}
|
|
83
|
-
return dbAsset;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
async getAssetsContent(assetIds) {
|
|
88
|
-
const assetsToSync = filterInstances(await ModuleBE_AssetsDB.query.all(assetIds));
|
|
89
|
-
const assetFiles = await Promise.all(assetsToSync.map(asset => ModuleBE_AssetsStorage.getFile(asset)));
|
|
90
|
-
const assetContent = await Promise.all(assetFiles.map(asset => asset.read()));
|
|
91
|
-
return assetIds.map((id, index) => ({ asset: assetsToSync[index], content: assetContent[index] }));
|
|
92
|
-
}
|
|
93
|
-
registerTypeValidator(mimeType, validator) {
|
|
94
|
-
}
|
|
95
|
-
async queryUnique(where, transaction) {
|
|
96
|
-
const dbAsset = await this.query.uniqueCustom({ where });
|
|
97
|
-
const signedUrl = (dbAsset.signedUrl?.validUntil || 0) > currentTimeMillis() ? dbAsset.signedUrl : undefined;
|
|
98
|
-
if (!signedUrl) {
|
|
99
|
-
const url = await ModuleBE_AssetsStorage.getReadSignedUrl(dbAsset);
|
|
100
|
-
dbAsset.signedUrl = {
|
|
101
|
-
url,
|
|
102
|
-
validUntil: currentTimeMillis() + Day - Minute
|
|
103
137
|
};
|
|
104
138
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.fileValidator[key] = validationConfig;
|
|
111
|
-
};
|
|
112
|
-
__onCleanupSchedulerAct() {
|
|
113
|
-
return {
|
|
114
|
-
moduleKey: this.getName(),
|
|
115
|
-
interval: Day,
|
|
116
|
-
cleanup: () => this.cleanup(),
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
cleanup = async (interval = Hour, module = ModuleBE_AssetsTemp) => {
|
|
120
|
-
const entries = await module.query.custom({ where: { timestamp: { $lt: currentTimeMillis() - interval } } });
|
|
121
|
-
await Promise.all(entries.map(async (dbAsset) => {
|
|
122
|
-
const file = await ModuleBE_AssetsStorage.getFile(dbAsset);
|
|
123
|
-
if (!(await file.exists()))
|
|
124
|
-
return;
|
|
125
|
-
await file.delete();
|
|
126
|
-
}));
|
|
127
|
-
await module.delete.query({ where: { timestamp: { $lt: currentTimeMillis() - interval } } });
|
|
128
|
-
};
|
|
129
|
-
getUrl = async (files) => {
|
|
130
|
-
const bucketName = this.config?.bucketName;
|
|
131
|
-
const bucket = await ModuleBE_AssetsStorage.storage.getOrCreateBucket(bucketName);
|
|
132
|
-
return Promise.all(files.map(async (_file) => {
|
|
133
|
-
const key = _file.key || _file.mimeType;
|
|
134
|
-
// this will fail the entire request... should it?
|
|
135
|
-
if (!this.fileValidator[key])
|
|
136
|
-
throw new ImplementationMissingException(`Missing validator for type ${key}`);
|
|
137
|
-
const _id = generateHex(32);
|
|
138
|
-
const path = `${this.config.storagePath}/${_id}`;
|
|
139
|
-
const dbAsset = {
|
|
140
|
-
timestamp: currentTimeMillis(),
|
|
141
|
-
_id,
|
|
142
|
-
feId: _file.feId,
|
|
143
|
-
name: _file.name,
|
|
144
|
-
ext: _file.name.substring(_file.name.toLowerCase().lastIndexOf('.') + 1),
|
|
145
|
-
mimeType: _file.mimeType,
|
|
146
|
-
key,
|
|
147
|
-
path,
|
|
148
|
-
bucketName: bucket.bucketName
|
|
149
|
-
};
|
|
150
|
-
if (_file.public)
|
|
151
|
-
dbAsset.public = _file.public;
|
|
152
|
-
const dbTempMeta = await ModuleBE_AssetsTemp.set.item(dbAsset);
|
|
153
|
-
const fileWrapper = await bucket.getFile(dbTempMeta.path);
|
|
154
|
-
const url = await fileWrapper.getWriteSignedUrl(_file.mimeType, Hour);
|
|
155
|
-
return {
|
|
156
|
-
signedUrl: url.signedUrl,
|
|
157
|
-
asset: dbTempMeta
|
|
158
|
-
};
|
|
159
|
-
}));
|
|
160
|
-
};
|
|
161
|
-
processAssetManually = async (feId) => {
|
|
162
|
-
let query = { limit: 1 };
|
|
163
|
-
if (feId)
|
|
164
|
-
query = { where: { feId } };
|
|
165
|
-
const unprocessedFiles = await ModuleBE_AssetsTemp.query.custom(query);
|
|
166
|
-
return Promise.all(unprocessedFiles.map(asset => this.__processAsset(asset.path)));
|
|
167
|
-
};
|
|
168
|
-
__processAsset = async (filePath) => {
|
|
169
|
-
if (!filePath)
|
|
170
|
-
throw new MUSTNeverHappenException('Missing file path');
|
|
171
|
-
if (!filePath.match(this.config.pathRegexp))
|
|
172
|
-
return this.logVerbose(`File was added to storage in path: ${filePath}, NOT via file uploader`);
|
|
173
|
-
this.logDebug(`Looking for file with path: ${filePath}`);
|
|
174
|
-
let dbTempAsset;
|
|
175
|
-
try {
|
|
176
|
-
dbTempAsset = await ModuleBE_AssetsTemp.query.uniqueWhere({ path: filePath });
|
|
139
|
+
async getAssetsContent(assetIds) {
|
|
140
|
+
const assetsToSync = filterInstances(await ModuleBE_AssetsDB.query.all(assetIds));
|
|
141
|
+
const assetFiles = await Promise.all(assetsToSync.map(asset => ModuleBE_AssetsStorage.getFile(asset)));
|
|
142
|
+
const assetContent = await Promise.all(assetFiles.map(asset => asset.read()));
|
|
143
|
+
return assetIds.map((id, index) => ({ asset: assetsToSync[index], content: assetContent[index] }));
|
|
177
144
|
}
|
|
178
|
-
|
|
179
|
-
return;
|
|
145
|
+
registerTypeValidator(mimeType, validator) {
|
|
180
146
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
mimetypeValidator = validationConfig.validator;
|
|
191
|
-
if (!mimetypeValidator && validationConfig.fileType && validationConfig.fileType.includes(dbTempAsset.mimeType))
|
|
192
|
-
mimetypeValidator = this.mimeTypeValidator[dbTempAsset.mimeType];
|
|
193
|
-
if (!mimetypeValidator)
|
|
194
|
-
return this.notifyFrontend(FileStatus.ErrorNoValidator, dbTempAsset);
|
|
195
|
-
const file = await ModuleBE_AssetsStorage.getFile(dbTempAsset);
|
|
196
|
-
try {
|
|
197
|
-
const metadata = (await file.getMetadata());
|
|
198
|
-
if (!metadata)
|
|
199
|
-
return this.notifyFrontend(FileStatus.ErrorRetrievingMetadata, dbTempAsset);
|
|
200
|
-
await fileSizeValidator(file, metadata, validationConfig.minSize, validationConfig.maxSize);
|
|
201
|
-
const fileType = await mimetypeValidator(file, dbTempAsset);
|
|
202
|
-
dbTempAsset.md5Hash = metadata.md5Hash;
|
|
203
|
-
if (fileType && dbTempAsset.ext !== fileType.ext) {
|
|
204
|
-
this.logWarning(`renaming the file extension name: ${dbTempAsset.ext} => ${fileType.ext}`);
|
|
205
|
-
dbTempAsset.ext = fileType.ext;
|
|
147
|
+
async queryUnique(where, transaction) {
|
|
148
|
+
const dbAsset = await this.query.uniqueCustom({ where });
|
|
149
|
+
const signedUrl = (dbAsset.signedUrl?.validUntil || 0) > currentTimeMillis() ? dbAsset.signedUrl : undefined;
|
|
150
|
+
if (!signedUrl) {
|
|
151
|
+
const url = await ModuleBE_AssetsStorage.getReadSignedUrl(dbAsset);
|
|
152
|
+
dbAsset.signedUrl = {
|
|
153
|
+
url,
|
|
154
|
+
validUntil: currentTimeMillis() + Day - Minute
|
|
155
|
+
};
|
|
206
156
|
}
|
|
157
|
+
return dbAsset;
|
|
207
158
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
159
|
+
register = (key, validationConfig) => {
|
|
160
|
+
if (this.fileValidator[key] && this.fileValidator[key] !== validationConfig)
|
|
161
|
+
throw new BadImplementationException(`File Validator already exists for key: ${key}`);
|
|
162
|
+
this.fileValidator[key] = validationConfig;
|
|
163
|
+
};
|
|
164
|
+
__onCleanupSchedulerAct() {
|
|
165
|
+
return {
|
|
166
|
+
moduleKey: this.getName(),
|
|
167
|
+
interval: Day,
|
|
168
|
+
cleanup: () => this.cleanup(),
|
|
169
|
+
};
|
|
212
170
|
}
|
|
213
|
-
|
|
171
|
+
cleanup = async (interval = Hour, module = ModuleBE_AssetsTemp) => {
|
|
172
|
+
const entries = await module.query.custom({ where: { timestamp: { $lt: currentTimeMillis() - interval } } });
|
|
173
|
+
await Promise.all(entries.map(async (dbAsset) => {
|
|
174
|
+
const file = await ModuleBE_AssetsStorage.getFile(dbAsset);
|
|
175
|
+
if (!(await file.exists()))
|
|
176
|
+
return;
|
|
177
|
+
await file.delete();
|
|
178
|
+
}));
|
|
179
|
+
await module.delete.query({ where: { timestamp: { $lt: currentTimeMillis() - interval } } });
|
|
180
|
+
};
|
|
181
|
+
getUrl = async (files) => {
|
|
182
|
+
const bucketName = this.config?.bucketName;
|
|
183
|
+
const bucket = await ModuleBE_AssetsStorage.storage.getOrCreateBucket(bucketName);
|
|
184
|
+
return Promise.all(files.map(async (_file) => {
|
|
185
|
+
const key = _file.key || _file.mimeType;
|
|
186
|
+
// this will fail the entire request... should it?
|
|
187
|
+
if (!this.fileValidator[key])
|
|
188
|
+
throw new ImplementationMissingException(`Missing validator for type ${key}`);
|
|
189
|
+
const _id = generateHex(32);
|
|
190
|
+
const path = `${this.config.storagePath}/${_id}`;
|
|
191
|
+
const dbAsset = {
|
|
192
|
+
timestamp: currentTimeMillis(),
|
|
193
|
+
_id,
|
|
194
|
+
feId: _file.feId,
|
|
195
|
+
name: _file.name,
|
|
196
|
+
ext: _file.name.substring(_file.name.toLowerCase().lastIndexOf('.') + 1),
|
|
197
|
+
mimeType: _file.mimeType,
|
|
198
|
+
key,
|
|
199
|
+
path,
|
|
200
|
+
bucketName: bucket.bucketName
|
|
201
|
+
};
|
|
202
|
+
if (_file.public)
|
|
203
|
+
dbAsset.public = _file.public;
|
|
204
|
+
const dbTempMeta = await ModuleBE_AssetsTemp.set.item(dbAsset);
|
|
205
|
+
const fileWrapper = await bucket.getFile(dbTempMeta.path);
|
|
206
|
+
const url = await fileWrapper.getWriteSignedUrl(_file.mimeType, Hour);
|
|
207
|
+
return {
|
|
208
|
+
signedUrl: url.signedUrl,
|
|
209
|
+
asset: dbTempMeta
|
|
210
|
+
};
|
|
211
|
+
}));
|
|
212
|
+
};
|
|
213
|
+
processAssetImpl = async (feId) => {
|
|
214
|
+
let query = { limit: 1 };
|
|
215
|
+
if (feId)
|
|
216
|
+
query = { where: { feId } };
|
|
217
|
+
const unprocessedFiles = await ModuleBE_AssetsTemp.query.custom(query);
|
|
218
|
+
return Promise.all(unprocessedFiles.map(asset => this.__processAsset(asset.path)));
|
|
219
|
+
};
|
|
220
|
+
__processAsset = async (filePath) => {
|
|
221
|
+
if (!filePath)
|
|
222
|
+
throw new MUSTNeverHappenException('Missing file path');
|
|
223
|
+
if (!filePath.match(this.config.pathRegexp))
|
|
224
|
+
return this.logVerbose(`File was added to storage in path: ${filePath}, NOT via file uploader`);
|
|
225
|
+
this.logDebug(`Looking for file with path: ${filePath}`);
|
|
226
|
+
let dbTempAsset;
|
|
214
227
|
try {
|
|
215
|
-
|
|
216
|
-
await file.makePublic();
|
|
228
|
+
dbTempAsset = await ModuleBE_AssetsTemp.query.uniqueWhere({ path: filePath });
|
|
217
229
|
}
|
|
218
230
|
catch (e) {
|
|
219
|
-
return
|
|
231
|
+
return;
|
|
220
232
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
233
|
+
if (!dbTempAsset)
|
|
234
|
+
throw new ThisShouldNotHappenException(`Could not find meta for file with path: ${filePath}`);
|
|
235
|
+
// Temp and main asset share shape; cast for APIs that expect DB_Asset
|
|
236
|
+
const assetForApi = dbTempAsset;
|
|
237
|
+
await this.notifyFrontend(FileStatus.Processing, assetForApi);
|
|
238
|
+
this.logDebug(`Found temp meta with _id: ${dbTempAsset._id}`, dbTempAsset);
|
|
239
|
+
const validationConfig = this.fileValidator[dbTempAsset.key];
|
|
240
|
+
if (!validationConfig)
|
|
241
|
+
return this.notifyFrontend(FileStatus.ErrorNoConfig, assetForApi);
|
|
242
|
+
let mimetypeValidator = DefaultMimetypeValidator;
|
|
243
|
+
if (validationConfig.validator)
|
|
244
|
+
mimetypeValidator = validationConfig.validator;
|
|
245
|
+
if (!mimetypeValidator && validationConfig.fileType && validationConfig.fileType.includes(dbTempAsset.mimeType))
|
|
246
|
+
mimetypeValidator = this.mimeTypeValidator[dbTempAsset.mimeType];
|
|
247
|
+
if (!mimetypeValidator)
|
|
248
|
+
return this.notifyFrontend(FileStatus.ErrorNoValidator, assetForApi);
|
|
249
|
+
const file = await ModuleBE_AssetsStorage.getFile(assetForApi);
|
|
250
|
+
try {
|
|
251
|
+
const metadata = (await file.getMetadata());
|
|
252
|
+
if (!metadata)
|
|
253
|
+
return this.notifyFrontend(FileStatus.ErrorRetrievingMetadata, assetForApi);
|
|
254
|
+
await fileSizeValidator(file, metadata, validationConfig.minSize, validationConfig.maxSize);
|
|
255
|
+
const fileType = await mimetypeValidator(file, assetForApi);
|
|
256
|
+
dbTempAsset.md5Hash = metadata.md5Hash;
|
|
257
|
+
if (fileType && dbTempAsset.ext !== fileType.ext) {
|
|
258
|
+
this.logWarning(`renaming the file extension name: ${dbTempAsset.ext} => ${fileType.ext}`);
|
|
259
|
+
dbTempAsset.ext = fileType.ext;
|
|
260
|
+
}
|
|
227
261
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
262
|
+
catch (e) {
|
|
263
|
+
//TODO delete the file and the temp doc
|
|
264
|
+
this.logError(`Error while processing asset: ${dbTempAsset.name}`, e);
|
|
265
|
+
return this.notifyFrontend(FileStatus.ErrorWhileProcessing, assetForApi);
|
|
266
|
+
}
|
|
267
|
+
if (dbTempAsset.public) {
|
|
268
|
+
try {
|
|
269
|
+
// need to handle the response status!
|
|
270
|
+
await file.makePublic();
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
return this.notifyFrontend(FileStatus.ErrorMakingPublic, assetForApi);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const finalDbAsset = await this.runTransaction(async (transaction) => {
|
|
277
|
+
const duplicatedAssets = await this.query.custom({ where: { md5Hash: dbTempAsset.md5Hash } }, transaction);
|
|
278
|
+
if (duplicatedAssets.length && duplicatedAssets[0]) {
|
|
279
|
+
this.logWarning(`${dbTempAsset.feId} is a duplicated entry for ${duplicatedAssets[0]._id}`);
|
|
280
|
+
return { ...duplicatedAssets[0], feId: dbTempAsset.feId };
|
|
281
|
+
}
|
|
282
|
+
const doc = this.collection.doc.item(assetForApi);
|
|
283
|
+
await ModuleBE_AssetsTemp.delete.unique(dbTempAsset._id, transaction);
|
|
284
|
+
const assetForMain = { ...dbTempAsset, _id: dbTempAsset._id };
|
|
285
|
+
return await doc.set(assetForMain, transaction);
|
|
286
|
+
});
|
|
287
|
+
return this.notifyFrontend(FileStatus.Completed, finalDbAsset);
|
|
288
|
+
};
|
|
289
|
+
notifyFrontend = async (status, asset, feId) => {
|
|
290
|
+
if (status !== FileStatus.Completed && status !== FileStatus.Processing) {
|
|
291
|
+
const message = `Error while processing asset: ${status}\n Failed on \n Asset: ${asset.feId}\n Key: ${asset.key}\n Type: ${asset.mimeType}\n File: ${asset.name}`;
|
|
292
|
+
throw new ApiException(500, message);
|
|
293
|
+
}
|
|
294
|
+
this.logDebug(`notify FE about asset ${feId}: ${status}`);
|
|
295
|
+
return PushMessageBE_FileUploadStatus.push({ status, asset }, { feId: feId || asset.feId });
|
|
296
|
+
};
|
|
241
297
|
};
|
|
242
|
-
}
|
|
298
|
+
})();
|
|
299
|
+
export { ModuleBE_AssetsDB_Class };
|
|
243
300
|
export const ModuleBE_AssetsDB = new ModuleBE_AssetsDB_Class();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ModuleBE_BaseDB } from '@nu-art/
|
|
3
|
-
export declare class ModuleBE_AssetsDeleted_Class extends ModuleBE_BaseDB<
|
|
1
|
+
import { DatabaseDef_AssetsDeleted } from '@nu-art/file-upload-shared';
|
|
2
|
+
import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
3
|
+
export declare class ModuleBE_AssetsDeleted_Class extends ModuleBE_BaseDB<DatabaseDef_AssetsDeleted> {
|
|
4
4
|
constructor();
|
|
5
5
|
}
|
|
6
6
|
export declare const ModuleBE_AssetsDeleted: ModuleBE_AssetsDeleted_Class;
|
|
@@ -1,23 +1,5 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Permissions management system, define access level for each of
|
|
3
|
-
* your server apis, and restrict users by giving them access levels
|
|
4
|
-
*
|
|
5
|
-
* Copyright (C) 2020 Adam van der Kruk aka TacB0sS
|
|
6
|
-
*
|
|
7
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
* you may not use this file except in compliance with the License.
|
|
9
|
-
* You may obtain a copy of the License at
|
|
10
|
-
*
|
|
11
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
*
|
|
13
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
* See the License for the specific language governing permissions and
|
|
17
|
-
* limitations under the License.
|
|
18
|
-
*/
|
|
19
1
|
import { DBDef_TempDeleted } from '@nu-art/file-upload-shared';
|
|
20
|
-
import { ModuleBE_BaseDB } from '@nu-art/
|
|
2
|
+
import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
21
3
|
export class ModuleBE_AssetsDeleted_Class extends ModuleBE_BaseDB {
|
|
22
4
|
constructor() {
|
|
23
5
|
super(DBDef_TempDeleted);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { OnSyncEnvCompleted } from '@nu-art/thunderstorm-backend';
|
|
2
1
|
import { Module, TypedMap } from '@nu-art/ts-common';
|
|
3
2
|
import { StorageWrapperBE } from '@nu-art/firebase-backend';
|
|
4
3
|
import { DB_Asset } from '@nu-art/file-upload-shared';
|
|
4
|
+
/** Local interface (replaces thunderstorm-backend). */
|
|
5
|
+
export interface OnSyncEnvCompleted {
|
|
6
|
+
__onSyncEnvCompleted(env: string, baseUrl: string, requiredHeaders: TypedMap<string>): Promise<void>;
|
|
7
|
+
}
|
|
5
8
|
type Config = {
|
|
6
9
|
batchItemCount: number;
|
|
7
10
|
};
|
|
@@ -1,26 +1,6 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Permissions management system, define access level for each of
|
|
3
|
-
* your server apis, and restrict users by giving them access levels
|
|
4
|
-
*
|
|
5
|
-
* Copyright (C) 2020 Adam van der Kruk aka TacB0sS
|
|
6
|
-
*
|
|
7
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
* you may not use this file except in compliance with the License.
|
|
9
|
-
* You may obtain a copy of the License at
|
|
10
|
-
*
|
|
11
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
*
|
|
13
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
* See the License for the specific language governing permissions and
|
|
17
|
-
* limitations under the License.
|
|
18
|
-
*/
|
|
19
|
-
import { AxiosHttpModule } from '@nu-art/thunderstorm-backend';
|
|
20
1
|
import { filterInstances, Module, NotImplementedYetException } from '@nu-art/ts-common';
|
|
21
2
|
import { ModuleBE_Firebase } from '@nu-art/firebase-backend';
|
|
22
3
|
import { ApiDef_Assets } from '@nu-art/file-upload-shared';
|
|
23
|
-
import { HttpMethod } from '@nu-art/thunderstorm-shared';
|
|
24
4
|
import { ModuleBE_AssetsDB } from './ModuleBE_AssetsDB.js';
|
|
25
5
|
export class ModuleBE_AssetsStorage_Class extends Module {
|
|
26
6
|
storage;
|
|
@@ -46,29 +26,32 @@ export class ModuleBE_AssetsStorage_Class extends Module {
|
|
|
46
26
|
assetsToSync = filterInstances(await Promise.all(dbAssets.map(async (dbAsset) => {
|
|
47
27
|
const fileWrapper = await this.getFile(dbAsset);
|
|
48
28
|
if (await fileWrapper.exists())
|
|
49
|
-
return;
|
|
29
|
+
return undefined;
|
|
50
30
|
return dbAsset;
|
|
51
31
|
})));
|
|
52
32
|
if (assetsToSync.length > 0)
|
|
53
33
|
continue;
|
|
54
|
-
|
|
34
|
+
for (const dbAsset of assetsToSync) {
|
|
35
|
+
const base = baseUrl.replace(/\/$/, '');
|
|
36
|
+
const path = ApiDef_Assets.getReadSignedUrl.path;
|
|
55
37
|
let _signedUrl;
|
|
56
38
|
try {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
|
|
39
|
+
const res = await fetch(`${base}/${path}`, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { ...requiredHeaders, 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify({ _id: dbAsset._id })
|
|
43
|
+
});
|
|
44
|
+
const data = await res.json();
|
|
45
|
+
_signedUrl = data.signedUrl;
|
|
63
46
|
}
|
|
64
47
|
catch (e) {
|
|
65
48
|
console.log(e);
|
|
49
|
+
continue;
|
|
66
50
|
}
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
.executeSync();
|
|
51
|
+
const fileRes = await fetch(_signedUrl);
|
|
52
|
+
const fileContent = await fileRes.text();
|
|
70
53
|
await (await this.getFile(dbAsset)).write(fileContent);
|
|
71
|
-
}
|
|
54
|
+
}
|
|
72
55
|
} while (dbAssets.length > 0);
|
|
73
56
|
}
|
|
74
57
|
getReadSignedUrl = async (dbAsset) => (await (await this.getFile(dbAsset)).getReadSignedUrl()).signedUrl;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ModuleBE_BaseDB } from '@nu-art/
|
|
3
|
-
export declare class ModuleBE_AssetsTemp_Class extends ModuleBE_BaseDB<
|
|
1
|
+
import { DatabaseDef_AssetsTemp } from '@nu-art/file-upload-shared';
|
|
2
|
+
import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
3
|
+
export declare class ModuleBE_AssetsTemp_Class extends ModuleBE_BaseDB<DatabaseDef_AssetsTemp> {
|
|
4
4
|
constructor();
|
|
5
5
|
}
|
|
6
6
|
export declare const ModuleBE_AssetsTemp: ModuleBE_AssetsTemp_Class;
|
|
@@ -1,23 +1,5 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Permissions management system, define access level for each of
|
|
3
|
-
* your server apis, and restrict users by giving them access levels
|
|
4
|
-
*
|
|
5
|
-
* Copyright (C) 2020 Adam van der Kruk aka TacB0sS
|
|
6
|
-
*
|
|
7
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
* you may not use this file except in compliance with the License.
|
|
9
|
-
* You may obtain a copy of the License at
|
|
10
|
-
*
|
|
11
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
*
|
|
13
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
* See the License for the specific language governing permissions and
|
|
17
|
-
* limitations under the License.
|
|
18
|
-
*/
|
|
19
1
|
import { DBDef_TempAssets } from '@nu-art/file-upload-shared';
|
|
20
|
-
import { ModuleBE_BaseDB } from '@nu-art/
|
|
2
|
+
import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
21
3
|
export class ModuleBE_AssetsTemp_Class extends ModuleBE_BaseDB {
|
|
22
4
|
constructor() {
|
|
23
5
|
super(DBDef_TempAssets);
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { ModuleBE_StorageListener } from '@nu-art/firebase-backend';
|
|
2
|
-
import { DefaultDef_ServiceAccount, RequiresServiceAccount, ServiceAccountCredentials } from '@nu-art/thunderstorm-backend/modules/_tdb/service-accounts';
|
|
3
2
|
import { StorageEvent } from 'firebase-functions/storage';
|
|
3
|
+
/** Local types (replaces thunderstorm-backend service-accounts). */
|
|
4
|
+
export type ServiceAccountCredentials = {
|
|
5
|
+
serviceAccount?: {
|
|
6
|
+
accountId?: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export interface RequiresServiceAccount {
|
|
10
|
+
__requiresServiceAccount(): DefaultDef_ServiceAccount;
|
|
11
|
+
}
|
|
12
|
+
export type DefaultDef_ServiceAccount = {
|
|
13
|
+
moduleName: string;
|
|
14
|
+
email: string;
|
|
15
|
+
groupIds: string[];
|
|
16
|
+
};
|
|
4
17
|
export interface OnAssetUploaded {
|
|
5
18
|
__processAsset(filePath?: string): void;
|
|
6
19
|
}
|
|
@@ -29,7 +29,7 @@ export class ModuleBE_BucketListener_Class extends ModuleBE_StorageListener {
|
|
|
29
29
|
// need to create a dispatch that collects a list of services that requires service account and
|
|
30
30
|
// service account details, the permissions create project will create these account for the rest of the system ;
|
|
31
31
|
return new MemStorage().init(async () => {
|
|
32
|
-
const accountId = this.config
|
|
32
|
+
const accountId = this.config?.serviceAccount?.accountId;
|
|
33
33
|
if (!accountId)
|
|
34
34
|
throw new BadImplementationException('Need to perform project setup to setup this feature');
|
|
35
35
|
MemKey_AccountId.set(accountId);
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nu-art/file-upload-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.500.0",
|
|
4
4
|
"description": "File Uploader - Express & Typescript based backend framework Backend",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"TacB0sS",
|
|
7
7
|
"infra",
|
|
8
8
|
"nu-art",
|
|
9
9
|
"storm",
|
|
10
|
-
"thunderstorm",
|
|
11
10
|
"typescript"
|
|
12
11
|
],
|
|
13
12
|
"homepage": "https://github.com/nu-art-js/file-upload",
|
|
@@ -31,21 +30,23 @@
|
|
|
31
30
|
"build": "tsc"
|
|
32
31
|
},
|
|
33
32
|
"dependencies": {
|
|
34
|
-
"@nu-art/file-upload-shared": "0.
|
|
33
|
+
"@nu-art/file-upload-shared": "0.500.0",
|
|
35
34
|
"@google-cloud/common": "^5.0.2",
|
|
36
35
|
"@google-cloud/firestore": "^7.11.0",
|
|
37
36
|
"@google-cloud/storage": "^7.15.0",
|
|
38
|
-
"@nu-art/firebase-backend": "0.
|
|
39
|
-
"@nu-art/firebase-shared": "0.
|
|
40
|
-
"@nu-art/permissions-backend": "0.
|
|
41
|
-
"@nu-art/permissions-shared": "0.
|
|
42
|
-
"@nu-art/push-pub-sub-backend": "0.
|
|
43
|
-
"@nu-art/push-pub-sub-shared": "0.
|
|
44
|
-
"@nu-art/
|
|
45
|
-
"@nu-art/
|
|
46
|
-
"@nu-art/
|
|
47
|
-
"@nu-art/
|
|
48
|
-
"@nu-art/
|
|
37
|
+
"@nu-art/firebase-backend": "0.500.0",
|
|
38
|
+
"@nu-art/firebase-shared": "0.500.0",
|
|
39
|
+
"@nu-art/permissions-backend": "0.500.0",
|
|
40
|
+
"@nu-art/permissions-shared": "0.500.0",
|
|
41
|
+
"@nu-art/push-pub-sub-backend": "0.500.0",
|
|
42
|
+
"@nu-art/push-pub-sub-shared": "0.500.0",
|
|
43
|
+
"@nu-art/api-types": "{{THUNDERSTORM_VERSION}}",
|
|
44
|
+
"@nu-art/db-api-backend": "0.500.0",
|
|
45
|
+
"@nu-art/db-api-shared": "0.500.0",
|
|
46
|
+
"@nu-art/http-server": "{{THUNDERSTORM_VERSION}}",
|
|
47
|
+
"@nu-art/ts-common": "0.500.0",
|
|
48
|
+
"@nu-art/user-account-backend": "0.500.0",
|
|
49
|
+
"@nu-art/user-account-shared": "0.500.0",
|
|
49
50
|
"express": "^4.18.2",
|
|
50
51
|
"file-type": "^21.0.0",
|
|
51
52
|
"firebase": "^11.9.0",
|