@fonderie/auth 5.0.0 → 5.0.1
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/README.md +2 -2
- package/brain/outcomes.md +1 -0
- package/brain/signatures.md +20 -0
- package/dist/dtos/user.cjs +26 -3
- package/dist/dtos/user.cjs.map +1 -1
- package/dist/dtos/user.js +26 -3
- package/dist/dtos/user.js.map +1 -1
- package/dist/index.cjs +232 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -7
- package/dist/index.d.ts +24 -7
- package/dist/index.js +221 -19
- package/dist/index.js.map +1 -1
- package/dist/middlewares/index.cjs +19 -1
- package/dist/middlewares/index.cjs.map +1 -1
- package/dist/middlewares/index.d.cts +1 -1
- package/dist/middlewares/index.d.ts +1 -1
- package/dist/middlewares/index.js +19 -1
- package/dist/middlewares/index.js.map +1 -1
- package/dist/{session-DxsmQjXX.d.cts → session-CTSCI09F.d.cts} +8 -2
- package/dist/{session-DxsmQjXX.d.ts → session-CTSCI09F.d.ts} +8 -2
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Also exports `toUserDTO`, `normalizeEmail`, and the full type surface
|
|
|
35
35
|
You've shipped this plumbing before — auth, teams, billing, messaging —
|
|
36
36
|
and the next project will ask for it again. Fonderie packages it once:
|
|
37
37
|
plain TypeScript modules for
|
|
38
|
-
[`@fonderie/core`](https://github.com/
|
|
38
|
+
[`@fonderie/core`](https://github.com/fonderiejs/sdk/tree/main/packages/core),
|
|
39
39
|
PostgreSQL-backed, self-hosted, MIT. No external control plane, no
|
|
40
40
|
per-seat anything. Register the modules you need; skip the ones you don't.
|
|
41
41
|
|
|
@@ -43,7 +43,7 @@ per-seat anything. Register the modules you need; skip the ones you don't.
|
|
|
43
43
|
other brick trusts the `ctx.user` this one establishes.
|
|
44
44
|
|
|
45
45
|
Browse the whole set at
|
|
46
|
-
[
|
|
46
|
+
[fonderiejs/sdk](https://github.com/fonderiejs/sdk) · follow
|
|
47
47
|
[@fonderiejs](https://x.com/fonderiejs)
|
|
48
48
|
|
|
49
49
|
## License
|
package/brain/outcomes.md
CHANGED
|
@@ -131,6 +131,7 @@ Raw SQL ships in `node_modules/@fonderie/auth/dist/migrations/sql/` — read it
|
|
|
131
131
|
| DELETE | `/users` | `requireAuth → verifyGate → user.deleteMe` |
|
|
132
132
|
| GET | `/users` | `requireAuth → user.me` |
|
|
133
133
|
| PUT | `/users/email` | `requireAuth → verifyGate → validate(updateEmailSchema) → user.updateEmail` |
|
|
134
|
+
| GET | `/users/export` | `requireAuth → user.exportMe` |
|
|
134
135
|
| PUT | `/users/password` | `requireAuth → validate(changePasswordSchema) → user.changePassword` |
|
|
135
136
|
| PUT | `/users/phone` | `requireAuth → verifyGate → validate(updatePhoneSchema) → user.updatePhone` |
|
|
136
137
|
| PUT | `/users/preferences` | `requireAuth → verifyGate → validate(updatePreferencesSchema) → user.updatePreferences` |
|
package/brain/signatures.md
CHANGED
|
@@ -63,10 +63,12 @@ interface IAuthConfig extends IAuthSecrets, IAuthRuntimeConfig {
|
|
|
63
63
|
}) => Partial<IAuthRuntimeConfig>;
|
|
64
64
|
routes?: Partial<Record<AuthRouteId, AuthRouteOverride>>;
|
|
65
65
|
legacyVerify?: (plain: string, hash: string) => boolean | Promise<boolean>;
|
|
66
|
+
dataExportContributors?: IDataExportContributor[];
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
interface IAuthSecrets {
|
|
69
70
|
jwtSecret: string;
|
|
71
|
+
mfaSecretKey?: string;
|
|
70
72
|
google?: {
|
|
71
73
|
clientId: string;
|
|
72
74
|
clientSecret: string;
|
|
@@ -81,6 +83,11 @@ interface IAuthRuntimeConfig {
|
|
|
81
83
|
requireVerification?: boolean;
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
interface IDataExportContributor {
|
|
87
|
+
name: string;
|
|
88
|
+
collect: (userId: string) => Promise<unknown> | unknown;
|
|
89
|
+
}
|
|
90
|
+
|
|
84
91
|
const AUTH_CONFIG_KEYS: { sessionDuration: string; verificationCooldown: string; mfa: string; requireVerification: string; }
|
|
85
92
|
|
|
86
93
|
const MESSAGE_KEYS: { readonly emailRegistration: "email-registration"; readonly emailVerification: "email-verification"; readonly passwordReset: "password-reset"; readonly phoneOtp: "phone-otp"; readonly mfaEnabled: "mfa-enabled"; readonly mfaDisabled: "mfa-disabled"; readonly mfaBackupCodesRegenerated: "mfa-backup-codes-regenerated"; readonly emailChanged: "email-changed"; readonly phoneChanged: "phone-changed"; }
|
|
@@ -131,6 +138,19 @@ function normalizeEmailSafe(email: string): string | null
|
|
|
131
138
|
|
|
132
139
|
function importUser(store: IStoreAdapter, user: IImportUser): Promise<{ id: string; }>
|
|
133
140
|
|
|
141
|
+
function purgeSoftDeletedUsers(store: IStoreAdapter, { olderThanDays }: IPurgeOptions): Promise<number>
|
|
142
|
+
|
|
143
|
+
function startUserRetention(store: IStoreAdapter, options: IUserRetentionScheduleOptions): { stop: () => void; }
|
|
144
|
+
|
|
145
|
+
interface IPurgeOptions {
|
|
146
|
+
olderThanDays: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface IUserRetentionScheduleOptions extends IPurgeOptions {
|
|
150
|
+
intervalMs?: number;
|
|
151
|
+
onPurge?: (deleted: number) => void;
|
|
152
|
+
}
|
|
153
|
+
|
|
134
154
|
interface IImportUser {
|
|
135
155
|
id?: string;
|
|
136
156
|
email: string;
|
package/dist/dtos/user.cjs
CHANGED
|
@@ -32,8 +32,27 @@ var DEFAULT_PREFERENCES = {
|
|
|
32
32
|
dateFormat: "MM/DD/YYYY",
|
|
33
33
|
timeFormat: "hh:mm A"
|
|
34
34
|
};
|
|
35
|
+
function cleanPreferences(prefs) {
|
|
36
|
+
const out = {};
|
|
37
|
+
if (typeof prefs.locale === "string") out.locale = prefs.locale;
|
|
38
|
+
if (typeof prefs.timezone === "string") out.timezone = prefs.timezone;
|
|
39
|
+
if (typeof prefs.emailDigest === "string") out.emailDigest = prefs.emailDigest;
|
|
40
|
+
if (typeof prefs.dateFormat === "string") out.dateFormat = prefs.dateFormat;
|
|
41
|
+
if (typeof prefs.timeFormat === "string") out.timeFormat = prefs.timeFormat;
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function cleanNotifications(value) {
|
|
45
|
+
if (!value || typeof value !== "object") return {};
|
|
46
|
+
const out = {};
|
|
47
|
+
for (const key of ["email", "inApp", "sms", "push"]) {
|
|
48
|
+
const v = value[key];
|
|
49
|
+
if (typeof v === "boolean") out[key] = v;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
35
53
|
function toUserDTO(user, phoneVerified = false) {
|
|
36
54
|
const prefs = user.preferences ?? {};
|
|
55
|
+
const cleaned = cleanPreferences(prefs);
|
|
37
56
|
return {
|
|
38
57
|
id: (0, import_core.stringOrEmpty)(user.id),
|
|
39
58
|
email: (0, import_core.stringOrEmpty)(user.email),
|
|
@@ -45,9 +64,13 @@ function toUserDTO(user, phoneVerified = false) {
|
|
|
45
64
|
lastLogin: user.lastLogin instanceof Date ? user.lastLogin.toISOString() : "",
|
|
46
65
|
preferences: {
|
|
47
66
|
...DEFAULT_PREFERENCES,
|
|
48
|
-
...
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
...cleaned,
|
|
68
|
+
notifications: {
|
|
69
|
+
...DEFAULT_PREFERENCES.notifications,
|
|
70
|
+
...cleanNotifications(prefs.notifications)
|
|
71
|
+
},
|
|
72
|
+
locale: user.locale || cleaned.locale || "en-US",
|
|
73
|
+
timezone: user.timezone || cleaned.timezone || "UTC"
|
|
51
74
|
},
|
|
52
75
|
isEmailVerified: user.emailVerifiedAt !== null,
|
|
53
76
|
isPhoneVerified: phoneVerified,
|
package/dist/dtos/user.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/dtos/user.ts"],"sourcesContent":["import { stringOrEmpty, booleanOrFalse } from '@fonderie/core';\n\nimport type { IUser, IUserPreferences } from '../types';\n\nexport interface IUserDTO {\n\tid: string;\n\temail: string;\n\tfirstName: string;\n\tlastName: string;\n\tphone: string;\n\tprofileImageUrl: string;\n\tisActive: boolean;\n\tlastLogin: string;\n\tpreferences: IUserPreferences;\n\tisEmailVerified: boolean;\n\tisPhoneVerified: boolean;\n\tmfaEnabled: boolean;\n\tsuspended: boolean;\n\twhitelist: boolean;\n\tipWhitelist: string[];\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nconst DEFAULT_PREFERENCES: IUserPreferences = {\n\tlocale: 'en-US',\n\ttimezone: 'UTC',\n\tnotifications: { email: true, inApp: true, sms: false, push: false },\n\temailDigest: 'immediate',\n\tdateFormat: 'MM/DD/YYYY',\n\ttimeFormat: 'hh:mm A',\n};\n\nexport function toUserDTO(user: IUser, phoneVerified = false): IUserDTO {\n\tconst prefs = user.preferences ?? ({} as IUserPreferences);\n\treturn {\n\t\tid: stringOrEmpty(user.id),\n\t\temail: stringOrEmpty(user.email),\n\t\tfirstName: stringOrEmpty(user.firstName),\n\t\tlastName: stringOrEmpty(user.lastName),\n\t\tphone: stringOrEmpty(user.phone),\n\t\tprofileImageUrl: stringOrEmpty(user.profileImageUrl),\n\t\tisActive: typeof user.isActive === 'boolean' ? user.isActive : true,\n\t\tlastLogin: user.lastLogin instanceof Date ? user.lastLogin.toISOString() : '',\n\t\tpreferences: {\n\t\t\t...DEFAULT_PREFERENCES,\n\t\t\t...prefs,\n\t\t\tlocale: user.locale ||
|
|
1
|
+
{"version":3,"sources":["../../src/dtos/user.ts"],"sourcesContent":["import { stringOrEmpty, booleanOrFalse } from '@fonderie/core';\n\nimport type { IUser, IUserPreferences } from '../types';\n\nexport interface IUserDTO {\n\tid: string;\n\temail: string;\n\tfirstName: string;\n\tlastName: string;\n\tphone: string;\n\tprofileImageUrl: string;\n\tisActive: boolean;\n\tlastLogin: string;\n\tpreferences: IUserPreferences;\n\tisEmailVerified: boolean;\n\tisPhoneVerified: boolean;\n\tmfaEnabled: boolean;\n\tsuspended: boolean;\n\twhitelist: boolean;\n\tipWhitelist: string[];\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nconst DEFAULT_PREFERENCES: IUserPreferences = {\n\tlocale: 'en-US',\n\ttimezone: 'UTC',\n\tnotifications: { email: true, inApp: true, sms: false, push: false },\n\temailDigest: 'immediate',\n\tdateFormat: 'MM/DD/YYYY',\n\ttimeFormat: 'hh:mm A',\n};\n\n// Historical rows may hold garbage preferences (the update schema accepted\n// `unknown` for four keys until it was typed). Only well-typed values\n// survive into the DTO, and notifications deep-merge over the defaults so a\n// partial stored object can't shrink the shape the client type promises.\nfunction cleanPreferences(prefs: IUserPreferences): Partial<IUserPreferences> {\n\tconst out: Partial<IUserPreferences> = {};\n\tif (typeof prefs.locale === 'string') out.locale = prefs.locale;\n\tif (typeof prefs.timezone === 'string') out.timezone = prefs.timezone;\n\tif (typeof prefs.emailDigest === 'string') out.emailDigest = prefs.emailDigest;\n\tif (typeof prefs.dateFormat === 'string') out.dateFormat = prefs.dateFormat;\n\tif (typeof prefs.timeFormat === 'string') out.timeFormat = prefs.timeFormat;\n\treturn out;\n}\n\nfunction cleanNotifications(value: unknown): Partial<IUserPreferences['notifications']> {\n\tif (!value || typeof value !== 'object') return {};\n\tconst out: Partial<IUserPreferences['notifications']> = {};\n\tfor (const key of ['email', 'inApp', 'sms', 'push'] as const) {\n\t\tconst v = (value as Record<string, unknown>)[key];\n\t\tif (typeof v === 'boolean') out[key] = v;\n\t}\n\treturn out;\n}\n\nexport function toUserDTO(user: IUser, phoneVerified = false): IUserDTO {\n\tconst prefs = user.preferences ?? ({} as IUserPreferences);\n\tconst cleaned = cleanPreferences(prefs);\n\treturn {\n\t\tid: stringOrEmpty(user.id),\n\t\temail: stringOrEmpty(user.email),\n\t\tfirstName: stringOrEmpty(user.firstName),\n\t\tlastName: stringOrEmpty(user.lastName),\n\t\tphone: stringOrEmpty(user.phone),\n\t\tprofileImageUrl: stringOrEmpty(user.profileImageUrl),\n\t\tisActive: typeof user.isActive === 'boolean' ? user.isActive : true,\n\t\tlastLogin: user.lastLogin instanceof Date ? user.lastLogin.toISOString() : '',\n\t\tpreferences: {\n\t\t\t...DEFAULT_PREFERENCES,\n\t\t\t...cleaned,\n\t\t\tnotifications: {\n\t\t\t\t...DEFAULT_PREFERENCES.notifications,\n\t\t\t\t...cleanNotifications(prefs.notifications),\n\t\t\t},\n\t\t\tlocale: user.locale || cleaned.locale || 'en-US',\n\t\t\ttimezone: user.timezone || cleaned.timezone || 'UTC',\n\t\t},\n\t\tisEmailVerified: user.emailVerifiedAt !== null,\n\t\tisPhoneVerified: phoneVerified,\n\t\tmfaEnabled: booleanOrFalse(user.mfaEnabled),\n\t\tsuspended: booleanOrFalse(user.suspended),\n\t\twhitelist: booleanOrFalse(user.whitelist),\n\t\tipWhitelist: Array.isArray(user.ipWhitelist) ? user.ipWhitelist : [],\n\t\tcreatedAt: user.createdAt instanceof Date ? user.createdAt.toISOString() : '',\n\t\tupdatedAt: user.updatedAt instanceof Date ? user.updatedAt.toISOString() : '',\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAA8C;AAwB9C,IAAM,sBAAwC;AAAA,EAC7C,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe,EAAE,OAAO,MAAM,OAAO,MAAM,KAAK,OAAO,MAAM,MAAM;AAAA,EACnE,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,YAAY;AACb;AAMA,SAAS,iBAAiB,OAAoD;AAC7E,QAAM,MAAiC,CAAC;AACxC,MAAI,OAAO,MAAM,WAAW,SAAU,KAAI,SAAS,MAAM;AACzD,MAAI,OAAO,MAAM,aAAa,SAAU,KAAI,WAAW,MAAM;AAC7D,MAAI,OAAO,MAAM,gBAAgB,SAAU,KAAI,cAAc,MAAM;AACnE,MAAI,OAAO,MAAM,eAAe,SAAU,KAAI,aAAa,MAAM;AACjE,MAAI,OAAO,MAAM,eAAe,SAAU,KAAI,aAAa,MAAM;AACjE,SAAO;AACR;AAEA,SAAS,mBAAmB,OAA4D;AACvF,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO,CAAC;AACjD,QAAM,MAAkD,CAAC;AACzD,aAAW,OAAO,CAAC,SAAS,SAAS,OAAO,MAAM,GAAY;AAC7D,UAAM,IAAK,MAAkC,GAAG;AAChD,QAAI,OAAO,MAAM,UAAW,KAAI,GAAG,IAAI;AAAA,EACxC;AACA,SAAO;AACR;AAEO,SAAS,UAAU,MAAa,gBAAgB,OAAiB;AACvE,QAAM,QAAQ,KAAK,eAAgB,CAAC;AACpC,QAAM,UAAU,iBAAiB,KAAK;AACtC,SAAO;AAAA,IACN,QAAI,2BAAc,KAAK,EAAE;AAAA,IACzB,WAAO,2BAAc,KAAK,KAAK;AAAA,IAC/B,eAAW,2BAAc,KAAK,SAAS;AAAA,IACvC,cAAU,2BAAc,KAAK,QAAQ;AAAA,IACrC,WAAO,2BAAc,KAAK,KAAK;AAAA,IAC/B,qBAAiB,2BAAc,KAAK,eAAe;AAAA,IACnD,UAAU,OAAO,KAAK,aAAa,YAAY,KAAK,WAAW;AAAA,IAC/D,WAAW,KAAK,qBAAqB,OAAO,KAAK,UAAU,YAAY,IAAI;AAAA,IAC3E,aAAa;AAAA,MACZ,GAAG;AAAA,MACH,GAAG;AAAA,MACH,eAAe;AAAA,QACd,GAAG,oBAAoB;AAAA,QACvB,GAAG,mBAAmB,MAAM,aAAa;AAAA,MAC1C;AAAA,MACA,QAAQ,KAAK,UAAU,QAAQ,UAAU;AAAA,MACzC,UAAU,KAAK,YAAY,QAAQ,YAAY;AAAA,IAChD;AAAA,IACA,iBAAiB,KAAK,oBAAoB;AAAA,IAC1C,iBAAiB;AAAA,IACjB,gBAAY,4BAAe,KAAK,UAAU;AAAA,IAC1C,eAAW,4BAAe,KAAK,SAAS;AAAA,IACxC,eAAW,4BAAe,KAAK,SAAS;AAAA,IACxC,aAAa,MAAM,QAAQ,KAAK,WAAW,IAAI,KAAK,cAAc,CAAC;AAAA,IACnE,WAAW,KAAK,qBAAqB,OAAO,KAAK,UAAU,YAAY,IAAI;AAAA,IAC3E,WAAW,KAAK,qBAAqB,OAAO,KAAK,UAAU,YAAY,IAAI;AAAA,EAC5E;AACD;","names":[]}
|
package/dist/dtos/user.js
CHANGED
|
@@ -8,8 +8,27 @@ var DEFAULT_PREFERENCES = {
|
|
|
8
8
|
dateFormat: "MM/DD/YYYY",
|
|
9
9
|
timeFormat: "hh:mm A"
|
|
10
10
|
};
|
|
11
|
+
function cleanPreferences(prefs) {
|
|
12
|
+
const out = {};
|
|
13
|
+
if (typeof prefs.locale === "string") out.locale = prefs.locale;
|
|
14
|
+
if (typeof prefs.timezone === "string") out.timezone = prefs.timezone;
|
|
15
|
+
if (typeof prefs.emailDigest === "string") out.emailDigest = prefs.emailDigest;
|
|
16
|
+
if (typeof prefs.dateFormat === "string") out.dateFormat = prefs.dateFormat;
|
|
17
|
+
if (typeof prefs.timeFormat === "string") out.timeFormat = prefs.timeFormat;
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
function cleanNotifications(value) {
|
|
21
|
+
if (!value || typeof value !== "object") return {};
|
|
22
|
+
const out = {};
|
|
23
|
+
for (const key of ["email", "inApp", "sms", "push"]) {
|
|
24
|
+
const v = value[key];
|
|
25
|
+
if (typeof v === "boolean") out[key] = v;
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
11
29
|
function toUserDTO(user, phoneVerified = false) {
|
|
12
30
|
const prefs = user.preferences ?? {};
|
|
31
|
+
const cleaned = cleanPreferences(prefs);
|
|
13
32
|
return {
|
|
14
33
|
id: stringOrEmpty(user.id),
|
|
15
34
|
email: stringOrEmpty(user.email),
|
|
@@ -21,9 +40,13 @@ function toUserDTO(user, phoneVerified = false) {
|
|
|
21
40
|
lastLogin: user.lastLogin instanceof Date ? user.lastLogin.toISOString() : "",
|
|
22
41
|
preferences: {
|
|
23
42
|
...DEFAULT_PREFERENCES,
|
|
24
|
-
...
|
|
25
|
-
|
|
26
|
-
|
|
43
|
+
...cleaned,
|
|
44
|
+
notifications: {
|
|
45
|
+
...DEFAULT_PREFERENCES.notifications,
|
|
46
|
+
...cleanNotifications(prefs.notifications)
|
|
47
|
+
},
|
|
48
|
+
locale: user.locale || cleaned.locale || "en-US",
|
|
49
|
+
timezone: user.timezone || cleaned.timezone || "UTC"
|
|
27
50
|
},
|
|
28
51
|
isEmailVerified: user.emailVerifiedAt !== null,
|
|
29
52
|
isPhoneVerified: phoneVerified,
|
package/dist/dtos/user.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/dtos/user.ts"],"sourcesContent":["import { stringOrEmpty, booleanOrFalse } from '@fonderie/core';\n\nimport type { IUser, IUserPreferences } from '../types';\n\nexport interface IUserDTO {\n\tid: string;\n\temail: string;\n\tfirstName: string;\n\tlastName: string;\n\tphone: string;\n\tprofileImageUrl: string;\n\tisActive: boolean;\n\tlastLogin: string;\n\tpreferences: IUserPreferences;\n\tisEmailVerified: boolean;\n\tisPhoneVerified: boolean;\n\tmfaEnabled: boolean;\n\tsuspended: boolean;\n\twhitelist: boolean;\n\tipWhitelist: string[];\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nconst DEFAULT_PREFERENCES: IUserPreferences = {\n\tlocale: 'en-US',\n\ttimezone: 'UTC',\n\tnotifications: { email: true, inApp: true, sms: false, push: false },\n\temailDigest: 'immediate',\n\tdateFormat: 'MM/DD/YYYY',\n\ttimeFormat: 'hh:mm A',\n};\n\nexport function toUserDTO(user: IUser, phoneVerified = false): IUserDTO {\n\tconst prefs = user.preferences ?? ({} as IUserPreferences);\n\treturn {\n\t\tid: stringOrEmpty(user.id),\n\t\temail: stringOrEmpty(user.email),\n\t\tfirstName: stringOrEmpty(user.firstName),\n\t\tlastName: stringOrEmpty(user.lastName),\n\t\tphone: stringOrEmpty(user.phone),\n\t\tprofileImageUrl: stringOrEmpty(user.profileImageUrl),\n\t\tisActive: typeof user.isActive === 'boolean' ? user.isActive : true,\n\t\tlastLogin: user.lastLogin instanceof Date ? user.lastLogin.toISOString() : '',\n\t\tpreferences: {\n\t\t\t...DEFAULT_PREFERENCES,\n\t\t\t...prefs,\n\t\t\tlocale: user.locale ||
|
|
1
|
+
{"version":3,"sources":["../../src/dtos/user.ts"],"sourcesContent":["import { stringOrEmpty, booleanOrFalse } from '@fonderie/core';\n\nimport type { IUser, IUserPreferences } from '../types';\n\nexport interface IUserDTO {\n\tid: string;\n\temail: string;\n\tfirstName: string;\n\tlastName: string;\n\tphone: string;\n\tprofileImageUrl: string;\n\tisActive: boolean;\n\tlastLogin: string;\n\tpreferences: IUserPreferences;\n\tisEmailVerified: boolean;\n\tisPhoneVerified: boolean;\n\tmfaEnabled: boolean;\n\tsuspended: boolean;\n\twhitelist: boolean;\n\tipWhitelist: string[];\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nconst DEFAULT_PREFERENCES: IUserPreferences = {\n\tlocale: 'en-US',\n\ttimezone: 'UTC',\n\tnotifications: { email: true, inApp: true, sms: false, push: false },\n\temailDigest: 'immediate',\n\tdateFormat: 'MM/DD/YYYY',\n\ttimeFormat: 'hh:mm A',\n};\n\n// Historical rows may hold garbage preferences (the update schema accepted\n// `unknown` for four keys until it was typed). Only well-typed values\n// survive into the DTO, and notifications deep-merge over the defaults so a\n// partial stored object can't shrink the shape the client type promises.\nfunction cleanPreferences(prefs: IUserPreferences): Partial<IUserPreferences> {\n\tconst out: Partial<IUserPreferences> = {};\n\tif (typeof prefs.locale === 'string') out.locale = prefs.locale;\n\tif (typeof prefs.timezone === 'string') out.timezone = prefs.timezone;\n\tif (typeof prefs.emailDigest === 'string') out.emailDigest = prefs.emailDigest;\n\tif (typeof prefs.dateFormat === 'string') out.dateFormat = prefs.dateFormat;\n\tif (typeof prefs.timeFormat === 'string') out.timeFormat = prefs.timeFormat;\n\treturn out;\n}\n\nfunction cleanNotifications(value: unknown): Partial<IUserPreferences['notifications']> {\n\tif (!value || typeof value !== 'object') return {};\n\tconst out: Partial<IUserPreferences['notifications']> = {};\n\tfor (const key of ['email', 'inApp', 'sms', 'push'] as const) {\n\t\tconst v = (value as Record<string, unknown>)[key];\n\t\tif (typeof v === 'boolean') out[key] = v;\n\t}\n\treturn out;\n}\n\nexport function toUserDTO(user: IUser, phoneVerified = false): IUserDTO {\n\tconst prefs = user.preferences ?? ({} as IUserPreferences);\n\tconst cleaned = cleanPreferences(prefs);\n\treturn {\n\t\tid: stringOrEmpty(user.id),\n\t\temail: stringOrEmpty(user.email),\n\t\tfirstName: stringOrEmpty(user.firstName),\n\t\tlastName: stringOrEmpty(user.lastName),\n\t\tphone: stringOrEmpty(user.phone),\n\t\tprofileImageUrl: stringOrEmpty(user.profileImageUrl),\n\t\tisActive: typeof user.isActive === 'boolean' ? user.isActive : true,\n\t\tlastLogin: user.lastLogin instanceof Date ? user.lastLogin.toISOString() : '',\n\t\tpreferences: {\n\t\t\t...DEFAULT_PREFERENCES,\n\t\t\t...cleaned,\n\t\t\tnotifications: {\n\t\t\t\t...DEFAULT_PREFERENCES.notifications,\n\t\t\t\t...cleanNotifications(prefs.notifications),\n\t\t\t},\n\t\t\tlocale: user.locale || cleaned.locale || 'en-US',\n\t\t\ttimezone: user.timezone || cleaned.timezone || 'UTC',\n\t\t},\n\t\tisEmailVerified: user.emailVerifiedAt !== null,\n\t\tisPhoneVerified: phoneVerified,\n\t\tmfaEnabled: booleanOrFalse(user.mfaEnabled),\n\t\tsuspended: booleanOrFalse(user.suspended),\n\t\twhitelist: booleanOrFalse(user.whitelist),\n\t\tipWhitelist: Array.isArray(user.ipWhitelist) ? user.ipWhitelist : [],\n\t\tcreatedAt: user.createdAt instanceof Date ? user.createdAt.toISOString() : '',\n\t\tupdatedAt: user.updatedAt instanceof Date ? user.updatedAt.toISOString() : '',\n\t};\n}\n"],"mappings":";AAAA,SAAS,eAAe,sBAAsB;AAwB9C,IAAM,sBAAwC;AAAA,EAC7C,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe,EAAE,OAAO,MAAM,OAAO,MAAM,KAAK,OAAO,MAAM,MAAM;AAAA,EACnE,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,YAAY;AACb;AAMA,SAAS,iBAAiB,OAAoD;AAC7E,QAAM,MAAiC,CAAC;AACxC,MAAI,OAAO,MAAM,WAAW,SAAU,KAAI,SAAS,MAAM;AACzD,MAAI,OAAO,MAAM,aAAa,SAAU,KAAI,WAAW,MAAM;AAC7D,MAAI,OAAO,MAAM,gBAAgB,SAAU,KAAI,cAAc,MAAM;AACnE,MAAI,OAAO,MAAM,eAAe,SAAU,KAAI,aAAa,MAAM;AACjE,MAAI,OAAO,MAAM,eAAe,SAAU,KAAI,aAAa,MAAM;AACjE,SAAO;AACR;AAEA,SAAS,mBAAmB,OAA4D;AACvF,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO,CAAC;AACjD,QAAM,MAAkD,CAAC;AACzD,aAAW,OAAO,CAAC,SAAS,SAAS,OAAO,MAAM,GAAY;AAC7D,UAAM,IAAK,MAAkC,GAAG;AAChD,QAAI,OAAO,MAAM,UAAW,KAAI,GAAG,IAAI;AAAA,EACxC;AACA,SAAO;AACR;AAEO,SAAS,UAAU,MAAa,gBAAgB,OAAiB;AACvE,QAAM,QAAQ,KAAK,eAAgB,CAAC;AACpC,QAAM,UAAU,iBAAiB,KAAK;AACtC,SAAO;AAAA,IACN,IAAI,cAAc,KAAK,EAAE;AAAA,IACzB,OAAO,cAAc,KAAK,KAAK;AAAA,IAC/B,WAAW,cAAc,KAAK,SAAS;AAAA,IACvC,UAAU,cAAc,KAAK,QAAQ;AAAA,IACrC,OAAO,cAAc,KAAK,KAAK;AAAA,IAC/B,iBAAiB,cAAc,KAAK,eAAe;AAAA,IACnD,UAAU,OAAO,KAAK,aAAa,YAAY,KAAK,WAAW;AAAA,IAC/D,WAAW,KAAK,qBAAqB,OAAO,KAAK,UAAU,YAAY,IAAI;AAAA,IAC3E,aAAa;AAAA,MACZ,GAAG;AAAA,MACH,GAAG;AAAA,MACH,eAAe;AAAA,QACd,GAAG,oBAAoB;AAAA,QACvB,GAAG,mBAAmB,MAAM,aAAa;AAAA,MAC1C;AAAA,MACA,QAAQ,KAAK,UAAU,QAAQ,UAAU;AAAA,MACzC,UAAU,KAAK,YAAY,QAAQ,YAAY;AAAA,IAChD;AAAA,IACA,iBAAiB,KAAK,oBAAoB;AAAA,IAC1C,iBAAiB;AAAA,IACjB,YAAY,eAAe,KAAK,UAAU;AAAA,IAC1C,WAAW,eAAe,KAAK,SAAS;AAAA,IACxC,WAAW,eAAe,KAAK,SAAS;AAAA,IACxC,aAAa,MAAM,QAAQ,KAAK,WAAW,IAAI,KAAK,cAAc,CAAC;AAAA,IACnE,WAAW,KAAK,qBAAqB,OAAO,KAAK,UAAU,YAAY,IAAI;AAAA,IAC3E,WAAW,KAAK,qBAAqB,OAAO,KAAK,UAAU,YAAY,IAAI;AAAA,EAC5E;AACD;","names":[]}
|