emberflow 1.0.8
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/LICENSE +21 -0
- package/README.md +40 -0
- package/lib/db-structure.d.ts +7 -0
- package/lib/db-structure.js +67 -0
- package/lib/db-structure.js.map +1 -0
- package/lib/index-utils.d.ts +20 -0
- package/lib/index-utils.js +324 -0
- package/lib/index-utils.js.map +1 -0
- package/lib/index.d.ts +23 -0
- package/lib/index.js +197 -0
- package/lib/index.js.map +1 -0
- package/lib/init-db-structure.d.ts +17 -0
- package/lib/init-db-structure.js +111 -0
- package/lib/init-db-structure.js.map +1 -0
- package/lib/logics/index.d.ts +2 -0
- package/lib/logics/index.js +77 -0
- package/lib/logics/index.js.map +1 -0
- package/lib/logics/view-logics.d.ts +3 -0
- package/lib/logics/view-logics.js +167 -0
- package/lib/logics/view-logics.js.map +1 -0
- package/lib/sample-custom/business-logics.d.ts +3 -0
- package/lib/sample-custom/business-logics.js +67 -0
- package/lib/sample-custom/business-logics.js.map +1 -0
- package/lib/sample-custom/db-structure.d.ts +29 -0
- package/lib/sample-custom/db-structure.js +36 -0
- package/lib/sample-custom/db-structure.js.map +1 -0
- package/lib/sample-custom/security.d.ts +2 -0
- package/lib/sample-custom/security.js +63 -0
- package/lib/sample-custom/security.js.map +1 -0
- package/lib/sample-custom/validators.d.ts +9 -0
- package/lib/sample-custom/validators.js +42 -0
- package/lib/sample-custom/validators.js.map +1 -0
- package/lib/tests/index-utils.test.d.ts +1 -0
- package/lib/tests/index-utils.test.js +600 -0
- package/lib/tests/index-utils.test.js.map +1 -0
- package/lib/tests/index.test.d.ts +1 -0
- package/lib/tests/index.test.js +496 -0
- package/lib/tests/index.test.js.map +1 -0
- package/lib/tests/init-db-structure.test.d.ts +1 -0
- package/lib/tests/init-db-structure.test.js +114 -0
- package/lib/tests/init-db-structure.test.js.map +1 -0
- package/lib/tests/logics/index.test.d.ts +1 -0
- package/lib/tests/logics/index.test.js +127 -0
- package/lib/tests/logics/index.test.js.map +1 -0
- package/lib/tests/logics/view-logics.test.d.ts +1 -0
- package/lib/tests/logics/view-logics.test.js +211 -0
- package/lib/tests/logics/view-logics.test.js.map +1 -0
- package/lib/tests/utils/misc.test.d.ts +1 -0
- package/lib/tests/utils/misc.test.js +31 -0
- package/lib/tests/utils/misc.test.js.map +1 -0
- package/lib/tests/utils/paths.test.d.ts +1 -0
- package/lib/tests/utils/paths.test.js +258 -0
- package/lib/tests/utils/paths.test.js.map +1 -0
- package/lib/tests/utils/query.test.d.ts +1 -0
- package/lib/tests/utils/query.test.js +72 -0
- package/lib/tests/utils/query.test.js.map +1 -0
- package/lib/tests/utils/sample.test.d.ts +1 -0
- package/lib/tests/utils/sample.test.js +52 -0
- package/lib/tests/utils/sample.test.js.map +1 -0
- package/lib/types.d.ts +78 -0
- package/lib/types.js +3 -0
- package/lib/types.js.map +1 -0
- package/lib/utils/db-structure.d.ts +2 -0
- package/lib/utils/db-structure.js +9 -0
- package/lib/utils/db-structure.js.map +1 -0
- package/lib/utils/misc.d.ts +1 -0
- package/lib/utils/misc.js +53 -0
- package/lib/utils/misc.js.map +1 -0
- package/lib/utils/paths.d.ts +16 -0
- package/lib/utils/paths.js +136 -0
- package/lib/utils/paths.js.map +1 -0
- package/lib/utils/query.d.ts +2 -0
- package/lib/utils/query.js +41 -0
- package/lib/utils/query.js.map +1 -0
- package/lib/utils/sample.d.ts +5 -0
- package/lib/utils/sample.js +37 -0
- package/lib/utils/sample.js.map +1 -0
- package/lib/utils.d.ts +12 -0
- package/lib/utils.js +67 -0
- package/lib/utils.js.map +1 -0
- package/package.json +37 -0
- package/src/sample-custom/business-logics.ts +51 -0
- package/src/sample-custom/db-structure.ts +33 -0
- package/src/sample-custom/security.ts +77 -0
- package/src/sample-custom/validators.ts +46 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hydrateDocPath = exports.expandAndGroupDocPaths = exports.filterSubDocPathsByEntity = exports.findMatchingDocPathRegex = exports._mockable = void 0;
|
|
4
|
+
const query_1 = require("./query");
|
|
5
|
+
const index_1 = require("../index");
|
|
6
|
+
exports._mockable = {
|
|
7
|
+
filterSubDocPathsByEntity: (entity, excludeEntities) => {
|
|
8
|
+
const path = index_1.docPaths[entity];
|
|
9
|
+
const paths = Object.values(index_1.docPaths);
|
|
10
|
+
// Find the doc paths of the excluded entities
|
|
11
|
+
const excludePaths = excludeEntities === null || excludeEntities === void 0 ? void 0 : excludeEntities.map((excludeEntity) => index_1.docPaths[excludeEntity]);
|
|
12
|
+
return paths.filter((p) => {
|
|
13
|
+
// Check if the path starts with the given entity's doc path
|
|
14
|
+
const startsWithPath = p.startsWith(path);
|
|
15
|
+
// Check if the path starts with any of the excluded entity's doc paths
|
|
16
|
+
const startsWithExcludedPath = excludePaths === null || excludePaths === void 0 ? void 0 : excludePaths.some((excludePath) => p.startsWith(excludePath));
|
|
17
|
+
return startsWithPath && !startsWithExcludedPath;
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
function findMatchingDocPathRegex(docPath) {
|
|
22
|
+
for (const key in index_1.docPathsRegex) {
|
|
23
|
+
if (index_1.docPathsRegex[key].test(docPath)) {
|
|
24
|
+
return { entity: key, regex: index_1.docPathsRegex[key] };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { entity: null, regex: null };
|
|
28
|
+
}
|
|
29
|
+
exports.findMatchingDocPathRegex = findMatchingDocPathRegex;
|
|
30
|
+
function filterSubDocPathsByEntity(entity, excludeEntities) {
|
|
31
|
+
return exports._mockable.filterSubDocPathsByEntity(entity, excludeEntities);
|
|
32
|
+
}
|
|
33
|
+
exports.filterSubDocPathsByEntity = filterSubDocPathsByEntity;
|
|
34
|
+
async function expandAndGroupDocPaths(startingDocPath, entityCondition, excludeEntities) {
|
|
35
|
+
const groupedPaths = {};
|
|
36
|
+
const { entity } = findMatchingDocPathRegex(startingDocPath);
|
|
37
|
+
if (!entity) {
|
|
38
|
+
return groupedPaths;
|
|
39
|
+
}
|
|
40
|
+
const entityDocPath = index_1.docPaths[entity];
|
|
41
|
+
const subDocPaths = filterSubDocPathsByEntity(entity, excludeEntities);
|
|
42
|
+
const values = Object.values(subDocPaths).map((p) => p.replace(entityDocPath, startingDocPath));
|
|
43
|
+
const sortedValues = values.sort();
|
|
44
|
+
const newPathMap = new Map();
|
|
45
|
+
const expandedPaths = [];
|
|
46
|
+
while (sortedValues.length > 0) {
|
|
47
|
+
const path = sortedValues.shift();
|
|
48
|
+
if (!path) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
let skipPath = false;
|
|
52
|
+
for (const key of [...newPathMap.keys()].sort()) {
|
|
53
|
+
if (path.startsWith(key)) {
|
|
54
|
+
skipPath = true;
|
|
55
|
+
const values = newPathMap.get(key);
|
|
56
|
+
const newPaths = (values || []).map((value) => path.replace(key, value));
|
|
57
|
+
sortedValues.push(...newPaths);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (skipPath)
|
|
62
|
+
continue;
|
|
63
|
+
if (/{\w+Id}$/.test(path)) {
|
|
64
|
+
const idIndex = path.lastIndexOf("/");
|
|
65
|
+
const collectionPath = path.substring(0, idIndex);
|
|
66
|
+
const { entity } = findMatchingDocPathRegex(path);
|
|
67
|
+
const ids = await (0, query_1.fetchIds)(collectionPath, entityCondition === null || entityCondition === void 0 ? void 0 : entityCondition[entity]);
|
|
68
|
+
const newPaths = ids.map((id) => path.replace(/{\w+Id}$/, id.toString()));
|
|
69
|
+
newPathMap.set(path, newPaths);
|
|
70
|
+
sortedValues.push(...newPaths);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
expandedPaths.push(path);
|
|
74
|
+
}
|
|
75
|
+
// Group expandedPaths based on docPaths keys and values
|
|
76
|
+
for (const [key, regex] of Object.entries(index_1.docPathsRegex)) {
|
|
77
|
+
const paths = expandedPaths.filter((p) => regex.test(p));
|
|
78
|
+
if (!paths.length)
|
|
79
|
+
continue;
|
|
80
|
+
groupedPaths[key] = paths;
|
|
81
|
+
}
|
|
82
|
+
return groupedPaths;
|
|
83
|
+
}
|
|
84
|
+
exports.expandAndGroupDocPaths = expandAndGroupDocPaths;
|
|
85
|
+
async function hydrateDocPath(destDocPath, entityCondition) {
|
|
86
|
+
const pathSegments = destDocPath.split("/");
|
|
87
|
+
const documentPaths = [];
|
|
88
|
+
// Create a queue to keep track of the remaining path segments to process
|
|
89
|
+
const queue = [[pathSegments, 0]];
|
|
90
|
+
// Process the queue until all path segments have been processed
|
|
91
|
+
while (queue.length > 0) {
|
|
92
|
+
const [segments, idx] = queue.shift();
|
|
93
|
+
// Find the next path segment that is wrapped by curly braces
|
|
94
|
+
let braceIdx = -1;
|
|
95
|
+
for (let i = idx; i < segments.length; i++) {
|
|
96
|
+
if (segments[i].startsWith("{")) {
|
|
97
|
+
braceIdx = i;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (braceIdx === -1) {
|
|
102
|
+
// We've reached the end of the path, so add it to the document paths
|
|
103
|
+
const path = segments.join("/");
|
|
104
|
+
if (idx < segments.length - 1) {
|
|
105
|
+
// This means that the path contains hard coded ids, so we need to check if that pat exists in the database
|
|
106
|
+
const doc = await index_1.admin.firestore().doc(path).get();
|
|
107
|
+
if (!doc.exists) {
|
|
108
|
+
console.error(`Document ${path} does not exist. Skipping...`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
documentPaths.push(path);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// Extract the collection path and fetch its IDs
|
|
116
|
+
const collectionPathSegments = segments.slice(0, braceIdx);
|
|
117
|
+
const collectionPath = collectionPathSegments.join("/");
|
|
118
|
+
const entity = segments[braceIdx].slice(1, -3);
|
|
119
|
+
const condition = entityCondition[entity];
|
|
120
|
+
const ids = await (0, query_1.fetchIds)(collectionPath, condition);
|
|
121
|
+
if (ids.length === 0) {
|
|
122
|
+
console.info(`No IDs found for ${collectionPath} with condition ${JSON.stringify(condition)}. Skipping...`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// Generate the document paths by merging the IDs with the collection path
|
|
126
|
+
const remainingPathSegments = segments.slice(braceIdx);
|
|
127
|
+
for (const id of ids) {
|
|
128
|
+
const documentPathSegments = [...collectionPathSegments, id, ...remainingPathSegments.slice(1)];
|
|
129
|
+
queue.push([documentPathSegments, braceIdx + 1]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return documentPaths;
|
|
134
|
+
}
|
|
135
|
+
exports.hydrateDocPath = hydrateDocPath;
|
|
136
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchIds = void 0;
|
|
4
|
+
const index_1 = require("../index");
|
|
5
|
+
const firebase_admin_1 = require("firebase-admin");
|
|
6
|
+
async function fetchIds(collectionPath, condition) {
|
|
7
|
+
const ids = [];
|
|
8
|
+
const baseQuery = index_1.admin.firestore().collection(collectionPath).select(firebase_admin_1.firestore.FieldPath.documentId());
|
|
9
|
+
async function executeQuery(query) {
|
|
10
|
+
const querySnapshot = await query.get();
|
|
11
|
+
querySnapshot.docs.forEach((doc) => {
|
|
12
|
+
ids.push(doc.id);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (condition &&
|
|
16
|
+
(condition.operator === "in" ||
|
|
17
|
+
condition.operator === "not-in" ||
|
|
18
|
+
condition.operator === "array-contains-any")) {
|
|
19
|
+
const chunkSize = 10;
|
|
20
|
+
const values = condition.value;
|
|
21
|
+
const chunks = [];
|
|
22
|
+
for (let i = 0; i < values.length; i += chunkSize) {
|
|
23
|
+
chunks.push(values.slice(i, i + chunkSize));
|
|
24
|
+
}
|
|
25
|
+
const promises = chunks.map((chunk) => {
|
|
26
|
+
const query = baseQuery.where(condition.fieldName, condition.operator, chunk);
|
|
27
|
+
return executeQuery(query);
|
|
28
|
+
});
|
|
29
|
+
await Promise.all(promises);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
let query = baseQuery;
|
|
33
|
+
if (condition) {
|
|
34
|
+
query = query.where(condition.fieldName, condition.operator, condition.value);
|
|
35
|
+
}
|
|
36
|
+
await executeQuery(query);
|
|
37
|
+
}
|
|
38
|
+
return ids;
|
|
39
|
+
}
|
|
40
|
+
exports.fetchIds = fetchIds;
|
|
41
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/utils/query.ts"],"names":[],"mappings":";;;AAAA,oCAA+B;AAC/B,mDAAyC;AAKlC,KAAK,UAAU,QAAQ,CAAC,cAAsB,EAAE,SAA0B;IAC/E,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,SAAS,GAAG,aAAK,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,0BAAS,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC,CAAC;IAExG,KAAK,UAAU,YAAY,CAAC,KAA0B;QACpD,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC;QACxC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACjC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IACE,SAAS;QACP,CACE,SAAS,CAAC,QAAQ,KAAK,IAAI;YACzB,SAAS,CAAC,QAAQ,KAAK,QAAQ;YAC/B,SAAS,CAAC,QAAQ,KAAK,oBAAoB,CAC9C,EACH;QACA,MAAM,SAAS,GAAG,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC;QAC/B,MAAM,MAAM,GAAG,EAAE,CAAC;QAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE;YACjD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;SAC7C;QACD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACpC,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC9E,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;KAC7B;SAAM;QACL,IAAI,KAAK,GAAG,SAAS,CAAC;QACtB,IAAI,SAAS,EAAE;YACb,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;SAC/E;QACD,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;KAC3B;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAvCD,4BAuCC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.sampleFunction = exports._mockable = void 0;
|
|
27
|
+
const admin = __importStar(require("firebase-admin"));
|
|
28
|
+
exports._mockable = {
|
|
29
|
+
createNowTimestamp: () => admin.firestore.Timestamp.now(),
|
|
30
|
+
};
|
|
31
|
+
function sampleFunction() {
|
|
32
|
+
const firestore = admin.firestore();
|
|
33
|
+
const now = exports._mockable.createNowTimestamp();
|
|
34
|
+
console.info(firestore != null, now);
|
|
35
|
+
}
|
|
36
|
+
exports.sampleFunction = sampleFunction;
|
|
37
|
+
//# sourceMappingURL=sample.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sample.js","sourceRoot":"","sources":["../../src/utils/sample.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,sDAAwC;AAE3B,QAAA,SAAS,GAAG;IACvB,kBAAkB,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE;CAC1D,CAAC;AAEF,SAAgB,cAAc;IAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,iBAAS,CAAC,kBAAkB,EAAE,CAAC;IAC3C,OAAO,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC;AACvC,CAAC;AAJD,wCAIC"}
|
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare function findMatchingDocPathRegex(docPath: string, docPathsRegex: Record<string, RegExp>): {
|
|
2
|
+
entity: string;
|
|
3
|
+
regex: RegExp;
|
|
4
|
+
} | {
|
|
5
|
+
entity: null;
|
|
6
|
+
regex: null;
|
|
7
|
+
};
|
|
8
|
+
declare function filterSubDocPathsByEntity(entity: string, docPaths: Record<string, string>): string[];
|
|
9
|
+
declare function expandAndGroupDocPaths(startingDocPath: string, idsFetcher: (collectionPath: string) => Promise<string[]>): Promise<{
|
|
10
|
+
[key: string]: string[];
|
|
11
|
+
}>;
|
|
12
|
+
export { expandAndGroupDocPaths, filterSubDocPathsByEntity, findMatchingDocPathRegex };
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findMatchingDocPathRegex = exports.filterSubDocPathsByEntity = exports.expandAndGroupDocPaths = void 0;
|
|
4
|
+
const index_1 = require("./index");
|
|
5
|
+
function findMatchingDocPathRegex(docPath, docPathsRegex) {
|
|
6
|
+
for (const key in docPathsRegex) {
|
|
7
|
+
if (docPathsRegex[key].test(docPath)) {
|
|
8
|
+
return { entity: key, regex: docPathsRegex[key] };
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return { entity: null, regex: null };
|
|
12
|
+
}
|
|
13
|
+
exports.findMatchingDocPathRegex = findMatchingDocPathRegex;
|
|
14
|
+
function filterSubDocPathsByEntity(entity, docPaths) {
|
|
15
|
+
const path = docPaths[entity];
|
|
16
|
+
const paths = Object.values(docPaths);
|
|
17
|
+
return paths.filter((p) => p.startsWith(path));
|
|
18
|
+
}
|
|
19
|
+
exports.filterSubDocPathsByEntity = filterSubDocPathsByEntity;
|
|
20
|
+
async function expandAndGroupDocPaths(startingDocPath, idsFetcher) {
|
|
21
|
+
const groupedPaths = {};
|
|
22
|
+
const { entity } = findMatchingDocPathRegex(startingDocPath, index_1.docPathsRegex);
|
|
23
|
+
if (!entity) {
|
|
24
|
+
return groupedPaths;
|
|
25
|
+
}
|
|
26
|
+
const entityDocPath = index_1.docPaths[entity];
|
|
27
|
+
const subDocPaths = filterSubDocPathsByEntity(entity, index_1.docPaths);
|
|
28
|
+
const values = Object.values(subDocPaths).map((p) => p.replace(entityDocPath, startingDocPath));
|
|
29
|
+
const sortedValues = values.sort();
|
|
30
|
+
const newPathMap = new Map();
|
|
31
|
+
const expandedPaths = [];
|
|
32
|
+
while (sortedValues.length > 0) {
|
|
33
|
+
const path = sortedValues.shift();
|
|
34
|
+
if (!path) {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
let skipPath = false;
|
|
38
|
+
for (const key of [...newPathMap.keys()].sort()) {
|
|
39
|
+
if (path.startsWith(key)) {
|
|
40
|
+
skipPath = true;
|
|
41
|
+
const values = newPathMap.get(key);
|
|
42
|
+
const newPaths = (values || []).map((value) => path.replace(key, value));
|
|
43
|
+
sortedValues.push(...newPaths);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (skipPath)
|
|
48
|
+
continue;
|
|
49
|
+
if (/{\w+Id}$/.test(path)) {
|
|
50
|
+
const idIndex = path.lastIndexOf("/");
|
|
51
|
+
const collectionPath = path.substring(0, idIndex);
|
|
52
|
+
const ids = await idsFetcher(collectionPath);
|
|
53
|
+
const newPaths = ids.map((id) => path.replace(/{\w+Id}$/, id.toString()));
|
|
54
|
+
newPathMap.set(path, newPaths);
|
|
55
|
+
sortedValues.push(...newPaths);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
expandedPaths.push(path);
|
|
59
|
+
}
|
|
60
|
+
// Group expandedPaths based on docPaths keys and values
|
|
61
|
+
for (const [key, regex] of Object.entries(index_1.docPathsRegex)) {
|
|
62
|
+
groupedPaths[key] = expandedPaths.filter((path) => path.match(regex));
|
|
63
|
+
}
|
|
64
|
+
return groupedPaths;
|
|
65
|
+
}
|
|
66
|
+
exports.expandAndGroupDocPaths = expandAndGroupDocPaths;
|
|
67
|
+
//# sourceMappingURL=utils.js.map
|
package/lib/utils.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "emberflow",
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"description": "A Firebase Cloud Functions library for handling document changes in a structured way",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib/",
|
|
9
|
+
"src/sample-custom"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"prepublishOnly": "tsc"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/aris-i/emberflow.git"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"firebase-admin": "^11.5.0",
|
|
20
|
+
"firebase-functions": "^4.2.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/chai": "^4.3.4",
|
|
24
|
+
"@types/jest": "^29.4.0",
|
|
25
|
+
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
|
26
|
+
"@typescript-eslint/parser": "^5.12.0",
|
|
27
|
+
"chai": "^4.3.7",
|
|
28
|
+
"eslint": "^8.9.0",
|
|
29
|
+
"eslint-config-google": "^0.14.0",
|
|
30
|
+
"eslint-plugin-import": "^2.25.4",
|
|
31
|
+
"firebase-functions-test": "^3.0.0",
|
|
32
|
+
"jest": "^29.4.3",
|
|
33
|
+
"ts-jest": "^29.0.5",
|
|
34
|
+
"typescript": "^4.9.0"
|
|
35
|
+
},
|
|
36
|
+
"private": false
|
|
37
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as admin from "firebase-admin";
|
|
2
|
+
import {firestore} from "firebase-admin";
|
|
3
|
+
import DocumentData = firestore.DocumentData;
|
|
4
|
+
// You should import from the path to the ember-flow package in your project
|
|
5
|
+
import {LogicConfig, LogicFn} from "../types";
|
|
6
|
+
|
|
7
|
+
const echoLogic: LogicFn = async (action) => {
|
|
8
|
+
const {document, timeCreated, path, modifiedFields} = action;
|
|
9
|
+
console.log(`Executing EchoLogic on document at ${path}...`);
|
|
10
|
+
|
|
11
|
+
const updatedDoc: DocumentData = {};
|
|
12
|
+
|
|
13
|
+
// Copy modified fields of document's @form to the document
|
|
14
|
+
if (document["@form"] && modifiedFields) {
|
|
15
|
+
for (const field of modifiedFields) {
|
|
16
|
+
if (document["@form"][field]) {
|
|
17
|
+
updatedDoc[field] = document["@form"][field];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Return the result of the logic function
|
|
23
|
+
return {
|
|
24
|
+
name: "EchoLogic",
|
|
25
|
+
status: "finished",
|
|
26
|
+
execTime: Date.now() - timeCreated.toMillis(),
|
|
27
|
+
timeFinished: admin.firestore.Timestamp.now(),
|
|
28
|
+
documents: [
|
|
29
|
+
{
|
|
30
|
+
action: "merge",
|
|
31
|
+
dstPath: path,
|
|
32
|
+
doc: updatedDoc,
|
|
33
|
+
instructions: {},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
const logics: LogicConfig[] = [
|
|
41
|
+
{
|
|
42
|
+
name: "EchoLogic",
|
|
43
|
+
actionTypes: ["create", "update"],
|
|
44
|
+
modifiedFields: "all",
|
|
45
|
+
entities: "all",
|
|
46
|
+
logicFn: echoLogic,
|
|
47
|
+
},
|
|
48
|
+
// more logics here
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export {logics};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// You should import from the path to the ember-flow package in your project
|
|
2
|
+
import {view} from "../utils/db-structure";
|
|
3
|
+
|
|
4
|
+
export enum Entity {
|
|
5
|
+
User = "user", // do not delete
|
|
6
|
+
// Add your custom entities below
|
|
7
|
+
Feed = "feed",
|
|
8
|
+
Friend = "friend",
|
|
9
|
+
Game = "game",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Map your custom entities to dbStructure below.
|
|
13
|
+
// Do not remove users and [Entity.User]
|
|
14
|
+
// by default, view matches the id attribute of the view so make the sure that a view has an id
|
|
15
|
+
export const dbStructure = {
|
|
16
|
+
users: {
|
|
17
|
+
[Entity.User]: {
|
|
18
|
+
feeds: {
|
|
19
|
+
[Entity.Feed]: {
|
|
20
|
+
createdBy: view(Entity.User, ["name", "email"]),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
friends: {
|
|
24
|
+
[Entity.Friend]: {
|
|
25
|
+
[view(Entity.User, ["name", "email"])]: {},
|
|
26
|
+
games: {
|
|
27
|
+
game: {},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {Entity} from "./db-structure";
|
|
2
|
+
// You should import from the path to the ember-flow package in your project
|
|
3
|
+
import {
|
|
4
|
+
SecurityConfig,
|
|
5
|
+
SecurityFn,
|
|
6
|
+
SecurityResult,
|
|
7
|
+
} from "../types";
|
|
8
|
+
|
|
9
|
+
// A security function that allows all actions
|
|
10
|
+
const allAllowed: SecurityFn = async (
|
|
11
|
+
entity,
|
|
12
|
+
doc,
|
|
13
|
+
actionType,
|
|
14
|
+
modifiedFields
|
|
15
|
+
) => {
|
|
16
|
+
console.log(`Security check for entity ${entity}, action type ${actionType},
|
|
17
|
+
and modified fields:`, modifiedFields);
|
|
18
|
+
return {
|
|
19
|
+
status: "allowed",
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const userSecurityFn: SecurityFn =
|
|
24
|
+
async (entity, doc, actionType, modifiedFields):
|
|
25
|
+
Promise<SecurityResult> => {
|
|
26
|
+
switch (actionType) {
|
|
27
|
+
case "create": {
|
|
28
|
+
// Example: allow the user to create a new account only
|
|
29
|
+
// if they are registering from a whitelisted domain
|
|
30
|
+
const email = doc["@form"].email;
|
|
31
|
+
const domain = email.split("@")[1];
|
|
32
|
+
const allowedDomains = ["example.com", "example.org"];
|
|
33
|
+
let result: SecurityResult;
|
|
34
|
+
if (!allowedDomains.includes(domain)) {
|
|
35
|
+
result = {
|
|
36
|
+
status: "rejected",
|
|
37
|
+
message: `Registration is only allowed from these domains:
|
|
38
|
+
${allowedDomains.join(", ")}`,
|
|
39
|
+
};
|
|
40
|
+
} else {
|
|
41
|
+
result = {
|
|
42
|
+
status: "allowed",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
case "update": {
|
|
49
|
+
// Example: do not allow the user to update their system role
|
|
50
|
+
if (modifiedFields?.includes("systemRole")) {
|
|
51
|
+
return {
|
|
52
|
+
status: "rejected",
|
|
53
|
+
message: "User is not allowed to change his system role",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "delete":
|
|
59
|
+
// Example: reject all delete requests
|
|
60
|
+
return {
|
|
61
|
+
status: "rejected",
|
|
62
|
+
message: "You are not allowed to delete this document",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
status: "allowed",
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const securityConfig: SecurityConfig = {
|
|
72
|
+
// Implement your security functions for each entity here
|
|
73
|
+
[Entity.User]: userSecurityFn,
|
|
74
|
+
[Entity.Feed]: allAllowed,
|
|
75
|
+
[Entity.Friend]: allAllowed,
|
|
76
|
+
};
|
|
77
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {Entity} from "./db-structure";
|
|
2
|
+
import {firestore} from "firebase-admin";
|
|
3
|
+
import DocumentData = firestore.DocumentData;
|
|
4
|
+
// You should import from the path to the ember-flow package in your project
|
|
5
|
+
import {ValidationResult, ValidatorConfig} from "../types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validates the user document.
|
|
9
|
+
*
|
|
10
|
+
* @param {DocumentData} document - The document data to validate.
|
|
11
|
+
* @param {string} docPath - The path to the document.
|
|
12
|
+
* @return {Promise<ValidationResult>} An object containing validation errors, if any.
|
|
13
|
+
*/
|
|
14
|
+
async function userValidator(document: DocumentData, docPath: string): Promise<ValidationResult> {
|
|
15
|
+
const data = document;
|
|
16
|
+
const result: ValidationResult = {};
|
|
17
|
+
if (!data || !data.name) {
|
|
18
|
+
result["name"] = ["Name is required"];
|
|
19
|
+
}
|
|
20
|
+
return Promise.resolve(result);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A blank validator that always returns an empty ValidationResult object.
|
|
25
|
+
*
|
|
26
|
+
* @param {DocumentData} document - The document data to validate.
|
|
27
|
+
* @param {string} docPath - The path to the document.
|
|
28
|
+
* @return {Promise<ValidationResult>} An empty ValidationResult object.
|
|
29
|
+
*/
|
|
30
|
+
async function blankValidator(document: DocumentData, docPath: string): Promise<ValidationResult> {
|
|
31
|
+
return Promise.resolve({});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The validator configuration object, mapping entity names to their respective
|
|
36
|
+
* validator functions.
|
|
37
|
+
*
|
|
38
|
+
* @type {ValidatorConfig}
|
|
39
|
+
*/
|
|
40
|
+
const validatorConfig: ValidatorConfig = {
|
|
41
|
+
[Entity.User]: userValidator,
|
|
42
|
+
[Entity.Feed]: blankValidator,
|
|
43
|
+
[Entity.Friend]: blankValidator,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export {validatorConfig};
|