@bash-app/bash-common 30.381.0 → 30.385.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.
Files changed (91) hide show
  1. package/dist/__tests__/accessScreening.test.js +39 -1
  2. package/dist/__tests__/accessScreening.test.js.map +1 -1
  3. package/dist/__tests__/applicationFunnel.test.d.ts +2 -0
  4. package/dist/__tests__/applicationFunnel.test.d.ts.map +1 -0
  5. package/dist/__tests__/applicationFunnel.test.js +126 -0
  6. package/dist/__tests__/applicationFunnel.test.js.map +1 -0
  7. package/dist/__tests__/definitionsHelpers.test.js +20 -1
  8. package/dist/__tests__/definitionsHelpers.test.js.map +1 -1
  9. package/dist/__tests__/fundingMeter.test.d.ts +2 -0
  10. package/dist/__tests__/fundingMeter.test.d.ts.map +1 -0
  11. package/dist/__tests__/fundingMeter.test.js +129 -0
  12. package/dist/__tests__/fundingMeter.test.js.map +1 -0
  13. package/dist/__tests__/giveaway.test.d.ts +2 -0
  14. package/dist/__tests__/giveaway.test.d.ts.map +1 -0
  15. package/dist/__tests__/giveaway.test.js +65 -0
  16. package/dist/__tests__/giveaway.test.js.map +1 -0
  17. package/dist/__tests__/promoterCompetitionUtils.test.d.ts +2 -0
  18. package/dist/__tests__/promoterCompetitionUtils.test.d.ts.map +1 -0
  19. package/dist/__tests__/promoterCompetitionUtils.test.js +262 -0
  20. package/dist/__tests__/promoterCompetitionUtils.test.js.map +1 -0
  21. package/dist/accessScreening.d.ts +18 -3
  22. package/dist/accessScreening.d.ts.map +1 -1
  23. package/dist/accessScreening.js +26 -0
  24. package/dist/accessScreening.js.map +1 -1
  25. package/dist/applicationFunnel.d.ts +58 -0
  26. package/dist/applicationFunnel.d.ts.map +1 -0
  27. package/dist/applicationFunnel.js +147 -0
  28. package/dist/applicationFunnel.js.map +1 -0
  29. package/dist/definitions.d.ts +145 -5
  30. package/dist/definitions.d.ts.map +1 -1
  31. package/dist/definitions.js +137 -5
  32. package/dist/definitions.js.map +1 -1
  33. package/dist/extendedSchemas.d.ts +166 -1
  34. package/dist/extendedSchemas.d.ts.map +1 -1
  35. package/dist/extendedSchemas.js +7 -1
  36. package/dist/extendedSchemas.js.map +1 -1
  37. package/dist/fundingMeter.d.ts +66 -0
  38. package/dist/fundingMeter.d.ts.map +1 -0
  39. package/dist/fundingMeter.js +99 -0
  40. package/dist/fundingMeter.js.map +1 -0
  41. package/dist/giveaway.d.ts +71 -0
  42. package/dist/giveaway.d.ts.map +1 -0
  43. package/dist/giveaway.js +117 -0
  44. package/dist/giveaway.js.map +1 -0
  45. package/dist/index.d.ts +5 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +6 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/utils/__tests__/bashHostIdentity.test.d.ts +2 -0
  50. package/dist/utils/__tests__/bashHostIdentity.test.d.ts.map +1 -0
  51. package/dist/utils/__tests__/bashHostIdentity.test.js +39 -0
  52. package/dist/utils/__tests__/bashHostIdentity.test.js.map +1 -0
  53. package/dist/utils/__tests__/promoterCompetitionUtils.test.d.ts +2 -0
  54. package/dist/utils/__tests__/promoterCompetitionUtils.test.d.ts.map +1 -0
  55. package/dist/utils/__tests__/promoterCompetitionUtils.test.js +104 -0
  56. package/dist/utils/__tests__/promoterCompetitionUtils.test.js.map +1 -0
  57. package/dist/utils/__tests__/slugUtils.test.js +29 -2
  58. package/dist/utils/__tests__/slugUtils.test.js.map +1 -1
  59. package/dist/utils/bashHostIdentity.d.ts +18 -0
  60. package/dist/utils/bashHostIdentity.d.ts.map +1 -0
  61. package/dist/utils/bashHostIdentity.js +17 -0
  62. package/dist/utils/bashHostIdentity.js.map +1 -0
  63. package/dist/utils/promoterCompetitionUtils.d.ts +89 -0
  64. package/dist/utils/promoterCompetitionUtils.d.ts.map +1 -0
  65. package/dist/utils/promoterCompetitionUtils.js +158 -0
  66. package/dist/utils/promoterCompetitionUtils.js.map +1 -0
  67. package/dist/utils/slugUtils.d.ts +2 -0
  68. package/dist/utils/slugUtils.d.ts.map +1 -1
  69. package/dist/utils/slugUtils.js +5 -1
  70. package/dist/utils/slugUtils.js.map +1 -1
  71. package/package.json +1 -1
  72. package/prisma/schema.prisma +415 -25
  73. package/src/__tests__/accessScreening.test.ts +69 -1
  74. package/src/__tests__/applicationFunnel.test.ts +197 -0
  75. package/src/__tests__/definitionsHelpers.test.ts +24 -0
  76. package/src/__tests__/fundingMeter.test.ts +143 -0
  77. package/src/__tests__/giveaway.test.ts +111 -0
  78. package/src/__tests__/promoterCompetitionUtils.test.ts +334 -0
  79. package/src/accessScreening.ts +49 -0
  80. package/src/applicationFunnel.ts +225 -0
  81. package/src/definitions.ts +181 -5
  82. package/src/extendedSchemas.ts +127 -1
  83. package/src/fundingMeter.ts +188 -0
  84. package/src/giveaway.ts +182 -0
  85. package/src/index.ts +14 -0
  86. package/src/utils/__tests__/bashHostIdentity.test.ts +57 -0
  87. package/src/utils/__tests__/promoterCompetitionUtils.test.ts +128 -0
  88. package/src/utils/__tests__/slugUtils.test.ts +44 -0
  89. package/src/utils/bashHostIdentity.ts +30 -0
  90. package/src/utils/promoterCompetitionUtils.ts +235 -0
  91. package/src/utils/slugUtils.ts +11 -0
