@bash-app/bash-common 30.391.0 → 30.396.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 +5 -0
- package/dist/__tests__/bashSectionTitleForBashType.test.js.map +1 -1
- package/dist/__tests__/budgetApproval.test.d.ts +2 -0
- package/dist/__tests__/budgetApproval.test.d.ts.map +1 -0
- package/dist/__tests__/budgetApproval.test.js +109 -0
- package/dist/__tests__/budgetApproval.test.js.map +1 -0
- package/dist/__tests__/crewSocialCopy.test.d.ts +2 -0
- package/dist/__tests__/crewSocialCopy.test.d.ts.map +1 -0
- package/dist/__tests__/crewSocialCopy.test.js +21 -0
- package/dist/__tests__/crewSocialCopy.test.js.map +1 -0
- package/dist/__tests__/discoveryScope.test.d.ts +2 -0
- package/dist/__tests__/discoveryScope.test.d.ts.map +1 -0
- package/dist/__tests__/discoveryScope.test.js +40 -0
- package/dist/__tests__/discoveryScope.test.js.map +1 -0
- package/dist/__tests__/suggestedNight.test.d.ts +2 -0
- package/dist/__tests__/suggestedNight.test.d.ts.map +1 -0
- package/dist/__tests__/suggestedNight.test.js +10 -0
- package/dist/__tests__/suggestedNight.test.js.map +1 -0
- package/dist/__tests__/suggestionScore.test.d.ts +2 -0
- package/dist/__tests__/suggestionScore.test.d.ts.map +1 -0
- package/dist/__tests__/suggestionScore.test.js +68 -0
- package/dist/__tests__/suggestionScore.test.js.map +1 -0
- package/dist/__tests__/userTaste.test.d.ts +2 -0
- package/dist/__tests__/userTaste.test.d.ts.map +1 -0
- package/dist/__tests__/userTaste.test.js +21 -0
- package/dist/__tests__/userTaste.test.js.map +1 -0
- package/dist/budgetApproval.d.ts +126 -0
- package/dist/budgetApproval.d.ts.map +1 -0
- package/dist/budgetApproval.js +161 -0
- package/dist/budgetApproval.js.map +1 -0
- package/dist/crewSocialCopy.d.ts +16 -0
- package/dist/crewSocialCopy.d.ts.map +1 -0
- package/dist/crewSocialCopy.js +23 -0
- package/dist/crewSocialCopy.js.map +1 -0
- package/dist/definitions.d.ts +1 -0
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +6 -0
- package/dist/definitions.js.map +1 -1
- package/dist/discoveryScope.d.ts +48 -0
- package/dist/discoveryScope.d.ts.map +1 -0
- package/dist/discoveryScope.js +114 -0
- package/dist/discoveryScope.js.map +1 -0
- package/dist/home.d.ts +6 -1
- package/dist/home.d.ts.map +1 -1
- package/dist/home.js +5 -1
- package/dist/home.js.map +1 -1
- package/dist/hostCrmCrowdPulse.d.ts +2 -0
- package/dist/hostCrmCrowdPulse.d.ts.map +1 -1
- package/dist/hostCrmCrowdPulse.js.map +1 -1
- package/dist/hostCrmSegments.d.ts +6 -0
- package/dist/hostCrmSegments.d.ts.map +1 -1
- package/dist/hostCrmSegments.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/sms/smsTemplates.d.ts +6 -1
- package/dist/sms/smsTemplates.d.ts.map +1 -1
- package/dist/sms/smsTemplates.js +1 -0
- package/dist/sms/smsTemplates.js.map +1 -1
- package/dist/suggestedNight.d.ts +12 -0
- package/dist/suggestedNight.d.ts.map +1 -0
- package/dist/suggestedNight.js +12 -0
- package/dist/suggestedNight.js.map +1 -0
- package/dist/suggestionScore.d.ts +42 -0
- package/dist/suggestionScore.d.ts.map +1 -0
- package/dist/suggestionScore.js +100 -0
- package/dist/suggestionScore.js.map +1 -0
- package/dist/userTaste.d.ts +40 -0
- package/dist/userTaste.d.ts.map +1 -0
- package/dist/userTaste.js +103 -0
- package/dist/userTaste.js.map +1 -0
- package/dist/utils/__tests__/featuredDiscoveryGeo.test.js +9 -1
- package/dist/utils/__tests__/featuredDiscoveryGeo.test.js.map +1 -1
- package/dist/utils/featuredDiscoveryGeo.d.ts +7 -0
- package/dist/utils/featuredDiscoveryGeo.d.ts.map +1 -1
- package/dist/utils/featuredDiscoveryGeo.js +11 -0
- package/dist/utils/featuredDiscoveryGeo.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +152 -50
- package/src/__tests__/bashSectionTitleForBashType.test.ts +10 -0
- package/src/__tests__/budgetApproval.test.ts +153 -0
- package/src/__tests__/crewSocialCopy.test.ts +25 -0
- package/src/__tests__/discoveryScope.test.ts +65 -0
- package/src/__tests__/suggestedNight.test.ts +11 -0
- package/src/__tests__/suggestionScore.test.ts +81 -0
- package/src/__tests__/userTaste.test.ts +33 -0
- package/src/budgetApproval.ts +303 -0
- package/src/crewSocialCopy.ts +37 -0
- package/src/definitions.ts +6 -0
- package/src/discoveryScope.ts +170 -0
- package/src/home.ts +15 -0
- package/src/hostCrmCrowdPulse.ts +2 -0
- package/src/hostCrmSegments.ts +6 -0
- package/src/index.ts +12 -3
- package/src/sms/smsTemplates.ts +6 -0
- package/src/suggestedNight.ts +20 -0
- package/src/suggestionScore.ts +140 -0
- package/src/userTaste.ts +152 -0
- package/src/utils/__tests__/featuredDiscoveryGeo.test.ts +13 -0
- package/src/utils/featuredDiscoveryGeo.ts +15 -0
package/prisma/schema.prisma
CHANGED
|
@@ -9,38 +9,6 @@ datasource db {
|
|
|
9
9
|
directUrl = env("POSTGRES_URL_NON_POOLING")
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
model Club {
|
|
13
|
-
id String @id @default(cuid())
|
|
14
|
-
name String
|
|
15
|
-
street String
|
|
16
|
-
userId String
|
|
17
|
-
price Int?
|
|
18
|
-
events BashEvent[]
|
|
19
|
-
admin ClubAdmin[]
|
|
20
|
-
members ClubMember[]
|
|
21
|
-
budgets Budget[] @relation("ClubBudgets")
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
model ClubAdmin {
|
|
25
|
-
id String @id @default(cuid())
|
|
26
|
-
userId String
|
|
27
|
-
clubId String
|
|
28
|
-
role ClubAdminRole
|
|
29
|
-
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
30
|
-
admin User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
model ClubMember {
|
|
34
|
-
id String @id @default(cuid())
|
|
35
|
-
userId String
|
|
36
|
-
clubId String
|
|
37
|
-
status ClubMemberStatus
|
|
38
|
-
statusHistory Json
|
|
39
|
-
price Int?
|
|
40
|
-
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
41
|
-
member User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
12
|
model BashComment {
|
|
45
13
|
id String @id @default(cuid())
|
|
46
14
|
creatorId String
|
|
@@ -937,6 +905,9 @@ model BashEvent {
|
|
|
937
905
|
/// Curated cause IDs from `CAUSE_TAGS` (bash-common) for discovery/search later.
|
|
938
906
|
causeTags String[] @default([])
|
|
939
907
|
eventType String @default("Other")
|
|
908
|
+
/// How far the draw goes — orthogonal to eventType. Hosts may set Local/State only.
|
|
909
|
+
discoveryScope DiscoveryScope @default(Unspecified)
|
|
910
|
+
discoveryScopeLabel String?
|
|
940
911
|
timezone String?
|
|
941
912
|
startDateTime DateTime?
|
|
942
913
|
endDateTime DateTime?
|
|
@@ -994,7 +965,6 @@ model BashEvent {
|
|
|
994
965
|
status BashStatus @default(Draft)
|
|
995
966
|
tags String[]
|
|
996
967
|
coverPhoto String?
|
|
997
|
-
clubId String?
|
|
998
968
|
dateTimePublished DateTime?
|
|
999
969
|
includedItems String[]
|
|
1000
970
|
videoLink String?
|
|
@@ -1276,13 +1246,13 @@ model BashEvent {
|
|
|
1276
1246
|
associatedBashesReferencingMe AssociatedBash[]
|
|
1277
1247
|
comments BashComment[]
|
|
1278
1248
|
amountOfGuests AmountOfGuests? @relation(fields: [amountOfGuestsId], references: [id], onDelete: Cascade)
|
|
1279
|
-
club Club? @relation(fields: [clubId], references: [id])
|
|
1280
1249
|
creator User @relation("CreatedEvent", fields: [creatorId], references: [id], onDelete: Cascade)
|
|
1281
1250
|
targetAudience TargetAudience? @relation(fields: [targetAudienceId], references: [id], onDelete: Cascade)
|
|
1282
1251
|
transferredFrom User? @relation("TransferredFrom", fields: [transferredFromId], references: [id])
|
|
1283
1252
|
payoutUser User? @relation("BashEventsPayoutRecipient", fields: [payoutUserId], references: [id], onDelete: SetNull)
|
|
1284
1253
|
venue Venue? @relation(fields: [venueId], references: [id])
|
|
1285
1254
|
messages BashEventMessage[]
|
|
1255
|
+
conversationMutes BashEventConversationMute[]
|
|
1286
1256
|
transferRequests BashEventTransferRequest[]
|
|
1287
1257
|
updateNotificationQueue EventUpdateNotificationQueue[]
|
|
1288
1258
|
slugHistory BashSlugHistory[]
|
|
@@ -1390,6 +1360,7 @@ model BashEvent {
|
|
|
1390
1360
|
|
|
1391
1361
|
// Organization Relations
|
|
1392
1362
|
budgets Budget[] @relation("EventBudgets")
|
|
1363
|
+
budgetApprovalRequests BudgetApprovalRequest[]
|
|
1393
1364
|
rsvps EventRSVP[] @relation("EventRSVPs")
|
|
1394
1365
|
|
|
1395
1366
|
// Groups & page views (Phases 0–4)
|
|
@@ -1435,6 +1406,7 @@ model BashEvent {
|
|
|
1435
1406
|
locationShareSessions LocationShareSession[]
|
|
1436
1407
|
/// Private Memories vault captures for this bash
|
|
1437
1408
|
memoryCaptures BashMemoryCapture[]
|
|
1409
|
+
suggestedNights SuggestedNight[]
|
|
1438
1410
|
|
|
1439
1411
|
@@index([templateId])
|
|
1440
1412
|
@@index([parentEventId])
|
|
@@ -1442,6 +1414,7 @@ model BashEvent {
|
|
|
1442
1414
|
@@index([hostedByServiceId])
|
|
1443
1415
|
@@index([eventSeriesId])
|
|
1444
1416
|
@@index([status, aiApprovalVerdict])
|
|
1417
|
+
@@index([discoveryScope, startDateTime])
|
|
1445
1418
|
}
|
|
1446
1419
|
|
|
1447
1420
|
/// Lineup / attribution roster for an event (optional act list for checkout tagging).
|
|
@@ -4624,6 +4597,7 @@ model User {
|
|
|
4624
4597
|
bashEventsPayoutRecipient BashEvent[] @relation("BashEventsPayoutRecipient")
|
|
4625
4598
|
bashEventMessagesSent BashEventMessage[] @relation("BashMessageSender")
|
|
4626
4599
|
bashEventMessagesRead BashEventMessageRead[] @relation("BashMessageReader")
|
|
4600
|
+
bashEventConversationMutes BashEventConversationMute[]
|
|
4627
4601
|
transferRequestsFrom BashEventTransferRequest[] @relation("TransferRequestsFrom")
|
|
4628
4602
|
transferRequestsTo BashEventTransferRequest[] @relation("TransferRequestsTo")
|
|
4629
4603
|
venueFBCarryoverCredits VenueFBCarryoverCredit[] @relation("VenueFBCarryoverHost")
|
|
@@ -4634,8 +4608,6 @@ model User {
|
|
|
4634
4608
|
blogPosts BlogPost[] @relation("BlogAuthor")
|
|
4635
4609
|
checkouts Checkout[]
|
|
4636
4610
|
waiverAcceptances WaiverAcceptance[]
|
|
4637
|
-
clubAdmins ClubAdmin[]
|
|
4638
|
-
clubMembers ClubMember[]
|
|
4639
4611
|
competitions Competition[]
|
|
4640
4612
|
competitionSponsorships CompetitionSponsor[]
|
|
4641
4613
|
competitionPrizeOffersSubmitted CompetitionPrizeOffer[] @relation("CompetitionPrizeOffersSubmitted")
|
|
@@ -4855,6 +4827,8 @@ model User {
|
|
|
4855
4827
|
approvedBudgets Budget[] @relation("BudgetApprover")
|
|
4856
4828
|
submittedBudgets Budget[] @relation("BudgetSubmitter")
|
|
4857
4829
|
ownedBudgets Budget[] @relation("UserBudgets")
|
|
4830
|
+
budgetApprovalsRequested BudgetApprovalRequest[] @relation("BudgetApprovalRequester")
|
|
4831
|
+
budgetApprovalsAsApprover BudgetApprovalRequest[] @relation("BudgetApprovalApprover")
|
|
4858
4832
|
departmentsHeaded Department[] @relation("DepartmentHead")
|
|
4859
4833
|
/// Enterprise platform (Phase 0-4): internal team members assigned as a named
|
|
4860
4834
|
/// account manager, and org members who submit/approve procurement requests
|
|
@@ -4895,6 +4869,8 @@ model User {
|
|
|
4895
4869
|
// Activity Intent / Experience Discovery
|
|
4896
4870
|
userDiscoveryPreferences UserDiscoveryPreferences?
|
|
4897
4871
|
activityIntents ActivityIntent[]
|
|
4872
|
+
tastePreferences UserTastePreference[]
|
|
4873
|
+
suggestedNights SuggestedNight[]
|
|
4898
4874
|
experienceInvitations ExperienceInvitation[]
|
|
4899
4875
|
experienceOpportunitiesCreated ExperienceOpportunity[] @relation("ExperienceOpportunityCreatedBy")
|
|
4900
4876
|
|
|
@@ -4944,6 +4920,12 @@ model UserPreferences {
|
|
|
4944
4920
|
organizerUpdatesNotify Boolean @default(true)
|
|
4945
4921
|
eventSuggestions Boolean @default(true)
|
|
4946
4922
|
eventReminderNotify Boolean @default(true)
|
|
4923
|
+
/// Opt-in: engine may propose nights (Build my nights strip).
|
|
4924
|
+
suggestedNightsEnabled Boolean @default(false)
|
|
4925
|
+
/// Favorite-nudge email/push. Migrated to match eventReminderNotify.
|
|
4926
|
+
suggestedNightReminderNotify Boolean @default(true)
|
|
4927
|
+
/// Hosts in the guest's CRM may filter blasts by taste chips.
|
|
4928
|
+
hostsMayTargetTaste Boolean @default(true)
|
|
4947
4929
|
newMessageNotify Boolean @default(true)
|
|
4948
4930
|
invitationNotify Boolean @default(true)
|
|
4949
4931
|
commentNotify Boolean @default(true)
|
|
@@ -5053,6 +5035,38 @@ model UserPreferences {
|
|
|
5053
5035
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
5054
5036
|
}
|
|
5055
5037
|
|
|
5038
|
+
model UserTastePreference {
|
|
5039
|
+
id String @id @default(cuid())
|
|
5040
|
+
userId String
|
|
5041
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
5042
|
+
kind UserTasteKind
|
|
5043
|
+
value String
|
|
5044
|
+
weight Int @default(1)
|
|
5045
|
+
source UserTasteSource @default(Explicit)
|
|
5046
|
+
updatedAt DateTime @updatedAt
|
|
5047
|
+
|
|
5048
|
+
@@unique([userId, kind, value])
|
|
5049
|
+
@@index([userId, kind])
|
|
5050
|
+
}
|
|
5051
|
+
|
|
5052
|
+
model SuggestedNight {
|
|
5053
|
+
id String @id @default(cuid())
|
|
5054
|
+
userId String
|
|
5055
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
5056
|
+
bashEventId String
|
|
5057
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
5058
|
+
status SuggestedNightStatus @default(Proposed)
|
|
5059
|
+
reason String[] @default([])
|
|
5060
|
+
proposedAt DateTime @default(now())
|
|
5061
|
+
acceptedAt DateTime?
|
|
5062
|
+
dismissedAt DateTime?
|
|
5063
|
+
remindedAt DateTime?
|
|
5064
|
+
|
|
5065
|
+
@@unique([userId, bashEventId])
|
|
5066
|
+
@@index([userId, status])
|
|
5067
|
+
@@index([bashEventId, status])
|
|
5068
|
+
}
|
|
5069
|
+
|
|
5056
5070
|
model UserFollowing {
|
|
5057
5071
|
id String @id @default(cuid())
|
|
5058
5072
|
userId String
|
|
@@ -7263,7 +7277,6 @@ model Budget {
|
|
|
7263
7277
|
// Flexible ownership - at least ONE should be set
|
|
7264
7278
|
organizationId String?
|
|
7265
7279
|
bashEventId String?
|
|
7266
|
-
clubId String?
|
|
7267
7280
|
departmentId String?
|
|
7268
7281
|
userId String? // For personal event budgets
|
|
7269
7282
|
/// Optional: budgets tied to a service profile (vendor/exhibitor/sponsor owner costs).
|
|
@@ -7301,7 +7314,6 @@ model Budget {
|
|
|
7301
7314
|
// Relations
|
|
7302
7315
|
organization Organization? @relation("OrganizationBudgets", fields: [organizationId], references: [id], onDelete: Cascade)
|
|
7303
7316
|
bashEvent BashEvent? @relation("EventBudgets", fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
7304
|
-
club Club? @relation("ClubBudgets", fields: [clubId], references: [id], onDelete: Cascade)
|
|
7305
7317
|
department Department? @relation("DepartmentBudgets", fields: [departmentId], references: [id], onDelete: Cascade)
|
|
7306
7318
|
user User? @relation("UserBudgets", fields: [userId], references: [id], onDelete: Cascade)
|
|
7307
7319
|
service Service? @relation("ServiceBudgets", fields: [serviceId], references: [id], onDelete: Cascade)
|
|
@@ -7312,7 +7324,6 @@ model Budget {
|
|
|
7312
7324
|
|
|
7313
7325
|
@@index([organizationId])
|
|
7314
7326
|
@@index([bashEventId])
|
|
7315
|
-
@@index([clubId])
|
|
7316
7327
|
@@index([departmentId])
|
|
7317
7328
|
@@index([userId])
|
|
7318
7329
|
@@index([serviceId])
|
|
@@ -7320,6 +7331,66 @@ model Budget {
|
|
|
7320
7331
|
@@index([fiscalYear])
|
|
7321
7332
|
}
|
|
7322
7333
|
|
|
7334
|
+
enum BudgetApprovalKind {
|
|
7335
|
+
CapAsk
|
|
7336
|
+
PlanSignOff
|
|
7337
|
+
}
|
|
7338
|
+
|
|
7339
|
+
enum BudgetApprovalStatus {
|
|
7340
|
+
Pending
|
|
7341
|
+
Approved
|
|
7342
|
+
ChangesRequested
|
|
7343
|
+
Declined
|
|
7344
|
+
Cancelled
|
|
7345
|
+
Superseded
|
|
7346
|
+
}
|
|
7347
|
+
|
|
7348
|
+
/// Event-level budget sign-off or pre-plan cap ask. Not per-line Budget.status.
|
|
7349
|
+
model BudgetApprovalRequest {
|
|
7350
|
+
id String @id @default(cuid())
|
|
7351
|
+
bashEventId String
|
|
7352
|
+
kind BudgetApprovalKind
|
|
7353
|
+
status BudgetApprovalStatus @default(Pending)
|
|
7354
|
+
requesterUserId String
|
|
7355
|
+
hostNote String?
|
|
7356
|
+
approverEmail String
|
|
7357
|
+
approverName String?
|
|
7358
|
+
approverUserId String?
|
|
7359
|
+
token String @unique @default(cuid())
|
|
7360
|
+
expiresAt DateTime
|
|
7361
|
+
snapshotJson Json
|
|
7362
|
+
snapshotTotalCents Int
|
|
7363
|
+
authorizedCents Int?
|
|
7364
|
+
decisionNote String?
|
|
7365
|
+
decidedAt DateTime?
|
|
7366
|
+
staleAt DateTime?
|
|
7367
|
+
createdAt DateTime @default(now())
|
|
7368
|
+
updatedAt DateTime @updatedAt
|
|
7369
|
+
|
|
7370
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
7371
|
+
requester User @relation("BudgetApprovalRequester", fields: [requesterUserId], references: [id], onDelete: Cascade)
|
|
7372
|
+
approverUser User? @relation("BudgetApprovalApprover", fields: [approverUserId], references: [id], onDelete: SetNull)
|
|
7373
|
+
lineNotes BudgetApprovalLineNote[]
|
|
7374
|
+
|
|
7375
|
+
@@index([bashEventId, status])
|
|
7376
|
+
@@index([approverEmail])
|
|
7377
|
+
}
|
|
7378
|
+
|
|
7379
|
+
model BudgetApprovalLineNote {
|
|
7380
|
+
id String @id @default(cuid())
|
|
7381
|
+
requestId String
|
|
7382
|
+
budgetId String?
|
|
7383
|
+
nameSnapshot String
|
|
7384
|
+
categorySnapshot String?
|
|
7385
|
+
allocatedCentsSnapshot Int
|
|
7386
|
+
note String
|
|
7387
|
+
createdAt DateTime @default(now())
|
|
7388
|
+
|
|
7389
|
+
request BudgetApprovalRequest @relation(fields: [requestId], references: [id], onDelete: Cascade)
|
|
7390
|
+
|
|
7391
|
+
@@index([requestId])
|
|
7392
|
+
}
|
|
7393
|
+
|
|
7323
7394
|
enum BudgetContributionKind {
|
|
7324
7395
|
Cash
|
|
7325
7396
|
InKind
|
|
@@ -8510,18 +8581,17 @@ model BashEventMessageRead {
|
|
|
8510
8581
|
@@index([userId])
|
|
8511
8582
|
}
|
|
8512
8583
|
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8584
|
+
/// Per-user mute for the night-of bash inbox thread (not discussion, not blasts).
|
|
8585
|
+
model BashEventConversationMute {
|
|
8586
|
+
id String @id @default(cuid())
|
|
8587
|
+
userId String
|
|
8588
|
+
bashEventId String
|
|
8589
|
+
mutedAt DateTime @default(now())
|
|
8590
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
8591
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
8518
8592
|
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
Inactive
|
|
8522
|
-
Canceled
|
|
8523
|
-
Paused
|
|
8524
|
-
Removed
|
|
8593
|
+
@@unique([userId, bashEventId])
|
|
8594
|
+
@@index([bashEventId])
|
|
8525
8595
|
}
|
|
8526
8596
|
|
|
8527
8597
|
enum TaskStatus {
|
|
@@ -8624,6 +8694,10 @@ enum NotificationType {
|
|
|
8624
8694
|
SideQuestWindowOpened // Attendee: a timed Side Quest just went live
|
|
8625
8695
|
/// Attendee/ticket holder: host changed significant bash details (time, place, title, …)
|
|
8626
8696
|
EventDetailsUpdated
|
|
8697
|
+
/// Approver already on Bash: host sent a budget sign-off or cap ask
|
|
8698
|
+
BudgetApprovalRequested
|
|
8699
|
+
/// Host: approver approved, requested changes, or declined a budget request
|
|
8700
|
+
BudgetApprovalDecided
|
|
8627
8701
|
}
|
|
8628
8702
|
|
|
8629
8703
|
/// Opt-in reminder for a scheduled tier release (v1.5).
|
|
@@ -8674,6 +8748,34 @@ enum BashEventSource {
|
|
|
8674
8748
|
Partner // Generic: use sourcePartnerId for partner key (e.g. 'mixlog', custom integrations)
|
|
8675
8749
|
}
|
|
8676
8750
|
|
|
8751
|
+
/// How far an event's draw goes. Orthogonal to BashEventType.
|
|
8752
|
+
enum DiscoveryScope {
|
|
8753
|
+
Local
|
|
8754
|
+
State
|
|
8755
|
+
National
|
|
8756
|
+
International
|
|
8757
|
+
Unspecified
|
|
8758
|
+
}
|
|
8759
|
+
|
|
8760
|
+
enum UserTasteKind {
|
|
8761
|
+
EventType
|
|
8762
|
+
CauseTag
|
|
8763
|
+
MusicGenre
|
|
8764
|
+
DiscoveryScope
|
|
8765
|
+
}
|
|
8766
|
+
|
|
8767
|
+
enum UserTasteSource {
|
|
8768
|
+
Explicit
|
|
8769
|
+
Inferred
|
|
8770
|
+
}
|
|
8771
|
+
|
|
8772
|
+
enum SuggestedNightStatus {
|
|
8773
|
+
Proposed
|
|
8774
|
+
Accepted
|
|
8775
|
+
Dismissed
|
|
8776
|
+
Expired
|
|
8777
|
+
}
|
|
8778
|
+
|
|
8677
8779
|
/// Channel used to create a BashEvent. See BashEvent.creationChannel doc comment.
|
|
8678
8780
|
enum CreationChannel {
|
|
8679
8781
|
Web
|
|
@@ -52,6 +52,16 @@ describe("bashSectionTitleForBashType", () => {
|
|
|
52
52
|
it("'Ideas' → 'Ideas'", () => {
|
|
53
53
|
expect(bashSectionTitleForBashType("Ideas")).toBe("Ideas");
|
|
54
54
|
});
|
|
55
|
+
|
|
56
|
+
it("scope rows use destination copy", () => {
|
|
57
|
+
expect(bashSectionTitleForBashType("National")).toBe(
|
|
58
|
+
"Across the country"
|
|
59
|
+
);
|
|
60
|
+
expect(bashSectionTitleForBashType("International")).toBe(
|
|
61
|
+
"Worth the flight"
|
|
62
|
+
);
|
|
63
|
+
expect(bashSectionTitleForBashType("CrewGoing")).toBe("The crew's going");
|
|
64
|
+
});
|
|
55
65
|
});
|
|
56
66
|
|
|
57
67
|
describe("BashEventType enum values get pluralized", () => {
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildBudgetApprovalSnapshot,
|
|
3
|
+
budgetApprovalSnapshotsMatch,
|
|
4
|
+
eventBudgetApprovalWizardPath,
|
|
5
|
+
formatBudgetCents,
|
|
6
|
+
isBudgetApprovalExpired,
|
|
7
|
+
isValidApproverEmail,
|
|
8
|
+
normalizeApproverEmail,
|
|
9
|
+
parseBudgetApprovalSnapshot,
|
|
10
|
+
resolveApproverDisplayName,
|
|
11
|
+
validateBudgetApprovalDecide,
|
|
12
|
+
} from "../budgetApproval.js";
|
|
13
|
+
|
|
14
|
+
describe("budgetApproval helpers", () => {
|
|
15
|
+
it("normalizes and validates approver emails", () => {
|
|
16
|
+
expect(normalizeApproverEmail(" Pat@Acme.com ")).toBe("pat@acme.com");
|
|
17
|
+
expect(isValidApproverEmail("pat@acme.com")).toBe(true);
|
|
18
|
+
expect(isValidApproverEmail("not-an-email")).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("hashes snapshots in a stable, order-independent way", () => {
|
|
22
|
+
const a = buildBudgetApprovalSnapshot([
|
|
23
|
+
{ id: "b", name: "DJ", category: null, totalAllocatedCents: 20000 },
|
|
24
|
+
{ id: "a", name: "Venue", category: "Rentals", totalAllocatedCents: 10000 },
|
|
25
|
+
]);
|
|
26
|
+
const b = buildBudgetApprovalSnapshot([
|
|
27
|
+
{ id: "a", name: "Venue", category: "Rentals", totalAllocatedCents: 10000 },
|
|
28
|
+
{ id: "b", name: "DJ", category: null, totalAllocatedCents: 20000 },
|
|
29
|
+
]);
|
|
30
|
+
expect(a.totalCents).toBe(30000);
|
|
31
|
+
expect(a.hash).toBe(b.hash);
|
|
32
|
+
expect(
|
|
33
|
+
budgetApprovalSnapshotsMatch(a.lines, parseBudgetApprovalSnapshot(b.lines))
|
|
34
|
+
).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("detects drift when an amount changes", () => {
|
|
38
|
+
const original = buildBudgetApprovalSnapshot([
|
|
39
|
+
{ id: "a", name: "Venue", category: null, totalAllocatedCents: 10000 },
|
|
40
|
+
]);
|
|
41
|
+
const changed = buildBudgetApprovalSnapshot([
|
|
42
|
+
{ id: "a", name: "Venue", category: null, totalAllocatedCents: 12000 },
|
|
43
|
+
]);
|
|
44
|
+
expect(budgetApprovalSnapshotsMatch(original.lines, changed.lines)).toBe(
|
|
45
|
+
false
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("requires cents on cap-ask approve and a note on request-changes", () => {
|
|
50
|
+
expect(
|
|
51
|
+
validateBudgetApprovalDecide({
|
|
52
|
+
action: "approve",
|
|
53
|
+
kind: "CapAsk",
|
|
54
|
+
authorizedCents: 0,
|
|
55
|
+
})
|
|
56
|
+
).toEqual({
|
|
57
|
+
ok: false,
|
|
58
|
+
error: "Enter an authorized amount greater than zero.",
|
|
59
|
+
});
|
|
60
|
+
expect(
|
|
61
|
+
validateBudgetApprovalDecide({
|
|
62
|
+
action: "approve",
|
|
63
|
+
kind: "CapAsk",
|
|
64
|
+
authorizedCents: 50000,
|
|
65
|
+
}).ok
|
|
66
|
+
).toBe(true);
|
|
67
|
+
expect(
|
|
68
|
+
validateBudgetApprovalDecide({
|
|
69
|
+
action: "changes",
|
|
70
|
+
kind: "PlanSignOff",
|
|
71
|
+
})
|
|
72
|
+
).toEqual({
|
|
73
|
+
ok: false,
|
|
74
|
+
error: "Add an overall note or a note on at least one line.",
|
|
75
|
+
});
|
|
76
|
+
expect(
|
|
77
|
+
validateBudgetApprovalDecide({
|
|
78
|
+
action: "changes",
|
|
79
|
+
kind: "PlanSignOff",
|
|
80
|
+
lineNotes: [
|
|
81
|
+
{
|
|
82
|
+
nameSnapshot: "Venue",
|
|
83
|
+
allocatedCentsSnapshot: 10000,
|
|
84
|
+
note: "Too high",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
}).ok
|
|
88
|
+
).toBe(true);
|
|
89
|
+
expect(
|
|
90
|
+
validateBudgetApprovalDecide({
|
|
91
|
+
action: "changes",
|
|
92
|
+
kind: "CapAsk",
|
|
93
|
+
lineNotes: [
|
|
94
|
+
{
|
|
95
|
+
nameSnapshot: "Venue",
|
|
96
|
+
allocatedCentsSnapshot: 10000,
|
|
97
|
+
note: "n/a",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
})
|
|
101
|
+
).toEqual({
|
|
102
|
+
ok: false,
|
|
103
|
+
error: "A cap ask has no budget lines to annotate.",
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("resolves display name decided > hint > email", () => {
|
|
108
|
+
expect(
|
|
109
|
+
resolveApproverDisplayName({
|
|
110
|
+
decidedName: "Pat Chen",
|
|
111
|
+
hostHintName: "Boss",
|
|
112
|
+
email: "pat@acme.com",
|
|
113
|
+
})
|
|
114
|
+
).toBe("Pat Chen");
|
|
115
|
+
expect(
|
|
116
|
+
resolveApproverDisplayName({
|
|
117
|
+
decidedName: " ",
|
|
118
|
+
hostHintName: "Boss",
|
|
119
|
+
email: "pat@acme.com",
|
|
120
|
+
})
|
|
121
|
+
).toBe("Boss");
|
|
122
|
+
expect(
|
|
123
|
+
resolveApproverDisplayName({
|
|
124
|
+
decidedName: null,
|
|
125
|
+
hostHintName: null,
|
|
126
|
+
email: "pat@acme.com",
|
|
127
|
+
})
|
|
128
|
+
).toBe("pat@acme.com");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("computes expiry from expiresAt", () => {
|
|
132
|
+
expect(
|
|
133
|
+
isBudgetApprovalExpired("2020-01-01T00:00:00.000Z", new Date("2026-01-01"))
|
|
134
|
+
).toBe(true);
|
|
135
|
+
expect(
|
|
136
|
+
isBudgetApprovalExpired("2030-01-01T00:00:00.000Z", new Date("2026-01-01"))
|
|
137
|
+
).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("formats money and the wizard deep link", () => {
|
|
141
|
+
expect(formatBudgetCents(420000)).toBe("$4,200");
|
|
142
|
+
expect(eventBudgetApprovalWizardPath("evt-1")).toContain(
|
|
143
|
+
"/bash-event-wizard/evt-1/"
|
|
144
|
+
);
|
|
145
|
+
expect(eventBudgetApprovalWizardPath("evt-1")).toContain("budgetTab=lines");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("parses invalid snapshot json as empty", () => {
|
|
149
|
+
expect(parseBudgetApprovalSnapshot(null)).toEqual([]);
|
|
150
|
+
expect(parseBudgetApprovalSnapshot("nope")).toEqual([]);
|
|
151
|
+
expect(parseBudgetApprovalSnapshot([{ id: 1 }])).toEqual([]);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { crewSocialProofCopy } from "../crewSocialCopy.js";
|
|
2
|
+
|
|
3
|
+
describe("crewSocialProofCopy", () => {
|
|
4
|
+
it("names visible crew", () => {
|
|
5
|
+
expect(
|
|
6
|
+
crewSocialProofCopy([
|
|
7
|
+
{ displayName: "Nico", attendanceVisible: true },
|
|
8
|
+
{ displayName: "Petra", attendanceVisible: true },
|
|
9
|
+
]).line
|
|
10
|
+
).toBe("Nico and Petra are going");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("does not use a count when names are hidden", () => {
|
|
14
|
+
const copy = crewSocialProofCopy([
|
|
15
|
+
{ displayName: "Sam", attendanceVisible: false },
|
|
16
|
+
{ displayName: "Alex", attendanceVisible: false },
|
|
17
|
+
]);
|
|
18
|
+
expect(copy.line).toBe("Your crew's on this one");
|
|
19
|
+
expect(copy.named).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("omits the line when nobody is going", () => {
|
|
23
|
+
expect(crewSocialProofCopy([]).line).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DiscoveryScope,
|
|
3
|
+
inferDiscoveryScopeFromText,
|
|
4
|
+
isHostSettableDiscoveryScope,
|
|
5
|
+
resolveDiscoveryScopeForWrite,
|
|
6
|
+
} from "../discoveryScope.js";
|
|
7
|
+
|
|
8
|
+
describe("discoveryScope", () => {
|
|
9
|
+
it("lets hosts set Local and State only", () => {
|
|
10
|
+
expect(isHostSettableDiscoveryScope(DiscoveryScope.Local)).toBe(true);
|
|
11
|
+
expect(isHostSettableDiscoveryScope(DiscoveryScope.State)).toBe(true);
|
|
12
|
+
expect(isHostSettableDiscoveryScope(DiscoveryScope.National)).toBe(false);
|
|
13
|
+
expect(isHostSettableDiscoveryScope(DiscoveryScope.International)).toBe(
|
|
14
|
+
false
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("infers State from state fair copy", () => {
|
|
19
|
+
expect(inferDiscoveryScopeFromText("Utah State Fair")).toBe(
|
|
20
|
+
DiscoveryScope.State
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("infers National from destination brands", () => {
|
|
25
|
+
expect(inferDiscoveryScopeFromText("EDC Las Vegas")).toBe(
|
|
26
|
+
DiscoveryScope.National
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("infers International from world-tour language", () => {
|
|
31
|
+
expect(inferDiscoveryScopeFromText("Tomorrowland world tour")).toBe(
|
|
32
|
+
DiscoveryScope.International
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("clamps host National without a hard signal", () => {
|
|
37
|
+
expect(
|
|
38
|
+
resolveDiscoveryScopeForWrite({
|
|
39
|
+
requested: DiscoveryScope.National,
|
|
40
|
+
role: "host",
|
|
41
|
+
title: "House party in Sugar House",
|
|
42
|
+
})
|
|
43
|
+
).toBe(DiscoveryScope.Unspecified);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("allows host National when inference already matches", () => {
|
|
47
|
+
expect(
|
|
48
|
+
resolveDiscoveryScopeForWrite({
|
|
49
|
+
requested: DiscoveryScope.National,
|
|
50
|
+
role: "host",
|
|
51
|
+
title: "Coachella Weekend 1",
|
|
52
|
+
})
|
|
53
|
+
).toBe(DiscoveryScope.National);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("lets scrapers set National", () => {
|
|
57
|
+
expect(
|
|
58
|
+
resolveDiscoveryScopeForWrite({
|
|
59
|
+
requested: DiscoveryScope.National,
|
|
60
|
+
role: "scraper",
|
|
61
|
+
title: "Random fest",
|
|
62
|
+
})
|
|
63
|
+
).toBe(DiscoveryScope.National);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { unfavoriteSuggestedNightAction } from "../suggestedNight.js";
|
|
2
|
+
|
|
3
|
+
describe("unfavoriteSuggestedNightAction", () => {
|
|
4
|
+
it("deletes when never reminded", () => {
|
|
5
|
+
expect(unfavoriteSuggestedNightAction(null)).toBe("delete");
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("dismisses after a nudge", () => {
|
|
9
|
+
expect(unfavoriteSuggestedNightAction(new Date())).toBe("dismiss");
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { DiscoveryScope } from "../discoveryScope.js";
|
|
2
|
+
import { scoreSuggestion, SUGGESTION_WEIGHTS } from "../suggestionScore.js";
|
|
3
|
+
import { UserTasteKind, UserTasteSource } from "../userTaste.js";
|
|
4
|
+
|
|
5
|
+
function base() {
|
|
6
|
+
return {
|
|
7
|
+
connectionsGoingVisible: 0,
|
|
8
|
+
connectionsGoingHidden: 0,
|
|
9
|
+
followedHost: false,
|
|
10
|
+
attendedHost: false,
|
|
11
|
+
eventType: "Festival",
|
|
12
|
+
causeTags: [] as string[],
|
|
13
|
+
musicGenre: null,
|
|
14
|
+
discoveryScope: DiscoveryScope.Local,
|
|
15
|
+
artistsToSee: [] as string[],
|
|
16
|
+
eventTitle: "Block party",
|
|
17
|
+
distanceMiles: 5,
|
|
18
|
+
radiusMiles: 50,
|
|
19
|
+
isFeatured: false,
|
|
20
|
+
trendingScore: 0,
|
|
21
|
+
paidFeatureTasteOverlap: false,
|
|
22
|
+
tastes: [] as Array<{
|
|
23
|
+
kind: string;
|
|
24
|
+
value: string;
|
|
25
|
+
weight: number;
|
|
26
|
+
source: string;
|
|
27
|
+
}>,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("scoreSuggestion", () => {
|
|
32
|
+
it("ranks crew going above paid taste overlap", () => {
|
|
33
|
+
const crew = scoreSuggestion({
|
|
34
|
+
...base(),
|
|
35
|
+
connectionsGoingVisible: 1,
|
|
36
|
+
});
|
|
37
|
+
const paid = scoreSuggestion({
|
|
38
|
+
...base(),
|
|
39
|
+
paidFeatureTasteOverlap: true,
|
|
40
|
+
tastes: [
|
|
41
|
+
{
|
|
42
|
+
kind: UserTasteKind.EventType,
|
|
43
|
+
value: "Festival",
|
|
44
|
+
weight: 1,
|
|
45
|
+
source: UserTasteSource.Explicit,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
expect(crew).toBeGreaterThan(SUGGESTION_WEIGHTS.paidFeatureTaste);
|
|
50
|
+
expect(crew).toBeGreaterThan(
|
|
51
|
+
paid - SUGGESTION_WEIGHTS.explicitTaste
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("lets travel taste break radius for National events only", () => {
|
|
56
|
+
const tastes = [
|
|
57
|
+
{
|
|
58
|
+
kind: UserTasteKind.DiscoveryScope,
|
|
59
|
+
value: DiscoveryScope.National,
|
|
60
|
+
weight: 1,
|
|
61
|
+
source: UserTasteSource.Explicit,
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
expect(
|
|
65
|
+
scoreSuggestion({
|
|
66
|
+
...base(),
|
|
67
|
+
discoveryScope: DiscoveryScope.National,
|
|
68
|
+
distanceMiles: 900,
|
|
69
|
+
tastes,
|
|
70
|
+
})
|
|
71
|
+
).toBeGreaterThan(0);
|
|
72
|
+
expect(
|
|
73
|
+
scoreSuggestion({
|
|
74
|
+
...base(),
|
|
75
|
+
discoveryScope: DiscoveryScope.Local,
|
|
76
|
+
distanceMiles: 900,
|
|
77
|
+
tastes,
|
|
78
|
+
})
|
|
79
|
+
).toBe(Number.NEGATIVE_INFINITY);
|
|
80
|
+
});
|
|
81
|
+
});
|