@bash-app/bash-common 30.35.0 → 30.37.0
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/package.json +1 -2
- package/prisma/schema.prisma +144 -17
- package/src/definitions.ts +214 -0
- package/src/extendedSchemas.ts +10 -7
- package/src/utils/badgeUtils.ts +69 -0
- package/src/utils/reviewUtils.ts +79 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bash-app/bash-common",
|
|
3
|
-
"version": "30.
|
|
3
|
+
"version": "30.37.0",
|
|
4
4
|
"description": "Common data and scripts to use on the frontend and backend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/jest": "^29.5.5",
|
|
40
40
|
"@types/luxon": "^3.4.2",
|
|
41
|
-
"@types/luxon": "^3.4.2",
|
|
42
41
|
"@types/node": "^20.11.1",
|
|
43
42
|
"@types/react": "^18.3.2",
|
|
44
43
|
"@types/shelljs": "^0.8.15",
|
package/prisma/schema.prisma
CHANGED
|
@@ -314,6 +314,7 @@ model BashEvent {
|
|
|
314
314
|
userPromoCodeRedemption UserPromoCodeRedemption[]
|
|
315
315
|
averageRating Float? @default(0)
|
|
316
316
|
serviceBookings ServiceBooking[] // Add this field to create the reverse relation
|
|
317
|
+
userReport UserReport[]
|
|
317
318
|
}
|
|
318
319
|
|
|
319
320
|
model Coordinates {
|
|
@@ -984,7 +985,7 @@ model User {
|
|
|
984
985
|
stripeCustomerId String? @unique
|
|
985
986
|
stripeAccountId String? @unique
|
|
986
987
|
isSuperUser Boolean @default(false)
|
|
987
|
-
|
|
988
|
+
isSuspended Boolean @default(false)
|
|
988
989
|
intent UserIntent?
|
|
989
990
|
googleCalendarAccess String?
|
|
990
991
|
givenName String?
|
|
@@ -997,6 +998,9 @@ model User {
|
|
|
997
998
|
gender Gender?
|
|
998
999
|
sex Sex?
|
|
999
1000
|
roles UserRole[] @default([User])
|
|
1001
|
+
aboutMe String?
|
|
1002
|
+
levelBadge String?
|
|
1003
|
+
temporaryBadges String[] @default([])
|
|
1000
1004
|
ownedServices Service[] @relation("OwnedService")
|
|
1001
1005
|
createdServices Service[] @relation("CreatedService")
|
|
1002
1006
|
createdEvents BashEvent[] @relation("CreatedEvent")
|
|
@@ -1054,13 +1058,11 @@ model User {
|
|
|
1054
1058
|
volunteerService VolunteerService[]
|
|
1055
1059
|
stripeAccounts StripeAccount[]
|
|
1056
1060
|
userPromoCodeRedemption UserPromoCodeRedemption[]
|
|
1057
|
-
accepted Boolean? @default(false)
|
|
1058
|
-
boughtTicket Boolean? @default(false)
|
|
1059
|
-
noPay Boolean? @default(false)
|
|
1060
|
-
supportedEvent Boolean? @default(false)
|
|
1061
|
+
accepted Boolean? @default(false)
|
|
1062
|
+
boughtTicket Boolean? @default(false)
|
|
1063
|
+
noPay Boolean? @default(false)
|
|
1064
|
+
supportedEvent Boolean? @default(false)
|
|
1061
1065
|
|
|
1062
|
-
// primaryStripeAccountDBId String? @unique
|
|
1063
|
-
// primaryStripeAccountDB StripeAccount? @relation("PrimaryStripeAccount", fields: [primaryStripeAccountDBId], references: [id], onDelete: Restrict, onUpdate: Restrict)
|
|
1064
1066
|
serviceBookingForCreator ServiceBooking[] @relation("BookingCreator")
|
|
1065
1067
|
serviceBookingForUser ServiceBooking[] @relation("BookingForUser")
|
|
1066
1068
|
serviceBookingCheckout ServiceBookingCheckout[]
|
|
@@ -1072,19 +1074,89 @@ model User {
|
|
|
1072
1074
|
unblocksCreated UnblockedUserHistory[] @relation("UserUnblocksMade")
|
|
1073
1075
|
unblocksReceived UnblockedUserHistory[] @relation("UserUnblocksReceived")
|
|
1074
1076
|
|
|
1075
|
-
preferences
|
|
1077
|
+
preferences UserPreferences?
|
|
1076
1078
|
|
|
1079
|
+
// Add fields for user suspension
|
|
1080
|
+
suspendedUntil DateTime?
|
|
1081
|
+
suspendedById String?
|
|
1082
|
+
suspendedBy User? @relation("SuspendedUsers", fields: [suspendedById], references: [id], onDelete: SetNull)
|
|
1083
|
+
suspendedUsers User[] @relation("SuspendedUsers")
|
|
1084
|
+
|
|
1085
|
+
// Add relations for reports and demerits
|
|
1086
|
+
reportsMade UserReport[] @relation("ReportsMade")
|
|
1087
|
+
reportsReceived UserReport[] @relation("ReportsReceived")
|
|
1088
|
+
reportsReviewed UserReport[] @relation("ReportsReviewed")
|
|
1089
|
+
demerits Demerit[] @relation("UserDemerits")
|
|
1090
|
+
issuedDemerits Demerit[] @relation("IssuedDemerits")
|
|
1091
|
+
expungedDemerits Demerit[] @relation("ExpungedDemerits")
|
|
1077
1092
|
}
|
|
1078
1093
|
|
|
1079
|
-
model
|
|
1080
|
-
id
|
|
1081
|
-
userId
|
|
1082
|
-
user
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1094
|
+
model UserPreferences {
|
|
1095
|
+
id String @id @default(cuid())
|
|
1096
|
+
userId String @unique
|
|
1097
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
1098
|
+
|
|
1099
|
+
// Notification Preferences
|
|
1100
|
+
emailNotifications Boolean @default(true)
|
|
1101
|
+
pushNotifications Boolean @default(true)
|
|
1102
|
+
smsNotifications Boolean @default(false)
|
|
1103
|
+
newMessageNotify Boolean @default(true)
|
|
1104
|
+
eventReminderNotify Boolean @default(true)
|
|
1105
|
+
friendRequestNotify Boolean @default(true)
|
|
1106
|
+
commentNotify Boolean @default(true)
|
|
1107
|
+
invitationNotify Boolean @default(true)
|
|
1108
|
+
eventUpdatesNotify Boolean @default(true)
|
|
1109
|
+
servicePromotionsNotify Boolean @default(true)
|
|
1110
|
+
|
|
1111
|
+
// Privacy Settings
|
|
1112
|
+
profileVisibility String @default("PUBLIC") // PUBLIC, FRIENDS, PRIVATE
|
|
1113
|
+
showOnlineStatus Boolean @default(true)
|
|
1114
|
+
allowTagging Boolean @default(true)
|
|
1115
|
+
allowDirectMessages Boolean @default(true)
|
|
1116
|
+
showActivityStatus Boolean @default(true)
|
|
1117
|
+
hiddenBashIds String[] @default([])
|
|
1118
|
+
hideActivitySection Boolean @default(false)
|
|
1119
|
+
allowLocationSharing Boolean @default(false)
|
|
1120
|
+
blockSearchEngineIndex Boolean @default(false)
|
|
1121
|
+
|
|
1122
|
+
// UI/UX Preferences
|
|
1123
|
+
theme String @default("SYSTEM") // LIGHT, DARK, SYSTEM
|
|
1124
|
+
language String @default("en-US")
|
|
1125
|
+
timeZone String @default("America/New_York")
|
|
1126
|
+
defaultLandingPage String @default("dashboard")
|
|
1127
|
+
contentDensity String @default("COMFORTABLE") // COMPACT, COMFORTABLE, SPACIOUS
|
|
1128
|
+
fontScale String @default("MEDIUM") // SMALL, MEDIUM, LARGE
|
|
1129
|
+
animationsEnabled Boolean @default(true)
|
|
1130
|
+
useHighContrastMode Boolean @default(false)
|
|
1131
|
+
|
|
1132
|
+
// Content Preferences
|
|
1133
|
+
contentFilters String[] @default([])
|
|
1134
|
+
topicInterests String[] @default([])
|
|
1135
|
+
hideSeenContent Boolean @default(false)
|
|
1136
|
+
autoplayVideos Boolean @default(true)
|
|
1137
|
+
contentSortPreference String @default("RECENT") // RECENT, POPULAR, RELEVANT
|
|
1138
|
+
contentLanguages String[] @default(["en"])
|
|
1139
|
+
showSensitiveContent Boolean @default(false)
|
|
1140
|
+
|
|
1141
|
+
// Calendar & Event Preferences
|
|
1142
|
+
defaultCalendarView String @default("MONTH") // DAY, WEEK, MONTH, AGENDA
|
|
1143
|
+
calendarStartDay Int @default(0) // 0=Sunday, 1=Monday
|
|
1144
|
+
defaultEventReminder Int @default(60) // Minutes before event
|
|
1145
|
+
attendancePrivacy String @default("PUBLIC") // PUBLIC, FRIENDS, PRIVATE
|
|
1146
|
+
|
|
1147
|
+
// Communications Preferences
|
|
1148
|
+
communicationFrequency String @default("NORMAL") // LOW, NORMAL, HIGH
|
|
1149
|
+
preferredContactMethod String @default("EMAIL") // EMAIL, PUSH, SMS
|
|
1150
|
+
|
|
1151
|
+
// Data & Payment Preferences
|
|
1152
|
+
dataUsageConsent Boolean @default(true)
|
|
1153
|
+
analyticsConsent Boolean @default(true)
|
|
1154
|
+
preferredCurrency String @default("USD")
|
|
1155
|
+
savePaymentInfo Boolean @default(false)
|
|
1156
|
+
showEventPrices Boolean @default(true)
|
|
1157
|
+
|
|
1158
|
+
createdAt DateTime @default(now())
|
|
1159
|
+
updatedAt DateTime @updatedAt
|
|
1088
1160
|
}
|
|
1089
1161
|
|
|
1090
1162
|
model Contact {
|
|
@@ -2084,3 +2156,58 @@ enum EntityType {
|
|
|
2084
2156
|
ORGANIZATION
|
|
2085
2157
|
BASH_EVENT // For BashEvents
|
|
2086
2158
|
}
|
|
2159
|
+
|
|
2160
|
+
// Adding new models for the demerit system
|
|
2161
|
+
|
|
2162
|
+
model UserReport {
|
|
2163
|
+
id String @id @default(cuid())
|
|
2164
|
+
reporterId String // Host/reporter user ID
|
|
2165
|
+
reporter User @relation("ReportsMade", fields: [reporterId], references: [id], onDelete: Cascade)
|
|
2166
|
+
reportedId String // Reported user ID
|
|
2167
|
+
reported User @relation("ReportsReceived", fields: [reportedId], references: [id], onDelete: Cascade)
|
|
2168
|
+
bashEventId String?
|
|
2169
|
+
bashEvent BashEvent? @relation(fields: [bashEventId], references: [id], onDelete: SetNull)
|
|
2170
|
+
reason String
|
|
2171
|
+
details String
|
|
2172
|
+
createdAt DateTime @default(now())
|
|
2173
|
+
status ReportStatus @default(Pending)
|
|
2174
|
+
reviewerId String? // Super user who reviewed the report
|
|
2175
|
+
reviewer User? @relation("ReportsReviewed", fields: [reviewerId], references: [id], onDelete: SetNull)
|
|
2176
|
+
reviewedAt DateTime?
|
|
2177
|
+
reviewNotes String?
|
|
2178
|
+
demerits Demerit[]
|
|
2179
|
+
|
|
2180
|
+
@@index([reporterId])
|
|
2181
|
+
@@index([reportedId])
|
|
2182
|
+
@@index([bashEventId])
|
|
2183
|
+
@@index([status])
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
model Demerit {
|
|
2187
|
+
id String @id @default(cuid())
|
|
2188
|
+
userId String
|
|
2189
|
+
user User @relation("UserDemerits", fields: [userId], references: [id], onDelete: Cascade)
|
|
2190
|
+
reportId String?
|
|
2191
|
+
report UserReport? @relation(fields: [reportId], references: [id], onDelete: SetNull)
|
|
2192
|
+
issuerId String // Super user who issued the demerit
|
|
2193
|
+
issuer User @relation("IssuedDemerits", fields: [issuerId], references: [id], onDelete: Cascade)
|
|
2194
|
+
reason String
|
|
2195
|
+
points Int @default(1)
|
|
2196
|
+
issuedAt DateTime @default(now())
|
|
2197
|
+
expiresAt DateTime?
|
|
2198
|
+
isExpunged Boolean @default(false)
|
|
2199
|
+
expungedAt DateTime?
|
|
2200
|
+
expungedById String?
|
|
2201
|
+
expungedBy User? @relation("ExpungedDemerits", fields: [expungedById], references: [id], onDelete: SetNull)
|
|
2202
|
+
|
|
2203
|
+
@@index([userId])
|
|
2204
|
+
@@index([issuerId])
|
|
2205
|
+
@@index([reportId])
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
enum ReportStatus {
|
|
2209
|
+
Pending
|
|
2210
|
+
Approved
|
|
2211
|
+
Rejected
|
|
2212
|
+
Resolved
|
|
2213
|
+
}
|
package/src/definitions.ts
CHANGED
|
@@ -946,3 +946,217 @@ export interface SocialMediaProfile {
|
|
|
946
946
|
url: string;
|
|
947
947
|
userId: string;
|
|
948
948
|
}
|
|
949
|
+
|
|
950
|
+
// Badge Types
|
|
951
|
+
export enum BadgeLevel {
|
|
952
|
+
Novice = "Novice",
|
|
953
|
+
Beginner = "Beginner",
|
|
954
|
+
Regular = "Regular",
|
|
955
|
+
Enthusiast = "Enthusiast",
|
|
956
|
+
Aficionado = "Aficionado",
|
|
957
|
+
Connoisseur = "Connoisseur",
|
|
958
|
+
Maverick = "Maverick",
|
|
959
|
+
Master = "Master",
|
|
960
|
+
Elite = "Elite",
|
|
961
|
+
Legend = "Legend"
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
export enum BadgeCategory {
|
|
965
|
+
Hosting = "Hosting",
|
|
966
|
+
Attendance = "Attendance",
|
|
967
|
+
Community = "Community",
|
|
968
|
+
Achievement = "Achievement",
|
|
969
|
+
Special = "Special"
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Type definitions for badges and reviews
|
|
973
|
+
export interface Badge {
|
|
974
|
+
id: string;
|
|
975
|
+
name: string;
|
|
976
|
+
description: string;
|
|
977
|
+
imageUrl: string;
|
|
978
|
+
category: BadgeCategory;
|
|
979
|
+
earnedOn?: string;
|
|
980
|
+
isActive: boolean;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
export interface LevelBadge extends Badge {
|
|
984
|
+
level: BadgeLevel;
|
|
985
|
+
requiredEvents: number;
|
|
986
|
+
requiredRating?: number;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
export interface TemporaryBadge extends Badge {
|
|
990
|
+
expiresOn?: string;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
export interface UserLevelBadge {
|
|
994
|
+
badgeId: string;
|
|
995
|
+
earnedOn: string;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
export interface UserTemporaryBadge {
|
|
999
|
+
badgeId: string;
|
|
1000
|
+
earnedOn: string;
|
|
1001
|
+
expiresOn?: string;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
export interface Review {
|
|
1005
|
+
id: string;
|
|
1006
|
+
rating: number; // 1-5 stars
|
|
1007
|
+
creatorId: string;
|
|
1008
|
+
bashEventId: string;
|
|
1009
|
+
createdAt: string;
|
|
1010
|
+
updatedAt: string;
|
|
1011
|
+
comments?: ReviewComment[];
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
export interface ReviewComment {
|
|
1015
|
+
id: string;
|
|
1016
|
+
creatorId: string;
|
|
1017
|
+
content: string;
|
|
1018
|
+
reviewId: string;
|
|
1019
|
+
createdAt?: string;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
export interface ReviewWithEvent extends Review {
|
|
1023
|
+
bashEvent: {
|
|
1024
|
+
id: string;
|
|
1025
|
+
title: string;
|
|
1026
|
+
startDateTime: string;
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
export interface ReviewWithUser extends Review {
|
|
1031
|
+
creator: {
|
|
1032
|
+
id: string;
|
|
1033
|
+
givenName?: string;
|
|
1034
|
+
familyName?: string;
|
|
1035
|
+
image?: string;
|
|
1036
|
+
email: string;
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
export interface ReviewWithUserAndEvent extends ReviewWithUser, ReviewWithEvent {}
|
|
1041
|
+
|
|
1042
|
+
export interface NewReview {
|
|
1043
|
+
rating: number;
|
|
1044
|
+
bashEventId: string;
|
|
1045
|
+
commentContent?: string;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
export interface ReviewStats {
|
|
1049
|
+
averageRating: number;
|
|
1050
|
+
totalRatings: number;
|
|
1051
|
+
ratings: {
|
|
1052
|
+
1: number;
|
|
1053
|
+
2: number;
|
|
1054
|
+
3: number;
|
|
1055
|
+
4: number;
|
|
1056
|
+
5: number;
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// Default badge collections
|
|
1061
|
+
export const DEFAULT_LEVEL_BADGES: Record<string, LevelBadge> = {
|
|
1062
|
+
novice: {
|
|
1063
|
+
id: "novice",
|
|
1064
|
+
name: "Novice Basher",
|
|
1065
|
+
description: "Just getting started with Bash. Has hosted 1-2 events.",
|
|
1066
|
+
imageUrl: "/badges/novice.png",
|
|
1067
|
+
category: BadgeCategory.Hosting,
|
|
1068
|
+
level: BadgeLevel.Novice,
|
|
1069
|
+
requiredEvents: 1,
|
|
1070
|
+
isActive: true
|
|
1071
|
+
},
|
|
1072
|
+
regular: {
|
|
1073
|
+
id: "regular",
|
|
1074
|
+
name: "Regular Basher",
|
|
1075
|
+
description: "A consistent Bash user who has hosted 5+ events with good ratings.",
|
|
1076
|
+
imageUrl: "/badges/regular.png",
|
|
1077
|
+
category: BadgeCategory.Hosting,
|
|
1078
|
+
level: BadgeLevel.Regular,
|
|
1079
|
+
requiredEvents: 5,
|
|
1080
|
+
requiredRating: 4.0,
|
|
1081
|
+
isActive: true
|
|
1082
|
+
},
|
|
1083
|
+
maverick: {
|
|
1084
|
+
id: "maverick",
|
|
1085
|
+
name: "Maverick Basher",
|
|
1086
|
+
description: "A seasoned Bash host with 15+ successful events and excellent ratings.",
|
|
1087
|
+
imageUrl: "/badges/maverick.png",
|
|
1088
|
+
category: BadgeCategory.Hosting,
|
|
1089
|
+
level: BadgeLevel.Maverick,
|
|
1090
|
+
requiredEvents: 15,
|
|
1091
|
+
requiredRating: 4.5,
|
|
1092
|
+
isActive: true
|
|
1093
|
+
},
|
|
1094
|
+
legend: {
|
|
1095
|
+
id: "legend",
|
|
1096
|
+
name: "Bash Legend",
|
|
1097
|
+
description: "An elite Bash host with 30+ events and exceptional community standing.",
|
|
1098
|
+
imageUrl: "/badges/legend.png",
|
|
1099
|
+
category: BadgeCategory.Hosting,
|
|
1100
|
+
level: BadgeLevel.Legend,
|
|
1101
|
+
requiredEvents: 30,
|
|
1102
|
+
requiredRating: 4.8,
|
|
1103
|
+
isActive: true
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
export const DEFAULT_TEMPORARY_BADGES: Record<string, TemporaryBadge> = {
|
|
1108
|
+
trendingStar: {
|
|
1109
|
+
id: "trendingStar",
|
|
1110
|
+
name: "Trending Star",
|
|
1111
|
+
description: "Your event was trending in the last 30 days!",
|
|
1112
|
+
imageUrl: "/badges/trending.png",
|
|
1113
|
+
category: BadgeCategory.Achievement,
|
|
1114
|
+
isActive: true,
|
|
1115
|
+
expiresOn: undefined // Set dynamically when awarded
|
|
1116
|
+
},
|
|
1117
|
+
topHost: {
|
|
1118
|
+
id: "topHost",
|
|
1119
|
+
name: "Top Host",
|
|
1120
|
+
description: "One of the top-rated hosts this month.",
|
|
1121
|
+
imageUrl: "/badges/top-host.png",
|
|
1122
|
+
category: BadgeCategory.Hosting,
|
|
1123
|
+
isActive: true,
|
|
1124
|
+
expiresOn: undefined // Set dynamically when awarded
|
|
1125
|
+
},
|
|
1126
|
+
earlyAdopter: {
|
|
1127
|
+
id: "earlyAdopter",
|
|
1128
|
+
name: "Early Adopter",
|
|
1129
|
+
description: "Joined the platform early and helped shape the community.",
|
|
1130
|
+
imageUrl: "/badges/early-adopter.png",
|
|
1131
|
+
category: BadgeCategory.Special,
|
|
1132
|
+
isActive: true
|
|
1133
|
+
},
|
|
1134
|
+
socialButterfly: {
|
|
1135
|
+
id: "socialButterfly",
|
|
1136
|
+
name: "Social Butterfly",
|
|
1137
|
+
description: "Attended 10+ events in the last 30 days.",
|
|
1138
|
+
imageUrl: "/badges/butterfly.png",
|
|
1139
|
+
category: BadgeCategory.Attendance,
|
|
1140
|
+
isActive: true,
|
|
1141
|
+
expiresOn: undefined // Set dynamically when awarded
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
// Badge API endpoints
|
|
1146
|
+
export const BADGE_API_ENDPOINTS = {
|
|
1147
|
+
GET_USER_BADGES: (userId: string) => `/user/${userId}/badges`,
|
|
1148
|
+
UPDATE_USER_BADGES: (userId: string) => `/user/${userId}/badges`,
|
|
1149
|
+
GET_HOST_RATING: (userId: string) => `/user/${userId}/host-rating`,
|
|
1150
|
+
GET_USER_TRENDING_EVENTS: (userId: string) => `/user/${userId}/trending-events`,
|
|
1151
|
+
GET_USER_TOP_HOST_STATUS: (userId: string) => `/user/${userId}/is-top-host`,
|
|
1152
|
+
CALCULATE_BADGES: (userId: string) => `/user/${userId}/calculate-badges`,
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1155
|
+
// Review API endpoints
|
|
1156
|
+
export const REVIEW_API_ENDPOINTS = {
|
|
1157
|
+
GET_HOST_REVIEWS: (userId: string) => `/user/${userId}/host-reviews`,
|
|
1158
|
+
GET_REVIEW_STATS: (userId: string) => `/user/${userId}/review-stats`,
|
|
1159
|
+
ELIGIBLE_FOR_REVIEW: (userId: string, hostId: string) =>
|
|
1160
|
+
`/user/${userId}/eligible-for-review/${hostId}`,
|
|
1161
|
+
CREATE_REVIEW: () => `/reviews`,
|
|
1162
|
+
};
|
package/src/extendedSchemas.ts
CHANGED
|
@@ -41,25 +41,24 @@ import {
|
|
|
41
41
|
SocialMediaPlatform,
|
|
42
42
|
SocialMediaProfile,
|
|
43
43
|
Sponsor,
|
|
44
|
+
SponsoredEvent,
|
|
44
45
|
StripeAccount,
|
|
45
46
|
TargetAudience,
|
|
46
47
|
Ticket,
|
|
48
|
+
TicketMetadata,
|
|
47
49
|
TicketTier,
|
|
50
|
+
TicketTransfer,
|
|
48
51
|
User,
|
|
52
|
+
UserPreferences,
|
|
49
53
|
UserSubscription,
|
|
50
54
|
Vendor,
|
|
51
55
|
Venue,
|
|
52
56
|
VolunteerService,
|
|
53
|
-
TicketTransfer,
|
|
54
|
-
TicketMetadata,
|
|
55
|
-
SponsoredEvent,
|
|
56
57
|
} from "@prisma/client";
|
|
57
58
|
import { SERVICE_LINK_DATA_TO_INCLUDE } from "./definitions";
|
|
58
|
-
import { serviceKeysArray } from "./utils/service/serviceUtils";
|
|
59
59
|
import {
|
|
60
|
-
createAllTrueObject,
|
|
61
60
|
RemoveCommonProperties,
|
|
62
|
-
UnionFromArray
|
|
61
|
+
UnionFromArray
|
|
63
62
|
} from "./utils/typeUtils";
|
|
64
63
|
|
|
65
64
|
//------------------------------------------------------user subscriptions------------------------------------------------------
|
|
@@ -113,6 +112,9 @@ export const FRONT_END_USER_DATA_TO_SELECT = {
|
|
|
113
112
|
boughtTicket: true,
|
|
114
113
|
noPay: true,
|
|
115
114
|
supportedEvent: true,
|
|
115
|
+
aboutMe: true,
|
|
116
|
+
levelBadge: true,
|
|
117
|
+
temporaryBadges: true,
|
|
116
118
|
} satisfies Prisma.UserSelect;
|
|
117
119
|
|
|
118
120
|
export const PRIVATE_USER_ACCOUNT_TO_SELECT = {
|
|
@@ -780,7 +782,8 @@ export const CONTACT_DATA_TO_INCLUDE = {
|
|
|
780
782
|
|
|
781
783
|
export interface UserExt extends User {
|
|
782
784
|
services?: Service[] | null;
|
|
783
|
-
|
|
785
|
+
preferences?: UserPreferences | null;
|
|
786
|
+
|
|
784
787
|
// Do not include in fetch as there could be thousands of these
|
|
785
788
|
userSubscription?: UserSubscriptionExt | null;
|
|
786
789
|
associatedBashes?: AssociatedBash[] | null;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { DEFAULT_LEVEL_BADGES, DEFAULT_TEMPORARY_BADGES, LevelBadge, TemporaryBadge, UserTemporaryBadge } from "../definitions";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get the level badge details for a given badge ID
|
|
5
|
+
* @param badgeId The ID of the level badge
|
|
6
|
+
* @returns The level badge details or undefined if not found
|
|
7
|
+
*/
|
|
8
|
+
export const getLevelBadgeById = (badgeId: string): LevelBadge | undefined => {
|
|
9
|
+
return DEFAULT_LEVEL_BADGES[badgeId];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the temporary badge details for a given badge ID
|
|
14
|
+
* @param badgeId The ID of the temporary badge
|
|
15
|
+
* @returns The temporary badge details or undefined if not found
|
|
16
|
+
*/
|
|
17
|
+
export const getTemporaryBadgeById = (badgeId: string): TemporaryBadge | undefined => {
|
|
18
|
+
return DEFAULT_TEMPORARY_BADGES[badgeId];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if a temporary badge is expired
|
|
23
|
+
* @param badge The temporary badge to check
|
|
24
|
+
* @returns True if the badge is expired, false otherwise
|
|
25
|
+
*/
|
|
26
|
+
export const isTemporaryBadgeExpired = (badge: UserTemporaryBadge): boolean => {
|
|
27
|
+
if (!badge.expiresOn) return false;
|
|
28
|
+
|
|
29
|
+
const expiryDate = new Date(badge.expiresOn);
|
|
30
|
+
const currentDate = new Date();
|
|
31
|
+
|
|
32
|
+
return expiryDate < currentDate;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Filter out expired temporary badges
|
|
37
|
+
* @param badges Array of temporary badges
|
|
38
|
+
* @returns Array of non-expired temporary badges
|
|
39
|
+
*/
|
|
40
|
+
export const filterExpiredTemporaryBadges = (badges: UserTemporaryBadge[]): UserTemporaryBadge[] => {
|
|
41
|
+
return badges.filter(badge => !isTemporaryBadgeExpired(badge));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Determine the appropriate level badge based on event count and rating
|
|
46
|
+
* @param eventCount Number of hosted events
|
|
47
|
+
* @param rating Average host rating
|
|
48
|
+
* @returns The ID of the highest level badge the user qualifies for, or null if none
|
|
49
|
+
*/
|
|
50
|
+
export const determineLevelBadge = (eventCount: number, rating: number): string | null => {
|
|
51
|
+
// Sort badge IDs by required events in descending order
|
|
52
|
+
const sortedBadgeIds = Object.keys(DEFAULT_LEVEL_BADGES).sort(
|
|
53
|
+
(a, b) => DEFAULT_LEVEL_BADGES[b].requiredEvents - DEFAULT_LEVEL_BADGES[a].requiredEvents
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Find the highest level badge the user qualifies for
|
|
57
|
+
for (const badgeId of sortedBadgeIds) {
|
|
58
|
+
const badge = DEFAULT_LEVEL_BADGES[badgeId];
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
eventCount >= badge.requiredEvents &&
|
|
62
|
+
(badge.requiredRating === undefined || rating >= badge.requiredRating)
|
|
63
|
+
) {
|
|
64
|
+
return badgeId;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Review, ReviewStats } from "../definitions";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calculate average rating from an array of reviews
|
|
5
|
+
* @param reviews Array of reviews
|
|
6
|
+
* @returns Average rating (1-5)
|
|
7
|
+
*/
|
|
8
|
+
export const calculateAverageRating = (reviews: Review[]): number => {
|
|
9
|
+
if (!reviews.length) return 0;
|
|
10
|
+
|
|
11
|
+
const sum = reviews.reduce((total, review) => total + review.rating, 0);
|
|
12
|
+
return parseFloat((sum / reviews.length).toFixed(1));
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate review statistics from an array of reviews
|
|
17
|
+
* @param reviews Array of reviews
|
|
18
|
+
* @returns ReviewStats object with distribution and averages
|
|
19
|
+
*/
|
|
20
|
+
export const generateReviewStats = (reviews: Review[]): ReviewStats => {
|
|
21
|
+
const ratings = {
|
|
22
|
+
1: 0,
|
|
23
|
+
2: 0,
|
|
24
|
+
3: 0,
|
|
25
|
+
4: 0,
|
|
26
|
+
5: 0
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Count each rating level
|
|
30
|
+
reviews.forEach(review => {
|
|
31
|
+
const rating = review.rating;
|
|
32
|
+
if (rating >= 1 && rating <= 5) {
|
|
33
|
+
ratings[rating as keyof typeof ratings]++;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
averageRating: calculateAverageRating(reviews),
|
|
39
|
+
totalRatings: reviews.length,
|
|
40
|
+
ratings
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Format rating as a string with star emoji
|
|
46
|
+
* @param rating Numeric rating
|
|
47
|
+
* @returns Formatted string (e.g., "4.5 ★★★★½")
|
|
48
|
+
*/
|
|
49
|
+
export const formatRatingWithStars = (rating: number): string => {
|
|
50
|
+
const fullStars = Math.floor(rating);
|
|
51
|
+
const hasHalfStar = rating % 1 >= 0.5;
|
|
52
|
+
|
|
53
|
+
let stars = '★'.repeat(fullStars);
|
|
54
|
+
if (hasHalfStar) stars += '½';
|
|
55
|
+
|
|
56
|
+
return `${rating.toFixed(1)} ${stars}`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if a user is eligible to review an event
|
|
61
|
+
* @param userId User ID
|
|
62
|
+
* @param eventId Event ID
|
|
63
|
+
* @param alreadyReviewed Array of event IDs user has already reviewed
|
|
64
|
+
* @param eventsAttended Array of event IDs user has attended
|
|
65
|
+
* @returns Boolean indicating if user can review the event
|
|
66
|
+
*/
|
|
67
|
+
export const isEligibleToReview = (
|
|
68
|
+
userId: string,
|
|
69
|
+
eventId: string,
|
|
70
|
+
hostId: string,
|
|
71
|
+
alreadyReviewed: string[],
|
|
72
|
+
eventsAttended: string[]
|
|
73
|
+
): boolean => {
|
|
74
|
+
return (
|
|
75
|
+
userId !== hostId && // User is not the host
|
|
76
|
+
eventsAttended.includes(eventId) && // User attended the event
|
|
77
|
+
!alreadyReviewed.includes(eventId) // User has not already reviewed the event
|
|
78
|
+
);
|
|
79
|
+
};
|