@mac777/project-pinecone-models 1.1.22 → 1.1.24
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/EmailVerification.d.ts +45 -0
- package/dist/EmailVerification.js +70 -0
- package/dist/Event.d.ts +9 -9
- package/dist/Event.js +40 -3
- package/dist/HostProfile.d.ts +96 -0
- package/dist/HostProfile.js +142 -0
- package/dist/PaymentDetails.d.ts +60 -0
- package/dist/PaymentDetails.js +120 -0
- package/dist/Payout.d.ts +12 -12
- package/dist/PhoneVerification.d.ts +54 -0
- package/dist/PhoneVerification.js +91 -0
- package/dist/Review.d.ts +38 -0
- package/dist/Review.js +200 -0
- package/dist/Ticket.d.ts +3 -3
- package/dist/User.d.ts +52 -195
- package/dist/User.js +86 -197
- package/dist/User.v2.d.ts +18 -18
- package/dist/VerificationDocument.d.ts +60 -0
- package/dist/VerificationDocument.js +111 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -1
- package/package.json +1 -1
- package/src/EmailVerification.ts +71 -0
- package/src/Event.ts +93 -3
- package/src/HostProfile.ts +145 -0
- package/src/PaymentDetails.ts +127 -0
- package/src/PhoneVerification.ts +93 -0
- package/src/Review.ts +268 -0
- package/src/User.ts +95 -201
- package/src/VerificationDocument.ts +117 -0
- package/src/index.ts +10 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Payment Details Schema
|
|
5
|
+
* Stores payment/payout information separate from User
|
|
6
|
+
*/
|
|
7
|
+
const paymentDetailsSchema = new mongoose.Schema({
|
|
8
|
+
userId: {
|
|
9
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
10
|
+
ref: 'User',
|
|
11
|
+
required: true,
|
|
12
|
+
unique: true,
|
|
13
|
+
index: true
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
// Payment Method
|
|
17
|
+
method: {
|
|
18
|
+
type: String,
|
|
19
|
+
enum: ['bkash', 'nagad', 'rocket', 'bank_transfer'],
|
|
20
|
+
required: true,
|
|
21
|
+
index: true
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// Mobile Money (bKash, Nagad, Rocket)
|
|
25
|
+
mobileNumber: {
|
|
26
|
+
type: String,
|
|
27
|
+
trim: true
|
|
28
|
+
},
|
|
29
|
+
accountHolderName: {
|
|
30
|
+
type: String,
|
|
31
|
+
trim: true
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// Bank Transfer
|
|
35
|
+
bankName: {
|
|
36
|
+
type: String,
|
|
37
|
+
trim: true
|
|
38
|
+
},
|
|
39
|
+
accountNumber: {
|
|
40
|
+
type: String,
|
|
41
|
+
trim: true
|
|
42
|
+
},
|
|
43
|
+
branchName: {
|
|
44
|
+
type: String,
|
|
45
|
+
trim: true
|
|
46
|
+
},
|
|
47
|
+
routingNumber: {
|
|
48
|
+
type: String,
|
|
49
|
+
trim: true
|
|
50
|
+
},
|
|
51
|
+
swiftCode: {
|
|
52
|
+
type: String,
|
|
53
|
+
trim: true
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// Verification
|
|
57
|
+
verified: {
|
|
58
|
+
type: Boolean,
|
|
59
|
+
default: false,
|
|
60
|
+
index: true
|
|
61
|
+
},
|
|
62
|
+
verifiedAt: {
|
|
63
|
+
type: Date
|
|
64
|
+
},
|
|
65
|
+
verifiedBy: {
|
|
66
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
67
|
+
ref: 'User'
|
|
68
|
+
},
|
|
69
|
+
rejectionReason: {
|
|
70
|
+
type: String
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Metadata
|
|
74
|
+
lastUpdatedBy: {
|
|
75
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
76
|
+
ref: 'User'
|
|
77
|
+
}
|
|
78
|
+
}, {
|
|
79
|
+
timestamps: true
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Indexes
|
|
83
|
+
paymentDetailsSchema.index({ userId: 1 });
|
|
84
|
+
paymentDetailsSchema.index({ verified: 1, createdAt: -1 });
|
|
85
|
+
paymentDetailsSchema.index({ method: 1, verified: 1 });
|
|
86
|
+
|
|
87
|
+
// Pre-save validation: Ensure required fields based on method
|
|
88
|
+
paymentDetailsSchema.pre('save', function(next) {
|
|
89
|
+
if (this.method === 'bank_transfer') {
|
|
90
|
+
if (!this.bankName || !this.accountNumber || !this.accountHolderName) {
|
|
91
|
+
return next(new Error('Bank name, account number, and account holder name are required for bank transfer'));
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
// Mobile money methods
|
|
95
|
+
if (!this.mobileNumber || !this.accountHolderName) {
|
|
96
|
+
return next(new Error('Mobile number and account holder name are required for mobile money'));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
next();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Methods
|
|
103
|
+
paymentDetailsSchema.methods.markAsVerified = function(verifiedBy: mongoose.Types.ObjectId) {
|
|
104
|
+
this.verified = true;
|
|
105
|
+
this.verifiedAt = new Date();
|
|
106
|
+
this.verifiedBy = verifiedBy;
|
|
107
|
+
this.rejectionReason = undefined;
|
|
108
|
+
return this.save();
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
paymentDetailsSchema.methods.reject = function(reason: string, rejectedBy: mongoose.Types.ObjectId) {
|
|
112
|
+
this.verified = false;
|
|
113
|
+
this.verifiedAt = undefined;
|
|
114
|
+
this.rejectionReason = reason;
|
|
115
|
+
this.lastUpdatedBy = rejectedBy;
|
|
116
|
+
return this.save();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
paymentDetailsSchema.methods.isMobileMoney = function() {
|
|
120
|
+
return ['bkash', 'nagad', 'rocket'].includes(this.method);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
paymentDetailsSchema.methods.isBankTransfer = function() {
|
|
124
|
+
return this.method === 'bank_transfer';
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export default paymentDetailsSchema;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Phone Verification Schema
|
|
5
|
+
* Handles OTP generation, verification, and rate limiting
|
|
6
|
+
*/
|
|
7
|
+
const phoneVerificationSchema = new mongoose.Schema({
|
|
8
|
+
userId: {
|
|
9
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
10
|
+
ref: 'User',
|
|
11
|
+
required: true,
|
|
12
|
+
index: true
|
|
13
|
+
},
|
|
14
|
+
phoneNumber: {
|
|
15
|
+
type: String,
|
|
16
|
+
required: true,
|
|
17
|
+
trim: true
|
|
18
|
+
},
|
|
19
|
+
otp: {
|
|
20
|
+
type: String,
|
|
21
|
+
required: true
|
|
22
|
+
},
|
|
23
|
+
expiresAt: {
|
|
24
|
+
type: Date,
|
|
25
|
+
required: true,
|
|
26
|
+
index: true
|
|
27
|
+
},
|
|
28
|
+
verified: {
|
|
29
|
+
type: Boolean,
|
|
30
|
+
default: false
|
|
31
|
+
},
|
|
32
|
+
verifiedAt: {
|
|
33
|
+
type: Date
|
|
34
|
+
},
|
|
35
|
+
// Attempt tracking (security)
|
|
36
|
+
attempts: {
|
|
37
|
+
type: Number,
|
|
38
|
+
default: 0
|
|
39
|
+
},
|
|
40
|
+
lastAttemptAt: {
|
|
41
|
+
type: Date
|
|
42
|
+
},
|
|
43
|
+
maxAttempts: {
|
|
44
|
+
type: Number,
|
|
45
|
+
default: 5
|
|
46
|
+
},
|
|
47
|
+
// Rate limiting
|
|
48
|
+
sentCount: {
|
|
49
|
+
type: Number,
|
|
50
|
+
default: 1
|
|
51
|
+
},
|
|
52
|
+
lastSentAt: {
|
|
53
|
+
type: Date,
|
|
54
|
+
default: Date.now
|
|
55
|
+
},
|
|
56
|
+
// Metadata
|
|
57
|
+
sentFrom: {
|
|
58
|
+
type: String // IP address
|
|
59
|
+
}
|
|
60
|
+
}, {
|
|
61
|
+
timestamps: true
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Indexes
|
|
65
|
+
phoneVerificationSchema.index({ userId: 1, phoneNumber: 1 });
|
|
66
|
+
phoneVerificationSchema.index({ userId: 1, verified: 1 });
|
|
67
|
+
phoneVerificationSchema.index({ phoneNumber: 1, verified: 1 });
|
|
68
|
+
|
|
69
|
+
// TTL Index: Auto-delete expired OTPs after they expire
|
|
70
|
+
phoneVerificationSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
|
|
71
|
+
|
|
72
|
+
// Methods
|
|
73
|
+
phoneVerificationSchema.methods.isExpired = function() {
|
|
74
|
+
return this.expiresAt < new Date();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
phoneVerificationSchema.methods.isMaxAttemptsReached = function() {
|
|
78
|
+
return this.attempts >= this.maxAttempts;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
phoneVerificationSchema.methods.incrementAttempts = function() {
|
|
82
|
+
this.attempts += 1;
|
|
83
|
+
this.lastAttemptAt = new Date();
|
|
84
|
+
return this.save();
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
phoneVerificationSchema.methods.canResend = function(cooldownSeconds = 60) {
|
|
88
|
+
if (!this.lastSentAt) return true;
|
|
89
|
+
const cooldownMs = cooldownSeconds * 1000;
|
|
90
|
+
return Date.now() - this.lastSentAt.getTime() > cooldownMs;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default phoneVerificationSchema;
|
package/src/Review.ts
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
export interface IReview {
|
|
4
|
+
eventId: mongoose.Types.ObjectId;
|
|
5
|
+
userId: mongoose.Types.ObjectId;
|
|
6
|
+
ticketId: mongoose.Types.ObjectId;
|
|
7
|
+
|
|
8
|
+
// Review Content
|
|
9
|
+
rating: number; // 1-5 stars
|
|
10
|
+
title: string;
|
|
11
|
+
comment: string;
|
|
12
|
+
|
|
13
|
+
// Context Metadata (smart dual-context approach)
|
|
14
|
+
reviewContext: {
|
|
15
|
+
hasCheckedIn: boolean;
|
|
16
|
+
checkInTime?: Date;
|
|
17
|
+
ticketTier: string;
|
|
18
|
+
ticketType: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Status & Moderation
|
|
22
|
+
status: 'pending' | 'approved' | 'hidden' | 'flagged';
|
|
23
|
+
isVisible: boolean;
|
|
24
|
+
moderationNotes?: string;
|
|
25
|
+
|
|
26
|
+
// Timestamps
|
|
27
|
+
submittedAt: Date;
|
|
28
|
+
moderatedAt?: Date;
|
|
29
|
+
moderatorId?: mongoose.Types.ObjectId;
|
|
30
|
+
|
|
31
|
+
// Community Features
|
|
32
|
+
helpfulVotes: number;
|
|
33
|
+
reportedCount: number;
|
|
34
|
+
reports: Array<{
|
|
35
|
+
userId: mongoose.Types.ObjectId;
|
|
36
|
+
reason: string;
|
|
37
|
+
reportedAt: Date;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const reviewSchema = new mongoose.Schema<IReview>({
|
|
42
|
+
eventId: {
|
|
43
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
44
|
+
ref: 'Event',
|
|
45
|
+
required: true,
|
|
46
|
+
index: true
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
userId: {
|
|
50
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
51
|
+
ref: 'User',
|
|
52
|
+
required: true,
|
|
53
|
+
index: true
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
ticketId: {
|
|
57
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
58
|
+
ref: 'Ticket',
|
|
59
|
+
required: true,
|
|
60
|
+
index: true
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Review Content
|
|
64
|
+
rating: {
|
|
65
|
+
type: Number,
|
|
66
|
+
required: true,
|
|
67
|
+
min: 1,
|
|
68
|
+
max: 5,
|
|
69
|
+
validate: {
|
|
70
|
+
validator: Number.isInteger,
|
|
71
|
+
message: 'Rating must be a whole number'
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
title: {
|
|
76
|
+
type: String,
|
|
77
|
+
required: true,
|
|
78
|
+
trim: true,
|
|
79
|
+
maxlength: 100
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
comment: {
|
|
83
|
+
type: String,
|
|
84
|
+
required: true,
|
|
85
|
+
trim: true,
|
|
86
|
+
maxlength: 1000
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// Context Metadata
|
|
90
|
+
reviewContext: {
|
|
91
|
+
hasCheckedIn: {
|
|
92
|
+
type: Boolean,
|
|
93
|
+
required: true
|
|
94
|
+
},
|
|
95
|
+
checkInTime: Date,
|
|
96
|
+
ticketTier: {
|
|
97
|
+
type: String,
|
|
98
|
+
required: true
|
|
99
|
+
},
|
|
100
|
+
ticketType: {
|
|
101
|
+
type: String,
|
|
102
|
+
required: true
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// Status & Moderation
|
|
107
|
+
status: {
|
|
108
|
+
type: String,
|
|
109
|
+
enum: ['pending', 'approved', 'hidden', 'flagged'],
|
|
110
|
+
default: 'pending',
|
|
111
|
+
index: true
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
isVisible: {
|
|
115
|
+
type: Boolean,
|
|
116
|
+
default: true
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
moderationNotes: {
|
|
120
|
+
type: String,
|
|
121
|
+
trim: true,
|
|
122
|
+
maxlength: 500
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Timestamps
|
|
126
|
+
submittedAt: {
|
|
127
|
+
type: Date,
|
|
128
|
+
default: Date.now,
|
|
129
|
+
index: true
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
moderatedAt: Date,
|
|
133
|
+
|
|
134
|
+
moderatorId: {
|
|
135
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
136
|
+
ref: 'User'
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// Community Features
|
|
140
|
+
helpfulVotes: {
|
|
141
|
+
type: Number,
|
|
142
|
+
default: 0,
|
|
143
|
+
min: 0
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
reportedCount: {
|
|
147
|
+
type: Number,
|
|
148
|
+
default: 0,
|
|
149
|
+
min: 0
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
reports: [{
|
|
153
|
+
userId: {
|
|
154
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
155
|
+
ref: 'User',
|
|
156
|
+
required: true
|
|
157
|
+
},
|
|
158
|
+
reason: {
|
|
159
|
+
type: String,
|
|
160
|
+
required: true,
|
|
161
|
+
enum: ['spam', 'abusive', 'fake', 'irrelevant', 'other']
|
|
162
|
+
},
|
|
163
|
+
reportedAt: {
|
|
164
|
+
type: Date,
|
|
165
|
+
default: Date.now
|
|
166
|
+
}
|
|
167
|
+
}]
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Compound indexes for efficient queries
|
|
171
|
+
reviewSchema.index({ eventId: 1, status: 1, isVisible: 1 });
|
|
172
|
+
reviewSchema.index({ userId: 1, eventId: 1 }, { unique: true }); // One review per user per event
|
|
173
|
+
reviewSchema.index({ submittedAt: -1 });
|
|
174
|
+
reviewSchema.index({ 'reviewContext.hasCheckedIn': 1 });
|
|
175
|
+
|
|
176
|
+
// Virtual for review type classification
|
|
177
|
+
reviewSchema.virtual('reviewType').get(function() {
|
|
178
|
+
return this.reviewContext.hasCheckedIn ? 'attended' : 'entry_access';
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Method to mark as helpful
|
|
182
|
+
reviewSchema.methods.markHelpful = function() {
|
|
183
|
+
this.helpfulVotes += 1;
|
|
184
|
+
return this.save();
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Method to report review
|
|
188
|
+
reviewSchema.methods.report = function(userId: string, reason: string) {
|
|
189
|
+
// Check if user already reported
|
|
190
|
+
const existingReport = this.reports.find((report: any) =>
|
|
191
|
+
report.userId.toString() === userId
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (!existingReport) {
|
|
195
|
+
this.reports.push({
|
|
196
|
+
userId: new mongoose.Types.ObjectId(userId),
|
|
197
|
+
reason,
|
|
198
|
+
reportedAt: new Date()
|
|
199
|
+
});
|
|
200
|
+
this.reportedCount += 1;
|
|
201
|
+
|
|
202
|
+
// Auto-flag if too many reports
|
|
203
|
+
if (this.reportedCount >= 3) {
|
|
204
|
+
this.status = 'flagged';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return this.save();
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Method for admin moderation
|
|
212
|
+
reviewSchema.methods.moderate = function(moderatorId: string, action: 'approve' | 'hide' | 'flag', notes?: string) {
|
|
213
|
+
this.moderatorId = new mongoose.Types.ObjectId(moderatorId);
|
|
214
|
+
this.moderatedAt = new Date();
|
|
215
|
+
|
|
216
|
+
switch (action) {
|
|
217
|
+
case 'approve':
|
|
218
|
+
this.status = 'approved';
|
|
219
|
+
this.isVisible = true;
|
|
220
|
+
break;
|
|
221
|
+
case 'hide':
|
|
222
|
+
this.status = 'hidden';
|
|
223
|
+
this.isVisible = false;
|
|
224
|
+
break;
|
|
225
|
+
case 'flag':
|
|
226
|
+
this.status = 'flagged';
|
|
227
|
+
this.isVisible = false;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (notes) {
|
|
232
|
+
this.moderationNotes = notes;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return this.save();
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Static method to calculate weighted rating for an event
|
|
239
|
+
reviewSchema.statics.calculateWeightedRating = async function(eventId: string) {
|
|
240
|
+
const reviews = await this.find({
|
|
241
|
+
eventId: new mongoose.Types.ObjectId(eventId),
|
|
242
|
+
status: 'approved',
|
|
243
|
+
isVisible: true
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (reviews.length === 0) return 0;
|
|
247
|
+
|
|
248
|
+
let totalWeightedRating = 0;
|
|
249
|
+
let totalWeight = 0;
|
|
250
|
+
|
|
251
|
+
reviews.forEach((review: any) => {
|
|
252
|
+
const weight = review.reviewContext.hasCheckedIn ? 1.0 : 0.6;
|
|
253
|
+
totalWeightedRating += review.rating * weight;
|
|
254
|
+
totalWeight += weight;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return totalWeight > 0 ? totalWeightedRating / totalWeight : 0;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Pre-save middleware to set default status
|
|
261
|
+
reviewSchema.pre('save', function(next) {
|
|
262
|
+
if (this.isNew && !this.status) {
|
|
263
|
+
this.status = 'pending';
|
|
264
|
+
}
|
|
265
|
+
next();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
export default reviewSchema;
|