vr-models 1.0.57 → 1.0.59
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/models/contact.models.d.ts +71 -0
- package/dist/models/{phoneContact.models.js → contact.models.js} +93 -29
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.js +1 -1
- package/dist/models/types.d.ts +1 -1
- package/dist/models/user.models.d.ts +15 -12
- package/dist/models/user.models.js +40 -26
- package/package.json +1 -1
- package/dist/models/phoneContact.models.d.ts +0 -71
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ModelStatic } from "vr-migrations";
|
|
2
|
+
import type { User } from "./user.models";
|
|
3
|
+
export declare const CONTACT_TYPES: readonly ["EMAIL", "PHONE", "WHATSAPP"];
|
|
4
|
+
export type ContactType = (typeof CONTACT_TYPES)[number];
|
|
5
|
+
export interface VerificationMetadata {
|
|
6
|
+
lastOtpSentAt?: Date;
|
|
7
|
+
otpAttempts?: number;
|
|
8
|
+
lastFailedAttemptAt?: Date;
|
|
9
|
+
isLocked?: boolean;
|
|
10
|
+
lockedUntil: Date | null;
|
|
11
|
+
carrier?: string;
|
|
12
|
+
countryCode?: string;
|
|
13
|
+
deactivatedAt?: Date | null;
|
|
14
|
+
deactivationReason?: string;
|
|
15
|
+
verificationToken?: string | null;
|
|
16
|
+
tokenExpiresAt?: Date | null;
|
|
17
|
+
}
|
|
18
|
+
export interface ContactAttributes {
|
|
19
|
+
id: string;
|
|
20
|
+
contactValue: string;
|
|
21
|
+
type: ContactType;
|
|
22
|
+
userId: string | null;
|
|
23
|
+
isVerified: boolean;
|
|
24
|
+
verifiedAt: Date | null;
|
|
25
|
+
isPrimary: boolean;
|
|
26
|
+
isActive: boolean;
|
|
27
|
+
otp: string | null;
|
|
28
|
+
otpExpiresAt: Date | null;
|
|
29
|
+
metadata: VerificationMetadata;
|
|
30
|
+
createdAt: Date;
|
|
31
|
+
updatedAt: Date;
|
|
32
|
+
user?: NonAttribute<User>;
|
|
33
|
+
}
|
|
34
|
+
export interface ContactCreationAttributes extends Omit<ContactAttributes, "id" | "createdAt" | "updatedAt" | "metadata"> {
|
|
35
|
+
id?: string;
|
|
36
|
+
metadata?: Partial<VerificationMetadata>;
|
|
37
|
+
}
|
|
38
|
+
export declare class Contact extends Model<InferAttributes<Contact>, InferCreationAttributes<Contact>> implements ContactAttributes {
|
|
39
|
+
id: CreationOptional<string>;
|
|
40
|
+
contactValue: string;
|
|
41
|
+
type: ContactType;
|
|
42
|
+
userId: CreationOptional<string | null>;
|
|
43
|
+
isVerified: CreationOptional<boolean>;
|
|
44
|
+
verifiedAt: CreationOptional<Date | null>;
|
|
45
|
+
isPrimary: CreationOptional<boolean>;
|
|
46
|
+
isActive: CreationOptional<boolean>;
|
|
47
|
+
otp: CreationOptional<string | null>;
|
|
48
|
+
otpExpiresAt: CreationOptional<Date | null>;
|
|
49
|
+
metadata: CreationOptional<VerificationMetadata>;
|
|
50
|
+
readonly createdAt: CreationOptional<Date>;
|
|
51
|
+
readonly updatedAt: CreationOptional<Date>;
|
|
52
|
+
user?: NonAttribute<User>;
|
|
53
|
+
static initialize(sequelize: any): void;
|
|
54
|
+
static associate(models: Record<string, ModelStatic<Model>>): void;
|
|
55
|
+
deactivate(reason?: string): Promise<void>;
|
|
56
|
+
reactivate(): Promise<void>;
|
|
57
|
+
markAsVerified(): Promise<void>;
|
|
58
|
+
isLocked(): boolean;
|
|
59
|
+
getRemainingLockTime(): number | null;
|
|
60
|
+
isExpired(): boolean;
|
|
61
|
+
getRemainingOTPTime(): number | null;
|
|
62
|
+
static findByContactValue(contactValue: string, type: ContactType): Promise<Contact | null>;
|
|
63
|
+
static findVerifiedByContactValue(contactValue: string, type: ContactType): Promise<Contact | null>;
|
|
64
|
+
static getUserContacts(userId: string): Promise<Contact[]>;
|
|
65
|
+
static getUserPrimaryContact(userId: string): Promise<Contact | null>;
|
|
66
|
+
static isContactAvailable(contactValue: string, type: ContactType): Promise<boolean>;
|
|
67
|
+
static cleanupExpiredOTPs(): Promise<void>;
|
|
68
|
+
getDisplayValue(): string;
|
|
69
|
+
getVerificationMethod(): string;
|
|
70
|
+
}
|
|
71
|
+
export type ContactModel = typeof Contact;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.Contact = exports.CONTACT_TYPES = void 0;
|
|
4
|
+
// src/models/contact.models.ts
|
|
4
5
|
const vr_migrations_1 = require("vr-migrations");
|
|
5
|
-
|
|
6
|
+
// ==================== CONTACT TYPES ====================
|
|
7
|
+
exports.CONTACT_TYPES = ["EMAIL", "PHONE", "WHATSAPP"];
|
|
8
|
+
class Contact extends vr_migrations_1.Model {
|
|
6
9
|
// Static initialization method
|
|
7
10
|
static initialize(sequelize) {
|
|
8
11
|
this.init({
|
|
@@ -12,13 +15,13 @@ class PhoneContact extends vr_migrations_1.Model {
|
|
|
12
15
|
primaryKey: true,
|
|
13
16
|
allowNull: false,
|
|
14
17
|
},
|
|
15
|
-
|
|
16
|
-
type: vr_migrations_1.DataTypes.STRING(
|
|
18
|
+
contactValue: {
|
|
19
|
+
type: vr_migrations_1.DataTypes.STRING(255),
|
|
20
|
+
allowNull: false,
|
|
21
|
+
},
|
|
22
|
+
type: {
|
|
23
|
+
type: vr_migrations_1.DataTypes.ENUM("EMAIL", "PHONE", "WHATSAPP"),
|
|
17
24
|
allowNull: false,
|
|
18
|
-
unique: true,
|
|
19
|
-
validate: {
|
|
20
|
-
is: /^\+?[0-9]{10,15}$/,
|
|
21
|
-
},
|
|
22
25
|
},
|
|
23
26
|
userId: {
|
|
24
27
|
type: vr_migrations_1.DataTypes.UUID,
|
|
@@ -79,27 +82,47 @@ class PhoneContact extends vr_migrations_1.Model {
|
|
|
79
82
|
},
|
|
80
83
|
}, {
|
|
81
84
|
sequelize,
|
|
82
|
-
modelName: "
|
|
83
|
-
tableName: "
|
|
85
|
+
modelName: "Contact",
|
|
86
|
+
tableName: "contacts",
|
|
84
87
|
timestamps: true,
|
|
85
88
|
freezeTableName: true,
|
|
89
|
+
validate: {
|
|
90
|
+
validContactValue() {
|
|
91
|
+
if (this.type === "EMAIL") {
|
|
92
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
93
|
+
if (!emailRegex.test(this.contactValue)) {
|
|
94
|
+
throw new Error("Invalid email address format");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (this.type === "PHONE" || this.type === "WHATSAPP") {
|
|
98
|
+
const phoneRegex = /^\+?[0-9]{10,15}$/;
|
|
99
|
+
if (!phoneRegex.test(this.contactValue)) {
|
|
100
|
+
throw new Error("Invalid phone number format");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
},
|
|
86
105
|
indexes: [
|
|
87
106
|
{
|
|
88
107
|
unique: true,
|
|
89
|
-
fields: ["
|
|
90
|
-
name: "
|
|
108
|
+
fields: ["contactValue", "type"],
|
|
109
|
+
name: "contacts_unique_value_type_idx",
|
|
91
110
|
},
|
|
92
111
|
{
|
|
93
112
|
fields: ["userId"],
|
|
94
|
-
name: "
|
|
113
|
+
name: "contacts_user_id_idx",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
fields: ["type"],
|
|
117
|
+
name: "contacts_type_idx",
|
|
95
118
|
},
|
|
96
119
|
{
|
|
97
120
|
fields: ["isVerified", "isPrimary"],
|
|
98
|
-
name: "
|
|
121
|
+
name: "contacts_verified_primary_idx",
|
|
99
122
|
},
|
|
100
123
|
{
|
|
101
124
|
fields: ["otpExpiresAt"],
|
|
102
|
-
name: "
|
|
125
|
+
name: "contacts_otp_expiry_idx",
|
|
103
126
|
},
|
|
104
127
|
],
|
|
105
128
|
});
|
|
@@ -113,6 +136,9 @@ class PhoneContact extends vr_migrations_1.Model {
|
|
|
113
136
|
onUpdate: "CASCADE",
|
|
114
137
|
});
|
|
115
138
|
}
|
|
139
|
+
// ===========================================================================
|
|
140
|
+
// INSTANCE METHODS
|
|
141
|
+
// ===========================================================================
|
|
116
142
|
async deactivate(reason) {
|
|
117
143
|
this.isActive = false;
|
|
118
144
|
this.metadata = {
|
|
@@ -131,6 +157,13 @@ class PhoneContact extends vr_migrations_1.Model {
|
|
|
131
157
|
};
|
|
132
158
|
await this.save();
|
|
133
159
|
}
|
|
160
|
+
async markAsVerified() {
|
|
161
|
+
this.isVerified = true;
|
|
162
|
+
this.verifiedAt = new Date();
|
|
163
|
+
this.otp = null;
|
|
164
|
+
this.otpExpiresAt = null;
|
|
165
|
+
await this.save();
|
|
166
|
+
}
|
|
134
167
|
isLocked() {
|
|
135
168
|
if (!this.metadata.isLocked)
|
|
136
169
|
return false;
|
|
@@ -161,34 +194,37 @@ class PhoneContact extends vr_migrations_1.Model {
|
|
|
161
194
|
const remaining = new Date(this.otpExpiresAt).getTime() - new Date().getTime();
|
|
162
195
|
return Math.max(0, Math.ceil(remaining / 1000));
|
|
163
196
|
}
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
static async findVerifiedByPhoneNumber(phoneNumber) {
|
|
197
|
+
// ===========================================================================
|
|
198
|
+
// STATIC METHODS
|
|
199
|
+
// ===========================================================================
|
|
200
|
+
static async findByContactValue(contactValue, type) {
|
|
171
201
|
return await this.findOne({
|
|
172
|
-
where: {
|
|
202
|
+
where: { contactValue, type },
|
|
173
203
|
});
|
|
174
204
|
}
|
|
175
|
-
static async
|
|
205
|
+
static async findVerifiedByContactValue(contactValue, type) {
|
|
176
206
|
return await this.findOne({
|
|
177
|
-
where: {
|
|
207
|
+
where: { contactValue, type, isVerified: true, isActive: true },
|
|
178
208
|
});
|
|
179
209
|
}
|
|
180
|
-
static async
|
|
210
|
+
static async getUserContacts(userId) {
|
|
181
211
|
return await this.findAll({
|
|
182
212
|
where: { userId, isActive: true },
|
|
183
213
|
order: [
|
|
184
214
|
["isPrimary", "DESC"],
|
|
215
|
+
["type", "ASC"],
|
|
185
216
|
["createdAt", "ASC"],
|
|
186
217
|
],
|
|
187
218
|
});
|
|
188
219
|
}
|
|
189
|
-
static async
|
|
220
|
+
static async getUserPrimaryContact(userId) {
|
|
221
|
+
return await this.findOne({
|
|
222
|
+
where: { userId, isPrimary: true, isActive: true },
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
static async isContactAvailable(contactValue, type) {
|
|
190
226
|
const existing = await this.findOne({
|
|
191
|
-
where: {
|
|
227
|
+
where: { contactValue, type, isVerified: true },
|
|
192
228
|
});
|
|
193
229
|
return !existing;
|
|
194
230
|
}
|
|
@@ -200,5 +236,33 @@ class PhoneContact extends vr_migrations_1.Model {
|
|
|
200
236
|
},
|
|
201
237
|
});
|
|
202
238
|
}
|
|
239
|
+
// ===========================================================================
|
|
240
|
+
// TYPE-SPECIFIC HELPERS
|
|
241
|
+
// ===========================================================================
|
|
242
|
+
getDisplayValue() {
|
|
243
|
+
if (this.type === "EMAIL") {
|
|
244
|
+
return this.contactValue;
|
|
245
|
+
}
|
|
246
|
+
else if (this.type === "PHONE" || this.type === "WHATSAPP") {
|
|
247
|
+
// Mask phone number
|
|
248
|
+
const phone = this.contactValue;
|
|
249
|
+
if (phone.length <= 4)
|
|
250
|
+
return phone;
|
|
251
|
+
return phone.slice(0, 3) + "****" + phone.slice(-3);
|
|
252
|
+
}
|
|
253
|
+
return this.contactValue;
|
|
254
|
+
}
|
|
255
|
+
getVerificationMethod() {
|
|
256
|
+
switch (this.type) {
|
|
257
|
+
case "EMAIL":
|
|
258
|
+
return "email";
|
|
259
|
+
case "PHONE":
|
|
260
|
+
return "sms";
|
|
261
|
+
case "WHATSAPP":
|
|
262
|
+
return "whatsapp";
|
|
263
|
+
default:
|
|
264
|
+
return "unknown";
|
|
265
|
+
}
|
|
266
|
+
}
|
|
203
267
|
}
|
|
204
|
-
exports.
|
|
268
|
+
exports.Contact = Contact;
|
package/dist/models/index.d.ts
CHANGED
|
@@ -12,5 +12,5 @@ export * from "./installment.models";
|
|
|
12
12
|
export * from "./ban.models";
|
|
13
13
|
export * from "./suspension.models";
|
|
14
14
|
export * from "./appSpecs.models";
|
|
15
|
-
export * from "./
|
|
16
|
-
export type { UserModel, SecurityClearanceModel, ProductModel, PricingModel, PaymentModel, PaymentEventLogModel, IdempotencyRecordModel, EventLogModel, DevicePaymentPlanModel, DeviceModel, InstallmentModel, BanModel, SuspensionModel, AppSpecsModel,
|
|
15
|
+
export * from "./contact.models";
|
|
16
|
+
export type { UserModel, SecurityClearanceModel, ProductModel, PricingModel, PaymentModel, PaymentEventLogModel, IdempotencyRecordModel, EventLogModel, DevicePaymentPlanModel, DeviceModel, InstallmentModel, BanModel, SuspensionModel, AppSpecsModel, ContactModel, } from "./types";
|
package/dist/models/index.js
CHANGED
|
@@ -28,4 +28,4 @@ __exportStar(require("./installment.models"), exports);
|
|
|
28
28
|
__exportStar(require("./ban.models"), exports);
|
|
29
29
|
__exportStar(require("./suspension.models"), exports);
|
|
30
30
|
__exportStar(require("./appSpecs.models"), exports);
|
|
31
|
-
__exportStar(require("./
|
|
31
|
+
__exportStar(require("./contact.models"), exports);
|
package/dist/models/types.d.ts
CHANGED
|
@@ -11,5 +11,5 @@ export type { InstallmentModel } from "./installment.models";
|
|
|
11
11
|
export type { BanModel } from "./ban.models";
|
|
12
12
|
export type { SuspensionModel } from "./suspension.models";
|
|
13
13
|
export type { AppSpecsModel } from "./appSpecs.models";
|
|
14
|
-
export type {
|
|
14
|
+
export type { ContactModel } from "./contact.models";
|
|
15
15
|
export type { PaymentEventLogModel } from "./paymentEventLog.models";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ModelStatic } from "vr-migrations";
|
|
2
|
+
import type { Device } from "./device.models";
|
|
2
3
|
import type { Payment } from "./payment.models";
|
|
3
4
|
import type { SecurityClearance } from "./securityClearance.models";
|
|
4
|
-
import type {
|
|
5
|
+
import type { Contact } from "./contact.models";
|
|
5
6
|
import { Ban } from "./ban.models";
|
|
6
7
|
import { Suspension } from "./suspension.models";
|
|
7
8
|
import { DevicePaymentPlan } from "./devicePaymentPlan.models";
|
|
@@ -18,7 +19,6 @@ export interface UserAttributes {
|
|
|
18
19
|
firstName: string;
|
|
19
20
|
lastName: string;
|
|
20
21
|
jacketId?: string | null;
|
|
21
|
-
email?: string | null;
|
|
22
22
|
password?: string | null;
|
|
23
23
|
securityClearanceId: string;
|
|
24
24
|
plateNumber?: string | null;
|
|
@@ -26,7 +26,7 @@ export interface UserAttributes {
|
|
|
26
26
|
birthDate?: number | null;
|
|
27
27
|
birthMonth?: number | null;
|
|
28
28
|
birthYear?: number | null;
|
|
29
|
-
|
|
29
|
+
primaryContactId: string | null;
|
|
30
30
|
isActive: boolean;
|
|
31
31
|
forgotPassword: boolean;
|
|
32
32
|
isDeactivated: boolean;
|
|
@@ -37,25 +37,26 @@ export interface UserAttributes {
|
|
|
37
37
|
createdAt: Date;
|
|
38
38
|
payments?: Payment[];
|
|
39
39
|
securityClearance?: SecurityClearance;
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
primaryContact?: Contact;
|
|
41
|
+
contacts?: Contact[];
|
|
42
42
|
bans?: Ban[];
|
|
43
43
|
suspensions?: Suspension[];
|
|
44
|
+
devices?: Device[];
|
|
45
|
+
registeredBy?: string | null;
|
|
44
46
|
}
|
|
45
|
-
export interface UserCreationAttributes extends Omit<UserAttributes, "id" | "createdAt" | "isActive" | "forgotPassword" | "isDeactivated" | "tokenVersion" | "
|
|
47
|
+
export interface UserCreationAttributes extends Omit<UserAttributes, "id" | "createdAt" | "isActive" | "forgotPassword" | "isDeactivated" | "tokenVersion" | "primaryContactId"> {
|
|
46
48
|
id?: string;
|
|
47
49
|
isActive?: boolean;
|
|
48
50
|
forgotPassword?: boolean;
|
|
49
51
|
isDeactivated?: boolean;
|
|
50
52
|
tokenVersion?: number;
|
|
51
|
-
|
|
53
|
+
primaryContactId?: string | null;
|
|
52
54
|
}
|
|
53
55
|
export declare class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> implements UserAttributes {
|
|
54
56
|
id: CreationOptional<string>;
|
|
55
57
|
firstName: string;
|
|
56
58
|
lastName: string;
|
|
57
59
|
jacketId: CreationOptional<string | null>;
|
|
58
|
-
email: CreationOptional<string | null>;
|
|
59
60
|
password: CreationOptional<string | null>;
|
|
60
61
|
securityClearanceId: string;
|
|
61
62
|
plateNumber: CreationOptional<string | null>;
|
|
@@ -63,7 +64,7 @@ export declare class User extends Model<InferAttributes<User>, InferCreationAttr
|
|
|
63
64
|
birthDate: CreationOptional<number | null>;
|
|
64
65
|
birthMonth: CreationOptional<number | null>;
|
|
65
66
|
birthYear: CreationOptional<number | null>;
|
|
66
|
-
|
|
67
|
+
primaryContactId: CreationOptional<string | null>;
|
|
67
68
|
isActive: CreationOptional<boolean>;
|
|
68
69
|
forgotPassword: CreationOptional<boolean>;
|
|
69
70
|
isDeactivated: CreationOptional<boolean>;
|
|
@@ -75,17 +76,19 @@ export declare class User extends Model<InferAttributes<User>, InferCreationAttr
|
|
|
75
76
|
payments?: NonAttribute<Payment[]>;
|
|
76
77
|
paymentPlans?: NonAttribute<DevicePaymentPlan[]>;
|
|
77
78
|
securityClearance?: NonAttribute<SecurityClearance>;
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
primaryContact?: NonAttribute<Contact>;
|
|
80
|
+
contacts?: NonAttribute<Contact[]>;
|
|
80
81
|
bans?: NonAttribute<Ban[]>;
|
|
81
82
|
suspensions?: NonAttribute<Suspension[]>;
|
|
83
|
+
registeredBy?: CreationOptional<string | null>;
|
|
82
84
|
static initialize(sequelize: any): void;
|
|
83
85
|
static associate(models: Record<string, ModelStatic<Model>>): void;
|
|
84
86
|
softDelete(deletedBy: string, reason?: string): Promise<void>;
|
|
85
87
|
updateLastLogin(): Promise<void>;
|
|
86
88
|
deactivate(): Promise<void>;
|
|
87
89
|
activate(): Promise<void>;
|
|
88
|
-
|
|
90
|
+
getPrimaryVerifiedContact(): Promise<Contact | null>;
|
|
91
|
+
hasVerifiedContactType(type: "EMAIL" | "PHONE" | "WHATSAPP"): Promise<boolean>;
|
|
89
92
|
getBirthDate(): Date | null;
|
|
90
93
|
getAge(): number | null;
|
|
91
94
|
}
|
|
@@ -5,8 +5,11 @@ exports.User = exports.GENDER_OPTIONS = void 0;
|
|
|
5
5
|
const vr_migrations_1 = require("vr-migrations");
|
|
6
6
|
// ==================== GENDER OPTIONS ====================
|
|
7
7
|
exports.GENDER_OPTIONS = ["male", "female"];
|
|
8
|
+
// ==================== USER MODEL ====================
|
|
8
9
|
class User extends vr_migrations_1.Model {
|
|
9
|
-
//
|
|
10
|
+
// ===========================================================================
|
|
11
|
+
// INITIALIZATION
|
|
12
|
+
// ===========================================================================
|
|
10
13
|
static initialize(sequelize) {
|
|
11
14
|
this.init({
|
|
12
15
|
id: {
|
|
@@ -27,11 +30,6 @@ class User extends vr_migrations_1.Model {
|
|
|
27
30
|
allowNull: true,
|
|
28
31
|
unique: true,
|
|
29
32
|
},
|
|
30
|
-
email: {
|
|
31
|
-
type: vr_migrations_1.DataTypes.STRING(100),
|
|
32
|
-
allowNull: true,
|
|
33
|
-
unique: true,
|
|
34
|
-
},
|
|
35
33
|
password: {
|
|
36
34
|
type: vr_migrations_1.DataTypes.STRING,
|
|
37
35
|
allowNull: true,
|
|
@@ -73,7 +71,11 @@ class User extends vr_migrations_1.Model {
|
|
|
73
71
|
max: new Date().getFullYear(),
|
|
74
72
|
},
|
|
75
73
|
},
|
|
76
|
-
|
|
74
|
+
primaryContactId: {
|
|
75
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
76
|
+
allowNull: true,
|
|
77
|
+
},
|
|
78
|
+
registeredBy: {
|
|
77
79
|
type: vr_migrations_1.DataTypes.UUID,
|
|
78
80
|
allowNull: true,
|
|
79
81
|
},
|
|
@@ -125,7 +127,9 @@ class User extends vr_migrations_1.Model {
|
|
|
125
127
|
freezeTableName: true,
|
|
126
128
|
});
|
|
127
129
|
}
|
|
128
|
-
//
|
|
130
|
+
// ===========================================================================
|
|
131
|
+
// ASSOCIATIONS
|
|
132
|
+
// ===========================================================================
|
|
129
133
|
static associate(models) {
|
|
130
134
|
this.belongsTo(models.SecurityClearance, {
|
|
131
135
|
foreignKey: "securityClearanceId",
|
|
@@ -133,15 +137,16 @@ class User extends vr_migrations_1.Model {
|
|
|
133
137
|
onDelete: "RESTRICT",
|
|
134
138
|
onUpdate: "CASCADE",
|
|
135
139
|
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
// NEW: Contact associations (renamed from PhoneContact)
|
|
141
|
+
this.belongsTo(models.Contact, {
|
|
142
|
+
foreignKey: "primaryContactId",
|
|
143
|
+
as: "primaryContact",
|
|
139
144
|
onDelete: "SET NULL",
|
|
140
145
|
onUpdate: "CASCADE",
|
|
141
146
|
});
|
|
142
|
-
this.hasMany(models.
|
|
147
|
+
this.hasMany(models.Contact, {
|
|
143
148
|
foreignKey: "userId",
|
|
144
|
-
as: "
|
|
149
|
+
as: "contacts",
|
|
145
150
|
onDelete: "SET NULL",
|
|
146
151
|
onUpdate: "CASCADE",
|
|
147
152
|
});
|
|
@@ -170,7 +175,9 @@ class User extends vr_migrations_1.Model {
|
|
|
170
175
|
onUpdate: "CASCADE",
|
|
171
176
|
});
|
|
172
177
|
}
|
|
173
|
-
//
|
|
178
|
+
// ===========================================================================
|
|
179
|
+
// CUSTOM INSTANCE METHODS
|
|
180
|
+
// ===========================================================================
|
|
174
181
|
async softDelete(deletedBy, reason) {
|
|
175
182
|
this.deletion = {
|
|
176
183
|
deleted: true,
|
|
@@ -195,18 +202,25 @@ class User extends vr_migrations_1.Model {
|
|
|
195
202
|
this.deactivatedAt = null;
|
|
196
203
|
await this.save();
|
|
197
204
|
}
|
|
198
|
-
// Helper to
|
|
199
|
-
async
|
|
200
|
-
|
|
201
|
-
.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
205
|
+
// Helper to get primary verified contact
|
|
206
|
+
async getPrimaryVerifiedContact() {
|
|
207
|
+
if (this.primaryContactId) {
|
|
208
|
+
const ContactModel = this.constructor.sequelize.models.Contact;
|
|
209
|
+
return await ContactModel.findOne({
|
|
210
|
+
where: {
|
|
211
|
+
id: this.primaryContactId,
|
|
212
|
+
isVerified: true,
|
|
213
|
+
isActive: true,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
// Helper to check if user has a verified contact of a specific type
|
|
220
|
+
async hasVerifiedContactType(type) {
|
|
221
|
+
if (!this.contacts)
|
|
222
|
+
return false;
|
|
223
|
+
return this.contacts.some((contact) => contact.type === type && contact.isVerified && contact.isActive);
|
|
210
224
|
}
|
|
211
225
|
// Helper to get full birth date as Date object
|
|
212
226
|
getBirthDate() {
|
package/package.json
CHANGED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ModelStatic } from "vr-migrations";
|
|
2
|
-
import type { User } from "./user.models";
|
|
3
|
-
export interface PhoneContactAttributes {
|
|
4
|
-
id: string;
|
|
5
|
-
phoneNumber: string;
|
|
6
|
-
userId: string | null;
|
|
7
|
-
isVerified: boolean;
|
|
8
|
-
verifiedAt: Date | null;
|
|
9
|
-
isPrimary: boolean;
|
|
10
|
-
isActive: boolean;
|
|
11
|
-
otp: string | null;
|
|
12
|
-
otpExpiresAt: Date | null;
|
|
13
|
-
metadata: {
|
|
14
|
-
lastOtpSentAt?: Date;
|
|
15
|
-
otpAttempts?: number;
|
|
16
|
-
lastFailedAttemptAt?: Date;
|
|
17
|
-
isLocked?: boolean;
|
|
18
|
-
lockedUntil: Date | null;
|
|
19
|
-
carrier?: string;
|
|
20
|
-
countryCode?: string;
|
|
21
|
-
deactivatedAt?: Date | null;
|
|
22
|
-
deactivationReason?: string;
|
|
23
|
-
};
|
|
24
|
-
createdAt: Date;
|
|
25
|
-
updatedAt: Date;
|
|
26
|
-
user?: NonAttribute<User>;
|
|
27
|
-
}
|
|
28
|
-
export interface PhoneContactCreationAttributes extends Omit<PhoneContactAttributes, "id" | "createdAt" | "updatedAt" | "metadata"> {
|
|
29
|
-
id?: string;
|
|
30
|
-
metadata?: Record<string, any>;
|
|
31
|
-
}
|
|
32
|
-
export declare class PhoneContact extends Model<InferAttributes<PhoneContact>, InferCreationAttributes<PhoneContact>> implements PhoneContactAttributes {
|
|
33
|
-
id: CreationOptional<string>;
|
|
34
|
-
phoneNumber: string;
|
|
35
|
-
userId: CreationOptional<string | null>;
|
|
36
|
-
isVerified: CreationOptional<boolean>;
|
|
37
|
-
verifiedAt: CreationOptional<Date | null>;
|
|
38
|
-
isPrimary: CreationOptional<boolean>;
|
|
39
|
-
isActive: CreationOptional<boolean>;
|
|
40
|
-
otp: CreationOptional<string | null>;
|
|
41
|
-
otpExpiresAt: CreationOptional<Date | null>;
|
|
42
|
-
metadata: CreationOptional<{
|
|
43
|
-
lastOtpSentAt?: Date;
|
|
44
|
-
otpAttempts?: number;
|
|
45
|
-
lastFailedAttemptAt?: Date;
|
|
46
|
-
isLocked?: boolean;
|
|
47
|
-
lockedUntil: Date | null;
|
|
48
|
-
carrier?: string;
|
|
49
|
-
countryCode?: string;
|
|
50
|
-
deactivatedAt?: Date | null;
|
|
51
|
-
deactivationReason?: string;
|
|
52
|
-
}>;
|
|
53
|
-
readonly createdAt: CreationOptional<Date>;
|
|
54
|
-
readonly updatedAt: CreationOptional<Date>;
|
|
55
|
-
user?: NonAttribute<User>;
|
|
56
|
-
static initialize(sequelize: any): void;
|
|
57
|
-
static associate(models: Record<string, ModelStatic<Model>>): void;
|
|
58
|
-
deactivate(reason?: string): Promise<void>;
|
|
59
|
-
reactivate(): Promise<void>;
|
|
60
|
-
isLocked(): boolean;
|
|
61
|
-
getRemainingLockTime(): number | null;
|
|
62
|
-
isExpired(): boolean;
|
|
63
|
-
getRemainingOTPTime(): number | null;
|
|
64
|
-
static findByPhoneNumber(phoneNumber: string): Promise<PhoneContact | null>;
|
|
65
|
-
static findVerifiedByPhoneNumber(phoneNumber: string): Promise<PhoneContact | null>;
|
|
66
|
-
static getUserPrimaryPhone(userId: string): Promise<PhoneContact | null>;
|
|
67
|
-
static getUserPhones(userId: string): Promise<PhoneContact[]>;
|
|
68
|
-
static isPhoneNumberAvailable(phoneNumber: string): Promise<boolean>;
|
|
69
|
-
static cleanupExpiredOTPs(): Promise<void>;
|
|
70
|
-
}
|
|
71
|
-
export type PhoneContactModel = typeof PhoneContact;
|