@tern-secure/backend 1.2.0-canary.v20251209193320 → 1.2.0-canary.v20251211004701
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/dist/functions/index.d.ts +84 -0
- package/dist/functions/index.d.ts.map +1 -0
- package/dist/functions/index.js +113 -0
- package/dist/functions/index.js.map +1 -0
- package/dist/functions/index.mjs +77 -0
- package/dist/functions/index.mjs.map +1 -0
- package/functions/package.json +5 -0
- package/package.json +17 -5
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { UserRecord } from 'firebase-admin/auth';
|
|
2
|
+
import * as functions from 'firebase-functions/v1';
|
|
3
|
+
export interface PostgresSyncOptions {
|
|
4
|
+
/**
|
|
5
|
+
* A function that executes a SQL query.
|
|
6
|
+
* Compatible with 'pg' Pool.query or Client.query.
|
|
7
|
+
* You can pass `pool.query.bind(pool)` here.
|
|
8
|
+
*/
|
|
9
|
+
query: (text: string, params?: any[]) => Promise<any>;
|
|
10
|
+
/**
|
|
11
|
+
* The name of the table to insert users into.
|
|
12
|
+
* @example 'users'
|
|
13
|
+
* @example 'public.profiles'
|
|
14
|
+
*/
|
|
15
|
+
tableName: string;
|
|
16
|
+
/**
|
|
17
|
+
* Map Firebase UserRecord fields to your database columns.
|
|
18
|
+
* Key: Firebase field (e.g., 'uid', 'email', 'displayName', 'photoURL')
|
|
19
|
+
* Value: Database column name
|
|
20
|
+
*/
|
|
21
|
+
fieldMapping: Partial<Record<keyof UserRecord, string>>;
|
|
22
|
+
/**
|
|
23
|
+
* Optional: Add extra static values or computed values.
|
|
24
|
+
* @example { role: 'user', created_at: new Date() }
|
|
25
|
+
*/
|
|
26
|
+
extraFields?: (user: UserRecord) => Record<string, any>;
|
|
27
|
+
/**
|
|
28
|
+
* Optional: Callback to run after successful sync
|
|
29
|
+
*/
|
|
30
|
+
onSuccess?: (user: UserRecord) => Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Optional: Callback to run on error
|
|
33
|
+
*/
|
|
34
|
+
onError?: (error: any, user: UserRecord) => Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Creates a Firebase Authentication Trigger that syncs new users to a Postgres database.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* export const syncUser = createPostgresSync({
|
|
41
|
+
* query: pool.query.bind(pool),
|
|
42
|
+
* tableName: 'users',
|
|
43
|
+
* fieldMapping: {
|
|
44
|
+
* uid: 'id',
|
|
45
|
+
* email: 'email',
|
|
46
|
+
* displayName: 'full_name'
|
|
47
|
+
* }
|
|
48
|
+
* });
|
|
49
|
+
*/
|
|
50
|
+
export declare const createPostgresSync: (options: PostgresSyncOptions) => functions.CloudFunction<UserRecord>;
|
|
51
|
+
export interface GenericSyncOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Map Firebase UserRecord fields to your database columns/fields.
|
|
54
|
+
* Key: Firebase field (e.g., 'uid', 'email')
|
|
55
|
+
* Value: Your database field name
|
|
56
|
+
*/
|
|
57
|
+
fieldMapping: Partial<Record<keyof UserRecord, string>>;
|
|
58
|
+
/**
|
|
59
|
+
* Optional: Add extra static values or computed values.
|
|
60
|
+
*/
|
|
61
|
+
extraFields?: (user: UserRecord) => Record<string, any>;
|
|
62
|
+
/**
|
|
63
|
+
* Function to save the mapped data to your database.
|
|
64
|
+
* Receives a plain object with the mapped keys and values.
|
|
65
|
+
*/
|
|
66
|
+
syncFn: (data: Record<string, any>, user: UserRecord) => Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Optional: Callback to run on error
|
|
69
|
+
*/
|
|
70
|
+
onError?: (error: any, user: UserRecord) => Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Creates a generic Firebase Authentication Trigger for syncing users to any database (Prisma, Drizzle, Mongo, etc).
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* export const syncUser = createGenericSync({
|
|
77
|
+
* fieldMapping: { uid: 'id', email: 'email' },
|
|
78
|
+
* syncFn: async (data) => {
|
|
79
|
+
* await prisma.user.create({ data });
|
|
80
|
+
* }
|
|
81
|
+
* });
|
|
82
|
+
*/
|
|
83
|
+
export declare const createGenericSync: (options: GenericSyncOptions) => functions.CloudFunction<UserRecord>;
|
|
84
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAC;AAEnD,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IAEtD;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IAExD;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAExD;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhD;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,mBAAmB,wCAqD9D,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IAExD;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAExD;;;OAGG;IACH,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvE;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAS,kBAAkB,wCA+B5D,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/functions/index.ts
|
|
31
|
+
var functions_exports = {};
|
|
32
|
+
__export(functions_exports, {
|
|
33
|
+
createGenericSync: () => createGenericSync,
|
|
34
|
+
createPostgresSync: () => createPostgresSync
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(functions_exports);
|
|
37
|
+
var functions = __toESM(require("firebase-functions/v1"));
|
|
38
|
+
var createPostgresSync = (options) => {
|
|
39
|
+
return functions.auth.user().onCreate(async (user) => {
|
|
40
|
+
const { query, tableName, fieldMapping, extraFields, onSuccess, onError } = options;
|
|
41
|
+
try {
|
|
42
|
+
const columns = [];
|
|
43
|
+
const values = [];
|
|
44
|
+
const placeholders = [];
|
|
45
|
+
Object.entries(fieldMapping).forEach(([userField, dbColumn]) => {
|
|
46
|
+
if (dbColumn) {
|
|
47
|
+
columns.push(dbColumn);
|
|
48
|
+
values.push(user[userField]);
|
|
49
|
+
placeholders.push(`$${values.length}`);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
if (extraFields) {
|
|
53
|
+
const extras = extraFields(user);
|
|
54
|
+
Object.entries(extras).forEach(([column, value]) => {
|
|
55
|
+
columns.push(column);
|
|
56
|
+
values.push(value);
|
|
57
|
+
placeholders.push(`$${values.length}`);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (columns.length === 0) {
|
|
61
|
+
console.warn("createPostgresSync: No fields mapped for insertion.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const queryText = `
|
|
65
|
+
INSERT INTO ${tableName} (${columns.join(", ")})
|
|
66
|
+
VALUES (${placeholders.join(", ")})
|
|
67
|
+
ON CONFLICT DO NOTHING
|
|
68
|
+
`;
|
|
69
|
+
await query(queryText, values);
|
|
70
|
+
if (onSuccess) {
|
|
71
|
+
await onSuccess(user);
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error("createPostgresSync: Failed to sync user", error);
|
|
75
|
+
if (onError) {
|
|
76
|
+
await onError(error, user);
|
|
77
|
+
} else {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
var createGenericSync = (options) => {
|
|
84
|
+
return functions.auth.user().onCreate(async (user) => {
|
|
85
|
+
const { fieldMapping, extraFields, syncFn, onError } = options;
|
|
86
|
+
try {
|
|
87
|
+
const data = {};
|
|
88
|
+
Object.entries(fieldMapping).forEach(([userField, dbField]) => {
|
|
89
|
+
if (dbField) {
|
|
90
|
+
data[dbField] = user[userField];
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
if (extraFields) {
|
|
94
|
+
const extras = extraFields(user);
|
|
95
|
+
Object.assign(data, extras);
|
|
96
|
+
}
|
|
97
|
+
await syncFn(data, user);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error("createGenericSync: Failed to sync user", error);
|
|
100
|
+
if (onError) {
|
|
101
|
+
await onError(error, user);
|
|
102
|
+
} else {
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
109
|
+
0 && (module.exports = {
|
|
110
|
+
createGenericSync,
|
|
111
|
+
createPostgresSync
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/functions/index.ts"],"sourcesContent":["import type { UserRecord } from 'firebase-admin/auth';\nimport * as functions from 'firebase-functions/v1';\n\nexport interface PostgresSyncOptions {\n /**\n * A function that executes a SQL query.\n * Compatible with 'pg' Pool.query or Client.query.\n * You can pass `pool.query.bind(pool)` here.\n */\n query: (text: string, params?: any[]) => Promise<any>;\n \n /**\n * The name of the table to insert users into.\n * @example 'users'\n * @example 'public.profiles'\n */\n tableName: string;\n\n /**\n * Map Firebase UserRecord fields to your database columns.\n * Key: Firebase field (e.g., 'uid', 'email', 'displayName', 'photoURL')\n * Value: Database column name\n */\n fieldMapping: Partial<Record<keyof UserRecord, string>>;\n\n /**\n * Optional: Add extra static values or computed values.\n * @example { role: 'user', created_at: new Date() }\n */\n extraFields?: (user: UserRecord) => Record<string, any>;\n \n /**\n * Optional: Callback to run after successful sync\n */\n onSuccess?: (user: UserRecord) => Promise<void>;\n\n /**\n * Optional: Callback to run on error\n */\n onError?: (error: any, user: UserRecord) => Promise<void>;\n}\n\n/**\n * Creates a Firebase Authentication Trigger that syncs new users to a Postgres database.\n * \n * @example\n * export const syncUser = createPostgresSync({\n * query: pool.query.bind(pool),\n * tableName: 'users',\n * fieldMapping: {\n * uid: 'id',\n * email: 'email',\n * displayName: 'full_name'\n * }\n * });\n */\nexport const createPostgresSync = (options: PostgresSyncOptions) => {\n return functions.auth.user().onCreate(async (user) => {\n const { query, tableName, fieldMapping, extraFields, onSuccess, onError } = options;\n\n try {\n const columns: string[] = [];\n const values: any[] = [];\n const placeholders: string[] = [];\n\n // Handle mapped fields\n Object.entries(fieldMapping).forEach(([userField, dbColumn]) => {\n if (dbColumn) {\n columns.push(dbColumn);\n values.push((user as any)[userField]);\n placeholders.push(`$${values.length}`);\n }\n });\n\n // Handle extra fields\n if (extraFields) {\n const extras = extraFields(user);\n Object.entries(extras).forEach(([column, value]) => {\n columns.push(column);\n values.push(value);\n placeholders.push(`$${values.length}`);\n });\n }\n\n if (columns.length === 0) {\n console.warn('createPostgresSync: No fields mapped for insertion.');\n return;\n }\n\n const queryText = `\n INSERT INTO ${tableName} (${columns.join(', ')})\n VALUES (${placeholders.join(', ')})\n ON CONFLICT DO NOTHING\n `;\n\n await query(queryText, values);\n\n if (onSuccess) {\n await onSuccess(user);\n }\n } catch (error) {\n console.error('createPostgresSync: Failed to sync user', error);\n if (onError) {\n await onError(error, user);\n } else {\n throw error;\n }\n }\n });\n};\n\nexport interface GenericSyncOptions {\n /**\n * Map Firebase UserRecord fields to your database columns/fields.\n * Key: Firebase field (e.g., 'uid', 'email')\n * Value: Your database field name\n */\n fieldMapping: Partial<Record<keyof UserRecord, string>>;\n\n /**\n * Optional: Add extra static values or computed values.\n */\n extraFields?: (user: UserRecord) => Record<string, any>;\n\n /**\n * Function to save the mapped data to your database.\n * Receives a plain object with the mapped keys and values.\n */\n syncFn: (data: Record<string, any>, user: UserRecord) => Promise<void>;\n\n /**\n * Optional: Callback to run on error\n */\n onError?: (error: any, user: UserRecord) => Promise<void>;\n}\n\n/**\n * Creates a generic Firebase Authentication Trigger for syncing users to any database (Prisma, Drizzle, Mongo, etc).\n * \n * @example\n * export const syncUser = createGenericSync({\n * fieldMapping: { uid: 'id', email: 'email' },\n * syncFn: async (data) => {\n * await prisma.user.create({ data });\n * }\n * });\n */\nexport const createGenericSync = (options: GenericSyncOptions) => {\n return functions.auth.user().onCreate(async (user) => {\n const { fieldMapping, extraFields, syncFn, onError } = options;\n\n try {\n const data: Record<string, any> = {};\n\n // Handle mapped fields\n Object.entries(fieldMapping).forEach(([userField, dbField]) => {\n if (dbField) {\n data[dbField] = (user as any)[userField];\n }\n });\n\n // Handle extra fields\n if (extraFields) {\n const extras = extraFields(user);\n Object.assign(data, extras);\n }\n\n await syncFn(data, user);\n\n } catch (error) {\n console.error('createGenericSync: Failed to sync user', error);\n if (onError) {\n await onError(error, user);\n } else {\n throw error;\n }\n }\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,gBAA2B;AAuDpB,IAAM,qBAAqB,CAAC,YAAiC;AAClE,SAAiB,eAAK,KAAK,EAAE,SAAS,OAAO,SAAS;AACpD,UAAM,EAAE,OAAO,WAAW,cAAc,aAAa,WAAW,QAAQ,IAAI;AAE5E,QAAI;AACF,YAAM,UAAoB,CAAC;AAC3B,YAAM,SAAgB,CAAC;AACvB,YAAM,eAAyB,CAAC;AAGhC,aAAO,QAAQ,YAAY,EAAE,QAAQ,CAAC,CAAC,WAAW,QAAQ,MAAM;AAC9D,YAAI,UAAU;AACZ,kBAAQ,KAAK,QAAQ;AACrB,iBAAO,KAAM,KAAa,SAAS,CAAC;AACpC,uBAAa,KAAK,IAAI,OAAO,MAAM,EAAE;AAAA,QACvC;AAAA,MACF,CAAC;AAGD,UAAI,aAAa;AACf,cAAM,SAAS,YAAY,IAAI;AAC/B,eAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,KAAK,MAAM;AAClD,kBAAQ,KAAK,MAAM;AACnB,iBAAO,KAAK,KAAK;AACjB,uBAAa,KAAK,IAAI,OAAO,MAAM,EAAE;AAAA,QACvC,CAAC;AAAA,MACH;AAEA,UAAI,QAAQ,WAAW,GAAG;AACxB,gBAAQ,KAAK,qDAAqD;AAClE;AAAA,MACF;AAEA,YAAM,YAAY;AAAA,sBACF,SAAS,KAAK,QAAQ,KAAK,IAAI,CAAC;AAAA,kBACpC,aAAa,KAAK,IAAI,CAAC;AAAA;AAAA;AAInC,YAAM,MAAM,WAAW,MAAM;AAE7B,UAAI,WAAW;AACb,cAAM,UAAU,IAAI;AAAA,MACtB;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,2CAA2C,KAAK;AAC9D,UAAI,SAAS;AACX,cAAM,QAAQ,OAAO,IAAI;AAAA,MAC3B,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAsCO,IAAM,oBAAoB,CAAC,YAAgC;AAChE,SAAiB,eAAK,KAAK,EAAE,SAAS,OAAO,SAAS;AACpD,UAAM,EAAE,cAAc,aAAa,QAAQ,QAAQ,IAAI;AAEvD,QAAI;AACF,YAAM,OAA4B,CAAC;AAGnC,aAAO,QAAQ,YAAY,EAAE,QAAQ,CAAC,CAAC,WAAW,OAAO,MAAM;AAC7D,YAAI,SAAS;AACX,eAAK,OAAO,IAAK,KAAa,SAAS;AAAA,QACzC;AAAA,MACF,CAAC;AAGD,UAAI,aAAa;AACf,cAAM,SAAS,YAAY,IAAI;AAC/B,eAAO,OAAO,MAAM,MAAM;AAAA,MAC5B;AAEA,YAAM,OAAO,MAAM,IAAI;AAAA,IAEzB,SAAS,OAAO;AACd,cAAQ,MAAM,0CAA0C,KAAK;AAC7D,UAAI,SAAS;AACX,cAAM,QAAQ,OAAO,IAAI;AAAA,MAC3B,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/functions/index.ts
|
|
2
|
+
import * as functions from "firebase-functions/v1";
|
|
3
|
+
var createPostgresSync = (options) => {
|
|
4
|
+
return functions.auth.user().onCreate(async (user) => {
|
|
5
|
+
const { query, tableName, fieldMapping, extraFields, onSuccess, onError } = options;
|
|
6
|
+
try {
|
|
7
|
+
const columns = [];
|
|
8
|
+
const values = [];
|
|
9
|
+
const placeholders = [];
|
|
10
|
+
Object.entries(fieldMapping).forEach(([userField, dbColumn]) => {
|
|
11
|
+
if (dbColumn) {
|
|
12
|
+
columns.push(dbColumn);
|
|
13
|
+
values.push(user[userField]);
|
|
14
|
+
placeholders.push(`$${values.length}`);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
if (extraFields) {
|
|
18
|
+
const extras = extraFields(user);
|
|
19
|
+
Object.entries(extras).forEach(([column, value]) => {
|
|
20
|
+
columns.push(column);
|
|
21
|
+
values.push(value);
|
|
22
|
+
placeholders.push(`$${values.length}`);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (columns.length === 0) {
|
|
26
|
+
console.warn("createPostgresSync: No fields mapped for insertion.");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const queryText = `
|
|
30
|
+
INSERT INTO ${tableName} (${columns.join(", ")})
|
|
31
|
+
VALUES (${placeholders.join(", ")})
|
|
32
|
+
ON CONFLICT DO NOTHING
|
|
33
|
+
`;
|
|
34
|
+
await query(queryText, values);
|
|
35
|
+
if (onSuccess) {
|
|
36
|
+
await onSuccess(user);
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error("createPostgresSync: Failed to sync user", error);
|
|
40
|
+
if (onError) {
|
|
41
|
+
await onError(error, user);
|
|
42
|
+
} else {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
var createGenericSync = (options) => {
|
|
49
|
+
return functions.auth.user().onCreate(async (user) => {
|
|
50
|
+
const { fieldMapping, extraFields, syncFn, onError } = options;
|
|
51
|
+
try {
|
|
52
|
+
const data = {};
|
|
53
|
+
Object.entries(fieldMapping).forEach(([userField, dbField]) => {
|
|
54
|
+
if (dbField) {
|
|
55
|
+
data[dbField] = user[userField];
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (extraFields) {
|
|
59
|
+
const extras = extraFields(user);
|
|
60
|
+
Object.assign(data, extras);
|
|
61
|
+
}
|
|
62
|
+
await syncFn(data, user);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error("createGenericSync: Failed to sync user", error);
|
|
65
|
+
if (onError) {
|
|
66
|
+
await onError(error, user);
|
|
67
|
+
} else {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
export {
|
|
74
|
+
createGenericSync,
|
|
75
|
+
createPostgresSync
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/functions/index.ts"],"sourcesContent":["import type { UserRecord } from 'firebase-admin/auth';\nimport * as functions from 'firebase-functions/v1';\n\nexport interface PostgresSyncOptions {\n /**\n * A function that executes a SQL query.\n * Compatible with 'pg' Pool.query or Client.query.\n * You can pass `pool.query.bind(pool)` here.\n */\n query: (text: string, params?: any[]) => Promise<any>;\n \n /**\n * The name of the table to insert users into.\n * @example 'users'\n * @example 'public.profiles'\n */\n tableName: string;\n\n /**\n * Map Firebase UserRecord fields to your database columns.\n * Key: Firebase field (e.g., 'uid', 'email', 'displayName', 'photoURL')\n * Value: Database column name\n */\n fieldMapping: Partial<Record<keyof UserRecord, string>>;\n\n /**\n * Optional: Add extra static values or computed values.\n * @example { role: 'user', created_at: new Date() }\n */\n extraFields?: (user: UserRecord) => Record<string, any>;\n \n /**\n * Optional: Callback to run after successful sync\n */\n onSuccess?: (user: UserRecord) => Promise<void>;\n\n /**\n * Optional: Callback to run on error\n */\n onError?: (error: any, user: UserRecord) => Promise<void>;\n}\n\n/**\n * Creates a Firebase Authentication Trigger that syncs new users to a Postgres database.\n * \n * @example\n * export const syncUser = createPostgresSync({\n * query: pool.query.bind(pool),\n * tableName: 'users',\n * fieldMapping: {\n * uid: 'id',\n * email: 'email',\n * displayName: 'full_name'\n * }\n * });\n */\nexport const createPostgresSync = (options: PostgresSyncOptions) => {\n return functions.auth.user().onCreate(async (user) => {\n const { query, tableName, fieldMapping, extraFields, onSuccess, onError } = options;\n\n try {\n const columns: string[] = [];\n const values: any[] = [];\n const placeholders: string[] = [];\n\n // Handle mapped fields\n Object.entries(fieldMapping).forEach(([userField, dbColumn]) => {\n if (dbColumn) {\n columns.push(dbColumn);\n values.push((user as any)[userField]);\n placeholders.push(`$${values.length}`);\n }\n });\n\n // Handle extra fields\n if (extraFields) {\n const extras = extraFields(user);\n Object.entries(extras).forEach(([column, value]) => {\n columns.push(column);\n values.push(value);\n placeholders.push(`$${values.length}`);\n });\n }\n\n if (columns.length === 0) {\n console.warn('createPostgresSync: No fields mapped for insertion.');\n return;\n }\n\n const queryText = `\n INSERT INTO ${tableName} (${columns.join(', ')})\n VALUES (${placeholders.join(', ')})\n ON CONFLICT DO NOTHING\n `;\n\n await query(queryText, values);\n\n if (onSuccess) {\n await onSuccess(user);\n }\n } catch (error) {\n console.error('createPostgresSync: Failed to sync user', error);\n if (onError) {\n await onError(error, user);\n } else {\n throw error;\n }\n }\n });\n};\n\nexport interface GenericSyncOptions {\n /**\n * Map Firebase UserRecord fields to your database columns/fields.\n * Key: Firebase field (e.g., 'uid', 'email')\n * Value: Your database field name\n */\n fieldMapping: Partial<Record<keyof UserRecord, string>>;\n\n /**\n * Optional: Add extra static values or computed values.\n */\n extraFields?: (user: UserRecord) => Record<string, any>;\n\n /**\n * Function to save the mapped data to your database.\n * Receives a plain object with the mapped keys and values.\n */\n syncFn: (data: Record<string, any>, user: UserRecord) => Promise<void>;\n\n /**\n * Optional: Callback to run on error\n */\n onError?: (error: any, user: UserRecord) => Promise<void>;\n}\n\n/**\n * Creates a generic Firebase Authentication Trigger for syncing users to any database (Prisma, Drizzle, Mongo, etc).\n * \n * @example\n * export const syncUser = createGenericSync({\n * fieldMapping: { uid: 'id', email: 'email' },\n * syncFn: async (data) => {\n * await prisma.user.create({ data });\n * }\n * });\n */\nexport const createGenericSync = (options: GenericSyncOptions) => {\n return functions.auth.user().onCreate(async (user) => {\n const { fieldMapping, extraFields, syncFn, onError } = options;\n\n try {\n const data: Record<string, any> = {};\n\n // Handle mapped fields\n Object.entries(fieldMapping).forEach(([userField, dbField]) => {\n if (dbField) {\n data[dbField] = (user as any)[userField];\n }\n });\n\n // Handle extra fields\n if (extraFields) {\n const extras = extraFields(user);\n Object.assign(data, extras);\n }\n\n await syncFn(data, user);\n\n } catch (error) {\n console.error('createGenericSync: Failed to sync user', error);\n if (onError) {\n await onError(error, user);\n } else {\n throw error;\n }\n }\n });\n};\n"],"mappings":";AACA,YAAY,eAAe;AAuDpB,IAAM,qBAAqB,CAAC,YAAiC;AAClE,SAAiB,eAAK,KAAK,EAAE,SAAS,OAAO,SAAS;AACpD,UAAM,EAAE,OAAO,WAAW,cAAc,aAAa,WAAW,QAAQ,IAAI;AAE5E,QAAI;AACF,YAAM,UAAoB,CAAC;AAC3B,YAAM,SAAgB,CAAC;AACvB,YAAM,eAAyB,CAAC;AAGhC,aAAO,QAAQ,YAAY,EAAE,QAAQ,CAAC,CAAC,WAAW,QAAQ,MAAM;AAC9D,YAAI,UAAU;AACZ,kBAAQ,KAAK,QAAQ;AACrB,iBAAO,KAAM,KAAa,SAAS,CAAC;AACpC,uBAAa,KAAK,IAAI,OAAO,MAAM,EAAE;AAAA,QACvC;AAAA,MACF,CAAC;AAGD,UAAI,aAAa;AACf,cAAM,SAAS,YAAY,IAAI;AAC/B,eAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,KAAK,MAAM;AAClD,kBAAQ,KAAK,MAAM;AACnB,iBAAO,KAAK,KAAK;AACjB,uBAAa,KAAK,IAAI,OAAO,MAAM,EAAE;AAAA,QACvC,CAAC;AAAA,MACH;AAEA,UAAI,QAAQ,WAAW,GAAG;AACxB,gBAAQ,KAAK,qDAAqD;AAClE;AAAA,MACF;AAEA,YAAM,YAAY;AAAA,sBACF,SAAS,KAAK,QAAQ,KAAK,IAAI,CAAC;AAAA,kBACpC,aAAa,KAAK,IAAI,CAAC;AAAA;AAAA;AAInC,YAAM,MAAM,WAAW,MAAM;AAE7B,UAAI,WAAW;AACb,cAAM,UAAU,IAAI;AAAA,MACtB;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,2CAA2C,KAAK;AAC9D,UAAI,SAAS;AACX,cAAM,QAAQ,OAAO,IAAI;AAAA,MAC3B,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAsCO,IAAM,oBAAoB,CAAC,YAAgC;AAChE,SAAiB,eAAK,KAAK,EAAE,SAAS,OAAO,SAAS;AACpD,UAAM,EAAE,cAAc,aAAa,QAAQ,QAAQ,IAAI;AAEvD,QAAI;AACF,YAAM,OAA4B,CAAC;AAGnC,aAAO,QAAQ,YAAY,EAAE,QAAQ,CAAC,CAAC,WAAW,OAAO,MAAM;AAC7D,YAAI,SAAS;AACX,eAAK,OAAO,IAAK,KAAa,SAAS;AAAA,QACzC;AAAA,MACF,CAAC;AAGD,UAAI,aAAa;AACf,cAAM,SAAS,YAAY,IAAI;AAC/B,eAAO,OAAO,MAAM,MAAM;AAAA,MAC5B;AAEA,YAAM,OAAO,MAAM,IAAI;AAAA,IAEzB,SAAS,OAAO;AACd,cAAQ,MAAM,0CAA0C,KAAK;AAC7D,UAAI,SAAS;AACX,cAAM,QAAQ,OAAO,IAAI;AAAA,MAC3B,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tern-secure/backend",
|
|
3
|
-
"version": "1.2.0-canary.
|
|
3
|
+
"version": "1.2.0-canary.v20251211004701",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/TernSecure/auth.git",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"admin",
|
|
16
16
|
"auth",
|
|
17
17
|
"app-check",
|
|
18
|
+
"functions",
|
|
18
19
|
"internal",
|
|
19
20
|
"jwt"
|
|
20
21
|
],
|
|
@@ -81,16 +82,25 @@
|
|
|
81
82
|
"default": "./dist/jwt/index.js"
|
|
82
83
|
}
|
|
83
84
|
},
|
|
85
|
+
"./functions": {
|
|
86
|
+
"import": {
|
|
87
|
+
"types": "./dist/functions/index.d.ts",
|
|
88
|
+
"default": "./dist/functions/index.mjs"
|
|
89
|
+
},
|
|
90
|
+
"require": {
|
|
91
|
+
"types": "./dist/functions/index.d.ts",
|
|
92
|
+
"default": "./dist/functions/index.js"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
84
95
|
"./package.json": "./package.json"
|
|
85
96
|
},
|
|
86
97
|
"main": "./dist/index.js",
|
|
87
98
|
"dependencies": {
|
|
88
|
-
"@upstash/redis": "^1.35.2",
|
|
89
99
|
"cookie": "1.0.2",
|
|
90
100
|
"jose": "^5.10.0",
|
|
91
101
|
"tslib": "2.8.1",
|
|
92
|
-
"@tern-secure/shared": "1.3.0-canary.
|
|
93
|
-
"@tern-secure/types": "1.1.0-canary.
|
|
102
|
+
"@tern-secure/shared": "1.3.0-canary.v20251211004701",
|
|
103
|
+
"@tern-secure/types": "1.1.0-canary.v20251211004701"
|
|
94
104
|
},
|
|
95
105
|
"devDependencies": {
|
|
96
106
|
"@types/js-cookie": "^3.0.6",
|
|
@@ -98,7 +108,9 @@
|
|
|
98
108
|
"vitest-environment-miniflare": "2.14.4"
|
|
99
109
|
},
|
|
100
110
|
"peerDependencies": {
|
|
101
|
-
"
|
|
111
|
+
"@upstash/redis": "^1.35.2",
|
|
112
|
+
"firebase-admin": "^12.7.0",
|
|
113
|
+
"firebase-functions": "^6.1.2"
|
|
102
114
|
},
|
|
103
115
|
"engines": {
|
|
104
116
|
"node": ">=20"
|