@@ -268,6 +268,19 @@ model Competition {
268
268
  /// How 1st/2nd/3rd place icons appear on leaderboards and profiles
269
269
  placementAwardStyle PlacementAwardStyle @default(Trophy)
270
270
 
271
+ /// PROMOTER competitions only — purpose distinguishes consumer contests from ticket-sales races
272
+ competitionPurpose CompetitionPurpose @default(CONSUMER)
273
+ /// PROMOTER: total ticket-sale goal for the competition
274
+ promoterSalesGoal Int?
275
+ /// PROMOTER: maximum organizer cash reward exposure in cents
276
+ promoterRewardBudgetCents Int?
277
+ /// PROMOTER: how milestone rewards are calculated
278
+ promoterRewardMode PromoterRewardMode @default(TOTAL_AT_MILESTONE)
279
+ /// PROMOTER: who can enroll as a promoter
280
+ promoterRecruitMode PromoterRecruitMode @default(InviteOnly)
281
+ /// PROMOTER: attribution window in days (purchases within N days of referral click count)
282
+ referralWindowDays Int? @default(30)
283
+
271
284
  /// Optional Adventures rewards when someone becomes an ACCEPTED participant
272
285
  participationBashPoints Int?
273
286
  participationEventBadgeId String?
@@ -292,6 +305,11 @@ model Competition {
292
305
  judgeScores CompetitionJudgeScore[]
293
306
  notifications Notification[]
294
307
  sideQuests SideQuest[]
308
+ giveaways Giveaway[]
309
+ promoterMilestones PromoterMilestone[]
310
+ promoterRewards PromoterReward[]
311
+ promoters Promoter[]
312
+ promoterEventPerformances PromoterEventPerformance[]
295
313
  participationEventBadge EventBadge? @relation("CompetitionParticipationBadge", fields: [participationEventBadgeId], references: [id], onDelete: SetNull)
296
314
  finalistEventBadge EventBadge? @relation("CompetitionFinalistBadge", fields: [finalistEventBadgeId], references: [id], onDelete: SetNull)
297
315
  winEventBadge EventBadge? @relation("CompetitionWinBadge", fields: [winEventBadgeId], references: [id], onDelete: SetNull)
@@ -305,6 +323,67 @@ model Competition {
305
323
  @@index([winEventBadgeId])
306
324
  }
307
325
 
326
+ /// Threshold-based reward step in a PROMOTER competition.
327
+ /// Each row defines what a promoter earns when they hit `threshold` qualified ticket sales,
328
+ /// or `rank` (e.g. rank=1 for first-place prize).
329
+ model PromoterMilestone {
330
+ id String @id @default(cuid())
331
+ competitionId String
332
+ /// null = rank-based prize (use `rank`); non-null = threshold-based
333
+ threshold Int?
334
+ /// null = threshold-based (use `threshold`); 1 = first-place prize, etc.
335
+ rank Int?
336
+ rewardType PromoterRewardType
337
+ /// Cash reward in cents (used when rewardType = CASH)
338
+ rewardAmountCents Int?
339
+ /// Human-readable label shown to promoters (e.g. "Free VIP upgrade")
340
+ rewardDescription String?
341
+ /// Ticket tier to award (for TICKET / TICKET_UPGRADE rewards)
342
+ ticketTierId String?
343
+ /// Linked sponsor prize (for SPONSOR_PRIZE rewards)
344
+ sponsorPrizeId String?
345
+ /// Controls display order in UIs
346
+ displayOrder Int @default(0)
347
+ createdAt DateTime @default(now())
348
+ updatedAt DateTime @updatedAt
349
+
350
+ competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
351
+ rewards PromoterReward[]
352
+
353
+ @@index([competitionId])
354
+ @@index([threshold])
355
+ @@index([rank])
356
+ }
357
+
358
+ /// Tracks a specific reward earned (or pending/revoked) by a promoter participant.
359
+ /// Created when a promoter's qualified sales cross a milestone threshold or they win a rank prize.
360
+ model PromoterReward {
361
+ id String @id @default(cuid())
362
+ competitionId String
363
+ /// FK to Promoter.id (the per-bash promoter row, not User)
364
+ promoterId String
365
+ /// null for rank-based rewards (use `rank`)
366
+ milestoneId String?
367
+ /// For rank prizes (1 = first place, etc.)
368
+ rank Int?
369
+ /// Snapshot of qualified sales at the time this reward status was last updated
370
+ qualifiedSalesSnapshot Int @default(0)
371
+ status PromoterRewardStatus @default(PENDING)
372
+ earnedAt DateTime?
373
+ fulfilledAt DateTime?
374
+ revokedAt DateTime?
375
+ revokeReason String?
376
+ createdAt DateTime @default(now())
377
+ updatedAt DateTime @updatedAt
378
+
379
+ competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
380
+ milestone PromoterMilestone? @relation(fields: [milestoneId], references: [id], onDelete: SetNull)
381
+
382
+ @@index([competitionId, promoterId])
383
+ @@index([promoterId])
384
+ @@index([status])
385
+ }
386
+
308
387
  model CompetitionJudgeAssignment {
309
388
  id String @id @default(cuid())
310
389
  competitionId String
@@ -611,10 +690,16 @@ model Promoter {
611
690
  updatedAt DateTime @updatedAt
612
691
  userId String? // null until invited promoter signs up (do not use host id as placeholder)
613
692
  invitedEmail String? // normalized invitee email for reconciliation when userId is null
693
+ /// When set, this promoter is enrolled in a specific PROMOTER competition
694
+ competitionId String?
695
+ /// Cached count of qualified ticket sales for the linked competition (updated async after checkout/refund)
696
+ qualifiedSales Int @default(0)
614
697
  promoCodes BashEventPromoCode[]
615
698
  promoterUser User? @relation(fields: [userId], references: [id])
699
+ competition Competition? @relation(fields: [competitionId], references: [id], onDelete: SetNull)
616
700
 
617
701
  @@unique([promoterId, promotedForBash])
702
+ @@index([competitionId])
618
703
  }
619
704
 
620
705
  model PromoterStats {
@@ -632,11 +717,85 @@ model PromoterStats {
632
717
  pendingCredits Int @default(0)
633
718
  availableCredits Int @default(0)
634
719
  promoterInvitations Int @default(0)
720
+ // Cross-competition lifetime aggregates (added in promoter network layer migration)
721
+ totalCompetitionsJoined Int @default(0)
722
+ totalCompetitionsWithSale Int @default(0)
723
+ totalMilestonesReached Int @default(0)
724
+ totalCompetitionsWon Int @default(0) // rank=1 achieved
725
+ totalEventsPromoted Int @default(0)
726
+ repeatOrganizerCount Int @default(0) // distinct organizers worked with 2+ times
727
+ lifetimeQualifiedTickets Int @default(0)
728
+ lifetimeRevenueCents Int @default(0)
635
729
  createdAt DateTime @default(now())
636
730
  updatedAt DateTime @updatedAt
637
731
  user User @relation(fields: [userId], references: [id])
638
732
  }
639
733
 
734
+ /// Persisted per-promoter per-event performance record.
735
+ /// Created when a promoter joins a PROMOTER competition; updated async after each qualified sale/refund.
736
+ /// Survives competition end and event archival.
737
+ model PromoterEventPerformance {
738
+ id String @id @default(cuid())
739
+ promoterUserId String
740
+ bashEventId String
741
+ competitionId String
742
+
743
+ clicks Int @default(0)
744
+ checkoutStarts Int @default(0)
745
+ qualifiedOrders Int @default(0)
746
+ qualifiedTickets Int @default(0)
747
+ revenueGeneratedCents Int @default(0)
748
+
749
+ highestMilestoneThreshold Int?
750
+ finalRank Int?
751
+
752
+ eventCategory String?
753
+ eventCity String?
754
+ eventRegion String?
755
+
756
+ joinedAt DateTime @default(now())
757
+ lastSaleAt DateTime?
758
+ completedAt DateTime?
759
+ createdAt DateTime @default(now())
760
+ updatedAt DateTime @updatedAt
761
+
762
+ promoterUser User @relation("PromoterEventPerformances", fields: [promoterUserId], references: [id], onDelete: Cascade)
763
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
764
+ competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
765
+
766
+ @@unique([promoterUserId, competitionId])
767
+ @@index([promoterUserId])
768
+ @@index([bashEventId])
769
+ @@index([eventCategory, promoterUserId])
770
+ @@index([eventCity, promoterUserId])
771
+ }
772
+
773
+ /// Cross-event relationship between a host (organizer) and a promoter.
774
+ /// Updated each time they work together. Enables "Invite Previous Promoters" and relationship depth metrics.
775
+ model OrganizerPromoterRelationship {
776
+ id String @id @default(cuid())
777
+ organizerUserId String
778
+ promoterUserId String
779
+
780
+ timesInvited Int @default(1)
781
+ timesJoined Int @default(0)
782
+ eventsWithSale Int @default(0)
783
+ totalTicketsGenerated Int @default(0)
784
+ totalRevenueGeneratedCents Int @default(0)
785
+ lastWorkedTogetherAt DateTime?
786
+ lastInvitedAt DateTime @default(now())
787
+
788
+ createdAt DateTime @default(now())
789
+ updatedAt DateTime @updatedAt
790
+
791
+ organizer User @relation("OrganizerPromoterRelationshipsAsOrganizer", fields: [organizerUserId], references: [id], onDelete: Cascade)
792
+ promoter User @relation("OrganizerPromoterRelationshipsAsPromoter", fields: [promoterUserId], references: [id], onDelete: Cascade)
793
+
794
+ @@unique([organizerUserId, promoterUserId])
795
+ @@index([organizerUserId])
796
+ @@index([promoterUserId])
797
+ }
798
+
640
799
  model PromoterClick {
641
800
  id String @id @default(cuid())
642
801
  promoterId String
@@ -936,6 +1095,12 @@ model BashEvent {
936
1095
  /// When true, only accounts with a published service profile (Vendor/Venue/Entertainment/Exhibitor/Sponsor/Event
937
1096
  /// Service) can view budget lines revealed via `Budget.showOnBashDetailPage`. Default off = anyone can click to view.
938
1097
  budgetRestrictedToServiceProviders Boolean @default(false)
1098
+ /// Public funding meter: value coming in toward included budget lines (not host spend).
1099
+ showFundingMeter Boolean @default(false)
1100
+ /// When false, public can Pay now only — Commit / pledge POST is rejected.
1101
+ fundingAllowCommitments Boolean @default(true)
1102
+ /// When true, open pledges fill the lighter meter segment. Default off: bar is collected only.
1103
+ fundingShowPledgesOnMeter Boolean @default(false)
939
1104
  /// When true on Rsvp-mode events, affirmative RSVPs land in a host approval queue first.
940
1105
  rsvpRequiresApproval Boolean @default(false)
941
1106
  /// Hide when guests RSVP'd on host-facing attendee views.
@@ -943,6 +1108,8 @@ model BashEvent {
943
1108
  /// Only allow inviting people who share a mutual bash with the host.
944
1109
  invitesLimitedToMutuals Boolean @default(false)
945
1110
  showOrganizerTitles Boolean @default(false)
1111
+ /// When false, public/guest surfaces hide host identity (cards + Meet the host).
1112
+ showHostOnBashPage Boolean @default(true)
946
1113
  /// When true, low-stock scarcity badges show "X% left" instead of "Only N left".
947
1114
  showPercentRemaining Boolean @default(false)
948
1115
  servicePreferences Json? // New tiered status: { "Entertainment": "need", "Sponsors": "booked_closed", ... }
@@ -1002,6 +1169,14 @@ model BashEvent {
1002
1169
  guestsCanInviteOthers Boolean @default(false)
1003
1170
  /// Private-event screening: route access requests to a host approval queue
1004
1171
  accessScreeningEnabled Boolean @default(false)
1172
+ /// Optional host application (markup + questions) before RSVP or ticket checkout.
1173
+ applicationEnabled Boolean @default(false)
1174
+ /// Rich-text intro shown at the top of the guest application.
1175
+ applicationIntroHtml String? @db.Text
1176
+ /// After this time, guests cannot submit a new application. Hosts can still review.
1177
+ applicationClosesAt DateTime?
1178
+ /// When true, auto-approve applicants this host has approved on a prior bash.
1179
+ autoApproveRegulars Boolean @default(false)
1005
1180
  /// Collaborative post-event photo/video album contributions
1006
1181
  albumEnabled Boolean @default(false)
1007
1182
  /// When true, attendee uploads require host approval before appearing in the public album.
@@ -1038,6 +1213,10 @@ model BashEvent {
1038
1213
  organizationId String?
1039
1214
  organization Organization? @relation("OrganizationBashEvents", fields: [organizationId], references: [id])
1040
1215
 
1216
+ /// Host-as-service identity. XOR with organizationId (enforced in upsert + DB check).
1217
+ hostedByServiceId String?
1218
+ hostedByService Service? @relation("BashEventHostedByService", fields: [hostedByServiceId], references: [id], onDelete: SetNull)
1219
+
1041
1220
  // Organization access controls (only apply when organizationId is set)
1042
1221
  requiresOrgMembership Boolean @default(false) // Must be member to attend/RSVP
1043
1222
  allowMemberGuests Boolean @default(false) // Members can bring +1s
@@ -1149,7 +1328,10 @@ model BashEvent {
1149
1328
  eventReferralRewards EventReferralReward[]
1150
1329
  eventReferrals EventReferral[]
1151
1330
  discussionComments EventDiscussionComment[]
1152
- bashEventDonations BashEventDonation[]
1331
+ bashEventDonations BashEventDonation[]
1332
+ budgetContributions BudgetContribution[]
1333
+ giveaways Giveaway[]
1334
+ promoterEventPerformances PromoterEventPerformance[]
1153
1335
 
1154
1336
  // Adventures / Side Quests
1155
1337
  sideQuests SideQuest[]
@@ -1215,6 +1397,7 @@ model BashEvent {
1215
1397
  @@index([templateId])
1216
1398
  @@index([parentEventId])
1217
1399
  @@index([organizationId])
1400
+ @@index([hostedByServiceId])
1218
1401
  @@index([eventSeriesId])
1219
1402
  @@index([status, aiApprovalVerdict])
1220
1403
  }
@@ -2025,11 +2208,16 @@ model PublicBashRsvp {
2025
2208
  plusOneNames String[] @default([])
2026
2209
  /// Set when the guest keeps or declines after a postponement; cleared on each new Postpone.
2027
2210
  postponeRespondedAt DateTime?
2211
+ /// Optional guest photo submitted with an RSVP application.
2212
+ applicationPhotoUrl String?
2213
+ /// Optional host sentence included when an RSVP application is denied.
2214
+ hostNote String?
2028
2215
  createdAt DateTime @default(now())
2029
2216
  updatedAt DateTime @updatedAt
2030
2217
 
2031
- bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
2032
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
2218
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
2219
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
2220
+ applicationAnswers BashEventQuestionAnswer[]
2033
2221
 
2034
2222
  @@unique([bashEventId, userId])
2035
2223
  @@index([bashEventId])
@@ -2190,6 +2378,7 @@ model SideQuest {
2190
2378
  eventBadge EventBadge? @relation(fields: [eventBadgeId], references: [id], onDelete: SetNull)
2191
2379
  competition Competition? @relation(fields: [competitionId], references: [id], onDelete: SetNull)
2192
2380
  completions SideQuestCompletion[]
2381
+ giveaways Giveaway[]
2193
2382
  milestonesUnlocking SideQuestMilestone[] @relation("MilestoneUnlocksQuest")
2194
2383
  checkpoints SideQuestCheckpoint[]
2195
2384
  progressHits SideQuestProgressHit[]
@@ -2464,34 +2653,57 @@ enum BashEventQuestionType {
2464
2653
  YesNo
2465
2654
  }
2466
2655
 
2467
- /// Host-defined custom RSVP / checkout question for a bash.
2656
+ /// What happens when a choice answer is not in acceptableOptions.
2657
+ enum QuestionMismatchAction {
2658
+ None
2659
+ FlagForReview
2660
+ AutoDeny
2661
+ }
2662
+
2663
+ /// Host-defined custom RSVP / checkout / application question for a bash.
2468
2664
  model BashEventCustomQuestion {
2469
- id String @id @default(cuid())
2470
- bashEventId String
2471
- prompt String
2472
- type BashEventQuestionType @default(ShortText)
2473
- options String[] @default([])
2474
- required Boolean @default(false)
2475
- sortOrder Int @default(0)
2476
- createdAt DateTime @default(now())
2477
- updatedAt DateTime @updatedAt
2665
+ id String @id @default(cuid())
2666
+ bashEventId String
2667
+ prompt String
2668
+ type BashEventQuestionType @default(ShortText)
2669
+ options String[] @default([])
2670
+ required Boolean @default(false)
2671
+ sortOrder Int @default(0)
2672
+ /// null = every applying surface (all application tiers + RSVP).
2673
+ ticketTierId String?
2674
+ mismatchAction QuestionMismatchAction @default(None)
2675
+ acceptableOptions String[] @default([])
2676
+ /// When set, only show this question if the parent answer matches showWhenAnswer.
2677
+ parentQuestionId String?
2678
+ showWhenAnswer String?
2679
+ createdAt DateTime @default(now())
2680
+ updatedAt DateTime @updatedAt
2478
2681
 
2479
2682
  @@index([bashEventId])
2683
+ @@index([ticketTierId])
2684
+ @@index([parentQuestionId])
2480
2685
  }
2481
2686
 
2482
- /// An attendee's answer to a custom question (captured at RSVP / checkout).
2687
+ /// An attendee's answer to a custom question (captured at RSVP / checkout / application).
2483
2688
  model BashEventQuestionAnswer {
2484
- id String @id @default(cuid())
2485
- questionId String
2486
- bashEventId String
2487
- userId String?
2488
- email String?
2489
- ticketId String?
2490
- answer String
2491
- createdAt DateTime @default(now())
2689
+ id String @id @default(cuid())
2690
+ questionId String
2691
+ bashEventId String
2692
+ userId String?
2693
+ email String?
2694
+ ticketId String?
2695
+ accessRequestId String?
2696
+ publicRsvpId String?
2697
+ answer String
2698
+ createdAt DateTime @default(now())
2699
+
2700
+ accessRequest BashEventAccessRequest? @relation(fields: [accessRequestId], references: [id], onDelete: SetNull)
2701
+ publicRsvp PublicBashRsvp? @relation(fields: [publicRsvpId], references: [id], onDelete: SetNull)
2492
2702
 
2493
2703
  @@index([questionId])
2494
2704
  @@index([bashEventId])
2705
+ @@index([accessRequestId])
2706
+ @@index([publicRsvpId])
2495
2707
  }
2496
2708
 
2497
2709
  enum BashEventAccessRequestStatus {
@@ -2510,10 +2722,17 @@ model BashEventAccessRequest {
2510
2722
  status BashEventAccessRequestStatus @default(Pending)
2511
2723
  reviewedByUserId String?
2512
2724
  reviewedAt DateTime?
2513
- createdAt DateTime @default(now())
2514
- updatedAt DateTime @updatedAt
2725
+ /// Optional host sentence included when the request is denied.
2726
+ hostNote String?
2727
+ ticketTierId String?
2728
+ applicationPhotoUrl String?
2729
+ createdAt DateTime @default(now())
2730
+ updatedAt DateTime @updatedAt
2731
+
2732
+ answers BashEventQuestionAnswer[]
2515
2733
 
2516
2734
  @@index([bashEventId, status])
2735
+ @@index([ticketTierId])
2517
2736
  }
2518
2737
 
2519
2738
  /// A collaborative album contribution (photo/video URL) for a bash.
@@ -3324,6 +3543,8 @@ model TicketTier {
3324
3543
 
3325
3544
  /// Host opt-in: buyers of this tier may choose mailed or venue will-call physical delivery (see TicketPrintOrder).
3326
3545
  allowsPhysicalDelivery Boolean @default(false)
3546
+ /// Guests must complete the event application and be approved before buying this tier.
3547
+ requiresApplication Boolean @default(false)
3327
3548
 
3328
3549
  @@unique([bashEventId, title])
3329
3550
  @@index([availableAt])
@@ -3474,6 +3695,7 @@ model Ticket {
3474
3695
  flyerConversions FlyerConversion[]
3475
3696
  coatCheckEntitlements CoatCheckEntitlement[]
3476
3697
  printOrder TicketPrintOrder?
3698
+ giveawayRedemptions GiveawayRedemption[]
3477
3699
 
3478
3700
  @@index([bashEventId])
3479
3701
  @@index([bashEventId, seatId])
@@ -4451,6 +4673,7 @@ model User {
4451
4673
  eventFeaturedGuestSpots EventFeaturedGuest[] @relation("FeaturedGuestOnEvent")
4452
4674
  eventAttendeeVisibilities EventAttendeeVisibility[] @relation("AttendeeVisibilityOnEvent")
4453
4675
  bashEventDonations BashEventDonation[]
4676
+ budgetContributions BudgetContribution[]
4454
4677
  bashCreativeSubmissions BashCreativeSubmission[]
4455
4678
  ticketsOnWaitlist Ticket[] @relation("TicketsOnWaitlist")
4456
4679
  transfersFrom TicketTransfer[] @relation("TransfersFrom")
@@ -4557,6 +4780,9 @@ model User {
4557
4780
  coatCheckEntitlementsOwned CoatCheckEntitlement[] @relation("CoatCheckOwner")
4558
4781
  coatCheckEntitlementsCheckedIn CoatCheckEntitlement[] @relation("CoatCheckCheckedInBy")
4559
4782
  coatCheckEntitlementsClaimed CoatCheckEntitlement[] @relation("CoatCheckClaimedBy")
4783
+ giveawayRedemptions GiveawayRedemption[] @relation("GiveawayRedemptionUser")
4784
+ giveawayRedemptionsReceived GiveawayRedemption[] @relation("GiveawayReceivedBy")
4785
+ giveawayRedemptionsVoided GiveawayRedemption[] @relation("GiveawayVoidedBy")
4560
4786
  bashBusWaitlistSignup BashBusWaitlistSignup?
4561
4787
  collectibleRedemptions CollectibleRedemption[]
4562
4788
  bashFeedSaves BashFeedSave[]
@@ -4636,6 +4862,11 @@ model User {
4636
4862
 
4637
4863
  phoneVerificationCodes PhoneVerificationCode[]
4638
4864
  smsSendLogs SmsSendLog[]
4865
+
4866
+ // Promoter Network Layer relations
4867
+ promoterEventPerformances PromoterEventPerformance[] @relation("PromoterEventPerformances")
4868
+ organizerPromoterRelationshipsAsOrganizer OrganizerPromoterRelationship[] @relation("OrganizerPromoterRelationshipsAsOrganizer")
4869
+ organizerPromoterRelationshipsAsPromoter OrganizerPromoterRelationship[] @relation("OrganizerPromoterRelationshipsAsPromoter")
4639
4870
  }
4640
4871
 
4641
4872
  model PhoneVerificationCode {
@@ -5285,6 +5516,7 @@ model Service {
5285
5516
  serviceCategoryRequests ServiceCategoryRequest[] // Services created from approved category requests
5286
5517
  associatedBashesReferencingMe AssociatedBash[] @relation("AssociatedBashToService")
5287
5518
  bashEvent BashEvent[] @relation("BashEventToService")
5519
+ hostedBashEvents BashEvent[] @relation("BashEventHostedByService")
5288
5520
  media Media[] @relation("MediaToService")
5289
5521
  analytics ServiceAnalytics[] // Track service analytics
5290
5522
  scoutReferrals ScoutReferral[] @relation("ScoutServiceProfile")
@@ -5308,6 +5540,7 @@ model Service {
5308
5540
  serviceReviews ServiceReview[]
5309
5541
  providerHostReviews ProviderHostReview[]
5310
5542
  sideQuestCheckpoints SideQuestCheckpoint[]
5543
+ giveaways Giveaway[]
5311
5544
  serviceRankings ServiceRankings?
5312
5545
 
5313
5546
  @@index([serviceListingStripeSubscriptionId])
@@ -6993,6 +7226,8 @@ model Budget {
6993
7226
  remainingCents Int? // Can be computed: totalAllocatedCents - spentCents
6994
7227
  /// Host opt-in: reveal this line's category + amount to providers on the public bash detail page.
6995
7228
  showOnBashDetailPage Boolean @default(false)
7229
+ /// When true (default), this line's allocation is part of the public funding goal.
7230
+ includeOnFundingMeter Boolean @default(true)
6996
7231
 
6997
7232
  // Categorization
6998
7233
  category String? // "Marketing", "Catering", "Venue", "Annual", "Activities"
@@ -7022,7 +7257,8 @@ model Budget {
7022
7257
  service Service? @relation("ServiceBudgets", fields: [serviceId], references: [id], onDelete: Cascade)
7023
7258
  submitter User? @relation("BudgetSubmitter", fields: [submittedBy], references: [id])
7024
7259
  approver User? @relation("BudgetApprover", fields: [approverId], references: [id])
7025
- expenses Expense[]
7260
+ expenses Expense[]
7261
+ contributions BudgetContribution[]
7026
7262
 
7027
7263
  @@index([organizationId])
7028
7264
  @@index([bashEventId])
@@ -7034,6 +7270,116 @@ model Budget {
7034
7270
  @@index([fiscalYear])
7035
7271
  }
7036
7272
 
7273
+ enum BudgetContributionKind {
7274
+ Cash
7275
+ InKind
7276
+ }
7277
+
7278
+ enum BudgetContributionStatus {
7279
+ Pledged
7280
+ Paid
7281
+ Fulfilled
7282
+ Declined
7283
+ Cancelled
7284
+ }
7285
+
7286
+ enum BudgetContributionSource {
7287
+ Stripe
7288
+ HostEntered
7289
+ PublicPledge
7290
+ }
7291
+
7292
+ /// Value coming in toward a bash budget line (host-entered, public pledge, or Stripe).
7293
+ /// `budgetId` is required on create for HostEntered / PublicPledge / Stripe; null is unallocated only.
7294
+ model BudgetContribution {
7295
+ id String @id @default(cuid())
7296
+ bashEventId String
7297
+ budgetId String?
7298
+ contributorUserId String?
7299
+ displayName String
7300
+ kind BudgetContributionKind
7301
+ status BudgetContributionStatus
7302
+ source BudgetContributionSource
7303
+ amountCents Int
7304
+ note String?
7305
+ stripeCheckoutSessionId String? @unique
7306
+ stripePaymentIntentId String? @unique
7307
+ createdAt DateTime @default(now())
7308
+ updatedAt DateTime @updatedAt
7309
+
7310
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
7311
+ budget Budget? @relation(fields: [budgetId], references: [id], onDelete: SetNull)
7312
+ contributor User? @relation(fields: [contributorUserId], references: [id], onDelete: SetNull)
7313
+
7314
+ @@index([bashEventId])
7315
+ @@index([budgetId])
7316
+ @@index([contributorUserId])
7317
+ @@index([status])
7318
+ }
7319
+
7320
+ enum GiveawayEligibility {
7321
+ AnyoneGoing
7322
+ TicketHolders
7323
+ CheckedIn
7324
+ CompetitionParticipants
7325
+ SideQuestCompleters
7326
+ }
7327
+
7328
+ /// Host/sponsor swag handed out at the bash. Not merch (for sale) and not a competition prize.
7329
+ model Giveaway {
7330
+ id String @id @default(cuid())
7331
+ bashEventId String
7332
+ name String
7333
+ description String?
7334
+ imageUrl String?
7335
+ quantityTotal Int
7336
+ quantityRemaining Int
7337
+ displayOrder Int @default(0)
7338
+ sponsorLabel String?
7339
+ sponsorServiceId String?
7340
+ eligibility GiveawayEligibility
7341
+ competitionId String?
7342
+ sideQuestId String?
7343
+ pickupNote String?
7344
+ createdAt DateTime @default(now())
7345
+ updatedAt DateTime @updatedAt
7346
+
7347
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
7348
+ competition Competition? @relation(fields: [competitionId], references: [id], onDelete: SetNull)
7349
+ sideQuest SideQuest? @relation(fields: [sideQuestId], references: [id], onDelete: SetNull)
7350
+ sponsorService Service? @relation(fields: [sponsorServiceId], references: [id], onDelete: SetNull)
7351
+ redemptions GiveawayRedemption[]
7352
+
7353
+ @@index([bashEventId])
7354
+ @@index([bashEventId, displayOrder])
7355
+ @@index([competitionId])
7356
+ @@index([sideQuestId])
7357
+ }
7358
+
7359
+ /// Event-team confirmed handout. Partial unique (giveawayId, attendeeId) WHERE voidedAt IS NULL — migration SQL only.
7360
+ model GiveawayRedemption {
7361
+ id String @id @default(cuid())
7362
+ giveawayId String
7363
+ attendeeId String
7364
+ userId String?
7365
+ ticketId String?
7366
+ receivedAt DateTime @default(now())
7367
+ receivedByUserId String
7368
+ voidedAt DateTime?
7369
+ voidedByUserId String?
7370
+
7371
+ giveaway Giveaway @relation(fields: [giveawayId], references: [id], onDelete: Cascade)
7372
+ user User? @relation("GiveawayRedemptionUser", fields: [userId], references: [id], onDelete: SetNull)
7373
+ ticket Ticket? @relation(fields: [ticketId], references: [id], onDelete: SetNull)
7374
+ receivedBy User @relation("GiveawayReceivedBy", fields: [receivedByUserId], references: [id], onDelete: Restrict)
7375
+ voidedBy User? @relation("GiveawayVoidedBy", fields: [voidedByUserId], references: [id], onDelete: SetNull)
7376
+
7377
+ @@index([giveawayId])
7378
+ @@index([attendeeId])
7379
+ @@index([ticketId])
7380
+ @@index([userId])
7381
+ }
7382
+
7037
7383
  enum ExpenseExtractionStatus {
7038
7384
  none
7039
7385
  pending
@@ -8176,6 +8522,13 @@ enum NotificationType {
8176
8522
  CompetitionPrizeOfferReceived // Host: someone offered a prize contribution
8177
8523
  CompetitionPrizeOfferAccepted // Offeror: host accepted your prize offer
8178
8524
  CompetitionPrizeOfferDeclined // Offeror: host declined your prize offer
8525
+ PromoterCompetitionJoined // Organizer: a promoter joined the challenge
8526
+ PromoterCompetitionMilestoneReached // Promoter: you hit a milestone and earned a reward
8527
+ PromoterCompetitionLeaderboardChange // Promoter: your leaderboard rank changed
8528
+ PromoterCompetitionEnding // Promoter: competition ending in 24h (with earned progress)
8529
+ PromoterCompetitionCompleted // Promoter + organizer: competition finished
8530
+ PromoterCompetitionRewardFulfilled // Promoter: organizer marked your reward as fulfilled
8531
+ PreviousPromoterInvited // Promoter: organizer you've worked with is inviting you back
8179
8532
  BashRating
8180
8533
  BashRatingMilestone
8181
8534
  BashFeedPost
@@ -8872,6 +9225,41 @@ enum CompetitionType {
8872
9225
  AUDIENCE_VOTING // Audience Voting-Based
8873
9226
  }
8874
9227
 
9228
+ enum CompetitionPurpose {
9229
+ CONSUMER // existing behavior — vote/bracket/raffle/contest
9230
+ PROMOTER // ticket sales contest — rewards promoters per milestone
9231
+ }
9232
+
9233
+ enum PromoterRewardMode {
9234
+ TOTAL_AT_MILESTONE // "10 sales = $40 total earned" (default)
9235
+ ADDITIVE // milestone rewards stack on each other
9236
+ }
9237
+
9238
+ enum PromoterRecruitMode {
9239
+ Public // anyone can join from bash page
9240
+ InviteOnly // organizer sends invites (default)
9241
+ Private // no enrollment shown publicly
9242
+ }
9243
+
9244
+ enum PromoterRewardType {
9245
+ CASH
9246
+ TICKET
9247
+ TICKET_UPGRADE
9248
+ TABLE
9249
+ ACCESS
9250
+ MERCH
9251
+ SPONSOR_PRIZE
9252
+ CREDIT
9253
+ CUSTOM
9254
+ }
9255
+
9256
+ enum PromoterRewardStatus {
9257
+ PENDING // milestone not yet reached
9258
+ EARNED // threshold crossed; awaiting fulfillment
9259
+ FULFILLED // organizer marked delivered
9260
+ REVOKED // refunds pushed promoter below threshold; FULFILLED rewards are never auto-revoked
9261
+ }
9262
+
8875
9263
  enum PlacementAwardStyle {
8876
9264
  Trophy // Gold/silver/bronze trophies for 1st/2nd/3rd
8877
9265
  Medal // Gold/silver/bronze medals for 1st/2nd/3rd
@@ -12212,6 +12600,8 @@ model AppliedDiscount {
12212
12600
  attributionCampaignId String?
12213
12601
  commissionRate Float?
12214
12602
  commissionAmount Int? // In cents
12603
+ /// Set when this discount was applied under a PROMOTER competition — used for attribution recalculation
12604
+ competitionId String?
12215
12605
 
12216
12606
  // Display
12217
12607
  displayReason String? // "Early Bird Discount" for receipt