@mxpicture/gcp-functions 0.1.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.
Files changed (38) hide show
  1. package/README.md +38 -0
  2. package/dist/cjs/FunctionHandlers.js +18 -0
  3. package/dist/cjs/api/IApi.js +52 -0
  4. package/dist/cjs/common/firebase.js +174 -0
  5. package/dist/cjs/common/http.js +147 -0
  6. package/dist/cjs/common/types.common.js +30 -0
  7. package/dist/cjs/handler/HttpHandler.js +41 -0
  8. package/dist/cjs/handler/IHttpHandlerItem.js +169 -0
  9. package/dist/cjs/index.js +42 -0
  10. package/dist/cjs/lib.js +25 -0
  11. package/dist/cjs/package.js +21 -0
  12. package/dist/cjs/store/IStore.js +107 -0
  13. package/dist/cjs/store/types.store.js +15 -0
  14. package/dist/esm/FunctionHandlers.js +18 -0
  15. package/dist/esm/api/IApi.js +52 -0
  16. package/dist/esm/common/firebase.js +174 -0
  17. package/dist/esm/common/http.js +147 -0
  18. package/dist/esm/common/types.common.js +30 -0
  19. package/dist/esm/handler/HttpHandler.js +41 -0
  20. package/dist/esm/handler/IHttpHandlerItem.js +169 -0
  21. package/dist/esm/index.js +42 -0
  22. package/dist/esm/lib.js +25 -0
  23. package/dist/esm/package.js +21 -0
  24. package/dist/esm/store/IStore.js +107 -0
  25. package/dist/esm/store/types.store.js +15 -0
  26. package/dist/types/FunctionHandlers.d.ts +12 -0
  27. package/dist/types/api/IApi.d.ts +27 -0
  28. package/dist/types/common/firebase.d.ts +11 -0
  29. package/dist/types/common/http.d.ts +20 -0
  30. package/dist/types/common/types.common.d.ts +31 -0
  31. package/dist/types/handler/HttpHandler.d.ts +15 -0
  32. package/dist/types/handler/IHttpHandlerItem.d.ts +23 -0
  33. package/dist/types/index.d.ts +3 -0
  34. package/dist/types/lib.d.ts +11 -0
  35. package/dist/types/package.d.ts +1 -0
  36. package/dist/types/store/IStore.d.ts +25 -0
  37. package/dist/types/store/types.store.d.ts +23 -0
  38. package/package.json +74 -0
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # gcp-functions-lib
2
+
3
+ Example Node library using TypeScript and Zod. Produces:
4
+
5
+ - CommonJS build at `dist/cjs`
6
+ - ESM build at `dist/esm`
7
+ - Type declarations at `dist/types`
8
+
9
+ Usage (after npm publish or local link):
10
+
11
+ ESM:
12
+
13
+ ```js
14
+ import { validateUser } from "gcp-functions-lib";
15
+ ```
16
+
17
+ CommonJS:
18
+
19
+ ```js
20
+ const { validateUser } = require("gcp-functions-lib");
21
+ ```
22
+
23
+ Build:
24
+
25
+ ```bash
26
+ npm install
27
+ npm run build
28
+ ```
29
+
30
+ ## Setup husky
31
+
32
+ ```bash
33
+ npm install --save-dev @commitlint/config-conventional @commitlint/cli husky commitizen cz-conventional-changelog husky
34
+ npx husky-init
35
+ npm i
36
+ npm run prepare
37
+ npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
38
+ ```
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FunctionHandlers = void 0;
4
+ class FunctionHandlers {
5
+ constructor() {
6
+ this.items = {};
7
+ }
8
+ add(handler) {
9
+ this.items[handler.name] = handler;
10
+ }
11
+ functions() {
12
+ const result = {};
13
+ for (const [key, item] of Object.entries(this.items))
14
+ result[key] = item.setup();
15
+ return result;
16
+ }
17
+ }
18
+ exports.FunctionHandlers = FunctionHandlers;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IApi = void 0;
4
+ const zod_meta_1 = require("@mxpicture/zod-meta");
5
+ class IApi {
6
+ constructor(store, shape) {
7
+ this.store = store;
8
+ this.shape = shape;
9
+ }
10
+ metaItems() {
11
+ return new zod_meta_1.Meta(this.shape).items();
12
+ }
13
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
14
+ async create(doc, params) {
15
+ const storeDoc = await this.store.create(doc);
16
+ return this.fromStore(storeDoc);
17
+ }
18
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
19
+ async get(id, params) {
20
+ const storeDoc = await this.store.get(id);
21
+ return this.fromStore(storeDoc);
22
+ }
23
+ async query(params) {
24
+ const storeDocs = await this.store.query(params);
25
+ return await Promise.all(storeDocs.map((s) => this.fromStore(s)));
26
+ }
27
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
28
+ async count(params) {
29
+ return this.store.count();
30
+ }
31
+ async update(id, doc,
32
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
33
+ params) {
34
+ let storeDoc = await this.toStore(doc);
35
+ storeDoc = await this.store.update(id, storeDoc);
36
+ return this.fromStore(storeDoc);
37
+ }
38
+ async updatePartial(id, doc) {
39
+ const storeDoc = await this.toStorePartial(doc);
40
+ await this.store.update(id, storeDoc);
41
+ return this.get(id);
42
+ }
43
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
44
+ async delete(id, params) {
45
+ await this.store.delete(id);
46
+ }
47
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
48
+ async exists(id, params) {
49
+ return this.store.exists(id);
50
+ }
51
+ }
52
+ exports.IApi = IApi;
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.round = exports.verifyConnectionToken = exports.sessionLogout = exports.sessionLogin = exports.verifyAuthAll = exports.verifySessionCookie = exports.verifyToken = exports.extractToken = exports.db = exports.app = void 0;
4
+ const auth_1 = require("firebase-admin/auth");
5
+ const firestore_1 = require("firebase-admin/firestore");
6
+ const app_1 = require("firebase-admin/app");
7
+ exports.app = (0, app_1.initializeApp)();
8
+ exports.db = (0, firestore_1.getFirestore)();
9
+ const extractToken = (request) => {
10
+ const auth = request.headers["authorization"];
11
+ if (!auth)
12
+ return;
13
+ // Read the ID Token from the Authorization header.
14
+ if (auth && auth.startsWith("Bearer "))
15
+ return auth.split("Bearer ")[1];
16
+ // Read the ID Token from cookie.
17
+ if (request.cookies)
18
+ return request.cookies.__session;
19
+ return;
20
+ };
21
+ exports.extractToken = extractToken;
22
+ // Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
23
+ // The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
24
+ // `Authorization: Bearer <Firebase ID Token>`.
25
+ // when decoded successfully, the ID Token content will be added as `request.user`.
26
+ const verifyToken = (request, response, next) => {
27
+ const token = (0, exports.extractToken)(request);
28
+ if (!token) {
29
+ response
30
+ .status(401)
31
+ .json({ status: "unauthorized", message: "Unauthorized" });
32
+ return;
33
+ }
34
+ (0, auth_1.getAuth)()
35
+ .verifyIdToken(token)
36
+ .then((decodedIdToken) => {
37
+ // Only process if the user just signed in in the last 5 minutes.
38
+ if (Date.now() / 1000 - decodedIdToken.auth_time >= 5 * 60)
39
+ return response.status(401).json({
40
+ status: "unauthorized",
41
+ message: "Recent sign in required!",
42
+ });
43
+ request.user = decodedIdToken;
44
+ request.headers["x-id-token"] = token;
45
+ return next();
46
+ }, (error) => {
47
+ response.status(401).json({
48
+ status: "unauthorized",
49
+ message: JSON.stringify(error),
50
+ });
51
+ })
52
+ .catch((error) => {
53
+ response.status(401).json({
54
+ status: "unauthorized",
55
+ message: `Error while verifying Firebase ID token: ${error}`,
56
+ });
57
+ });
58
+ };
59
+ exports.verifyToken = verifyToken;
60
+ const verifySessionCookie = (request, response, next) => {
61
+ const sessionCookie =
62
+ // request.cookies.session ||
63
+ // request.cookies.zsession ||
64
+ request.cookies.__session || request.headers.zsession || "";
65
+ // Verify the session cookie. In this case an additional check is added to detect
66
+ // if the user's Firebase session was revoked, user deleted/disabled, etc.
67
+ (0, auth_1.getAuth)()
68
+ .verifySessionCookie(sessionCookie, true /** checkRevoked */)
69
+ .then(() => {
70
+ next();
71
+ }, (error) => {
72
+ response.status(401).json({
73
+ status: "unauthorized",
74
+ message: JSON.stringify(error),
75
+ });
76
+ })
77
+ .catch((error) => {
78
+ response.status(401).json({
79
+ status: "unauthorized",
80
+ message: JSON.stringify(error),
81
+ });
82
+ });
83
+ };
84
+ exports.verifySessionCookie = verifySessionCookie;
85
+ const verifyAuthAll = (token) => {
86
+ return (request, response, next) => {
87
+ const t = token();
88
+ // connection token
89
+ const id = request.headers["x-connection-token"];
90
+ if (typeof id === "string") {
91
+ if (id === t)
92
+ return next();
93
+ return response.status(401).json({
94
+ status: "unauthorized",
95
+ message: "",
96
+ });
97
+ }
98
+ // message connection token
99
+ if ((0, exports.extractToken)(request))
100
+ return (0, exports.verifyToken)(request, response, next);
101
+ // session cookie
102
+ return (0, exports.verifySessionCookie)(request, response, next);
103
+ };
104
+ };
105
+ exports.verifyAuthAll = verifyAuthAll;
106
+ const sessionLogin = (request, response) => {
107
+ // Get the ID token passed and the CSRF token.
108
+ const idToken = `${request.headers["x-id-token"]}`;
109
+ // Set session expiration to 5 days.
110
+ const expiresIn = 60 * 60 * 24 * 5 * 1000;
111
+ // Create the session cookie. This will also verify the ID token in the process.
112
+ // The session cookie will have the same claims as the ID token.
113
+ // To only allow session cookie setting on recent sign-in, auth_time in ID token
114
+ // can be checked to ensure user was recently signed in before creating a session cookie.
115
+ (0, auth_1.getAuth)()
116
+ .createSessionCookie(idToken, { expiresIn })
117
+ .then((sessionCookie) => {
118
+ // Set cookie policy for session cookie.
119
+ // cloud functions will only allow __session (issue only deployed)
120
+ response.cookie("__session", sessionCookie, {
121
+ maxAge: expiresIn,
122
+ httpOnly: true,
123
+ secure: true,
124
+ });
125
+ // const now = new Date();
126
+ // const zsession =
127
+ // "zsession=" +
128
+ // sessionCookie +
129
+ // "; expires=" +
130
+ // now.toUTCString() +
131
+ // "; path=/";
132
+ response.end(JSON.stringify({
133
+ status: "success",
134
+ // zsession: zsession,
135
+ }));
136
+ }, (error) => {
137
+ response.status(401).json({
138
+ status: "unauthorized",
139
+ message: JSON.stringify(error),
140
+ });
141
+ })
142
+ .catch((error) => {
143
+ response.status(401).json({
144
+ status: "unauthorized",
145
+ message: JSON.stringify(error),
146
+ });
147
+ });
148
+ };
149
+ exports.sessionLogin = sessionLogin;
150
+ const sessionLogout = (request, response) => {
151
+ response.clearCookie("session");
152
+ response.clearCookie("zsession");
153
+ response.clearCookie("__session");
154
+ };
155
+ exports.sessionLogout = sessionLogout;
156
+ const verifyConnectionToken = (token) => {
157
+ return async (request, response, next) => {
158
+ const id = request.headers["x-connection-token"];
159
+ if (id === token()) {
160
+ next();
161
+ return;
162
+ }
163
+ response.status(401).json({
164
+ status: "unauthorized",
165
+ message: "",
166
+ });
167
+ };
168
+ };
169
+ exports.verifyConnectionToken = verifyConnectionToken;
170
+ const round = (value, digits = 3) => {
171
+ const factor = Math.pow(10, digits);
172
+ return Math.round(value * factor) / factor;
173
+ };
174
+ exports.round = round;
@@ -0,0 +1,147 @@
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 () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.fixRequestBody = exports.hasBody = exports.contentstream = void 0;
40
+ const querystring = __importStar(require("querystring"));
41
+ const zlib_1 = require("zlib");
42
+ const zlib = __importStar(require("zlib"));
43
+ const http_errors_1 = __importDefault(require("http-errors"));
44
+ // export const handleErrorMessage = (
45
+ // response: express.Response,
46
+ // error: unknown,
47
+ // code: number = StatusCodes.BAD_REQUEST,
48
+ // ) => {
49
+ // // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ // const ae = error as any;
51
+ // const result = { message: "" };
52
+ // if (error instanceof Error) result.message = error.message;
53
+ // else if (typeof error === "string") result.message = error;
54
+ // else if (ae.message) result.message = ae.message;
55
+ // else if (ae.payload) {
56
+ // if (ae.payload.message && typeof ae.payload.message === "string")
57
+ // result.message = ae.payload.message;
58
+ // else if (typeof ae.payload === "string") result.message = ae.payload;
59
+ // } else result.message = "unknown";
60
+ // return response.status(code).json(result);
61
+ // };
62
+ const hasBrotliSupport = "createBrotliDecompress" in zlib;
63
+ /**
64
+ * Get the content stream of the request.
65
+ *
66
+ * @param {object} req
67
+ * @param {function} debug
68
+ * @param {boolean} [inflate=true]
69
+ * @return {object}
70
+ * @api private
71
+ */
72
+ const contentstream = (req, inflate, debug = console) => {
73
+ const encoding = (req.headers["content-encoding"] || "identity").toLowerCase();
74
+ // const length = req.headers["content-length"];
75
+ let stream;
76
+ debug?.log('content-encoding "%s"', encoding);
77
+ if (inflate === false && encoding !== "identity") {
78
+ throw (0, http_errors_1.default)(415, "content encoding unsupported", {
79
+ encoding: encoding,
80
+ type: "encoding.unsupported",
81
+ });
82
+ }
83
+ switch (encoding) {
84
+ case "deflate":
85
+ stream = (0, zlib_1.createInflate)();
86
+ debug?.log("inflate body");
87
+ req.pipe(stream);
88
+ break;
89
+ case "gzip":
90
+ stream = (0, zlib_1.createGunzip)();
91
+ debug?.log("gunzip body");
92
+ req.pipe(stream);
93
+ break;
94
+ case "identity":
95
+ stream = req;
96
+ // stream.length = length;
97
+ break;
98
+ case "br":
99
+ if (hasBrotliSupport) {
100
+ stream = (0, zlib_1.createBrotliDecompress)();
101
+ debug?.log("brotli decompress body");
102
+ req.pipe(stream);
103
+ }
104
+ break;
105
+ }
106
+ if (stream === undefined) {
107
+ throw (0, http_errors_1.default)(415, 'unsupported content encoding "' + encoding + '"', {
108
+ encoding: encoding,
109
+ type: "encoding.unsupported",
110
+ });
111
+ }
112
+ return stream;
113
+ };
114
+ exports.contentstream = contentstream;
115
+ const hasBody = (request) => {
116
+ const contentType = request.headers["content-type"];
117
+ return contentType
118
+ ? contentType.includes("application/json") ||
119
+ contentType.includes("+json") ||
120
+ contentType.includes("application/x-www-form-urlencoded")
121
+ : false;
122
+ };
123
+ exports.hasBody = hasBody;
124
+ /**
125
+ * Fix proxied body if bodyParser is involved.
126
+ */
127
+ const fixRequestBody = (proxyReq, requestBody) => {
128
+ if (!requestBody) {
129
+ return;
130
+ }
131
+ const contentType = proxyReq.getHeader("Content-Type");
132
+ const writeBody = (bodyData) => {
133
+ // deepcode ignore ContentLengthInCode: bodyParser fix
134
+ proxyReq.setHeader("Content-Length", `'${Buffer.byteLength(bodyData)}'`);
135
+ proxyReq.write(bodyData);
136
+ };
137
+ if (contentType &&
138
+ (contentType.includes("application/json") || contentType.includes("+json"))) {
139
+ writeBody(JSON.stringify(requestBody));
140
+ }
141
+ if (contentType &&
142
+ contentType.includes("application/x-www-form-urlencoded") &&
143
+ !Array.isArray(requestBody)) {
144
+ writeBody(querystring.stringify(requestBody));
145
+ }
146
+ };
147
+ exports.fixRequestBody = fixRequestBody;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.castHttpError = exports.PrimitiveType = exports.HttpMethod = exports.HttpProtocol = void 0;
4
+ var HttpProtocol;
5
+ (function (HttpProtocol) {
6
+ HttpProtocol["http"] = "http";
7
+ HttpProtocol["https"] = "https";
8
+ })(HttpProtocol || (exports.HttpProtocol = HttpProtocol = {}));
9
+ var HttpMethod;
10
+ (function (HttpMethod) {
11
+ HttpMethod["POST"] = "POST";
12
+ HttpMethod["PATCH"] = "PATCH";
13
+ HttpMethod["PUT"] = "PUT";
14
+ HttpMethod["GET"] = "GET";
15
+ HttpMethod["DELETE"] = "DELETE";
16
+ })(HttpMethod || (exports.HttpMethod = HttpMethod = {}));
17
+ var PrimitiveType;
18
+ (function (PrimitiveType) {
19
+ PrimitiveType["string"] = "string";
20
+ PrimitiveType["number"] = "number";
21
+ PrimitiveType["boolean"] = "boolean";
22
+ })(PrimitiveType || (exports.PrimitiveType = PrimitiveType = {}));
23
+ const castHttpError = (error) => {
24
+ if (!error.message || typeof error.message !== "string")
25
+ return;
26
+ if (error.context && typeof error.message !== "string")
27
+ return;
28
+ return { message: error.message, context: error.context };
29
+ };
30
+ exports.castHttpError = castHttpError;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HttpHandler = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const cookie_parser_1 = __importDefault(require("cookie-parser"));
9
+ const cors_1 = __importDefault(require("cors"));
10
+ const https_1 = require("firebase-functions/v2/https");
11
+ // export interface HandlerEnv {
12
+ // connectionToken: () => string;
13
+ // }
14
+ class HttpHandler {
15
+ constructor(name) {
16
+ this.name = name;
17
+ this.corsAllowList = [];
18
+ this.items = [];
19
+ this.exp = (0, express_1.default)();
20
+ }
21
+ add(...items) {
22
+ this.items.push(...items);
23
+ return this;
24
+ }
25
+ baseSetup() {
26
+ this.exp.use((0, cors_1.default)({ credentials: true, origin: true }));
27
+ this.exp.use((0, cookie_parser_1.default)());
28
+ this.exp.disable("x-powered-by");
29
+ // this.exp.use(verifySessionCookie); // todo verify token (maybe connection token)
30
+ }
31
+ setup() {
32
+ this.baseSetup();
33
+ for (const item of this.items)
34
+ item.setup(this.exp);
35
+ return this.createFunction();
36
+ }
37
+ createFunction() {
38
+ return (0, https_1.onRequest)({ cors: [/firebase\.com$/, /web\.app$/] }, this.exp);
39
+ }
40
+ }
41
+ exports.HttpHandler = HttpHandler;
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.IHttpHandlerItem = void 0;
7
+ const raw_body_1 = __importDefault(require("raw-body"));
8
+ const http_js_1 = require("../common/http.js");
9
+ const https_1 = require("firebase-functions/v2/https");
10
+ const zod_1 = require("zod");
11
+ const types_common_js_1 = require("../common/types.common.js");
12
+ const http_status_codes_1 = require("@mxpicture/http-status-codes");
13
+ // export interface HandlerEnv {
14
+ // connectionToken: () => string;
15
+ // }
16
+ class IHttpHandlerItem {
17
+ constructor(name, api) {
18
+ this.name = name;
19
+ this.api = api;
20
+ this.corsAllowList = [];
21
+ }
22
+ setup(exp) {
23
+ exp.get(`/api/${this.name}`, async (req, res) => {
24
+ try {
25
+ const items = await this.api.query();
26
+ return this.httpSuccess(res, items);
27
+ }
28
+ catch (error) {
29
+ return this.httpErrorUnnown(res, error);
30
+ }
31
+ });
32
+ exp.get(`/api/${this.name}_meta`, async (req, res) => {
33
+ try {
34
+ const items = this.api.metaItems();
35
+ return this.httpSuccess(res, items);
36
+ }
37
+ catch (error) {
38
+ return this.httpErrorUnnown(res, error);
39
+ }
40
+ });
41
+ exp.get(`/api/${this.name}/:id`, async (req, res) => {
42
+ try {
43
+ const item = await this.api.get(req.params.id);
44
+ return this.httpSuccess(res, item);
45
+ }
46
+ catch (error) {
47
+ return this.httpErrorUnnown(res, error);
48
+ }
49
+ });
50
+ exp.post(`/api/${this.name}`, async (req, res) => {
51
+ try {
52
+ const item = await this.extractBodyItem(req);
53
+ const valResult = await this.api.validate(item);
54
+ if (!valResult.success)
55
+ throw (0, zod_1.treeifyError)(valResult.error);
56
+ const validated = await this.api.create(valResult.data);
57
+ return this.httpSuccess(res, validated);
58
+ }
59
+ catch (error) {
60
+ return this.httpErrorUnnown(res, error);
61
+ }
62
+ });
63
+ exp.patch(`/api/${this.name}/:id`, async (req, res) => {
64
+ try {
65
+ const item = await this.extractBodyItem(req);
66
+ const valResult = await this.api.validatePartial(item);
67
+ if (!valResult.success)
68
+ throw (0, zod_1.treeifyError)(valResult.error);
69
+ const validated = await this.api.updatePartial(req.params.id, valResult.data);
70
+ return this.httpSuccess(res, validated);
71
+ }
72
+ catch (error) {
73
+ return this.httpErrorUnnown(res, error);
74
+ }
75
+ });
76
+ }
77
+ httpSuccess(res, payload, statusCode = http_status_codes_1.StatusCodes.OK) {
78
+ const result = { statusCode, payload };
79
+ return res.status(statusCode).json(result);
80
+ }
81
+ httpError(res, errors, statusCode = http_status_codes_1.StatusCodes.BAD_REQUEST) {
82
+ const result = { statusCode, errors };
83
+ return res.status(statusCode).json(result);
84
+ }
85
+ httpErrorUnnown(res, error, statusCode = http_status_codes_1.StatusCodes.BAD_REQUEST) {
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ const ae = error;
88
+ const messages = [];
89
+ if (error instanceof Error) {
90
+ messages.push({ message: error.message });
91
+ }
92
+ else if (typeof error === "string") {
93
+ messages.push({ message: error });
94
+ }
95
+ else if (ae.message) {
96
+ const err = (0, types_common_js_1.castHttpError)(ae.message);
97
+ if (err)
98
+ messages.push(err);
99
+ }
100
+ else if (ae.errors) {
101
+ messages.push(...this.extractZodError(ae));
102
+ }
103
+ else if (ae.payload) {
104
+ if (ae.payload.message && typeof ae.payload.message === "string")
105
+ messages.push({ message: ae.payload.message });
106
+ else if (typeof ae.payload === "string")
107
+ messages.push({ message: ae.payload });
108
+ }
109
+ else {
110
+ messages.push({ message: "unknown" });
111
+ }
112
+ return this.httpError(res, messages, statusCode);
113
+ }
114
+ extractZodError(tree, ...contexts) {
115
+ const errors = tree.errors?.map((e) => ({
116
+ message: e,
117
+ context: this.contextPath(...contexts),
118
+ })) ?? [];
119
+ const items = "items" in tree && tree.items
120
+ ? tree.items
121
+ : "properties" in tree && tree.properties
122
+ ? tree.properties
123
+ : [];
124
+ if (Array.isArray(items)) {
125
+ for (let i = 0; i < items.length; i++)
126
+ errors.push(...this.extractZodError(items[i], ...contexts, i));
127
+ }
128
+ else {
129
+ for (const [key, item] of Object.entries(items))
130
+ errors.push(...this.extractZodError(item, ...contexts, key));
131
+ }
132
+ return errors;
133
+ }
134
+ contextPath(...parts) {
135
+ const ps = parts
136
+ .filter((p) => !!p)
137
+ .map((p) => (typeof p === "string" ? p : `${p}`));
138
+ return ps.join(".");
139
+ }
140
+ createFunction(exp) {
141
+ return (0, https_1.onRequest)({ cors: [/firebase\.com$/, /web\.app$/] }, exp);
142
+ }
143
+ async extractBody(req) {
144
+ if (!req.headers["content-type"])
145
+ throw new Error("No content type provided");
146
+ const body = req.body ??
147
+ JSON.parse(await (0, raw_body_1.default)((0, http_js_1.contentstream)(req, false), {
148
+ encoding: "utf-8",
149
+ }));
150
+ if (Array.isArray(body))
151
+ return body;
152
+ if (body && Object.keys(body).length > 0)
153
+ return body;
154
+ throw new Error("Invalid / not existing body");
155
+ }
156
+ async extractBodyItem(req) {
157
+ const extr = await this.extractBody(req);
158
+ if (!extr || Array.isArray(extr))
159
+ throw new Error(`No item found in body`);
160
+ return extr;
161
+ }
162
+ async extractBodyItems(req) {
163
+ const extr = await this.extractBody(req);
164
+ if (!extr || !Array.isArray(extr))
165
+ throw new Error(`No items found in body`);
166
+ return extr;
167
+ }
168
+ }
169
+ exports.IHttpHandlerItem = IHttpHandlerItem;