vr-models 1.0.42 → 1.0.44

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.
@@ -22,7 +22,7 @@ export interface EventLogAttributes {
22
22
  metadata: Record<string, any>;
23
23
  ipAddress: string | null;
24
24
  userAgent: string | null;
25
- deviceSession: DeviceSessionPayload | null;
25
+ deviceSession?: DeviceSessionPayload | null;
26
26
  authTokenVersion?: number;
27
27
  createdAt: Date;
28
28
  actor?: User;
@@ -112,10 +112,11 @@ class EventLog extends vr_migrations_1.Model {
112
112
  type: vr_migrations_1.DataTypes.TEXT,
113
113
  allowNull: true,
114
114
  },
115
+ // In EventLog.initialize()
115
116
  deviceSession: {
116
117
  type: vr_migrations_1.DataTypes.JSONB,
117
118
  allowNull: true,
118
- field: "session", // Keep column name as 'session' for backward compatibility
119
+ field: "session",
119
120
  validate: {
120
121
  isValidDeviceSessionObject(value) {
121
122
  if (value === null || value === undefined)
@@ -123,12 +124,44 @@ class EventLog extends vr_migrations_1.Model {
123
124
  if (typeof value !== "object" || Array.isArray(value)) {
124
125
  throw new Error("Device session must be a session object");
125
126
  }
126
- if (typeof value.sessionId !== "string" ||
127
- typeof value.startedAt !== "number" ||
128
- typeof value.expiresAt !== "number" ||
129
- typeof value.userId !== "string" ||
130
- typeof value.tokenVersion !== "number") {
131
- throw new Error("Invalid device session object: must have sessionId, startedAt, expiresAt, userId, and tokenVersion");
127
+ // Required fields for device sessions
128
+ const required = [
129
+ "sessionId",
130
+ "startedAt",
131
+ "expiresAt",
132
+ "userId",
133
+ "tokenVersion",
134
+ "deviceSerialNumber",
135
+ "minutesGranted",
136
+ "isExtension",
137
+ "paymentPlanId",
138
+ ];
139
+ for (const field of required) {
140
+ if (!(field in value)) {
141
+ throw new Error(`Device session missing required field: ${field}`);
142
+ }
143
+ }
144
+ // Type validation
145
+ if (typeof value.sessionId !== "string") {
146
+ throw new Error("sessionId must be a string");
147
+ }
148
+ if (typeof value.startedAt !== "number") {
149
+ throw new Error("startedAt must be a number");
150
+ }
151
+ if (typeof value.expiresAt !== "number") {
152
+ throw new Error("expiresAt must be a number");
153
+ }
154
+ if (typeof value.userId !== "string") {
155
+ throw new Error("userId must be a string");
156
+ }
157
+ if (typeof value.tokenVersion !== "number") {
158
+ throw new Error("tokenVersion must be a number");
159
+ }
160
+ if (typeof value.deviceSerialNumber !== "string") {
161
+ throw new Error("deviceSerialNumber must be a string");
162
+ }
163
+ if (typeof value.minutesGranted !== "number") {
164
+ throw new Error("minutesGranted must be a number");
132
165
  }
133
166
  },
134
167
  },
@@ -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;
@@ -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" | "isSuspended" | "forgotPassword" | "isDeactivated" | "tokenVersion"> {
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 (example 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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vr-models",
3
- "version": "1.0.42",
3
+ "version": "1.0.44",
4
4
  "description": "Shared database models package for VR applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",