@nu-art/thunderstorm-backend 0.400.5
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/_entity/app-config/ModuleBE_AppConfigAPI.d.ts +9 -0
- package/_entity/app-config/ModuleBE_AppConfigAPI.js +20 -0
- package/_entity/app-config/ModuleBE_AppConfigDB.d.ts +27 -0
- package/_entity/app-config/ModuleBE_AppConfigDB.js +91 -0
- package/_entity/app-config/index.d.ts +2 -0
- package/_entity/app-config/index.js +2 -0
- package/_entity/app-config/module-pack.d.ts +2 -0
- package/_entity/app-config/module-pack.js +3 -0
- package/_entity/backup-doc/ModuleBE_BackupDocDB.d.ts +52 -0
- package/_entity/backup-doc/ModuleBE_BackupDocDB.js +350 -0
- package/_entity/backup-doc/ModuleBE_BackupScheduler.d.ts +7 -0
- package/_entity/backup-doc/ModuleBE_BackupScheduler.js +14 -0
- package/_entity/backup-doc/index.d.ts +3 -0
- package/_entity/backup-doc/index.js +3 -0
- package/_entity/backup-doc/module-pack.d.ts +2 -0
- package/_entity/backup-doc/module-pack.js +3 -0
- package/_entity/editable-test/ModuleBE_EditableTestDB.d.ts +8 -0
- package/_entity/editable-test/ModuleBE_EditableTestDB.js +8 -0
- package/_entity/editable-test/index.d.ts +1 -0
- package/_entity/editable-test/index.js +1 -0
- package/_entity/editable-test/module-pack.d.ts +1 -0
- package/_entity/editable-test/module-pack.js +3 -0
- package/_entity.d.ts +3 -0
- package/_entity.js +3 -0
- package/core/BaseStorm.d.ts +17 -0
- package/core/BaseStorm.js +77 -0
- package/core/Storm.d.ts +15 -0
- package/core/Storm.js +93 -0
- package/core/db-def.d.ts +10 -0
- package/core/db-def.js +11 -0
- package/core/default-storm.d.ts +3 -0
- package/core/default-storm.js +30 -0
- package/core/storm-modulepack.d.ts +3 -0
- package/core/storm-modulepack.js +20 -0
- package/core/typed-api.d.ts +7 -0
- package/core/typed-api.js +46 -0
- package/exceptions.d.ts +1 -0
- package/exceptions.js +21 -0
- package/index.d.ts +27 -0
- package/index.js +48 -0
- package/modules/CleanupScheduler.d.ts +14 -0
- package/modules/CleanupScheduler.js +50 -0
- package/modules/ModuleBE_APIs.d.ts +11 -0
- package/modules/ModuleBE_APIs.js +19 -0
- package/modules/ModuleBE_CSVParser.d.ts +9 -0
- package/modules/ModuleBE_CSVParser.js +50 -0
- package/modules/ModuleBE_ForceUpgrade.d.ts +21 -0
- package/modules/ModuleBE_ForceUpgrade.js +70 -0
- package/modules/ModuleBE_ServerInfo.d.ts +20 -0
- package/modules/ModuleBE_ServerInfo.js +76 -0
- package/modules/_imports.d.ts +6 -0
- package/modules/_imports.js +26 -0
- package/modules/_tdb/service-accounts.d.ts +19 -0
- package/modules/_tdb/service-accounts.js +2 -0
- package/modules/action-processor/Action_SetupProject.d.ts +9 -0
- package/modules/action-processor/Action_SetupProject.js +23 -0
- package/modules/action-processor/ModuleBE_ActionProcessor.d.ts +11 -0
- package/modules/action-processor/ModuleBE_ActionProcessor.js +67 -0
- package/modules/action-processor/types.d.ts +10 -0
- package/modules/action-processor/types.js +1 -0
- package/modules/archiving/ModuleBE_Archiving.d.ts +119 -0
- package/modules/archiving/ModuleBE_Archiving.js +236 -0
- package/modules/collection-actions/ModuleBE_CollectionActions.d.ts +12 -0
- package/modules/collection-actions/ModuleBE_CollectionActions.js +69 -0
- package/modules/collection-actions/dispatcher.d.ts +7 -0
- package/modules/collection-actions/dispatcher.js +2 -0
- package/modules/db-api-gen/ModuleBE_BaseApi.d.ts +16 -0
- package/modules/db-api-gen/ModuleBE_BaseApi.js +74 -0
- package/modules/db-api-gen/ModuleBE_BaseDB.d.ts +78 -0
- package/modules/db-api-gen/ModuleBE_BaseDB.js +298 -0
- package/modules/http/AxiosHttpModule.d.ts +25 -0
- package/modules/http/AxiosHttpModule.js +132 -0
- package/modules/http/types.d.ts +6 -0
- package/modules/http/types.js +1 -0
- package/modules/proxy/ModuleBE_RemoteProxy.d.ts +35 -0
- package/modules/proxy/ModuleBE_RemoteProxy.js +86 -0
- package/modules/proxy/RemoteProxyCaller.d.ts +19 -0
- package/modules/proxy/RemoteProxyCaller.js +82 -0
- package/modules/proxy/assert-secret-middleware.d.ts +2 -0
- package/modules/proxy/assert-secret-middleware.js +24 -0
- package/modules/server/HeaderKey.d.ts +8 -0
- package/modules/server/HeaderKey.js +41 -0
- package/modules/server/HttpServer.d.ts +41 -0
- package/modules/server/HttpServer.js +223 -0
- package/modules/server/consts.d.ts +13 -0
- package/modules/server/consts.js +9 -0
- package/modules/server/route-resolvers/RouteResolver_Dummy.d.ts +7 -0
- package/modules/server/route-resolvers/RouteResolver_Dummy.js +34 -0
- package/modules/server/route-resolvers/RouteResolver_ModulePath.d.ts +22 -0
- package/modules/server/route-resolvers/RouteResolver_ModulePath.js +84 -0
- package/modules/server/route-resolvers/index.d.ts +7 -0
- package/modules/server/route-resolvers/index.js +21 -0
- package/modules/server/server-api.d.ts +85 -0
- package/modules/server/server-api.js +362 -0
- package/modules/server/server-errors.d.ts +4 -0
- package/modules/server/server-errors.js +79 -0
- package/modules/sync-env/ModuleBE_SyncEnv.d.ts +36 -0
- package/modules/sync-env/ModuleBE_SyncEnv.js +212 -0
- package/modules/sync-manager/ModuleBE_SyncManager.d.ts +63 -0
- package/modules/sync-manager/ModuleBE_SyncManager.js +254 -0
- package/package.json +104 -0
- package/shared.d.ts +1 -0
- package/shared.js +21 -0
- package/test/StormTest.d.ts +23 -0
- package/test/StormTest.js +49 -0
- package/utils/file.d.ts +2 -0
- package/utils/file.js +29 -0
- package/utils/promisify-request.d.ts +3 -0
- package/utils/promisify-request.js +33 -0
- package/utils/types.d.ts +11 -0
- package/utils/types.js +21 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ModuleBE_BaseApi_Class } from '../../modules/db-api-gen/ModuleBE_BaseApi.js';
|
|
2
|
+
import { DBProto_AppConfig } from '@nu-art/thunderstorm-shared';
|
|
3
|
+
declare class ModuleBE_AppConfigAPI_Class extends ModuleBE_BaseApi_Class<DBProto_AppConfig> {
|
|
4
|
+
constructor();
|
|
5
|
+
init(): void;
|
|
6
|
+
private getConfigByKey;
|
|
7
|
+
}
|
|
8
|
+
export declare const ModuleBE_AppConfigAPI: ModuleBE_AppConfigAPI_Class;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createQueryServerApi } from '../../core/typed-api.js';
|
|
2
|
+
import { addRoutes } from '../../modules/ModuleBE_APIs.js';
|
|
3
|
+
import { ModuleBE_BaseApi_Class } from '../../modules/db-api-gen/ModuleBE_BaseApi.js';
|
|
4
|
+
import { ApiDef_AppConfig } from '@nu-art/thunderstorm-shared/_entity/app-config/api-def';
|
|
5
|
+
import { ModuleBE_AppConfigDB } from './ModuleBE_AppConfigDB.js';
|
|
6
|
+
class ModuleBE_AppConfigAPI_Class extends ModuleBE_BaseApi_Class {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(ModuleBE_AppConfigDB);
|
|
9
|
+
}
|
|
10
|
+
init() {
|
|
11
|
+
super.init();
|
|
12
|
+
addRoutes([
|
|
13
|
+
createQueryServerApi(ApiDef_AppConfig._v1.getConfigByKey, this.getConfigByKey)
|
|
14
|
+
]);
|
|
15
|
+
}
|
|
16
|
+
getConfigByKey = async (request) => {
|
|
17
|
+
return ModuleBE_AppConfigDB.getResolverDataByKey(request.key);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export const ModuleBE_AppConfigAPI = new ModuleBE_AppConfigAPI_Class();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DB_AppConfig, DBProto_AppConfig } from '@nu-art/thunderstorm-shared';
|
|
2
|
+
import { DBApiConfigV3, ModuleBE_BaseDB } from '../../modules/db-api-gen/ModuleBE_BaseDB.js';
|
|
3
|
+
import { Logger, PreDB, TypedKeyValue } from '@nu-art/ts-common';
|
|
4
|
+
type InferType<T> = T extends AppConfigKey_BE<infer ValueType> ? ValueType : never;
|
|
5
|
+
type Config = DBApiConfigV3<DBProto_AppConfig> & {};
|
|
6
|
+
export declare class ModuleBE_AppConfigDB_Class extends ModuleBE_BaseDB<DBProto_AppConfig, Config> {
|
|
7
|
+
private keyMap;
|
|
8
|
+
constructor();
|
|
9
|
+
protected preWriteProcessing(dbInstance: PreDB<DB_AppConfig>, originalDbInstance: DBProto_AppConfig['dbType'], transaction?: FirebaseFirestore.Transaction): Promise<void>;
|
|
10
|
+
createDefaults: (logger?: Logger) => Promise<void>;
|
|
11
|
+
getResolverDataByKey: (key: string) => Promise<any>;
|
|
12
|
+
registerKey<K extends AppConfigKey_BE<any>>(appConfigKey: K): void;
|
|
13
|
+
getAppKey: <K extends AppConfigKey_BE<any>>(appConfigKey: K, logger?: Logger) => Promise<InferType<K>>;
|
|
14
|
+
setAppKey: <K extends AppConfigKey_BE<any>>(appConfigKey: K, data: InferType<K>) => Promise<DB_AppConfig<any>>;
|
|
15
|
+
_deleteAppKey: <K extends AppConfigKey_BE<any>>(appConfigKey: K) => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare const ModuleBE_AppConfigDB: ModuleBE_AppConfigDB_Class;
|
|
18
|
+
export declare class AppConfigKey_BE<Binder extends TypedKeyValue<string | number | object, any>> {
|
|
19
|
+
readonly key: Binder['key'];
|
|
20
|
+
readonly resolver: (logger: Logger) => Promise<Binder['value']>;
|
|
21
|
+
readonly dataManipulator: (data: Binder['value']) => Promise<Binder['value']>;
|
|
22
|
+
constructor(key: Binder['key'], resolver: (logger: Logger) => Promise<Binder['value']>, dataManipulator?: (data: Binder['value']) => Promise<Binder['value']>);
|
|
23
|
+
get(): Promise<Binder['value']>;
|
|
24
|
+
set(value: Binder['value']): Promise<void>;
|
|
25
|
+
delete(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { DBDef_AppConfig } from '@nu-art/thunderstorm-shared';
|
|
2
|
+
import { ModuleBE_BaseDB } from '../../modules/db-api-gen/ModuleBE_BaseDB.js';
|
|
3
|
+
import { _keys, ApiException } from '@nu-art/ts-common';
|
|
4
|
+
export class ModuleBE_AppConfigDB_Class extends ModuleBE_BaseDB {
|
|
5
|
+
keyMap = {};
|
|
6
|
+
// ######################## Lifecycle ########################
|
|
7
|
+
constructor() {
|
|
8
|
+
super(DBDef_AppConfig);
|
|
9
|
+
}
|
|
10
|
+
async preWriteProcessing(dbInstance, originalDbInstance, transaction) {
|
|
11
|
+
this.logVerbose('############## Pre Manipulation ##############');
|
|
12
|
+
this.logVerbose(dbInstance);
|
|
13
|
+
const appKey = this.keyMap[dbInstance.key];
|
|
14
|
+
dbInstance.data = await appKey.dataManipulator(dbInstance.data);
|
|
15
|
+
this.logVerbose('############## Post Manipulation ##############');
|
|
16
|
+
this.logVerbose(dbInstance);
|
|
17
|
+
}
|
|
18
|
+
createDefaults = async (logger = this) => {
|
|
19
|
+
const keys = _keys(this.keyMap);
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
try {
|
|
22
|
+
await this.getAppKey(this.keyMap[key]);
|
|
23
|
+
// const config = await this.getAppKey(this.keyMap[key]);
|
|
24
|
+
// this.logInfo(`Set App-Config default value for '${key}'`, config);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
logger.logError(`Failed to create app-config for key ${key}`, err);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
// ######################## API ########################
|
|
32
|
+
getResolverDataByKey = async (key) => {
|
|
33
|
+
const appConfigKey = this.keyMap[key];
|
|
34
|
+
if (!appConfigKey)
|
|
35
|
+
throw new ApiException(404, `Could not find an app config with key ${key}`);
|
|
36
|
+
return this.getAppKey(appConfigKey);
|
|
37
|
+
};
|
|
38
|
+
// ######################## Logic ########################
|
|
39
|
+
registerKey(appConfigKey) {
|
|
40
|
+
this.keyMap[appConfigKey.key] = appConfigKey;
|
|
41
|
+
}
|
|
42
|
+
getAppKey = async (appConfigKey, logger = this) => {
|
|
43
|
+
try {
|
|
44
|
+
const config = await this.query.uniqueCustom({ where: { key: appConfigKey.key } });
|
|
45
|
+
return config?.data;
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
const data = await appConfigKey.resolver(logger);
|
|
49
|
+
await this.setAppKey(appConfigKey, data);
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
setAppKey = async (appConfigKey, data) => {
|
|
54
|
+
let _config;
|
|
55
|
+
try {
|
|
56
|
+
_config = await this.query.uniqueCustom({ where: { key: appConfigKey.key } });
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
_config = { key: appConfigKey.key };
|
|
60
|
+
}
|
|
61
|
+
_config.data = data;
|
|
62
|
+
return this.set.item(_config);
|
|
63
|
+
};
|
|
64
|
+
_deleteAppKey = async (appConfigKey) => {
|
|
65
|
+
await this.delete.query({ where: { key: appConfigKey.key } });
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export const ModuleBE_AppConfigDB = new ModuleBE_AppConfigDB_Class();
|
|
69
|
+
//TODO: Add validation by key
|
|
70
|
+
export class AppConfigKey_BE {
|
|
71
|
+
key;
|
|
72
|
+
resolver;
|
|
73
|
+
dataManipulator = (data) => data;
|
|
74
|
+
constructor(key, resolver, dataManipulator) {
|
|
75
|
+
this.key = key;
|
|
76
|
+
this.resolver = resolver;
|
|
77
|
+
if (dataManipulator)
|
|
78
|
+
this.dataManipulator = dataManipulator;
|
|
79
|
+
ModuleBE_AppConfigDB.registerKey(this);
|
|
80
|
+
}
|
|
81
|
+
async get() {
|
|
82
|
+
return await ModuleBE_AppConfigDB.getAppKey(this);
|
|
83
|
+
}
|
|
84
|
+
async set(value) {
|
|
85
|
+
// @ts-ignore
|
|
86
|
+
await ModuleBE_AppConfig.setAppKey(this, value);
|
|
87
|
+
}
|
|
88
|
+
async delete() {
|
|
89
|
+
await ModuleBE_AppConfigDB._deleteAppKey(this);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Module, PreDB, TypedMap, UniqueId } from '@nu-art/ts-common';
|
|
2
|
+
import { DBApiConfigV3, ModuleBE_BaseDB } from '../../modules/db-api-gen/ModuleBE_BaseDB.js';
|
|
3
|
+
import { FirestoreQuery } from '@nu-art/firebase-shared';
|
|
4
|
+
import { Readable } from 'stream';
|
|
5
|
+
import { FirestoreCollectionV3 } from '@nu-art/firebase-backend/firestore-v3/FirestoreCollectionV3';
|
|
6
|
+
import { DB_BackupDoc, DBProto_BackupDoc, FetchBackupDoc } from '@nu-art/thunderstorm-shared';
|
|
7
|
+
import { Request_BackupId, Response_BackupDocs } from '@nu-art/thunderstorm-shared/_entity/backup-doc/api-def';
|
|
8
|
+
export interface OnModuleCleanupV2 {
|
|
9
|
+
__onCleanupInvokedV2: () => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
type Config = DBApiConfigV3<DBProto_BackupDoc> & {
|
|
12
|
+
keepInterval: number;
|
|
13
|
+
minTimeThreshold: number;
|
|
14
|
+
excludedDbKeys?: string[];
|
|
15
|
+
};
|
|
16
|
+
type DBModules = ModuleBE_BaseDB<any>;
|
|
17
|
+
/**
|
|
18
|
+
* This module is in charge of making a backup of the db in firebase storage,
|
|
19
|
+
* in order to test this module locally run firebase functions:shell command in a terminal
|
|
20
|
+
* when the firebase> shows up write the name of the function you want to run and call it "functionName()"
|
|
21
|
+
* **/
|
|
22
|
+
export declare class ModuleBE_BackupDocDB_Class extends Module<Config> {
|
|
23
|
+
collection: FirestoreCollectionV3<DBProto_BackupDoc>;
|
|
24
|
+
constructor();
|
|
25
|
+
protected init(): void;
|
|
26
|
+
getBackupStatusCollection: () => FirestoreCollectionV3<DBProto_BackupDoc>;
|
|
27
|
+
/**
|
|
28
|
+
* Get metadata objects per each collection module that needs to be backed up.
|
|
29
|
+
*/
|
|
30
|
+
getBackupDetails: () => DBModules[];
|
|
31
|
+
getBackupInfo(backupId: string, baseUrl: string, headers: TypedMap<string | string[]>): Promise<FetchBackupDoc>;
|
|
32
|
+
getBackupStreamFromId: (backupInfo: FetchBackupDoc) => Promise<Readable>;
|
|
33
|
+
query: (ourQuery: FirestoreQuery<DB_BackupDoc>) => Promise<DB_BackupDoc[]>;
|
|
34
|
+
queryUnique: (backupDocId: UniqueId) => Promise<DB_BackupDoc | undefined>;
|
|
35
|
+
upsert: (instance: PreDB<DB_BackupDoc>) => Promise<DB_BackupDoc>;
|
|
36
|
+
deleteItem: (instance: DB_BackupDoc) => Promise<DB_BackupDoc | undefined>;
|
|
37
|
+
private getDefaultPath;
|
|
38
|
+
initiateBackup: (force?: boolean, pathInBucket?: string) => Promise<{
|
|
39
|
+
pathToBackup: string;
|
|
40
|
+
backupId: string;
|
|
41
|
+
} | undefined>;
|
|
42
|
+
createBackupReadStream: (backupInfo: FetchBackupDoc) => Promise<Readable>;
|
|
43
|
+
createBackupReadStreamFromBucket: (pathInBucket: string) => Promise<Readable>;
|
|
44
|
+
/**
|
|
45
|
+
* @param body - needs to contain backupId with the key to fetch.
|
|
46
|
+
*/
|
|
47
|
+
fetchBackupDocs: (body: Request_BackupId) => Promise<Response_BackupDocs>;
|
|
48
|
+
fetchLatestBackupDoc: () => Promise<Response_BackupDocs>;
|
|
49
|
+
private fetchDocImpl;
|
|
50
|
+
}
|
|
51
|
+
export declare const ModuleBE_BackupDocDB: ModuleBE_BackupDocDB_Class;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { __stringify, _logger_logException, ApiException, BadImplementationException, cloneObj, currentTimeMillis, Day, Dispatcher, filterInstances, Format_YYYYMMDD_HHmmss, formatTimestamp, LogLevel, Minute, Module, RuntimeModules, sortArray } from '@nu-art/ts-common';
|
|
2
|
+
import { ModuleBE_Firebase } from '@nu-art/firebase-backend';
|
|
3
|
+
import { _EmptyQuery } from '@nu-art/firebase-shared';
|
|
4
|
+
import { Readable } from 'stream';
|
|
5
|
+
import { HttpMethod } from '@nu-art/thunderstorm-shared';
|
|
6
|
+
import { addRoutes } from '../../modules/ModuleBE_APIs.js';
|
|
7
|
+
import { ApiDef_BackupDoc } from '@nu-art/thunderstorm-shared/_entity/backup-doc/api-def';
|
|
8
|
+
import { createQueryServerApi } from '../../core/typed-api.js';
|
|
9
|
+
import { DBDef_BackupDoc } from '@nu-art/thunderstorm-shared/_entity/backup-doc/db-def';
|
|
10
|
+
import { HttpCodes } from '@nu-art/ts-common/core/exceptions/http-codes';
|
|
11
|
+
import { MemKey_HttpRequestHeaders } from '../../modules/server/consts.js';
|
|
12
|
+
import { AxiosHttpModule } from '../../index.js';
|
|
13
|
+
import { CSVModuleV3 } from '@nu-art/ts-common/modules/CSVModuleV3';
|
|
14
|
+
import { ModuleBE_CollectionActions } from '../../modules/collection-actions/ModuleBE_CollectionActions.js';
|
|
15
|
+
const dispatch_onModuleCleanupV2 = new Dispatcher('__onCleanupInvokedV2');
|
|
16
|
+
/**
|
|
17
|
+
* This module is in charge of making a backup of the db in firebase storage,
|
|
18
|
+
* in order to test this module locally run firebase functions:shell command in a terminal
|
|
19
|
+
* when the firebase> shows up write the name of the function you want to run and call it "functionName()"
|
|
20
|
+
* **/
|
|
21
|
+
export class ModuleBE_BackupDocDB_Class extends Module {
|
|
22
|
+
collection;
|
|
23
|
+
constructor() {
|
|
24
|
+
super();
|
|
25
|
+
this.setMinLevel(LogLevel.Verbose);
|
|
26
|
+
this.setDefaultConfig({ minTimeThreshold: Day, keepInterval: 7 * Day });
|
|
27
|
+
}
|
|
28
|
+
init() {
|
|
29
|
+
super.init();
|
|
30
|
+
this.collection = this.getBackupStatusCollection();
|
|
31
|
+
addRoutes([
|
|
32
|
+
createQueryServerApi(ApiDef_BackupDoc._v1.initiateBackup, () => this.initiateBackup()),
|
|
33
|
+
createQueryServerApi(ApiDef_BackupDoc._v1.fetchBackupDocs, this.fetchBackupDocs),
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
getBackupStatusCollection = () => {
|
|
37
|
+
return ModuleBE_Firebase
|
|
38
|
+
.createAdminSession()
|
|
39
|
+
.getFirestoreV3()
|
|
40
|
+
.getCollection(DBDef_BackupDoc);
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Get metadata objects per each collection module that needs to be backed up.
|
|
44
|
+
*/
|
|
45
|
+
getBackupDetails = () => {
|
|
46
|
+
return RuntimeModules()
|
|
47
|
+
.filter((module) => {
|
|
48
|
+
if (!module || !module.dbDef)
|
|
49
|
+
return false;
|
|
50
|
+
if (this.config.excludedDbKeys?.includes(module.dbDef.dbKey)) {
|
|
51
|
+
this.logWarningBold(`Skipping module ${module.dbDef.dbKey} since it's in the exclusion list.`);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
// private async getBackupInfo(queryParams: Request_GetMetadata) {
|
|
58
|
+
async getBackupInfo(backupId, baseUrl, headers) {
|
|
59
|
+
const url = `${baseUrl}/v1/fetch-backup-docs-v2`;
|
|
60
|
+
const outputDef = {
|
|
61
|
+
method: HttpMethod.GET,
|
|
62
|
+
path: '',
|
|
63
|
+
fullUrl: url
|
|
64
|
+
};
|
|
65
|
+
const requestBody = { backupId };
|
|
66
|
+
try {
|
|
67
|
+
let request = AxiosHttpModule
|
|
68
|
+
.createRequest(outputDef)
|
|
69
|
+
.setUrlParams(requestBody);
|
|
70
|
+
request = request.addHeaders(headers);
|
|
71
|
+
const response = await request.executeSync();
|
|
72
|
+
const backupInfo = response.backupInfo;
|
|
73
|
+
const wrongBackupIdDescriptor = backupInfo?._id !== backupId;
|
|
74
|
+
if (wrongBackupIdDescriptor)
|
|
75
|
+
throw new BadImplementationException(`Received backup descriptors with wrong backupId! provided id: ${backupId} received id: ${backupInfo?._id}`);
|
|
76
|
+
return backupInfo;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
throw new ApiException(500, err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
getBackupStreamFromId = async (backupInfo) => {
|
|
83
|
+
if (!backupInfo.backupFilePath)
|
|
84
|
+
throw new ApiException(404, 'Backup file path not found');
|
|
85
|
+
this.logInfo(`---- Fetching Backup Stream from: ${backupInfo.firestoreSignedUrl} ----`);
|
|
86
|
+
const signedUrlDef = {
|
|
87
|
+
method: HttpMethod.GET,
|
|
88
|
+
path: '',
|
|
89
|
+
fullUrl: backupInfo.firestoreSignedUrl
|
|
90
|
+
};
|
|
91
|
+
return (await AxiosHttpModule
|
|
92
|
+
.createRequest(signedUrlDef)
|
|
93
|
+
.setResponseType('stream')
|
|
94
|
+
.executeSync());
|
|
95
|
+
};
|
|
96
|
+
// ##################### Collection Interaction #####################
|
|
97
|
+
query = async (ourQuery) => {
|
|
98
|
+
return await this.collection.query.custom(ourQuery);
|
|
99
|
+
};
|
|
100
|
+
queryUnique = async (backupDocId) => {
|
|
101
|
+
return await this.collection.query.unique(backupDocId);
|
|
102
|
+
};
|
|
103
|
+
upsert = async (instance) => {
|
|
104
|
+
return await this.collection.create.item(instance);
|
|
105
|
+
};
|
|
106
|
+
deleteItem = async (instance) => {
|
|
107
|
+
return await this.collection.delete.unique(instance._id);
|
|
108
|
+
};
|
|
109
|
+
// ##################### Backup Actions #####################
|
|
110
|
+
getDefaultPath = () => {
|
|
111
|
+
const nowMs = currentTimeMillis();
|
|
112
|
+
const timeFormat = formatTimestamp(Format_YYYYMMDD_HHmmss, nowMs);
|
|
113
|
+
return `backup/${timeFormat}`;
|
|
114
|
+
};
|
|
115
|
+
initiateBackup = async (force = false, pathInBucket = this.getDefaultPath()) => {
|
|
116
|
+
const nowMs = currentTimeMillis();
|
|
117
|
+
const backupPath = `${pathInBucket}/firestore-backup.csv`;
|
|
118
|
+
const metadataPath = `${pathInBucket}/metadata.json`;
|
|
119
|
+
const configPath = `${pathInBucket}/firebase-backup.json`;
|
|
120
|
+
const query = {
|
|
121
|
+
where: {},
|
|
122
|
+
orderBy: [{ key: 'timestamp', order: 'asc' }],
|
|
123
|
+
limit: 1
|
|
124
|
+
};
|
|
125
|
+
const docs = await this.query(query);
|
|
126
|
+
const latestDoc = docs[0];
|
|
127
|
+
if (!force && latestDoc && latestDoc.timestamp + this.config.minTimeThreshold > nowMs)
|
|
128
|
+
return; // If the oldest doc is still in the keeping timeframe, don't delete any docs.
|
|
129
|
+
if (this.config.excludedDbKeys)
|
|
130
|
+
this.logInfo(`Found excluded modules list: ${this.config.excludedDbKeys}`);
|
|
131
|
+
try {
|
|
132
|
+
this.logInfo('Cleaning modules...');
|
|
133
|
+
await dispatch_onModuleCleanupV2.dispatchModuleAsync();
|
|
134
|
+
this.logInfo('Cleaned modules!');
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
this.logWarning(`modules cleanup has failed with error`, e);
|
|
138
|
+
const errorMessage = `modules cleanup has failed with error\nError: ${_logger_logException(e)}`;
|
|
139
|
+
throw new ApiException(500, errorMessage, e);
|
|
140
|
+
}
|
|
141
|
+
const modules = filterInstances(this.getBackupDetails());
|
|
142
|
+
const backupsCounter = 0;
|
|
143
|
+
try {
|
|
144
|
+
this.logInfo('Upgrading Collections');
|
|
145
|
+
await ModuleBE_CollectionActions.upgrade_All({});
|
|
146
|
+
this.logInfo('Collections Upgraded');
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
this.logError(e);
|
|
150
|
+
throw HttpCodes._5XX.INTERNAL_SERVER_ERROR('Could not upgrade collections', 'failed collection upgrade', e);
|
|
151
|
+
}
|
|
152
|
+
const firebaseSessionAdmin = ModuleBE_Firebase.createAdminSession();
|
|
153
|
+
const storage = firebaseSessionAdmin.getStorage();
|
|
154
|
+
const bucket = await storage.getMainBucket();
|
|
155
|
+
let metadata;
|
|
156
|
+
if (modules.length === 0)
|
|
157
|
+
throw new ApiException(404, 'No modules to backup');
|
|
158
|
+
try {
|
|
159
|
+
this.logDebug('Creating backup file...');
|
|
160
|
+
const file = await bucket.getFile(backupPath);
|
|
161
|
+
const reader = new DBModuleReader(modules);
|
|
162
|
+
const writer = file.createWriteStream({ gzip: true });
|
|
163
|
+
const formatter = CSVModuleV3.provideFormatter();
|
|
164
|
+
await new Promise((resolve, reject) => {
|
|
165
|
+
reader
|
|
166
|
+
.pipe(formatter)
|
|
167
|
+
.pipe(writer)
|
|
168
|
+
.on('close', () => {
|
|
169
|
+
metadata = { ...reader.getMetadata(), timestamp: nowMs };
|
|
170
|
+
resolve();
|
|
171
|
+
})
|
|
172
|
+
.on('error', err => reject(err));
|
|
173
|
+
});
|
|
174
|
+
this.logDebug('Backup file created');
|
|
175
|
+
this.logDebug('Backing up config db');
|
|
176
|
+
const database = firebaseSessionAdmin.getDatabase();
|
|
177
|
+
const configBackup = await database.ref('/').get();
|
|
178
|
+
const configFile = await bucket.getFile(configPath);
|
|
179
|
+
await configFile.write(configBackup);
|
|
180
|
+
this.logDebug('Config file created');
|
|
181
|
+
this.logDebug('Creating metadata file...');
|
|
182
|
+
const metadataFile = await bucket.getFile(metadataPath);
|
|
183
|
+
await metadataFile.write(metadata);
|
|
184
|
+
this.logDebug('Metadata file created');
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
this.logWarning(`backup of ${modules[backupsCounter].dbDef.dbKey} has failed with error`, e);
|
|
188
|
+
const errorMessage = `Error backing up firestore collection config:\n ${__stringify(modules[backupsCounter].config, true)}\nError: ${_logger_logException(e)}`;
|
|
189
|
+
throw new ApiException(500, errorMessage, e);
|
|
190
|
+
}
|
|
191
|
+
const dbBackup = await this.upsert({
|
|
192
|
+
timestamp: nowMs,
|
|
193
|
+
backupPath,
|
|
194
|
+
metadataPath,
|
|
195
|
+
firebasePath: configPath,
|
|
196
|
+
metadata: metadata,
|
|
197
|
+
});
|
|
198
|
+
this.logWarning(dbBackup);
|
|
199
|
+
const oldBackupsToDelete = await this.query({ where: { timestamp: { $lt: nowMs - this.config.keepInterval } } });
|
|
200
|
+
if (oldBackupsToDelete.length === 0) {
|
|
201
|
+
this.logInfoBold('No older backups to delete');
|
|
202
|
+
return { pathToBackup: backupPath, backupId: dbBackup._id };
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
this.logInfoBold('Received older backups to delete, count: ' + oldBackupsToDelete.length);
|
|
206
|
+
const backupDeleteOperations = oldBackupsToDelete
|
|
207
|
+
.map(doc => filterInstances([doc.metadataPath, doc.backupPath, doc.firebasePath]))
|
|
208
|
+
.flat()
|
|
209
|
+
.map(path => async () => {
|
|
210
|
+
try {
|
|
211
|
+
const file = await bucket.getFile(path);
|
|
212
|
+
file.delete();
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
this.logError(`Failed deleting file at path: ${path}`, err);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
await Promise.all(backupDeleteOperations);
|
|
219
|
+
await this.collection.delete.all(oldBackupsToDelete);
|
|
220
|
+
this.logInfoBold('Successfully deleted old backups');
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
this.logWarning(`Error while cleaning up older backups`, err);
|
|
224
|
+
throw new ApiException(500, err);
|
|
225
|
+
}
|
|
226
|
+
return { pathToBackup: backupPath, backupId: dbBackup._id };
|
|
227
|
+
};
|
|
228
|
+
createBackupReadStream = async (backupInfo) => {
|
|
229
|
+
const stream = await this.getBackupStreamFromId(backupInfo);
|
|
230
|
+
const transformer = CSVModuleV3.provideFormatterFromCsv();
|
|
231
|
+
return stream.pipe(transformer);
|
|
232
|
+
};
|
|
233
|
+
createBackupReadStreamFromBucket = async (pathInBucket) => {
|
|
234
|
+
const file = await ModuleBE_Firebase.createAdminSession().getStorage().getFile(pathInBucket);
|
|
235
|
+
const stream = file.createReadStream({ decompress: true });
|
|
236
|
+
const transformer = CSVModuleV3.provideFormatterFromCsv();
|
|
237
|
+
return stream.pipe(transformer);
|
|
238
|
+
};
|
|
239
|
+
// ##################### BackupDoc Fetching #####################
|
|
240
|
+
/**
|
|
241
|
+
* @param body - needs to contain backupId with the key to fetch.
|
|
242
|
+
*/
|
|
243
|
+
fetchBackupDocs = async (body) => {
|
|
244
|
+
const backupDoc = await this.queryUnique(body.backupId);
|
|
245
|
+
if (!backupDoc)
|
|
246
|
+
throw new ApiException(500, `no backup doc found with this id ${body.backupId}`);
|
|
247
|
+
return await this.fetchDocImpl(backupDoc);
|
|
248
|
+
};
|
|
249
|
+
fetchLatestBackupDoc = async () => {
|
|
250
|
+
const docs = await this.query(_EmptyQuery);
|
|
251
|
+
if (!docs.length)
|
|
252
|
+
throw HttpCodes._4XX.NOT_FOUND('No backups to fetch', 'No backups in db to fetch');
|
|
253
|
+
const latest = sortArray(docs, doc => doc.__created, true)[0];
|
|
254
|
+
return await this.fetchDocImpl(latest);
|
|
255
|
+
};
|
|
256
|
+
fetchDocImpl = async (doc) => {
|
|
257
|
+
const bucket = await ModuleBE_Firebase.createAdminSession().getStorage().getMainBucket();
|
|
258
|
+
const contentType = MemKey_HttpRequestHeaders.get()['content-type'];
|
|
259
|
+
const firebaseDescriptor = await (await bucket.getFile(doc.firebasePath)).getReadSignedUrl(10 * Minute, contentType);
|
|
260
|
+
const firestoreDescriptor = await (await bucket.getFile(doc.backupPath)).getReadSignedUrl(10 * Minute, contentType);
|
|
261
|
+
return {
|
|
262
|
+
backupInfo: {
|
|
263
|
+
_id: doc._id,
|
|
264
|
+
backupFilePath: doc.backupPath,
|
|
265
|
+
metadataFilePath: doc.metadataPath,
|
|
266
|
+
firebaseFilePath: doc.firebasePath,
|
|
267
|
+
firebaseSignedUrl: firebaseDescriptor.signedUrl,
|
|
268
|
+
firestoreSignedUrl: firestoreDescriptor.signedUrl,
|
|
269
|
+
metadata: doc.metadata
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
export const ModuleBE_BackupDocDB = new ModuleBE_BackupDocDB_Class();
|
|
275
|
+
class DBModuleReader extends Readable {
|
|
276
|
+
dbModules;
|
|
277
|
+
pageSize;
|
|
278
|
+
page = 0;
|
|
279
|
+
moduleIndex = 0;
|
|
280
|
+
done = false;
|
|
281
|
+
metadata;
|
|
282
|
+
constructor(dbModules, pageSize = 1000) {
|
|
283
|
+
super({ objectMode: true });
|
|
284
|
+
this.dbModules = dbModules;
|
|
285
|
+
this.pageSize = pageSize;
|
|
286
|
+
this.metadata = { collectionsData: [], timestamp: currentTimeMillis() };
|
|
287
|
+
}
|
|
288
|
+
// ##################### Metadata #####################
|
|
289
|
+
getMetadata = () => cloneObj(this.metadata);
|
|
290
|
+
updateMetadata = (module, items) => {
|
|
291
|
+
const dbKey = module.dbDef.dbKey;
|
|
292
|
+
const collectionData = this.metadata.collectionsData.find(data => data.dbKey === dbKey);
|
|
293
|
+
if (!collectionData)
|
|
294
|
+
return this.metadata.collectionsData.push({
|
|
295
|
+
dbKey: module.dbDef.dbKey,
|
|
296
|
+
numOfDocs: items.length,
|
|
297
|
+
version: module.dbDef.versions[0],
|
|
298
|
+
});
|
|
299
|
+
collectionData.numOfDocs += items.length;
|
|
300
|
+
};
|
|
301
|
+
// ##################### Logic #####################
|
|
302
|
+
getItems = async () => {
|
|
303
|
+
if (this.done)
|
|
304
|
+
return;
|
|
305
|
+
const module = this.dbModules[this.moduleIndex];
|
|
306
|
+
if (!module) {
|
|
307
|
+
this.done = true;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const dbKey = module.dbDef.dbKey;
|
|
311
|
+
try {
|
|
312
|
+
const items = await module.query.unManipulatedQuery({
|
|
313
|
+
..._EmptyQuery,
|
|
314
|
+
limit: { page: this.page, itemsCount: this.pageSize },
|
|
315
|
+
});
|
|
316
|
+
this.updateMetadata(module, items);
|
|
317
|
+
return items.map(item => ({
|
|
318
|
+
dbKey: dbKey,
|
|
319
|
+
_id: item._id,
|
|
320
|
+
document: JSON.stringify(item),
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
this.emit('error', err);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
advanceModule = () => {
|
|
328
|
+
this.page = 0;
|
|
329
|
+
this.moduleIndex++;
|
|
330
|
+
};
|
|
331
|
+
async _read() {
|
|
332
|
+
const items = await this.getItems();
|
|
333
|
+
if (!items) { //Only getting undefined if no more items to get, end stream
|
|
334
|
+
this.push(null);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
//If got an array but it was empty, move on to next module
|
|
338
|
+
if (!items.length) {
|
|
339
|
+
this.advanceModule();
|
|
340
|
+
await this._read();
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
items.forEach(item => this.push(item));
|
|
344
|
+
//If the amount of items was under the pageSize, there will be no more items to get from the module, move to the next
|
|
345
|
+
if (items.length < this.pageSize)
|
|
346
|
+
this.advanceModule();
|
|
347
|
+
else
|
|
348
|
+
this.page++;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ModuleBE_FirebaseScheduler } from '@nu-art/firebase-backend';
|
|
2
|
+
declare class ModuleBE_BackupScheduler_Class extends ModuleBE_FirebaseScheduler {
|
|
3
|
+
constructor();
|
|
4
|
+
onScheduledEvent: () => Promise<any>;
|
|
5
|
+
}
|
|
6
|
+
export declare const ModuleBE_BackupScheduler: ModuleBE_BackupScheduler_Class;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ModuleBE_FirebaseScheduler } from '@nu-art/firebase-backend';
|
|
2
|
+
import { ModuleBE_BackupDocDB } from './ModuleBE_BackupDocDB.js';
|
|
3
|
+
class ModuleBE_BackupScheduler_Class extends ModuleBE_FirebaseScheduler {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
this.setSchedule('every 24 hours');
|
|
7
|
+
this.setDefaultConfig({ timeoutSeconds: 540, maxInstances: 1, memory: '2GB' });
|
|
8
|
+
}
|
|
9
|
+
onScheduledEvent = async () => {
|
|
10
|
+
this.logInfoBold(`Running function ${this.getName()}`);
|
|
11
|
+
await ModuleBE_BackupDocDB.initiateBackup();
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export const ModuleBE_BackupScheduler = new ModuleBE_BackupScheduler_Class();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DBProto_EditableTest } from '@nu-art/thunderstorm-shared/_entity/editable-test/types';
|
|
2
|
+
import { DBApiConfigV3, ModuleBE_BaseDB } from '../../modules/db-api-gen/ModuleBE_BaseDB.js';
|
|
3
|
+
type Config = DBApiConfigV3<DBProto_EditableTest> & {};
|
|
4
|
+
export declare class ModuleBE_EditableTestDB_Class extends ModuleBE_BaseDB<DBProto_EditableTest, Config> {
|
|
5
|
+
constructor();
|
|
6
|
+
}
|
|
7
|
+
export declare const ModuleBE_EditableTestDB: ModuleBE_EditableTestDB_Class;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ModuleBE_BaseDB } from '../../modules/db-api-gen/ModuleBE_BaseDB.js';
|
|
2
|
+
import { DBDef_EditableTest } from '@nu-art/thunderstorm-shared/_entity/editable-test/db-def';
|
|
3
|
+
export class ModuleBE_EditableTestDB_Class extends ModuleBE_BaseDB {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(DBDef_EditableTest);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export const ModuleBE_EditableTestDB = new ModuleBE_EditableTestDB_Class();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ModuleBE_EditableTestDB.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ModuleBE_EditableTestDB.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ModulePackBE_EditableTest: (import("./ModuleBE_EditableTestDB.js").ModuleBE_EditableTestDB_Class | import("../../index.js").ModuleBE_BaseApi_Class<import("@nu-art/thunderstorm-shared").DBProto_EditableTest>)[];
|
package/_entity.d.ts
ADDED
package/_entity.js
ADDED