@bash-app/bash-common 30.292.0 → 30.295.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/dist/__tests__/bashSectionTitleForBashType.test.js +3 -0
- package/dist/__tests__/bashSectionTitleForBashType.test.js.map +1 -1
- package/dist/definitions.d.ts +77 -0
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +1 -0
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +7 -0
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/hostCrmConstants.d.ts +11 -0
- package/dist/hostCrmConstants.d.ts.map +1 -0
- package/dist/hostCrmConstants.js +10 -0
- package/dist/hostCrmConstants.js.map +1 -0
- package/dist/hostCrmSegments.d.ts +47 -0
- package/dist/hostCrmSegments.d.ts.map +1 -0
- package/dist/hostCrmSegments.js +65 -0
- package/dist/hostCrmSegments.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/membershipDefinitions.d.ts +9 -0
- package/dist/membershipDefinitions.d.ts.map +1 -1
- package/dist/membershipDefinitions.js +10 -1
- package/dist/membershipDefinitions.js.map +1 -1
- package/dist/utils/__tests__/reputationUtils.serviceListing.test.js +18 -0
- package/dist/utils/__tests__/reputationUtils.serviceListing.test.js.map +1 -1
- package/dist/utils/__tests__/reviewDimensionUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/reviewDimensionUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/reviewDimensionUtils.test.js +48 -0
- package/dist/utils/__tests__/reviewDimensionUtils.test.js.map +1 -0
- package/dist/utils/__tests__/trustScoreUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/trustScoreUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/trustScoreUtils.test.js +101 -0
- package/dist/utils/__tests__/trustScoreUtils.test.js.map +1 -0
- package/dist/utils/reputationUtils.d.ts.map +1 -1
- package/dist/utils/reputationUtils.js +7 -1
- package/dist/utils/reputationUtils.js.map +1 -1
- package/dist/utils/reviewDimensionUtils.d.ts +33 -0
- package/dist/utils/reviewDimensionUtils.d.ts.map +1 -0
- package/dist/utils/reviewDimensionUtils.js +35 -0
- package/dist/utils/reviewDimensionUtils.js.map +1 -0
- package/dist/utils/trustScoreUtils.d.ts +25 -0
- package/dist/utils/trustScoreUtils.d.ts.map +1 -0
- package/dist/utils/trustScoreUtils.js +99 -0
- package/dist/utils/trustScoreUtils.js.map +1 -0
- package/package.json +1 -1
- package/prisma/schema.prisma +181 -15
- package/src/__tests__/bashSectionTitleForBashType.test.ts +4 -0
- package/src/definitions.ts +85 -1
- package/src/extendedSchemas.ts +7 -0
- package/src/hostCrmConstants.ts +13 -0
- package/src/hostCrmSegments.ts +126 -0
- package/src/index.ts +4 -0
- package/src/membershipDefinitions.ts +12 -1
- package/src/utils/__tests__/reputationUtils.serviceListing.test.ts +24 -0
- package/src/utils/__tests__/reviewDimensionUtils.test.ts +65 -0
- package/src/utils/__tests__/trustScoreUtils.test.ts +105 -0
- package/src/utils/reputationUtils.ts +7 -1
- package/src/utils/reviewDimensionUtils.ts +70 -0
- package/src/utils/trustScoreUtils.ts +136 -0
package/prisma/schema.prisma
CHANGED
|
@@ -91,6 +91,7 @@ model BashFeedPost {
|
|
|
91
91
|
saves BashFeedSave[]
|
|
92
92
|
reposts BashFeedRepost[]
|
|
93
93
|
helpfulVotes ReviewHelpfulVote[]
|
|
94
|
+
serviceReviews ServiceReview[]
|
|
94
95
|
|
|
95
96
|
@@unique([userId, repostOfPostId])
|
|
96
97
|
@@index([bashId])
|
|
@@ -345,6 +346,8 @@ model CompetitionPrizeOffer {
|
|
|
345
346
|
sponsorLabel String?
|
|
346
347
|
sourceServiceId String?
|
|
347
348
|
sourceSku String?
|
|
349
|
+
/// Existing prize slot the offerer wants to sponsor (optional)
|
|
350
|
+
targetPrizeId String?
|
|
348
351
|
hostNote String?
|
|
349
352
|
createdAt DateTime @default(now())
|
|
350
353
|
updatedAt DateTime @updatedAt
|
|
@@ -352,10 +355,12 @@ model CompetitionPrizeOffer {
|
|
|
352
355
|
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
|
353
356
|
offeredBy User @relation("CompetitionPrizeOffersSubmitted", fields: [offeredByUserId], references: [id], onDelete: Cascade)
|
|
354
357
|
sourceService Service? @relation("CompetitionPrizeOfferSourceService", fields: [sourceServiceId], references: [id], onDelete: SetNull)
|
|
358
|
+
targetPrize Prize? @relation("CompetitionPrizeOfferTarget", fields: [targetPrizeId], references: [id], onDelete: SetNull)
|
|
355
359
|
|
|
356
360
|
@@index([competitionId])
|
|
357
361
|
@@index([offeredByUserId])
|
|
358
362
|
@@index([status])
|
|
363
|
+
@@index([targetPrizeId])
|
|
359
364
|
}
|
|
360
365
|
|
|
361
366
|
model CompetitionParticipant {
|
|
@@ -911,6 +916,9 @@ model BashEvent {
|
|
|
911
916
|
postEventMessageSentAt DateTime?
|
|
912
917
|
broadcastCount Int @default(0)
|
|
913
918
|
lastBroadcastAt DateTime?
|
|
919
|
+
/// When true, notify opted-in CRM audience on publish (optional tag in crmPublishNotifyTag).
|
|
920
|
+
crmPublishNotifyEnabled Boolean @default(false)
|
|
921
|
+
crmPublishNotifyTag String?
|
|
914
922
|
|
|
915
923
|
// Idea-specific fields (only used when status = 'Idea')
|
|
916
924
|
ideaExpiresAt DateTime?
|
|
@@ -966,6 +974,8 @@ model BashEvent {
|
|
|
966
974
|
sentReminders SentReminder[]
|
|
967
975
|
recurrence Recurrence?
|
|
968
976
|
reviews Review[]
|
|
977
|
+
serviceReviews ServiceReview[]
|
|
978
|
+
providerHostReviews ProviderHostReview[]
|
|
969
979
|
serviceBookings ServiceBooking[]
|
|
970
980
|
sponsorBookingRequests SponsorBookingRequest[] @relation("SponsorBookingEvent")
|
|
971
981
|
sponsorships SponsoredEvent[]
|
|
@@ -1960,15 +1970,23 @@ enum BackgroundEffect {
|
|
|
1960
1970
|
Starfield
|
|
1961
1971
|
}
|
|
1962
1972
|
|
|
1973
|
+
enum EventDiscussionTopic {
|
|
1974
|
+
General
|
|
1975
|
+
Icebreaker
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1963
1978
|
model EventDiscussionComment {
|
|
1964
|
-
id String
|
|
1979
|
+
id String @id @default(cuid())
|
|
1965
1980
|
bashEventId String
|
|
1966
1981
|
authorId String
|
|
1967
|
-
body String
|
|
1982
|
+
body String @db.Text
|
|
1968
1983
|
mentionedUserIds String[] // User IDs mentioned in the comment
|
|
1969
1984
|
parentId String? // For nested replies (mirrors BashFeedComment)
|
|
1970
|
-
|
|
1971
|
-
|
|
1985
|
+
topic EventDiscussionTopic @default(General)
|
|
1986
|
+
/// Snapshot of the icebreaker prompt this answer belongs to (Icebreaker topic only).
|
|
1987
|
+
icebreakerPrompt String? @db.Text
|
|
1988
|
+
createdAt DateTime @default(now())
|
|
1989
|
+
updatedAt DateTime @updatedAt
|
|
1972
1990
|
|
|
1973
1991
|
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
1974
1992
|
author User @relation("EventDiscussionComments", fields: [authorId], references: [id], onDelete: Cascade)
|
|
@@ -1979,6 +1997,7 @@ model EventDiscussionComment {
|
|
|
1979
1997
|
@@index([bashEventId])
|
|
1980
1998
|
@@index([authorId])
|
|
1981
1999
|
@@index([parentId])
|
|
2000
|
+
@@index([bashEventId, topic, icebreakerPrompt])
|
|
1982
2001
|
}
|
|
1983
2002
|
|
|
1984
2003
|
model EventDiscussionCommentLike {
|
|
@@ -2099,6 +2118,38 @@ model HostAttendeeProfile {
|
|
|
2099
2118
|
@@index([hostUserId])
|
|
2100
2119
|
}
|
|
2101
2120
|
|
|
2121
|
+
/// Host-defined reusable CRM audience filters (tag, cadence, lapsed, cohorts).
|
|
2122
|
+
model HostCrmSavedSegment {
|
|
2123
|
+
id String @id @default(cuid())
|
|
2124
|
+
hostUserId String
|
|
2125
|
+
name String
|
|
2126
|
+
rules Json
|
|
2127
|
+
createdAt DateTime @default(now())
|
|
2128
|
+
updatedAt DateTime @updatedAt
|
|
2129
|
+
|
|
2130
|
+
host User @relation("HostCrmSavedSegments", fields: [hostUserId], references: [id], onDelete: Cascade)
|
|
2131
|
+
|
|
2132
|
+
@@unique([hostUserId, name])
|
|
2133
|
+
@@index([hostUserId])
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
/// Per-recipient CRM email cooldown (parity with smsSendLog for CRM_BLAST).
|
|
2137
|
+
model HostCrmEmailSendLog {
|
|
2138
|
+
id String @id @default(cuid())
|
|
2139
|
+
hostUserId String
|
|
2140
|
+
userId String?
|
|
2141
|
+
email String?
|
|
2142
|
+
templateKey String
|
|
2143
|
+
sentAt DateTime @default(now())
|
|
2144
|
+
|
|
2145
|
+
host User @relation("HostCrmEmailSendLogsHost", fields: [hostUserId], references: [id], onDelete: Cascade)
|
|
2146
|
+
user User? @relation("HostCrmEmailSendLogsUser", fields: [userId], references: [id], onDelete: SetNull)
|
|
2147
|
+
|
|
2148
|
+
@@index([hostUserId, sentAt])
|
|
2149
|
+
@@index([userId, templateKey, sentAt])
|
|
2150
|
+
@@index([email, templateKey, sentAt])
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2102
2153
|
// Tracks referrals: attendee A referred person B who bought a ticket
|
|
2103
2154
|
model EventReferral {
|
|
2104
2155
|
id String @id @default(cuid())
|
|
@@ -2393,6 +2444,8 @@ model TicketTier {
|
|
|
2393
2444
|
preReleaseDisplay String @default("Tease") // "Tease" | "Hidden" before availableAt when OnDate
|
|
2394
2445
|
isManuallySoldOut Boolean @default(false)
|
|
2395
2446
|
notifyOnRelease Boolean @default(true)
|
|
2447
|
+
/// Optional CRM tag: also notify opted-in past attendees with this tag when tier releases.
|
|
2448
|
+
crmNotifyTag String?
|
|
2396
2449
|
tierReleaseNotifiedAt DateTime?
|
|
2397
2450
|
availableWhenInterestReached Int? // UnlockWhenInterestReached: min IdeaInterest count
|
|
2398
2451
|
unlockWhenTicketsSold Int? // UnlockWhenTicketsSold: min tickets sold across event to unlock this tier
|
|
@@ -2679,7 +2732,7 @@ model Recurrence {
|
|
|
2679
2732
|
repeatYearlyDate DateTime?
|
|
2680
2733
|
// Per-occurrence exceptions for the computed series (no occurrence rows are materialized):
|
|
2681
2734
|
// dates the host removed from the series (matched against the generated occurrence start instants).
|
|
2682
|
-
excludedDates DateTime[]
|
|
2735
|
+
excludedDates DateTime[] @default([])
|
|
2683
2736
|
// Per-occurrence date/time moves: JSON map of original-occurrence ISO start -> { startDateTime, endDateTime? }.
|
|
2684
2737
|
occurrenceOverrides Json?
|
|
2685
2738
|
createdAt DateTime @default(now())
|
|
@@ -2875,6 +2928,8 @@ model Prize {
|
|
|
2875
2928
|
winnerUserId String?
|
|
2876
2929
|
wonAt DateTime? // When the prize was awarded
|
|
2877
2930
|
claimedAt DateTime? // When the prize was claimed
|
|
2931
|
+
/// Host-set amount sponsors pay to fund this prize slot (cents). Shown when seeking prize contributions.
|
|
2932
|
+
sponsorCostCents Int?
|
|
2878
2933
|
/// Platform user who donated this prize (optional)
|
|
2879
2934
|
contributorUserId String?
|
|
2880
2935
|
/// Sponsor marketplace service the prize was picked from (optional)
|
|
@@ -2907,6 +2962,7 @@ model Prize {
|
|
|
2907
2962
|
winner User? @relation(fields: [winnerUserId], references: [id], onDelete: Cascade)
|
|
2908
2963
|
contributor User? @relation("PrizeContributor", fields: [contributorUserId], references: [id], onDelete: SetNull)
|
|
2909
2964
|
sourceService Service? @relation("PrizeSourceService", fields: [sourceServiceId], references: [id], onDelete: SetNull)
|
|
2965
|
+
sponsorshipOffers CompetitionPrizeOffer[] @relation("CompetitionPrizeOfferTarget")
|
|
2910
2966
|
bashPointsTransaction BashCreditTransaction? @relation(fields: [bashPointsTransactionId], references: [id], onDelete: SetNull)
|
|
2911
2967
|
|
|
2912
2968
|
@@index([competitionId])
|
|
@@ -2918,11 +2974,22 @@ model Prize {
|
|
|
2918
2974
|
|
|
2919
2975
|
model Review {
|
|
2920
2976
|
id String @id @default(cuid())
|
|
2977
|
+
/// Overall rating (1–5). Headline aggregate uses this field.
|
|
2921
2978
|
rating Int
|
|
2922
2979
|
creatorId String
|
|
2923
2980
|
bashEventId String
|
|
2924
2981
|
createdAt DateTime @default(now())
|
|
2925
2982
|
updatedAt DateTime @updatedAt
|
|
2983
|
+
/// Optional multi-dimensional event ratings (Overall required via rating).
|
|
2984
|
+
organizationRating Int?
|
|
2985
|
+
atmosphereRating Int?
|
|
2986
|
+
valueRating Int?
|
|
2987
|
+
safetyRating Int?
|
|
2988
|
+
wouldAttendAgain Boolean?
|
|
2989
|
+
/// Trust badges stamped at write time from platform verification checks.
|
|
2990
|
+
verifiedBooking Boolean @default(false)
|
|
2991
|
+
verifiedAttendance Boolean @default(false)
|
|
2992
|
+
verifiedServiceCompletion Boolean @default(false)
|
|
2926
2993
|
/// Public reply from the event host (service provider) to the reviewer.
|
|
2927
2994
|
hostReplyText String? @db.Text
|
|
2928
2995
|
hostReplyAt DateTime?
|
|
@@ -2934,22 +3001,93 @@ model Review {
|
|
|
2934
3001
|
@@unique([creatorId, bashEventId])
|
|
2935
3002
|
}
|
|
2936
3003
|
|
|
3004
|
+
/// Bash-native review of a service listing at a specific event (attendee or host).
|
|
3005
|
+
model ServiceReview {
|
|
3006
|
+
id String @id @default(cuid())
|
|
3007
|
+
creatorId String
|
|
3008
|
+
serviceId String
|
|
3009
|
+
bashEventId String
|
|
3010
|
+
reviewerRole ServiceReviewerRole
|
|
3011
|
+
overallRating Int
|
|
3012
|
+
communicationRating Int?
|
|
3013
|
+
professionalismRating Int?
|
|
3014
|
+
qualityRating Int?
|
|
3015
|
+
wouldHireAgain Boolean?
|
|
3016
|
+
verifiedBooking Boolean @default(false)
|
|
3017
|
+
verifiedAttendance Boolean @default(false)
|
|
3018
|
+
verifiedServiceCompletion Boolean @default(false)
|
|
3019
|
+
comment String? @db.Text
|
|
3020
|
+
providerReplyText String? @db.Text
|
|
3021
|
+
providerReplyAt DateTime?
|
|
3022
|
+
/// Null until blind host↔provider window reveals (attendee reviews publish immediately).
|
|
3023
|
+
publishedAt DateTime?
|
|
3024
|
+
feedPostId String?
|
|
3025
|
+
createdAt DateTime @default(now())
|
|
3026
|
+
updatedAt DateTime @updatedAt
|
|
3027
|
+
|
|
3028
|
+
creator User @relation("ServiceReviewsCreated", fields: [creatorId], references: [id], onDelete: Cascade)
|
|
3029
|
+
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
|
3030
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
3031
|
+
feedPost BashFeedPost? @relation(fields: [feedPostId], references: [id], onDelete: SetNull)
|
|
3032
|
+
helpfulVotes ReviewHelpfulVote[]
|
|
3033
|
+
|
|
3034
|
+
@@unique([creatorId, serviceId, bashEventId])
|
|
3035
|
+
@@index([serviceId, publishedAt])
|
|
3036
|
+
@@index([bashEventId])
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
/// Provider reviews the host after a confirmed booking (blind mutual window with host ServiceReview).
|
|
3040
|
+
model ProviderHostReview {
|
|
3041
|
+
id String @id @default(cuid())
|
|
3042
|
+
creatorId String
|
|
3043
|
+
hostUserId String
|
|
3044
|
+
serviceId String
|
|
3045
|
+
bashEventId String
|
|
3046
|
+
overallRating Int
|
|
3047
|
+
communicationRating Int?
|
|
3048
|
+
professionalismRating Int?
|
|
3049
|
+
qualityRating Int?
|
|
3050
|
+
wouldWorkAgain Boolean?
|
|
3051
|
+
verifiedBooking Boolean @default(false)
|
|
3052
|
+
verifiedServiceCompletion Boolean @default(false)
|
|
3053
|
+
comment String? @db.Text
|
|
3054
|
+
publishedAt DateTime?
|
|
3055
|
+
createdAt DateTime @default(now())
|
|
3056
|
+
updatedAt DateTime @updatedAt
|
|
3057
|
+
|
|
3058
|
+
creator User @relation("ProviderHostReviewsCreated", fields: [creatorId], references: [id], onDelete: Cascade)
|
|
3059
|
+
host User @relation("ProviderHostReviewsReceived", fields: [hostUserId], references: [id], onDelete: Cascade)
|
|
3060
|
+
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
|
3061
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
3062
|
+
|
|
3063
|
+
@@unique([creatorId, bashEventId, serviceId])
|
|
3064
|
+
@@index([hostUserId, publishedAt])
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
enum ServiceReviewerRole {
|
|
3068
|
+
Attendee
|
|
3069
|
+
Host
|
|
3070
|
+
}
|
|
3071
|
+
|
|
2937
3072
|
/// Profile-only helpfulness votes on host reviews (Review rows or BashFeed posts shown as reviews).
|
|
2938
3073
|
model ReviewHelpfulVote {
|
|
2939
|
-
id
|
|
2940
|
-
userId
|
|
2941
|
-
reviewId
|
|
2942
|
-
feedPostId
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
3074
|
+
id String @id @default(cuid())
|
|
3075
|
+
userId String
|
|
3076
|
+
reviewId String?
|
|
3077
|
+
feedPostId String?
|
|
3078
|
+
serviceReviewId String?
|
|
3079
|
+
isHelpful Boolean
|
|
3080
|
+
createdAt DateTime @default(now())
|
|
3081
|
+
updatedAt DateTime @updatedAt
|
|
2946
3082
|
|
|
2947
|
-
user
|
|
2948
|
-
review
|
|
2949
|
-
feedPost
|
|
3083
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
3084
|
+
review Review? @relation(fields: [reviewId], references: [id], onDelete: Cascade)
|
|
3085
|
+
feedPost BashFeedPost? @relation(fields: [feedPostId], references: [id], onDelete: Cascade)
|
|
3086
|
+
serviceReview ServiceReview? @relation(fields: [serviceReviewId], references: [id], onDelete: Cascade)
|
|
2950
3087
|
|
|
2951
3088
|
@@index([reviewId])
|
|
2952
3089
|
@@index([feedPostId])
|
|
3090
|
+
@@index([serviceReviewId])
|
|
2953
3091
|
@@index([userId])
|
|
2954
3092
|
}
|
|
2955
3093
|
|
|
@@ -2960,6 +3098,9 @@ model GoogleReview {
|
|
|
2960
3098
|
text String?
|
|
2961
3099
|
createdAt DateTime @default(now())
|
|
2962
3100
|
serviceId String
|
|
3101
|
+
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
|
3102
|
+
|
|
3103
|
+
@@index([serviceId])
|
|
2963
3104
|
}
|
|
2964
3105
|
|
|
2965
3106
|
model Session {
|
|
@@ -3085,6 +3226,10 @@ model User {
|
|
|
3085
3226
|
status UserStatus
|
|
3086
3227
|
hostRating Float?
|
|
3087
3228
|
totalRatings Int?
|
|
3229
|
+
/// Secondary composite trust signal (0–100) for host profile detail.
|
|
3230
|
+
trustScore Int?
|
|
3231
|
+
trustScoreUpdatedAt DateTime?
|
|
3232
|
+
verifiedBookingsCount Int @default(0)
|
|
3088
3233
|
totalEventsHosted Int? @default(0) // Total published/completed events hosted
|
|
3089
3234
|
totalAttendeesHosted Int? @default(0) // Total checked-in attendees across all events
|
|
3090
3235
|
totalBookingsAccepted Int? @default(0) // Service provider stat
|
|
@@ -3278,6 +3423,9 @@ model User {
|
|
|
3278
3423
|
remindersAssignedToMe Reminder[] @relation("RemindersAssignedToMe")
|
|
3279
3424
|
sentReminders SentReminder[]
|
|
3280
3425
|
reviews Review[]
|
|
3426
|
+
serviceReviewsCreated ServiceReview[] @relation("ServiceReviewsCreated")
|
|
3427
|
+
providerHostReviewsCreated ProviderHostReview[] @relation("ProviderHostReviewsCreated")
|
|
3428
|
+
providerHostReviewsReceived ProviderHostReview[] @relation("ProviderHostReviewsReceived")
|
|
3281
3429
|
firstFreeListingId String?
|
|
3282
3430
|
createdServices Service[] @relation("CreatedService")
|
|
3283
3431
|
ownedServices Service[] @relation("OwnedService")
|
|
@@ -3369,6 +3517,9 @@ model User {
|
|
|
3369
3517
|
eventDiscussionComments EventDiscussionComment[] @relation("EventDiscussionComments")
|
|
3370
3518
|
hostAttendeeProfilesOwned HostAttendeeProfile[] @relation("HostAttendeeProfilesOwned")
|
|
3371
3519
|
hostAttendeeProfilesTarget HostAttendeeProfile[] @relation("HostAttendeeProfilesTarget")
|
|
3520
|
+
hostCrmSavedSegments HostCrmSavedSegment[] @relation("HostCrmSavedSegments")
|
|
3521
|
+
hostCrmEmailSendLogsAsHost HostCrmEmailSendLog[] @relation("HostCrmEmailSendLogsHost")
|
|
3522
|
+
hostCrmEmailSendLogsAsUser HostCrmEmailSendLog[] @relation("HostCrmEmailSendLogsUser")
|
|
3372
3523
|
bashListsCreated BashList[]
|
|
3373
3524
|
userItineraries UserItinerary[]
|
|
3374
3525
|
aiRecommendationLogs AIRecommendationLog[]
|
|
@@ -3528,6 +3679,14 @@ model UserPreferences {
|
|
|
3528
3679
|
preferredCurrency String @default("USD")
|
|
3529
3680
|
savePaymentInfo Boolean @default(false)
|
|
3530
3681
|
showEventPrices Boolean @default(true)
|
|
3682
|
+
/// Default: notify CRM audience when publishing a new public bash.
|
|
3683
|
+
hostCrmPublishNotifyDefault Boolean @default(false)
|
|
3684
|
+
/// Optional host-authored post-event email subject (CRM); cron uses when set.
|
|
3685
|
+
postEventCrmTemplateSubject String?
|
|
3686
|
+
postEventCrmTemplateBody String? @db.Text
|
|
3687
|
+
/// When true, weekly cron emails lapsed CRM attendees (6+ months).
|
|
3688
|
+
hostCrmWinBackAutoEnabled Boolean @default(false)
|
|
3689
|
+
hostCrmWinBackLastRunAt DateTime?
|
|
3531
3690
|
|
|
3532
3691
|
// BashFeed Preferences
|
|
3533
3692
|
bashFeedDefaultTab String @default("all") // "all", "upcoming", "live", "past", "saved", "my-posts"
|
|
@@ -3948,6 +4107,10 @@ model Service {
|
|
|
3948
4107
|
externalReviewSource String?
|
|
3949
4108
|
externalRating Float?
|
|
3950
4109
|
externalReviewCount Int?
|
|
4110
|
+
/// Secondary composite trust signal (0–100) for detail pages and ranking.
|
|
4111
|
+
trustScore Int?
|
|
4112
|
+
trustScoreUpdatedAt DateTime?
|
|
4113
|
+
verifiedCompletionsCount Int @default(0)
|
|
3951
4114
|
stripeAccountId String?
|
|
3952
4115
|
paymentAccountId String?
|
|
3953
4116
|
targetAudienceId String? @unique
|
|
@@ -4033,6 +4196,9 @@ model Service {
|
|
|
4033
4196
|
competitionPrizeOffersSourced CompetitionPrizeOffer[] @relation("CompetitionPrizeOfferSourceService")
|
|
4034
4197
|
venueMatchCandidates VenueMatchCandidate[]
|
|
4035
4198
|
supplyLeadConversions VenueSupplyLead[] @relation("SupplyLeadConvertedService")
|
|
4199
|
+
googleReviews GoogleReview[]
|
|
4200
|
+
serviceReviews ServiceReview[]
|
|
4201
|
+
providerHostReviews ProviderHostReview[]
|
|
4036
4202
|
|
|
4037
4203
|
@@index([serviceListingStripeSubscriptionId])
|
|
4038
4204
|
@@index([paymentAccountId])
|
|
@@ -44,6 +44,10 @@ describe("bashSectionTitleForBashType", () => {
|
|
|
44
44
|
it("'Trending' → 'Trending'", () => {
|
|
45
45
|
expect(bashSectionTitleForBashType("Trending")).toBe("Trending");
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
it("'Ideas' → 'Ideas'", () => {
|
|
49
|
+
expect(bashSectionTitleForBashType("Ideas")).toBe("Ideas");
|
|
50
|
+
});
|
|
47
51
|
});
|
|
48
52
|
|
|
49
53
|
describe("BashEventType enum values get pluralized", () => {
|
package/src/definitions.ts
CHANGED
|
@@ -1471,6 +1471,7 @@ export const INCOME_RANGE_VALUES: ReadonlyArray<IncomeRange> = [
|
|
|
1471
1471
|
|
|
1472
1472
|
/** Homepage / discovery section labels for synthetic rows (not `BashEventType` enum values). */
|
|
1473
1473
|
const SYNTHETIC_BASH_SECTION_TITLE: Record<string, string> = {
|
|
1474
|
+
Ideas: "Ideas",
|
|
1474
1475
|
Featured: "Featured",
|
|
1475
1476
|
Trending: "Trending",
|
|
1476
1477
|
};
|
|
@@ -1817,11 +1818,19 @@ export interface UserTemporaryBadge {
|
|
|
1817
1818
|
|
|
1818
1819
|
export interface Review {
|
|
1819
1820
|
id: string;
|
|
1820
|
-
rating: number; // 1-5 stars
|
|
1821
|
+
rating: number; // 1-5 stars (Overall)
|
|
1821
1822
|
creatorId: string;
|
|
1822
1823
|
bashEventId: string;
|
|
1823
1824
|
createdAt: string;
|
|
1824
1825
|
updatedAt: string;
|
|
1826
|
+
organizationRating?: number | null;
|
|
1827
|
+
atmosphereRating?: number | null;
|
|
1828
|
+
valueRating?: number | null;
|
|
1829
|
+
safetyRating?: number | null;
|
|
1830
|
+
wouldAttendAgain?: boolean | null;
|
|
1831
|
+
verifiedBooking?: boolean;
|
|
1832
|
+
verifiedAttendance?: boolean;
|
|
1833
|
+
verifiedServiceCompletion?: boolean;
|
|
1825
1834
|
comments?: ReviewComment[];
|
|
1826
1835
|
}
|
|
1827
1836
|
|
|
@@ -1853,10 +1862,83 @@ export interface ReviewWithUser extends Review {
|
|
|
1853
1862
|
|
|
1854
1863
|
export interface ReviewWithUserAndEvent extends ReviewWithUser, ReviewWithEvent {}
|
|
1855
1864
|
|
|
1865
|
+
/** Wizard / API shape for BashEvent.itinerary JSON (see prisma BashEvent.itinerary). */
|
|
1866
|
+
export interface ItineraryItem {
|
|
1867
|
+
id: string;
|
|
1868
|
+
title: string;
|
|
1869
|
+
startTime: string;
|
|
1870
|
+
endTime: string;
|
|
1871
|
+
description?: string;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1856
1874
|
export interface NewReview {
|
|
1857
1875
|
rating: number;
|
|
1858
1876
|
bashEventId: string;
|
|
1859
1877
|
commentContent?: string;
|
|
1878
|
+
organizationRating?: number;
|
|
1879
|
+
atmosphereRating?: number;
|
|
1880
|
+
valueRating?: number;
|
|
1881
|
+
safetyRating?: number;
|
|
1882
|
+
wouldAttendAgain?: boolean;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
export interface ServiceReviewDimensions {
|
|
1886
|
+
communicationRating?: number | null;
|
|
1887
|
+
professionalismRating?: number | null;
|
|
1888
|
+
qualityRating?: number | null;
|
|
1889
|
+
wouldHireAgain?: boolean | null;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
export interface NewServiceReview extends ServiceReviewDimensions {
|
|
1893
|
+
overallRating: number;
|
|
1894
|
+
bashEventId: string;
|
|
1895
|
+
comment?: string;
|
|
1896
|
+
createPost?: boolean;
|
|
1897
|
+
postContent?: string;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
export interface ServiceReviewPublic {
|
|
1901
|
+
id: string;
|
|
1902
|
+
overallRating: number;
|
|
1903
|
+
reviewerRole: "Attendee" | "Host";
|
|
1904
|
+
comment?: string | null;
|
|
1905
|
+
createdAt: string;
|
|
1906
|
+
publishedAt?: string | null;
|
|
1907
|
+
verifiedBooking: boolean;
|
|
1908
|
+
verifiedAttendance: boolean;
|
|
1909
|
+
verifiedServiceCompletion: boolean;
|
|
1910
|
+
communicationRating?: number | null;
|
|
1911
|
+
professionalismRating?: number | null;
|
|
1912
|
+
qualityRating?: number | null;
|
|
1913
|
+
wouldHireAgain?: boolean | null;
|
|
1914
|
+
providerReplyText?: string | null;
|
|
1915
|
+
providerReplyAt?: string | null;
|
|
1916
|
+
feedPostId?: string | null;
|
|
1917
|
+
creator?: {
|
|
1918
|
+
id: string;
|
|
1919
|
+
givenName?: string | null;
|
|
1920
|
+
familyName?: string | null;
|
|
1921
|
+
image?: string | null;
|
|
1922
|
+
};
|
|
1923
|
+
bashEvent?: {
|
|
1924
|
+
id: string;
|
|
1925
|
+
title: string;
|
|
1926
|
+
startDateTime: string;
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
export interface ServiceReviewStats {
|
|
1931
|
+
overallAverage: number | null;
|
|
1932
|
+
totalReviews: number;
|
|
1933
|
+
dimensionAverages: import("./utils/reviewDimensionUtils.js").ReviewDimensionAverages;
|
|
1934
|
+
googleAverage?: number | null;
|
|
1935
|
+
googleCount?: number;
|
|
1936
|
+
trustScore?: number | null;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
export interface TrustScoreBreakdownPublic {
|
|
1940
|
+
score: number | null;
|
|
1941
|
+
components: Record<string, number>;
|
|
1860
1942
|
}
|
|
1861
1943
|
|
|
1862
1944
|
export interface ReviewStats {
|
|
@@ -1869,6 +1951,8 @@ export interface ReviewStats {
|
|
|
1869
1951
|
4: number;
|
|
1870
1952
|
5: number;
|
|
1871
1953
|
};
|
|
1954
|
+
dimensionAverages?: import("./utils/reviewDimensionUtils.js").ReviewDimensionAverages;
|
|
1955
|
+
trustScore?: number | null;
|
|
1872
1956
|
}
|
|
1873
1957
|
|
|
1874
1958
|
/** Helpful / Not helpful tallies on profile review cards (API: host-reviews). */
|
package/src/extendedSchemas.ts
CHANGED
|
@@ -287,6 +287,7 @@ export interface CompetitionPrizeOfferExt {
|
|
|
287
287
|
sponsorLabel?: string | null;
|
|
288
288
|
sourceServiceId?: string | null;
|
|
289
289
|
sourceSku?: string | null;
|
|
290
|
+
targetPrizeId?: string | null;
|
|
290
291
|
hostNote?: string | null;
|
|
291
292
|
createdAt: Date | string;
|
|
292
293
|
respondedAt?: Date | string | null;
|
|
@@ -296,6 +297,12 @@ export interface CompetitionPrizeOfferExt {
|
|
|
296
297
|
serviceName: string | null;
|
|
297
298
|
coverPhoto: string | null;
|
|
298
299
|
} | null;
|
|
300
|
+
targetPrize?: {
|
|
301
|
+
id: string;
|
|
302
|
+
name: string;
|
|
303
|
+
placeName: string | null;
|
|
304
|
+
sponsorCostCents: number | null;
|
|
305
|
+
} | null;
|
|
299
306
|
}
|
|
300
307
|
|
|
301
308
|
/** Competition row + relations; Override keeps new columns visible before consumers refresh Prisma. */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Per-recipient CRM email cooldown — matches SMS CRM_BLAST window. */
|
|
2
|
+
export const HOST_CRM_EMAIL_COOLDOWN_MS = 48 * 60 * 60 * 1000;
|
|
3
|
+
|
|
4
|
+
export const HOST_CRM_EMAIL_TEMPLATE_KEYS = {
|
|
5
|
+
blast: "CRM_BLAST",
|
|
6
|
+
publishNotify: "CRM_PUBLISH_NOTIFY",
|
|
7
|
+
winBack: "CRM_WINBACK",
|
|
8
|
+
postEvent: "CRM_POST_EVENT",
|
|
9
|
+
tierRelease: "CRM_TIER_RELEASE",
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export type HostCrmEmailTemplateKey =
|
|
13
|
+
(typeof HOST_CRM_EMAIL_TEMPLATE_KEYS)[keyof typeof HOST_CRM_EMAIL_TEMPLATE_KEYS];
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { SocialEventCadence } from "@prisma/client";
|
|
2
|
+
|
|
3
|
+
/** Rule-based CRM segment (stored as JSON on HostCrmSavedSegment.rules). */
|
|
4
|
+
export type HostCrmSegmentRules = {
|
|
5
|
+
tag?: string;
|
|
6
|
+
cadence?: SocialEventCadence;
|
|
7
|
+
minEventCount?: number;
|
|
8
|
+
lapsedDays?: number;
|
|
9
|
+
/** Ticket buyers for this bash event id */
|
|
10
|
+
sourceEventId?: string;
|
|
11
|
+
/** Exclude anyone who bought tickets to this bash */
|
|
12
|
+
excludeEventId?: string;
|
|
13
|
+
/** Attendees who purchased a tier with this exact title on any host bash */
|
|
14
|
+
tierTitle?: string;
|
|
15
|
+
/** Match attendee city (case-insensitive substring) */
|
|
16
|
+
city?: string;
|
|
17
|
+
/** Built-in preset id (e.g. cadence_often) when rules alone are insufficient */
|
|
18
|
+
presetId?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type HostCrmAudienceMember = {
|
|
22
|
+
userId: string | null;
|
|
23
|
+
email: string | null;
|
|
24
|
+
name: string;
|
|
25
|
+
image: string | null;
|
|
26
|
+
eventCount: number;
|
|
27
|
+
eventTitles: string[];
|
|
28
|
+
tags: string[];
|
|
29
|
+
notes: string | null;
|
|
30
|
+
profileId: string | null;
|
|
31
|
+
socialEventCadence: SocialEventCadence | null;
|
|
32
|
+
reachableEmail: boolean;
|
|
33
|
+
reachableSms: boolean;
|
|
34
|
+
lastAttendedAt: string | null;
|
|
35
|
+
city: string | null;
|
|
36
|
+
tierTitles: string[];
|
|
37
|
+
/** Bash event ids this person attended (for cohort / source filters) */
|
|
38
|
+
sourceEventIds?: string[];
|
|
39
|
+
/** True when row is email-only (guest checkout) without a linked user id */
|
|
40
|
+
emailOnly: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function applyHostCrmSegmentRules(
|
|
44
|
+
audience: HostCrmAudienceMember[],
|
|
45
|
+
rules: HostCrmSegmentRules,
|
|
46
|
+
now: Date = new Date()
|
|
47
|
+
): HostCrmAudienceMember[] {
|
|
48
|
+
let rows = audience;
|
|
49
|
+
|
|
50
|
+
if (rules.tag?.trim()) {
|
|
51
|
+
const tag = rules.tag.trim();
|
|
52
|
+
rows = rows.filter((r) => r.tags.includes(tag));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (rules.cadence) {
|
|
56
|
+
rows = rows.filter((r) => r.socialEventCadence === rules.cadence);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof rules.minEventCount === "number" && rules.minEventCount > 0) {
|
|
60
|
+
rows = rows.filter((r) => r.eventCount >= rules.minEventCount!);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (typeof rules.lapsedDays === "number" && rules.lapsedDays > 0) {
|
|
64
|
+
const cutoff = now.getTime() - rules.lapsedDays * 24 * 60 * 60 * 1000;
|
|
65
|
+
rows = rows.filter((r) => {
|
|
66
|
+
if (!r.lastAttendedAt) return true;
|
|
67
|
+
return new Date(r.lastAttendedAt).getTime() < cutoff;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (rules.sourceEventId?.trim()) {
|
|
72
|
+
const sourceId = rules.sourceEventId.trim();
|
|
73
|
+
rows = rows.filter((r) => r.sourceEventIds?.includes(sourceId));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (rules.excludeEventId?.trim()) {
|
|
77
|
+
const excludeId = rules.excludeEventId.trim();
|
|
78
|
+
rows = rows.filter((r) => !r.sourceEventIds?.includes(excludeId));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (rules.tierTitle?.trim()) {
|
|
82
|
+
const tier = rules.tierTitle.trim().toLowerCase();
|
|
83
|
+
rows = rows.filter((r) =>
|
|
84
|
+
r.tierTitles.some((t) => t.toLowerCase() === tier)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (rules.city?.trim()) {
|
|
89
|
+
const cityNeedle = rules.city.trim().toLowerCase();
|
|
90
|
+
rows = rows.filter((r) => (r.city ?? "").toLowerCase().includes(cityNeedle));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return rows;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const HOST_CRM_PRESET_SEGMENTS: Array<{
|
|
97
|
+
id: string;
|
|
98
|
+
label: string;
|
|
99
|
+
description: string;
|
|
100
|
+
rules: HostCrmSegmentRules;
|
|
101
|
+
}> = [
|
|
102
|
+
{
|
|
103
|
+
id: "repeat_guests",
|
|
104
|
+
label: "Repeat guests (2+ bashes)",
|
|
105
|
+
description: "People who have been to at least two of your bashes",
|
|
106
|
+
rules: { minEventCount: 2 },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: "lapsed_90",
|
|
110
|
+
label: "Haven't been in 90 days",
|
|
111
|
+
description: "Past attendees whose last bash was more than 90 days ago",
|
|
112
|
+
rules: { lapsedDays: 90 },
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: "lapsed_180",
|
|
116
|
+
label: "Win-back (6+ months)",
|
|
117
|
+
description: "Past attendees who haven't been out with you in six months",
|
|
118
|
+
rules: { lapsedDays: 180 },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "cadence_often",
|
|
122
|
+
label: "Goes out often",
|
|
123
|
+
description: "Self-reported cadence: Often or Always",
|
|
124
|
+
rules: {},
|
|
125
|
+
},
|
|
126
|
+
];
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,8 @@ export * from "./venueMatch.js";
|
|
|
26
26
|
export * from "./entertainmentNotableVenues.js";
|
|
27
27
|
export * from "./productLaunch.js";
|
|
28
28
|
export * from "./accessScreening.js";
|
|
29
|
+
export * from "./hostCrmSegments.js";
|
|
30
|
+
export * from "./hostCrmConstants.js";
|
|
29
31
|
export * from "./userReportTypes.js";
|
|
30
32
|
export * from "./mirroredPrismaEnums.js";
|
|
31
33
|
export * from "./utils/featuredDiscoveryGeo.js";
|
|
@@ -405,6 +407,8 @@ export * from "./utils/service/serviceRateUtils.js";
|
|
|
405
407
|
export * from "./utils/serviceAvailabilityDisplay.js";
|
|
406
408
|
export * from "./utils/entertainmentServiceTypeOptions.js";
|
|
407
409
|
export * from "./utils/reputationUtils.js";
|
|
410
|
+
export * from "./utils/reviewDimensionUtils.js";
|
|
411
|
+
export * from "./utils/trustScoreUtils.js";
|
|
408
412
|
export * from "./utils/stripeAccountUtils.js";
|
|
409
413
|
export * from "./utils/userUtils.js";
|
|
410
414
|
export * from "./utils/venueTypeFormatter.js";
|