vr-models 1.0.43 → 1.0.45
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/index.d.ts +2 -1
- package/dist/models/index.js +1 -0
- package/dist/models/phoneContact.models.d.ts +76 -0
- package/dist/models/phoneContact.models.js +285 -0
- package/dist/models/types.d.ts +1 -0
- package/dist/models/user.models.d.ts +12 -14
- package/dist/models/user.models.js +30 -47
- package/package.json +1 -1
package/dist/models/index.d.ts
CHANGED
|
@@ -12,4 +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
|
|
15
|
+
export * from "./phoneContact.models";
|
|
16
|
+
export type { UserModel, TransactionModel, SecurityClearanceModel, ProductModel, PricingModel, PaymentModel, IdempotencyRecordModel, EventLogModel, DevicePaymentPlanModel, DeviceModel, InstallmentModel, BanModel, SuspensionModel, AppSpecsModel, PhoneContactModel, } from "./types";
|
package/dist/models/index.js
CHANGED
|
@@ -28,3 +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("./phoneContact.models"), exports);
|
|
@@ -0,0 +1,76 @@
|
|
|
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;
|
|
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;
|
|
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
|
+
generateOTP(length?: number): Promise<string>;
|
|
59
|
+
verifyOTP(otp: string): Promise<boolean>;
|
|
60
|
+
markAsVerified(): Promise<void>;
|
|
61
|
+
markAsPrimary(): Promise<void>;
|
|
62
|
+
unmarkAsPrimary(): Promise<void>;
|
|
63
|
+
deactivate(reason?: string): Promise<void>;
|
|
64
|
+
reactivate(): Promise<void>;
|
|
65
|
+
isLocked(): boolean;
|
|
66
|
+
getRemainingLockTime(): number | null;
|
|
67
|
+
isExpired(): boolean;
|
|
68
|
+
getRemainingOTPTime(): number | null;
|
|
69
|
+
static findByPhoneNumber(phoneNumber: string): Promise<PhoneContact | null>;
|
|
70
|
+
static findVerifiedByPhoneNumber(phoneNumber: string): Promise<PhoneContact | null>;
|
|
71
|
+
static getUserPrimaryPhone(userId: string): Promise<PhoneContact | null>;
|
|
72
|
+
static getUserPhones(userId: string): Promise<PhoneContact[]>;
|
|
73
|
+
static isPhoneNumberAvailable(phoneNumber: string): Promise<boolean>;
|
|
74
|
+
static cleanupExpiredOTPs(): Promise<void>;
|
|
75
|
+
}
|
|
76
|
+
export type PhoneContactModel = typeof PhoneContact;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PhoneContact = void 0;
|
|
4
|
+
const vr_migrations_1 = require("vr-migrations");
|
|
5
|
+
class PhoneContact extends vr_migrations_1.Model {
|
|
6
|
+
// Static initialization method
|
|
7
|
+
static initialize(sequelize) {
|
|
8
|
+
this.init({
|
|
9
|
+
id: {
|
|
10
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
11
|
+
defaultValue: vr_migrations_1.DataTypes.UUIDV4,
|
|
12
|
+
primaryKey: true,
|
|
13
|
+
allowNull: false,
|
|
14
|
+
},
|
|
15
|
+
phoneNumber: {
|
|
16
|
+
type: vr_migrations_1.DataTypes.STRING(20),
|
|
17
|
+
allowNull: false,
|
|
18
|
+
unique: true,
|
|
19
|
+
validate: {
|
|
20
|
+
is: /^\+?[0-9]{10,15}$/,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
userId: {
|
|
24
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
25
|
+
allowNull: true,
|
|
26
|
+
references: {
|
|
27
|
+
model: "users",
|
|
28
|
+
key: "id",
|
|
29
|
+
},
|
|
30
|
+
onUpdate: "CASCADE",
|
|
31
|
+
onDelete: "SET NULL",
|
|
32
|
+
},
|
|
33
|
+
isVerified: {
|
|
34
|
+
type: vr_migrations_1.DataTypes.BOOLEAN,
|
|
35
|
+
allowNull: false,
|
|
36
|
+
defaultValue: false,
|
|
37
|
+
},
|
|
38
|
+
verifiedAt: {
|
|
39
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
40
|
+
allowNull: true,
|
|
41
|
+
},
|
|
42
|
+
isPrimary: {
|
|
43
|
+
type: vr_migrations_1.DataTypes.BOOLEAN,
|
|
44
|
+
allowNull: false,
|
|
45
|
+
defaultValue: false,
|
|
46
|
+
},
|
|
47
|
+
isActive: {
|
|
48
|
+
type: vr_migrations_1.DataTypes.BOOLEAN,
|
|
49
|
+
allowNull: false,
|
|
50
|
+
defaultValue: true,
|
|
51
|
+
},
|
|
52
|
+
otp: {
|
|
53
|
+
type: vr_migrations_1.DataTypes.STRING(10),
|
|
54
|
+
allowNull: true,
|
|
55
|
+
},
|
|
56
|
+
otpExpiresAt: {
|
|
57
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
58
|
+
allowNull: true,
|
|
59
|
+
},
|
|
60
|
+
metadata: {
|
|
61
|
+
type: vr_migrations_1.DataTypes.JSONB,
|
|
62
|
+
allowNull: false,
|
|
63
|
+
defaultValue: {
|
|
64
|
+
otpAttempts: 0,
|
|
65
|
+
isLocked: false,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
createdAt: {
|
|
69
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
70
|
+
allowNull: false,
|
|
71
|
+
defaultValue: vr_migrations_1.DataTypes.NOW,
|
|
72
|
+
},
|
|
73
|
+
updatedAt: {
|
|
74
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
75
|
+
allowNull: false,
|
|
76
|
+
defaultValue: vr_migrations_1.DataTypes.NOW,
|
|
77
|
+
},
|
|
78
|
+
}, {
|
|
79
|
+
sequelize,
|
|
80
|
+
modelName: "PhoneContact",
|
|
81
|
+
tableName: "phone_contacts",
|
|
82
|
+
timestamps: true,
|
|
83
|
+
freezeTableName: true,
|
|
84
|
+
indexes: [
|
|
85
|
+
{
|
|
86
|
+
unique: true,
|
|
87
|
+
fields: ["phoneNumber"],
|
|
88
|
+
name: "phone_contacts_phone_number_unique",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
fields: ["userId"],
|
|
92
|
+
name: "phone_contacts_user_id_idx",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
fields: ["isVerified", "isPrimary"],
|
|
96
|
+
name: "phone_contacts_verified_primary_idx",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
fields: ["otpExpiresAt"],
|
|
100
|
+
name: "phone_contacts_otp_expiry_idx",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// Static association method
|
|
106
|
+
static associate(models) {
|
|
107
|
+
this.belongsTo(models.User, {
|
|
108
|
+
foreignKey: "userId",
|
|
109
|
+
as: "user",
|
|
110
|
+
onDelete: "SET NULL",
|
|
111
|
+
onUpdate: "CASCADE",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// Custom instance methods
|
|
115
|
+
async generateOTP(length = 6) {
|
|
116
|
+
const digits = "0123456789";
|
|
117
|
+
let otp = "";
|
|
118
|
+
for (let i = 0; i < length; i++) {
|
|
119
|
+
otp += digits[Math.floor(Math.random() * 10)];
|
|
120
|
+
}
|
|
121
|
+
const expiryMinutes = parseInt(process.env.OTP_EXPIRY_MINUTES || "15");
|
|
122
|
+
const expiresAt = new Date();
|
|
123
|
+
expiresAt.setMinutes(expiresAt.getMinutes() + expiryMinutes);
|
|
124
|
+
this.otp = otp;
|
|
125
|
+
this.otpExpiresAt = expiresAt;
|
|
126
|
+
// Update metadata
|
|
127
|
+
this.metadata = {
|
|
128
|
+
...this.metadata,
|
|
129
|
+
lastOtpSentAt: new Date(),
|
|
130
|
+
otpAttempts: 0,
|
|
131
|
+
};
|
|
132
|
+
await this.save();
|
|
133
|
+
return otp;
|
|
134
|
+
}
|
|
135
|
+
async verifyOTP(otp) {
|
|
136
|
+
// Check if locked
|
|
137
|
+
if (this.metadata.isLocked && this.metadata.lockedUntil) {
|
|
138
|
+
if (new Date() < new Date(this.metadata.lockedUntil)) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
// Unlock if past lock time
|
|
142
|
+
this.metadata.isLocked = false;
|
|
143
|
+
this.metadata.lockedUntil = undefined;
|
|
144
|
+
}
|
|
145
|
+
// Check OTP
|
|
146
|
+
if (!this.otp || !this.otpExpiresAt || this.otp !== otp) {
|
|
147
|
+
// Increment failed attempts
|
|
148
|
+
const attempts = (this.metadata.otpAttempts || 0) + 1;
|
|
149
|
+
this.metadata = {
|
|
150
|
+
...this.metadata,
|
|
151
|
+
otpAttempts: attempts,
|
|
152
|
+
lastFailedAttemptAt: new Date(),
|
|
153
|
+
};
|
|
154
|
+
// Lock after 5 failed attempts
|
|
155
|
+
if (attempts >= 5) {
|
|
156
|
+
const lockMinutes = parseInt(process.env.OTP_LOCK_MINUTES || "30");
|
|
157
|
+
const lockedUntil = new Date();
|
|
158
|
+
lockedUntil.setMinutes(lockedUntil.getMinutes() + lockMinutes);
|
|
159
|
+
this.metadata.isLocked = true;
|
|
160
|
+
this.metadata.lockedUntil = lockedUntil;
|
|
161
|
+
}
|
|
162
|
+
await this.save();
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
// Check if expired
|
|
166
|
+
if (new Date() > new Date(this.otpExpiresAt)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
// Clear OTP on success
|
|
170
|
+
this.otp = null;
|
|
171
|
+
this.otpExpiresAt = null;
|
|
172
|
+
this.metadata = {
|
|
173
|
+
...this.metadata,
|
|
174
|
+
otpAttempts: 0,
|
|
175
|
+
lastFailedAttemptAt: undefined,
|
|
176
|
+
};
|
|
177
|
+
await this.save();
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
async markAsVerified() {
|
|
181
|
+
this.isVerified = true;
|
|
182
|
+
this.verifiedAt = new Date();
|
|
183
|
+
await this.save();
|
|
184
|
+
}
|
|
185
|
+
async markAsPrimary() {
|
|
186
|
+
// If this contact is being set as primary, ensure it's verified
|
|
187
|
+
if (!this.isVerified) {
|
|
188
|
+
throw new Error("Cannot set unverified phone as primary");
|
|
189
|
+
}
|
|
190
|
+
this.isPrimary = true;
|
|
191
|
+
await this.save();
|
|
192
|
+
}
|
|
193
|
+
async unmarkAsPrimary() {
|
|
194
|
+
this.isPrimary = false;
|
|
195
|
+
await this.save();
|
|
196
|
+
}
|
|
197
|
+
async deactivate(reason) {
|
|
198
|
+
this.isActive = false;
|
|
199
|
+
this.metadata = {
|
|
200
|
+
...this.metadata,
|
|
201
|
+
deactivatedAt: new Date(),
|
|
202
|
+
deactivationReason: reason,
|
|
203
|
+
};
|
|
204
|
+
await this.save();
|
|
205
|
+
}
|
|
206
|
+
async reactivate() {
|
|
207
|
+
this.isActive = true;
|
|
208
|
+
this.metadata = {
|
|
209
|
+
...this.metadata,
|
|
210
|
+
deactivatedAt: undefined,
|
|
211
|
+
deactivationReason: undefined,
|
|
212
|
+
};
|
|
213
|
+
await this.save();
|
|
214
|
+
}
|
|
215
|
+
isLocked() {
|
|
216
|
+
if (!this.metadata.isLocked)
|
|
217
|
+
return false;
|
|
218
|
+
if (this.metadata.lockedUntil &&
|
|
219
|
+
new Date() > new Date(this.metadata.lockedUntil)) {
|
|
220
|
+
// Auto-unlock
|
|
221
|
+
this.metadata.isLocked = false;
|
|
222
|
+
this.metadata.lockedUntil = undefined;
|
|
223
|
+
this.save();
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
getRemainingLockTime() {
|
|
229
|
+
if (!this.metadata.isLocked || !this.metadata.lockedUntil)
|
|
230
|
+
return null;
|
|
231
|
+
const remaining = new Date(this.metadata.lockedUntil).getTime() - new Date().getTime();
|
|
232
|
+
return Math.max(0, Math.ceil(remaining / 1000));
|
|
233
|
+
}
|
|
234
|
+
isExpired() {
|
|
235
|
+
if (!this.otpExpiresAt)
|
|
236
|
+
return true;
|
|
237
|
+
return new Date() > new Date(this.otpExpiresAt);
|
|
238
|
+
}
|
|
239
|
+
getRemainingOTPTime() {
|
|
240
|
+
if (!this.otpExpiresAt)
|
|
241
|
+
return null;
|
|
242
|
+
const remaining = new Date(this.otpExpiresAt).getTime() - new Date().getTime();
|
|
243
|
+
return Math.max(0, Math.ceil(remaining / 1000));
|
|
244
|
+
}
|
|
245
|
+
// Static methods
|
|
246
|
+
static async findByPhoneNumber(phoneNumber) {
|
|
247
|
+
return await this.findOne({
|
|
248
|
+
where: { phoneNumber },
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
static async findVerifiedByPhoneNumber(phoneNumber) {
|
|
252
|
+
return await this.findOne({
|
|
253
|
+
where: { phoneNumber, isVerified: true, isActive: true },
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
static async getUserPrimaryPhone(userId) {
|
|
257
|
+
return await this.findOne({
|
|
258
|
+
where: { userId, isPrimary: true, isActive: true },
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
static async getUserPhones(userId) {
|
|
262
|
+
return await this.findAll({
|
|
263
|
+
where: { userId, isActive: true },
|
|
264
|
+
order: [
|
|
265
|
+
["isPrimary", "DESC"],
|
|
266
|
+
["createdAt", "ASC"],
|
|
267
|
+
],
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
static async isPhoneNumberAvailable(phoneNumber) {
|
|
271
|
+
const existing = await this.findOne({
|
|
272
|
+
where: { phoneNumber, isVerified: true },
|
|
273
|
+
});
|
|
274
|
+
return !existing;
|
|
275
|
+
}
|
|
276
|
+
static async cleanupExpiredOTPs() {
|
|
277
|
+
await this.update({ otp: null, otpExpiresAt: null }, {
|
|
278
|
+
where: {
|
|
279
|
+
otpExpiresAt: { [vr_migrations_1.Op.lt]: new Date() },
|
|
280
|
+
otp: { [vr_migrations_1.Op.ne]: null },
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
exports.PhoneContact = PhoneContact;
|
package/dist/models/types.d.ts
CHANGED
|
@@ -12,3 +12,4 @@ export type { InstallmentModel } from "./installment.models";
|
|
|
12
12
|
export type { BanModel } from "./ban.models";
|
|
13
13
|
export type { SuspensionModel } from "./suspension.models";
|
|
14
14
|
export type { AppSpecsModel } from "./appSpecs.models";
|
|
15
|
+
export type { PhoneContactModel } from "./phoneContact.models";
|
|
@@ -2,6 +2,7 @@ import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonA
|
|
|
2
2
|
import type { Device } from "./device.models";
|
|
3
3
|
import type { Payment } from "./payment.models";
|
|
4
4
|
import type { SecurityClearance } from "./securityClearance.models";
|
|
5
|
+
import type { PhoneContact } from "./phoneContact.models";
|
|
5
6
|
import { Ban } from "./ban.models";
|
|
6
7
|
import { Suspension } from "./suspension.models";
|
|
7
8
|
export interface DeletionStatus {
|
|
@@ -14,19 +15,15 @@ export interface UserAttributes {
|
|
|
14
15
|
id: string;
|
|
15
16
|
firstName: string;
|
|
16
17
|
lastName: string;
|
|
17
|
-
phoneNumber: string;
|
|
18
18
|
jacketId?: string | null;
|
|
19
19
|
email?: string | null;
|
|
20
20
|
password?: string | null;
|
|
21
21
|
securityClearanceId: string;
|
|
22
22
|
plateNumber?: string | null;
|
|
23
23
|
nationalId?: string | null;
|
|
24
|
+
primaryPhoneId: string | null;
|
|
24
25
|
isActive: boolean;
|
|
25
26
|
forgotPassword: boolean;
|
|
26
|
-
verified: boolean;
|
|
27
|
-
verifiedAt: Date | null;
|
|
28
|
-
otp: string | null;
|
|
29
|
-
otpExpiresAt: Date | null;
|
|
30
27
|
isDeactivated: boolean;
|
|
31
28
|
deactivatedAt: Date | null;
|
|
32
29
|
lastLoginAt: Date | null;
|
|
@@ -36,33 +33,32 @@ export interface UserAttributes {
|
|
|
36
33
|
devices?: Device[];
|
|
37
34
|
payments?: Payment[];
|
|
38
35
|
securityClearance?: SecurityClearance;
|
|
36
|
+
primaryPhone?: PhoneContact;
|
|
37
|
+
phones?: PhoneContact[];
|
|
39
38
|
bans?: Ban[];
|
|
40
39
|
suspensions?: Suspension[];
|
|
41
40
|
}
|
|
42
|
-
export interface UserCreationAttributes extends Omit<UserAttributes, "id" | "createdAt" | "isActive" | "
|
|
41
|
+
export interface UserCreationAttributes extends Omit<UserAttributes, "id" | "createdAt" | "isActive" | "forgotPassword" | "isDeactivated" | "tokenVersion" | "primaryPhoneId"> {
|
|
43
42
|
id?: string;
|
|
44
43
|
isActive?: boolean;
|
|
45
44
|
forgotPassword?: boolean;
|
|
46
45
|
isDeactivated?: boolean;
|
|
47
46
|
tokenVersion?: number;
|
|
47
|
+
primaryPhoneId?: string | null;
|
|
48
48
|
}
|
|
49
49
|
export declare class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> implements UserAttributes {
|
|
50
50
|
id: CreationOptional<string>;
|
|
51
51
|
firstName: string;
|
|
52
52
|
lastName: string;
|
|
53
|
-
phoneNumber: string;
|
|
54
53
|
jacketId: CreationOptional<string | null>;
|
|
55
54
|
email: CreationOptional<string | null>;
|
|
56
55
|
password: CreationOptional<string | null>;
|
|
57
56
|
securityClearanceId: string;
|
|
58
57
|
plateNumber: CreationOptional<string | null>;
|
|
59
58
|
nationalId: CreationOptional<string | null>;
|
|
59
|
+
primaryPhoneId: CreationOptional<string | null>;
|
|
60
60
|
isActive: CreationOptional<boolean>;
|
|
61
61
|
forgotPassword: CreationOptional<boolean>;
|
|
62
|
-
verified: CreationOptional<boolean>;
|
|
63
|
-
verifiedAt: CreationOptional<Date | null>;
|
|
64
|
-
otp: CreationOptional<string | null>;
|
|
65
|
-
otpExpiresAt: CreationOptional<Date | null>;
|
|
66
62
|
isDeactivated: CreationOptional<boolean>;
|
|
67
63
|
deactivatedAt: CreationOptional<Date | null>;
|
|
68
64
|
lastLoginAt: CreationOptional<Date | null>;
|
|
@@ -72,14 +68,16 @@ export declare class User extends Model<InferAttributes<User>, InferCreationAttr
|
|
|
72
68
|
devices?: NonAttribute<Device[]>;
|
|
73
69
|
payments?: NonAttribute<Payment[]>;
|
|
74
70
|
securityClearance?: NonAttribute<SecurityClearance>;
|
|
71
|
+
primaryPhone?: NonAttribute<PhoneContact>;
|
|
72
|
+
phones?: NonAttribute<PhoneContact[]>;
|
|
73
|
+
bans?: NonAttribute<Ban[]>;
|
|
74
|
+
suspensions?: NonAttribute<Suspension[]>;
|
|
75
75
|
static initialize(sequelize: any): void;
|
|
76
76
|
static associate(models: Record<string, ModelStatic<Model>>): void;
|
|
77
77
|
softDelete(deletedBy: string, reason?: string): Promise<void>;
|
|
78
78
|
updateLastLogin(): Promise<void>;
|
|
79
|
-
generateOTP(): Promise<string>;
|
|
80
|
-
clearOTP(): Promise<void>;
|
|
81
|
-
validateOTP(otp: string): Promise<boolean>;
|
|
82
79
|
deactivate(): Promise<void>;
|
|
83
80
|
activate(): Promise<void>;
|
|
81
|
+
hasVerifiedPhone(): Promise<boolean>;
|
|
84
82
|
}
|
|
85
83
|
export type UserModel = typeof User;
|
|
@@ -20,11 +20,6 @@ class User extends vr_migrations_1.Model {
|
|
|
20
20
|
type: vr_migrations_1.DataTypes.STRING(100),
|
|
21
21
|
allowNull: false,
|
|
22
22
|
},
|
|
23
|
-
phoneNumber: {
|
|
24
|
-
type: vr_migrations_1.DataTypes.STRING(100),
|
|
25
|
-
allowNull: false,
|
|
26
|
-
unique: true,
|
|
27
|
-
},
|
|
28
23
|
nationalId: {
|
|
29
24
|
type: vr_migrations_1.DataTypes.STRING(100),
|
|
30
25
|
allowNull: true,
|
|
@@ -52,6 +47,10 @@ class User extends vr_migrations_1.Model {
|
|
|
52
47
|
type: vr_migrations_1.DataTypes.STRING(100),
|
|
53
48
|
allowNull: true,
|
|
54
49
|
},
|
|
50
|
+
primaryPhoneId: {
|
|
51
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
52
|
+
allowNull: true,
|
|
53
|
+
},
|
|
55
54
|
isActive: {
|
|
56
55
|
type: vr_migrations_1.DataTypes.BOOLEAN,
|
|
57
56
|
defaultValue: true,
|
|
@@ -69,23 +68,6 @@ class User extends vr_migrations_1.Model {
|
|
|
69
68
|
type: vr_migrations_1.DataTypes.BOOLEAN,
|
|
70
69
|
defaultValue: false,
|
|
71
70
|
},
|
|
72
|
-
verified: {
|
|
73
|
-
type: vr_migrations_1.DataTypes.BOOLEAN,
|
|
74
|
-
allowNull: false,
|
|
75
|
-
defaultValue: false,
|
|
76
|
-
},
|
|
77
|
-
verifiedAt: {
|
|
78
|
-
type: vr_migrations_1.DataTypes.DATE,
|
|
79
|
-
allowNull: true,
|
|
80
|
-
},
|
|
81
|
-
otp: {
|
|
82
|
-
type: vr_migrations_1.DataTypes.STRING(50),
|
|
83
|
-
allowNull: true,
|
|
84
|
-
},
|
|
85
|
-
otpExpiresAt: {
|
|
86
|
-
type: vr_migrations_1.DataTypes.DATE,
|
|
87
|
-
allowNull: true,
|
|
88
|
-
},
|
|
89
71
|
isDeactivated: {
|
|
90
72
|
type: vr_migrations_1.DataTypes.BOOLEAN,
|
|
91
73
|
defaultValue: false,
|
|
@@ -125,6 +107,18 @@ class User extends vr_migrations_1.Model {
|
|
|
125
107
|
onDelete: "RESTRICT",
|
|
126
108
|
onUpdate: "CASCADE",
|
|
127
109
|
});
|
|
110
|
+
this.belongsTo(models.PhoneContact, {
|
|
111
|
+
foreignKey: "primaryPhoneId",
|
|
112
|
+
as: "primaryPhone",
|
|
113
|
+
onDelete: "SET NULL",
|
|
114
|
+
onUpdate: "CASCADE",
|
|
115
|
+
});
|
|
116
|
+
this.hasMany(models.PhoneContact, {
|
|
117
|
+
foreignKey: "userId",
|
|
118
|
+
as: "phones",
|
|
119
|
+
onDelete: "SET NULL",
|
|
120
|
+
onUpdate: "CASCADE",
|
|
121
|
+
});
|
|
128
122
|
this.hasMany(models.Device, {
|
|
129
123
|
foreignKey: "userId",
|
|
130
124
|
as: "devices",
|
|
@@ -137,14 +131,12 @@ class User extends vr_migrations_1.Model {
|
|
|
137
131
|
onDelete: "CASCADE",
|
|
138
132
|
onUpdate: "CASCADE",
|
|
139
133
|
});
|
|
140
|
-
// Suspensions
|
|
141
134
|
this.hasMany(models.Suspension, {
|
|
142
135
|
foreignKey: "userId",
|
|
143
136
|
as: "suspensions",
|
|
144
137
|
onDelete: "CASCADE",
|
|
145
138
|
onUpdate: "CASCADE",
|
|
146
139
|
});
|
|
147
|
-
// Bans
|
|
148
140
|
this.hasMany(models.Ban, {
|
|
149
141
|
foreignKey: "userId",
|
|
150
142
|
as: "bans",
|
|
@@ -152,7 +144,7 @@ class User extends vr_migrations_1.Model {
|
|
|
152
144
|
onUpdate: "CASCADE",
|
|
153
145
|
});
|
|
154
146
|
}
|
|
155
|
-
// Custom instance methods
|
|
147
|
+
// Custom instance methods
|
|
156
148
|
async softDelete(deletedBy, reason) {
|
|
157
149
|
this.deletion = {
|
|
158
150
|
deleted: true,
|
|
@@ -166,28 +158,6 @@ class User extends vr_migrations_1.Model {
|
|
|
166
158
|
this.lastLoginAt = new Date();
|
|
167
159
|
await this.save();
|
|
168
160
|
}
|
|
169
|
-
async generateOTP() {
|
|
170
|
-
const otp = Math.floor(100000 + Math.random() * 900000).toString();
|
|
171
|
-
const expires = new Date();
|
|
172
|
-
expires.setMinutes(expires.getMinutes() + 15); // OTP valid for 15 minutes
|
|
173
|
-
this.otp = otp;
|
|
174
|
-
this.otpExpiresAt = expires;
|
|
175
|
-
this.forgotPassword = true;
|
|
176
|
-
await this.save();
|
|
177
|
-
return otp;
|
|
178
|
-
}
|
|
179
|
-
async clearOTP() {
|
|
180
|
-
this.otp = null;
|
|
181
|
-
this.otpExpiresAt = null;
|
|
182
|
-
this.forgotPassword = false;
|
|
183
|
-
await this.save();
|
|
184
|
-
}
|
|
185
|
-
async validateOTP(otp) {
|
|
186
|
-
if (!this.otp || !this.otpExpiresAt || this.otp !== otp) {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
return new Date() < this.otpExpiresAt;
|
|
190
|
-
}
|
|
191
161
|
async deactivate() {
|
|
192
162
|
this.isDeactivated = true;
|
|
193
163
|
this.deactivatedAt = new Date();
|
|
@@ -198,5 +168,18 @@ class User extends vr_migrations_1.Model {
|
|
|
198
168
|
this.deactivatedAt = null;
|
|
199
169
|
await this.save();
|
|
200
170
|
}
|
|
171
|
+
// Helper to check if user has verified phone
|
|
172
|
+
async hasVerifiedPhone() {
|
|
173
|
+
const PhoneContact = this.constructor.sequelize.models
|
|
174
|
+
.PhoneContact;
|
|
175
|
+
const verifiedPhone = await PhoneContact.findOne({
|
|
176
|
+
where: {
|
|
177
|
+
userId: this.id,
|
|
178
|
+
isVerified: true,
|
|
179
|
+
isActive: true,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
return !!verifiedPhone;
|
|
183
|
+
}
|
|
201
184
|
}
|
|
202
185
|
exports.User = User;
|