@mac777/project-pinecone-models 1.1.21 → 1.1.23
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/AuditLog.js +1 -1
- package/dist/Review.d.ts +38 -0
- package/dist/Review.js +200 -0
- package/dist/Ticket.d.ts +3 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/src/AuditLog.ts +1 -1
- package/src/Review.ts +268 -0
- package/src/index.ts +3 -1
package/dist/AuditLog.js
CHANGED
|
@@ -22,7 +22,7 @@ var AuditStatus;
|
|
|
22
22
|
AuditStatus["FAILURE"] = "FAILURE";
|
|
23
23
|
})(AuditStatus || (exports.AuditStatus = AuditStatus = {}));
|
|
24
24
|
const auditLogSchema = new mongoose_1.Schema({
|
|
25
|
-
userId: { type: String, index: true },
|
|
25
|
+
userId: { type: String, ref: 'User', index: true },
|
|
26
26
|
action: { type: String, required: true, index: true },
|
|
27
27
|
category: {
|
|
28
28
|
type: String,
|
package/dist/Review.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
export interface IReview {
|
|
3
|
+
eventId: mongoose.Types.ObjectId;
|
|
4
|
+
userId: mongoose.Types.ObjectId;
|
|
5
|
+
ticketId: mongoose.Types.ObjectId;
|
|
6
|
+
rating: number;
|
|
7
|
+
title: string;
|
|
8
|
+
comment: string;
|
|
9
|
+
reviewContext: {
|
|
10
|
+
hasCheckedIn: boolean;
|
|
11
|
+
checkInTime?: Date;
|
|
12
|
+
ticketTier: string;
|
|
13
|
+
ticketType: string;
|
|
14
|
+
};
|
|
15
|
+
status: 'pending' | 'approved' | 'hidden' | 'flagged';
|
|
16
|
+
isVisible: boolean;
|
|
17
|
+
moderationNotes?: string;
|
|
18
|
+
submittedAt: Date;
|
|
19
|
+
moderatedAt?: Date;
|
|
20
|
+
moderatorId?: mongoose.Types.ObjectId;
|
|
21
|
+
helpfulVotes: number;
|
|
22
|
+
reportedCount: number;
|
|
23
|
+
reports: Array<{
|
|
24
|
+
userId: mongoose.Types.ObjectId;
|
|
25
|
+
reason: string;
|
|
26
|
+
reportedAt: Date;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
declare const reviewSchema: mongoose.Schema<IReview, mongoose.Model<IReview, any, any, any, mongoose.Document<unknown, any, IReview, any, {}> & IReview & {
|
|
30
|
+
_id: mongoose.Types.ObjectId;
|
|
31
|
+
} & {
|
|
32
|
+
__v: number;
|
|
33
|
+
}, any>, {}, {}, {}, {}, mongoose.DefaultSchemaOptions, IReview, mongoose.Document<unknown, {}, mongoose.FlatRecord<IReview>, {}, mongoose.ResolveSchemaOptions<mongoose.DefaultSchemaOptions>> & mongoose.FlatRecord<IReview> & {
|
|
34
|
+
_id: mongoose.Types.ObjectId;
|
|
35
|
+
} & {
|
|
36
|
+
__v: number;
|
|
37
|
+
}>;
|
|
38
|
+
export default reviewSchema;
|
package/dist/Review.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const mongoose_1 = __importDefault(require("mongoose"));
|
|
7
|
+
const reviewSchema = new mongoose_1.default.Schema({
|
|
8
|
+
eventId: {
|
|
9
|
+
type: mongoose_1.default.Schema.Types.ObjectId,
|
|
10
|
+
ref: 'Event',
|
|
11
|
+
required: true,
|
|
12
|
+
index: true
|
|
13
|
+
},
|
|
14
|
+
userId: {
|
|
15
|
+
type: mongoose_1.default.Schema.Types.ObjectId,
|
|
16
|
+
ref: 'User',
|
|
17
|
+
required: true,
|
|
18
|
+
index: true
|
|
19
|
+
},
|
|
20
|
+
ticketId: {
|
|
21
|
+
type: mongoose_1.default.Schema.Types.ObjectId,
|
|
22
|
+
ref: 'Ticket',
|
|
23
|
+
required: true,
|
|
24
|
+
index: true
|
|
25
|
+
},
|
|
26
|
+
// Review Content
|
|
27
|
+
rating: {
|
|
28
|
+
type: Number,
|
|
29
|
+
required: true,
|
|
30
|
+
min: 1,
|
|
31
|
+
max: 5,
|
|
32
|
+
validate: {
|
|
33
|
+
validator: Number.isInteger,
|
|
34
|
+
message: 'Rating must be a whole number'
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
title: {
|
|
38
|
+
type: String,
|
|
39
|
+
required: true,
|
|
40
|
+
trim: true,
|
|
41
|
+
maxlength: 100
|
|
42
|
+
},
|
|
43
|
+
comment: {
|
|
44
|
+
type: String,
|
|
45
|
+
required: true,
|
|
46
|
+
trim: true,
|
|
47
|
+
maxlength: 1000
|
|
48
|
+
},
|
|
49
|
+
// Context Metadata
|
|
50
|
+
reviewContext: {
|
|
51
|
+
hasCheckedIn: {
|
|
52
|
+
type: Boolean,
|
|
53
|
+
required: true
|
|
54
|
+
},
|
|
55
|
+
checkInTime: Date,
|
|
56
|
+
ticketTier: {
|
|
57
|
+
type: String,
|
|
58
|
+
required: true
|
|
59
|
+
},
|
|
60
|
+
ticketType: {
|
|
61
|
+
type: String,
|
|
62
|
+
required: true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
// Status & Moderation
|
|
66
|
+
status: {
|
|
67
|
+
type: String,
|
|
68
|
+
enum: ['pending', 'approved', 'hidden', 'flagged'],
|
|
69
|
+
default: 'pending',
|
|
70
|
+
index: true
|
|
71
|
+
},
|
|
72
|
+
isVisible: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
default: true
|
|
75
|
+
},
|
|
76
|
+
moderationNotes: {
|
|
77
|
+
type: String,
|
|
78
|
+
trim: true,
|
|
79
|
+
maxlength: 500
|
|
80
|
+
},
|
|
81
|
+
// Timestamps
|
|
82
|
+
submittedAt: {
|
|
83
|
+
type: Date,
|
|
84
|
+
default: Date.now,
|
|
85
|
+
index: true
|
|
86
|
+
},
|
|
87
|
+
moderatedAt: Date,
|
|
88
|
+
moderatorId: {
|
|
89
|
+
type: mongoose_1.default.Schema.Types.ObjectId,
|
|
90
|
+
ref: 'User'
|
|
91
|
+
},
|
|
92
|
+
// Community Features
|
|
93
|
+
helpfulVotes: {
|
|
94
|
+
type: Number,
|
|
95
|
+
default: 0,
|
|
96
|
+
min: 0
|
|
97
|
+
},
|
|
98
|
+
reportedCount: {
|
|
99
|
+
type: Number,
|
|
100
|
+
default: 0,
|
|
101
|
+
min: 0
|
|
102
|
+
},
|
|
103
|
+
reports: [{
|
|
104
|
+
userId: {
|
|
105
|
+
type: mongoose_1.default.Schema.Types.ObjectId,
|
|
106
|
+
ref: 'User',
|
|
107
|
+
required: true
|
|
108
|
+
},
|
|
109
|
+
reason: {
|
|
110
|
+
type: String,
|
|
111
|
+
required: true,
|
|
112
|
+
enum: ['spam', 'abusive', 'fake', 'irrelevant', 'other']
|
|
113
|
+
},
|
|
114
|
+
reportedAt: {
|
|
115
|
+
type: Date,
|
|
116
|
+
default: Date.now
|
|
117
|
+
}
|
|
118
|
+
}]
|
|
119
|
+
});
|
|
120
|
+
// Compound indexes for efficient queries
|
|
121
|
+
reviewSchema.index({ eventId: 1, status: 1, isVisible: 1 });
|
|
122
|
+
reviewSchema.index({ userId: 1, eventId: 1 }, { unique: true }); // One review per user per event
|
|
123
|
+
reviewSchema.index({ submittedAt: -1 });
|
|
124
|
+
reviewSchema.index({ 'reviewContext.hasCheckedIn': 1 });
|
|
125
|
+
// Virtual for review type classification
|
|
126
|
+
reviewSchema.virtual('reviewType').get(function () {
|
|
127
|
+
return this.reviewContext.hasCheckedIn ? 'attended' : 'entry_access';
|
|
128
|
+
});
|
|
129
|
+
// Method to mark as helpful
|
|
130
|
+
reviewSchema.methods.markHelpful = function () {
|
|
131
|
+
this.helpfulVotes += 1;
|
|
132
|
+
return this.save();
|
|
133
|
+
};
|
|
134
|
+
// Method to report review
|
|
135
|
+
reviewSchema.methods.report = function (userId, reason) {
|
|
136
|
+
// Check if user already reported
|
|
137
|
+
const existingReport = this.reports.find((report) => report.userId.toString() === userId);
|
|
138
|
+
if (!existingReport) {
|
|
139
|
+
this.reports.push({
|
|
140
|
+
userId: new mongoose_1.default.Types.ObjectId(userId),
|
|
141
|
+
reason,
|
|
142
|
+
reportedAt: new Date()
|
|
143
|
+
});
|
|
144
|
+
this.reportedCount += 1;
|
|
145
|
+
// Auto-flag if too many reports
|
|
146
|
+
if (this.reportedCount >= 3) {
|
|
147
|
+
this.status = 'flagged';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return this.save();
|
|
151
|
+
};
|
|
152
|
+
// Method for admin moderation
|
|
153
|
+
reviewSchema.methods.moderate = function (moderatorId, action, notes) {
|
|
154
|
+
this.moderatorId = new mongoose_1.default.Types.ObjectId(moderatorId);
|
|
155
|
+
this.moderatedAt = new Date();
|
|
156
|
+
switch (action) {
|
|
157
|
+
case 'approve':
|
|
158
|
+
this.status = 'approved';
|
|
159
|
+
this.isVisible = true;
|
|
160
|
+
break;
|
|
161
|
+
case 'hide':
|
|
162
|
+
this.status = 'hidden';
|
|
163
|
+
this.isVisible = false;
|
|
164
|
+
break;
|
|
165
|
+
case 'flag':
|
|
166
|
+
this.status = 'flagged';
|
|
167
|
+
this.isVisible = false;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
if (notes) {
|
|
171
|
+
this.moderationNotes = notes;
|
|
172
|
+
}
|
|
173
|
+
return this.save();
|
|
174
|
+
};
|
|
175
|
+
// Static method to calculate weighted rating for an event
|
|
176
|
+
reviewSchema.statics.calculateWeightedRating = async function (eventId) {
|
|
177
|
+
const reviews = await this.find({
|
|
178
|
+
eventId: new mongoose_1.default.Types.ObjectId(eventId),
|
|
179
|
+
status: 'approved',
|
|
180
|
+
isVisible: true
|
|
181
|
+
});
|
|
182
|
+
if (reviews.length === 0)
|
|
183
|
+
return 0;
|
|
184
|
+
let totalWeightedRating = 0;
|
|
185
|
+
let totalWeight = 0;
|
|
186
|
+
reviews.forEach((review) => {
|
|
187
|
+
const weight = review.reviewContext.hasCheckedIn ? 1.0 : 0.6;
|
|
188
|
+
totalWeightedRating += review.rating * weight;
|
|
189
|
+
totalWeight += weight;
|
|
190
|
+
});
|
|
191
|
+
return totalWeight > 0 ? totalWeightedRating / totalWeight : 0;
|
|
192
|
+
};
|
|
193
|
+
// Pre-save middleware to set default status
|
|
194
|
+
reviewSchema.pre('save', function (next) {
|
|
195
|
+
if (this.isNew && !this.status) {
|
|
196
|
+
this.status = 'pending';
|
|
197
|
+
}
|
|
198
|
+
next();
|
|
199
|
+
});
|
|
200
|
+
exports.default = reviewSchema;
|
package/dist/Ticket.d.ts
CHANGED
|
@@ -6,11 +6,11 @@ declare const ticketSchema: mongoose.Schema<any, mongoose.Model<any, any, any, a
|
|
|
6
6
|
eventId: mongoose.Types.ObjectId;
|
|
7
7
|
ticketVariantId: mongoose.Types.ObjectId;
|
|
8
8
|
orderId: mongoose.Types.ObjectId;
|
|
9
|
+
ticketType: string;
|
|
9
10
|
ticketNumber: string;
|
|
10
11
|
eventTitle: string;
|
|
11
12
|
eventDate: NativeDate;
|
|
12
13
|
eventVenue: string;
|
|
13
|
-
ticketType: string;
|
|
14
14
|
qrCode: string;
|
|
15
15
|
qrCodeUrl: string;
|
|
16
16
|
checkInStatus: "not_checked_in" | "checked_in";
|
|
@@ -28,11 +28,11 @@ declare const ticketSchema: mongoose.Schema<any, mongoose.Model<any, any, any, a
|
|
|
28
28
|
eventId: mongoose.Types.ObjectId;
|
|
29
29
|
ticketVariantId: mongoose.Types.ObjectId;
|
|
30
30
|
orderId: mongoose.Types.ObjectId;
|
|
31
|
+
ticketType: string;
|
|
31
32
|
ticketNumber: string;
|
|
32
33
|
eventTitle: string;
|
|
33
34
|
eventDate: NativeDate;
|
|
34
35
|
eventVenue: string;
|
|
35
|
-
ticketType: string;
|
|
36
36
|
qrCode: string;
|
|
37
37
|
qrCodeUrl: string;
|
|
38
38
|
checkInStatus: "not_checked_in" | "checked_in";
|
|
@@ -50,11 +50,11 @@ declare const ticketSchema: mongoose.Schema<any, mongoose.Model<any, any, any, a
|
|
|
50
50
|
eventId: mongoose.Types.ObjectId;
|
|
51
51
|
ticketVariantId: mongoose.Types.ObjectId;
|
|
52
52
|
orderId: mongoose.Types.ObjectId;
|
|
53
|
+
ticketType: string;
|
|
53
54
|
ticketNumber: string;
|
|
54
55
|
eventTitle: string;
|
|
55
56
|
eventDate: NativeDate;
|
|
56
57
|
eventVenue: string;
|
|
57
|
-
ticketType: string;
|
|
58
58
|
qrCode: string;
|
|
59
59
|
qrCodeUrl: string;
|
|
60
60
|
checkInStatus: "not_checked_in" | "checked_in";
|
package/dist/index.d.ts
CHANGED
|
@@ -8,5 +8,7 @@ export { default as userSchema } from './User';
|
|
|
8
8
|
export { default as eventViewsSchema } from './Event.views';
|
|
9
9
|
export { default as payoutSchema } from './Payout';
|
|
10
10
|
export { default as auditLogSchema } from './AuditLog';
|
|
11
|
+
export { default as reviewSchema } from './Review';
|
|
11
12
|
export type { IAuditLog } from './AuditLog';
|
|
13
|
+
export type { IReview } from './Review';
|
|
12
14
|
export { AuditSeverity, AuditCategory, AuditStatus } from './AuditLog';
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.AuditStatus = exports.AuditCategory = exports.AuditSeverity = exports.auditLogSchema = exports.payoutSchema = exports.eventViewsSchema = exports.userSchema = exports.orderSchema = exports.paymentSchema = exports.bkashSchema = exports.mediaSchema = exports.ticketSchema = exports.eventSchema = void 0;
|
|
6
|
+
exports.AuditStatus = exports.AuditCategory = exports.AuditSeverity = exports.reviewSchema = exports.auditLogSchema = exports.payoutSchema = exports.eventViewsSchema = exports.userSchema = exports.orderSchema = exports.paymentSchema = exports.bkashSchema = exports.mediaSchema = exports.ticketSchema = exports.eventSchema = void 0;
|
|
7
7
|
// Shared database models for Pinecone microservices
|
|
8
8
|
var Event_1 = require("./Event");
|
|
9
9
|
Object.defineProperty(exports, "eventSchema", { enumerable: true, get: function () { return __importDefault(Event_1).default; } });
|
|
@@ -25,6 +25,8 @@ var Payout_1 = require("./Payout");
|
|
|
25
25
|
Object.defineProperty(exports, "payoutSchema", { enumerable: true, get: function () { return __importDefault(Payout_1).default; } });
|
|
26
26
|
var AuditLog_1 = require("./AuditLog");
|
|
27
27
|
Object.defineProperty(exports, "auditLogSchema", { enumerable: true, get: function () { return __importDefault(AuditLog_1).default; } });
|
|
28
|
+
var Review_1 = require("./Review");
|
|
29
|
+
Object.defineProperty(exports, "reviewSchema", { enumerable: true, get: function () { return __importDefault(Review_1).default; } });
|
|
28
30
|
var AuditLog_2 = require("./AuditLog");
|
|
29
31
|
Object.defineProperty(exports, "AuditSeverity", { enumerable: true, get: function () { return AuditLog_2.AuditSeverity; } });
|
|
30
32
|
Object.defineProperty(exports, "AuditCategory", { enumerable: true, get: function () { return AuditLog_2.AuditCategory; } });
|
package/package.json
CHANGED
package/src/AuditLog.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface IAuditLog extends Document {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const auditLogSchema = new Schema<IAuditLog>({
|
|
45
|
-
userId: { type: String, index: true },
|
|
45
|
+
userId: { type: String, ref: 'User', index: true },
|
|
46
46
|
action: { type: String, required: true, index: true },
|
|
47
47
|
category: {
|
|
48
48
|
type: String,
|
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;
|
package/src/index.ts
CHANGED
|
@@ -9,5 +9,7 @@ export { default as userSchema } from './User';
|
|
|
9
9
|
export { default as eventViewsSchema } from './Event.views';
|
|
10
10
|
export { default as payoutSchema } from './Payout';
|
|
11
11
|
export { default as auditLogSchema } from './AuditLog';
|
|
12
|
+
export { default as reviewSchema } from './Review';
|
|
12
13
|
export type { IAuditLog } from './AuditLog';
|
|
13
|
-
export {
|
|
14
|
+
export type { IReview } from './Review';
|
|
15
|
+
export { AuditSeverity, AuditCategory, AuditStatus } from './AuditLog';
|