@bash-app/bash-common 30.281.0 → 30.285.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 (46) hide show
  1. package/dist/__tests__/storePrintArtworkUtils.test.d.ts +2 -0
  2. package/dist/__tests__/storePrintArtworkUtils.test.d.ts.map +1 -0
  3. package/dist/__tests__/storePrintArtworkUtils.test.js +35 -0
  4. package/dist/__tests__/storePrintArtworkUtils.test.js.map +1 -0
  5. package/dist/definitions.d.ts +3 -2
  6. package/dist/definitions.d.ts.map +1 -1
  7. package/dist/definitions.js +3 -2
  8. package/dist/definitions.js.map +1 -1
  9. package/dist/extendedSchemas.d.ts +15 -5
  10. package/dist/extendedSchemas.d.ts.map +1 -1
  11. package/dist/extendedSchemas.js.map +1 -1
  12. package/dist/icebreakerPrompts.d.ts +9 -0
  13. package/dist/icebreakerPrompts.d.ts.map +1 -0
  14. package/dist/icebreakerPrompts.js +77 -0
  15. package/dist/icebreakerPrompts.js.map +1 -0
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +4 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/mirroredPrismaEnums.d.ts +14 -0
  21. package/dist/mirroredPrismaEnums.d.ts.map +1 -1
  22. package/dist/mirroredPrismaEnums.js +13 -0
  23. package/dist/mirroredPrismaEnums.js.map +1 -1
  24. package/dist/storePrintArtworkUtils.d.ts +22 -0
  25. package/dist/storePrintArtworkUtils.d.ts.map +1 -0
  26. package/dist/storePrintArtworkUtils.js +74 -0
  27. package/dist/storePrintArtworkUtils.js.map +1 -0
  28. package/dist/storeTypes.d.ts +81 -0
  29. package/dist/storeTypes.d.ts.map +1 -0
  30. package/dist/storeTypes.js +20 -0
  31. package/dist/storeTypes.js.map +1 -0
  32. package/dist/venueMatch.d.ts +165 -0
  33. package/dist/venueMatch.d.ts.map +1 -0
  34. package/dist/venueMatch.js +68 -0
  35. package/dist/venueMatch.js.map +1 -0
  36. package/package.json +1 -1
  37. package/prisma/schema.prisma +382 -19
  38. package/src/__tests__/storePrintArtworkUtils.test.ts +53 -0
  39. package/src/definitions.ts +2 -2
  40. package/src/extendedSchemas.ts +25 -11
  41. package/src/icebreakerPrompts.ts +75 -0
  42. package/src/index.ts +4 -0
  43. package/src/mirroredPrismaEnums.ts +16 -0
  44. package/src/storePrintArtworkUtils.ts +93 -0
  45. package/src/storeTypes.ts +109 -0
  46. package/src/venueMatch.ts +202 -0
@@ -130,6 +130,7 @@ model BashFeedComment {
130
130
  parent BashFeedComment? @relation("FeedCommentReplies", fields: [parentId], references: [id], onDelete: Cascade)
131
131
  replies BashFeedComment[] @relation("FeedCommentReplies")
132
132
  likes BashFeedCommentLike[]
133
+ reports BashFeedReport[]
133
134
 
134
135
  @@index([postId])
135
136
  @@index([parentId])
@@ -169,7 +170,8 @@ model BashFeedRating {
169
170
  model BashFeedReport {
170
171
  id String @id @default(cuid())
171
172
  userId String
172
- postId String
173
+ postId String?
174
+ commentId String?
173
175
  reason BashFeedReportReason
174
176
  description String? @db.Text
175
177
  status ReportStatus @default(Pending)
@@ -177,11 +179,14 @@ model BashFeedReport {
177
179
  reviewedAt DateTime?
178
180
  createdAt DateTime @default(now())
179
181
 
180
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
181
- post BashFeedPost @relation(fields: [postId], references: [id], onDelete: Cascade)
182
- reviewer User? @relation("ReviewedReports", fields: [reviewedBy], references: [id])
182
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
183
+ post BashFeedPost? @relation(fields: [postId], references: [id], onDelete: Cascade)
184
+ comment BashFeedComment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
185
+ reviewer User? @relation("ReviewedReports", fields: [reviewedBy], references: [id])
183
186
 
187
+ @@unique([userId, commentId])
184
188
  @@index([postId])
189
+ @@index([commentId])
185
190
  @@index([status])
186
191
  @@index([userId])
187
192
  }
@@ -232,6 +237,13 @@ model Competition {
232
237
  startTime DateTime? // Competition start time (for voting)
233
238
  endTime DateTime? // Competition end time (for voting)
234
239
  allowSelfRegistration Boolean @default(false)
240
+ /// When true, participant requests are auto-approved (ACCEPTED) instead of queued for host review.
241
+ autoApproveParticipants Boolean @default(false)
242
+ /// How the participant roster appears on the bash page ("All" | "Hidden").
243
+ participantDisplayMode String @default("All")
244
+ /// AUDIENCE_VOTE only: when true, only attendees (ticket holders / RSVPs) may vote.
245
+ /// Default false keeps voting open to any signed-in viewer.
246
+ restrictVotingToAttendees Boolean @default(false)
235
247
  maxParticipants Int? // Optional cap on participants
236
248
 
237
249
  // Tournament bracket fields
@@ -311,6 +323,8 @@ model CompetitionSponsor {
311
323
  sponsorName String?
312
324
  logoUrl String?
313
325
  description String?
326
+ /// Optional outbound link to the sponsor's website (clickable on the public competition page)
327
+ websiteUrl String?
314
328
  paidOn String?
315
329
  competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
316
330
  sponsor User? @relation(fields: [userId], references: [id], onDelete: Cascade)
@@ -367,10 +381,9 @@ model CompetitionParticipant {
367
381
  votes CompetitionVote[]
368
382
 
369
383
  // Tournament bracket relations
370
- matchesAsParticipant1 CompetitionMatch[] @relation("MatchParticipant1")
371
- matchesAsParticipant2 CompetitionMatch[] @relation("MatchParticipant2")
372
- matchesWon CompetitionMatch[] @relation("MatchWinner")
373
- judgeScores CompetitionJudgeScore[]
384
+ matchEntries CompetitionMatchEntrant[]
385
+ matchesWon CompetitionMatch[] @relation("MatchWinner")
386
+ judgeScores CompetitionJudgeScore[]
374
387
 
375
388
  @@index([competitionId])
376
389
  @@index([userId])
@@ -379,26 +392,28 @@ model CompetitionParticipant {
379
392
  @@index([status])
380
393
  }
381
394
 
382
- // Tournament bracket matches
395
+ // Tournament bracket matches (1v1, heats, or battle royale via entrants join table)
383
396
  model CompetitionMatch {
384
397
  id String @id @default(cuid())
385
398
  competitionId String
386
399
  round Int // 1 = first round, 2 = semifinals, etc.
387
400
  matchNumber Int // Position in round (1, 2, 3...)
388
401
 
389
- participant1Id String?
390
- participant2Id String?
391
- winnerId String?
402
+ format CompetitionMatchFormat @default(HEAD_TO_HEAD)
403
+ capacity Int? // Max entrants (heat lanes, server slots)
404
+ advanceCount Int? // How many advance from this heat/pool
405
+ heatIndex Int? // Wave number within a round (e.g. heat 12 of 50)
406
+
407
+ winnerId String?
392
408
 
393
409
  scheduledTime DateTime?
394
410
  completedAt DateTime?
395
- score Json? // {participant1: 10, participant2: 8}
411
+ score Json? // Legacy H2H: {participant1, participant2}; multi-entrant scores live on entrants
396
412
  notes String? // Match notes
397
413
 
398
- competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
399
- participant1 CompetitionParticipant? @relation("MatchParticipant1", fields: [participant1Id], references: [id])
400
- participant2 CompetitionParticipant? @relation("MatchParticipant2", fields: [participant2Id], references: [id])
401
- winner CompetitionParticipant? @relation("MatchWinner", fields: [winnerId], references: [id])
414
+ competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
415
+ winner CompetitionParticipant? @relation("MatchWinner", fields: [winnerId], references: [id])
416
+ entrants CompetitionMatchEntrant[]
402
417
 
403
418
  createdAt DateTime @default(now())
404
419
  updatedAt DateTime @updatedAt
@@ -408,6 +423,29 @@ model CompetitionMatch {
408
423
  @@index([round])
409
424
  }
410
425
 
426
+ // N entrants per match — heats, battle royales, and head-to-head (slots 1 & 2)
427
+ model CompetitionMatchEntrant {
428
+ id String @id @default(cuid())
429
+ matchId String
430
+ participantId String
431
+ slot Int // Lane/pod position (1-based)
432
+ placement Int? // Finish order within the match
433
+ score Json? // Time, points, etc.
434
+ eliminatedAt DateTime?
435
+ advanced Boolean? // Heat qualifiers: true when advancing to next stage
436
+
437
+ match CompetitionMatch @relation(fields: [matchId], references: [id], onDelete: Cascade)
438
+ participant CompetitionParticipant @relation(fields: [participantId], references: [id], onDelete: Cascade)
439
+
440
+ createdAt DateTime @default(now())
441
+ updatedAt DateTime @updatedAt
442
+
443
+ @@unique([matchId, slot])
444
+ @@unique([matchId, participantId])
445
+ @@index([matchId])
446
+ @@index([participantId])
447
+ }
448
+
411
449
  model CompetitionVote {
412
450
  id String @id @default(cuid())
413
451
  competitionId String
@@ -823,6 +861,9 @@ model BashEvent {
823
861
  collaborationEnabled Boolean @default(true)
824
862
  /// Host opt-in: show icebreaker prompts on the bash detail page for guests
825
863
  icebreakerEnabled Boolean @default(false)
864
+ /// Optional host-authored / host-picked icebreaker prompt. When set, it is shown
865
+ /// instead of a random canned prompt on the bash detail page.
866
+ icebreakerPrompt String?
826
867
  /// Host-curated merch catalog for this event (informational; not a checkout line item for MVP)
827
868
  merchandiseItems Json?
828
869
  externalPriceMax Int? // Maximum ticket price in cents (for imported events)
@@ -905,6 +946,8 @@ model BashEvent {
905
946
  userConnections UserConnection[]
906
947
  aiRecommendationLogs AIRecommendationLog[]
907
948
  aiRecommendationCaches AIRecommendationCache[]
949
+ venueMatchRuns VenueMatchRun[]
950
+ venueSupplyLeadEvents VenueSupplyLeadEvent[]
908
951
  // Template tracking
909
952
  templateId String?
910
953
  template BashEventTemplate? @relation("CreatedFromTemplate", fields: [templateId], references: [id])
@@ -2026,8 +2069,9 @@ model TicketTier {
2026
2069
  title String @default("Free")
2027
2070
  sortOrder Int?
2028
2071
  description String?
2029
- maximumNumberOfTickets Int @default(100)
2030
- maximumTicketCount Int @default(100)
2072
+ maximumNumberOfTickets Int?
2073
+ /// @deprecated Legacy duplicate of maximumNumberOfTickets — do not read or write.
2074
+ maximumTicketCount Int?
2031
2075
  isDonationTier Boolean?
2032
2076
  hidden Boolean?
2033
2077
  promoCodes BashEventPromoCode[]
@@ -3019,6 +3063,8 @@ model User {
3019
3063
  userItineraries UserItinerary[]
3020
3064
  aiRecommendationLogs AIRecommendationLog[]
3021
3065
  scoutReferralsReceived ScoutReferral[] @relation("ScoutReferred")
3066
+ venueMatchRuns VenueMatchRun[] @relation("VenueMatchRunHost")
3067
+ assignedSupplyLeads VenueSupplyLead[] @relation("SupplyLeadAssignee")
3022
3068
 
3023
3069
  // Service Category Requests
3024
3070
  serviceCategoryRequests ServiceCategoryRequest[]
@@ -3040,6 +3086,7 @@ model User {
3040
3086
  bashFeedRatings BashFeedRating[]
3041
3087
  bashFeedReports BashFeedReport[]
3042
3088
  reviewedFeedReports BashFeedReport[] @relation("ReviewedReports")
3089
+ storeOrders StoreOrder[]
3043
3090
  bashFeedSaves BashFeedSave[]
3044
3091
  bashFeedReposts BashFeedRepost[]
3045
3092
  reviewHelpfulVotes ReviewHelpfulVote[]
@@ -3594,6 +3641,10 @@ model Service {
3594
3641
  verifiedAt DateTime? // When verification was approved
3595
3642
  licenseNumber String? // License/registration number
3596
3643
  certifications String[] @default([]) // Array of certification names
3644
+ /// Geo anchor for distance scoring (Venue Match + future geo queries).
3645
+ /// Backfilled from googlePlaceId / address via api/scripts/backfillServiceCoordinates.ts.
3646
+ latitude Float?
3647
+ longitude Float?
3597
3648
  bashFeedPosts BashFeedPost[]
3598
3649
  bashEventArtists BashEventArtist[]
3599
3650
  bashCreativeSubmissions BashCreativeSubmission[]
@@ -3640,6 +3691,8 @@ model Service {
3640
3691
  competitionPrizesSourced Prize[] @relation("PrizeSourceService")
3641
3692
  competitionSponsorCredits CompetitionSponsor[] @relation("CompetitionSponsorSourceService")
3642
3693
  competitionPrizeOffersSourced CompetitionPrizeOffer[] @relation("CompetitionPrizeOfferSourceService")
3694
+ venueMatchCandidates VenueMatchCandidate[]
3695
+ supplyLeadConversions VenueSupplyLead[] @relation("SupplyLeadConvertedService")
3643
3696
 
3644
3697
  @@index([serviceListingStripeSubscriptionId])
3645
3698
  @@index([paymentAccountId])
@@ -3647,6 +3700,7 @@ model Service {
3647
3700
  @@index([isVerified])
3648
3701
  @@index([isLicensed])
3649
3702
  @@index([isFeaturedService, featuredReason, featuredAt])
3703
+ @@index([latitude, longitude])
3650
3704
  }
3651
3705
 
3652
3706
  model StripeAccount {
@@ -6250,6 +6304,14 @@ enum BracketType {
6250
6304
  DOUBLE_ELIMINATION // Lose twice, you're out
6251
6305
  ROUND_ROBIN // Everyone plays everyone
6252
6306
  SWISS // Pairing based on performance
6307
+ HEAT // Wave-based qualifiers (many entrants per match, top performers advance)
6308
+ BATTLE_ROYALE // Last standing / placement in a multi-entrant match
6309
+ }
6310
+
6311
+ enum CompetitionMatchFormat {
6312
+ HEAD_TO_HEAD // Traditional 1v1 (slots 1 & 2)
6313
+ HEAT // Sequential wave with capacity-limited entrants
6314
+ BATTLE_ROYALE // All entrants compete simultaneously until one remains
6253
6315
  }
6254
6316
 
6255
6317
  enum ParticipantStatus {
@@ -9501,6 +9563,127 @@ enum OrgProductStatus {
9501
9563
  Archived
9502
9564
  }
9503
9565
 
9566
+ enum StoreProductStatus {
9567
+ Active
9568
+ Draft
9569
+ Archived
9570
+ }
9571
+
9572
+ enum StoreProductCategory {
9573
+ Apparel
9574
+ Accessories
9575
+ Other
9576
+ }
9577
+
9578
+ enum StorePrintArtwork {
9579
+ BashLogoBig
9580
+ BashLogoLetters
9581
+ BashLogoBigWhite
9582
+ BashLogoLettersWhite
9583
+ }
9584
+
9585
+ enum StoreFulfillmentProvider {
9586
+ Platform
9587
+ Printify
9588
+ Printful
9589
+ }
9590
+
9591
+ enum StoreOrderStatus {
9592
+ Pending
9593
+ Paid
9594
+ Fulfilled
9595
+ Failed
9596
+ Canceled
9597
+ }
9598
+
9599
+ enum StoreFulfillmentStatus {
9600
+ Pending
9601
+ Submitted
9602
+ Fulfilled
9603
+ Failed
9604
+ }
9605
+
9606
+ /// Official Bash merch sold on /store (merchant-of-record via Stripe).
9607
+ model StoreProduct {
9608
+ id String @id @default(cuid())
9609
+ slug String @unique
9610
+ title String
9611
+ description String? @db.Text
9612
+ images String[] @default([])
9613
+ priceCents Int
9614
+ inventory Int @default(0)
9615
+ status StoreProductStatus @default(Draft)
9616
+ category StoreProductCategory @default(Apparel)
9617
+ sortOrder Int @default(0)
9618
+ fulfillmentProvider StoreFulfillmentProvider @default(Platform)
9619
+ externalProductId String?
9620
+ printArtwork StorePrintArtwork?
9621
+ printArtworkUrl String?
9622
+ createdAt DateTime @default(now())
9623
+ updatedAt DateTime @updatedAt
9624
+ variants StoreProductVariant[]
9625
+ orderLines StoreOrderLine[]
9626
+
9627
+ @@index([status, sortOrder])
9628
+ }
9629
+
9630
+ model StoreProductVariant {
9631
+ id String @id @default(cuid())
9632
+ productId String
9633
+ label String
9634
+ priceCents Int?
9635
+ inventory Int @default(0)
9636
+ externalVariantId String?
9637
+ sortOrder Int @default(0)
9638
+ product StoreProduct @relation(fields: [productId], references: [id], onDelete: Cascade)
9639
+
9640
+ @@index([productId])
9641
+ }
9642
+
9643
+ model StoreOrder {
9644
+ id String @id @default(cuid())
9645
+ buyerId String
9646
+ status StoreOrderStatus @default(Pending)
9647
+ fulfillmentStatus StoreFulfillmentStatus @default(Pending)
9648
+ subtotalCents Int
9649
+ totalCents Int
9650
+ currency String @default("usd")
9651
+ stripeCheckoutSessionId String? @unique
9652
+ stripePaymentIntentId String?
9653
+ // Shipping address captured from Stripe checkout
9654
+ shippingName String?
9655
+ shippingLine1 String?
9656
+ shippingLine2 String?
9657
+ shippingCity String?
9658
+ shippingState String?
9659
+ shippingPostalCode String?
9660
+ shippingCountry String?
9661
+ createdAt DateTime @default(now())
9662
+ updatedAt DateTime @updatedAt
9663
+ buyer User @relation(fields: [buyerId], references: [id], onDelete: Cascade)
9664
+ lines StoreOrderLine[]
9665
+
9666
+ @@index([buyerId])
9667
+ @@index([status])
9668
+ }
9669
+
9670
+ model StoreOrderLine {
9671
+ id String @id @default(cuid())
9672
+ orderId String
9673
+ productId String?
9674
+ variantId String?
9675
+ titleSnapshot String
9676
+ unitPriceCents Int
9677
+ quantity Int @default(1)
9678
+ fulfillmentProvider StoreFulfillmentProvider
9679
+ externalProductId String?
9680
+ externalVariantId String?
9681
+ order StoreOrder @relation(fields: [orderId], references: [id], onDelete: Cascade)
9682
+ product StoreProduct? @relation(fields: [productId], references: [id], onDelete: SetNull)
9683
+
9684
+ @@index([orderId])
9685
+ }
9686
+
9504
9687
  /// Lifecycle of a host's external Stripe nonprofit-pricing application. Recorded
9505
9688
  /// for analytics; does NOT gate Bash-side verification or platform-fee waivers.
9506
9689
  enum StripeNonprofitPricingStatus {
@@ -9509,3 +9692,183 @@ enum StripeNonprofitPricingStatus {
9509
9692
  Approved
9510
9693
  Declined
9511
9694
  }
9695
+
9696
+ // ─────────────────────────────────────────────────────────────────────────────
9697
+ // Venue Match pipeline (retrieve → score → explain) + supply sales pipeline.
9698
+ // JSON column shapes live in bash-common/src/venueMatch.ts (VenueMatchIntent,
9699
+ // VenueMatchScoreBreakdown, VenueCandidateDisplay).
9700
+ // ─────────────────────────────────────────────────────────────────────────────
9701
+
9702
+ enum VenueMatchCandidateSource {
9703
+ Marketplace
9704
+ GooglePlaces
9705
+ }
9706
+
9707
+ enum VenueMatchHostAction {
9708
+ None
9709
+ Viewed
9710
+ Accepted
9711
+ AcceptedOffPlatform
9712
+ Dismissed
9713
+ CalledPhone
9714
+ InvitedVenue
9715
+ }
9716
+
9717
+ enum VenueSupplyLeadStage {
9718
+ Surfaced
9719
+ Prioritized
9720
+ Queued
9721
+ Contacted
9722
+ Interested
9723
+ DemoScheduled
9724
+ Listed
9725
+ Declined
9726
+ DoNotContact
9727
+ }
9728
+
9729
+ enum ExternalPlaceSource {
9730
+ OutscraperCsv
9731
+ OutscraperApi
9732
+ Manual
9733
+ }
9734
+
9735
+ /// One scoring run for a bash. Content-hash dedupes identical re-runs.
9736
+ model VenueMatchRun {
9737
+ id String @id @default(cuid())
9738
+ bashEventId String
9739
+ hostUserId String
9740
+ /// `VenueMatchIntent` — occasion, guestCount, anchor coords, vibe, budget.
9741
+ intent Json
9742
+ contentHash String
9743
+ /// Run telemetry: latencyMs, candidateCounts, googleCalls, cacheHits.
9744
+ meta Json?
9745
+ createdAt DateTime @default(now())
9746
+
9747
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
9748
+ hostUser User @relation("VenueMatchRunHost", fields: [hostUserId], references: [id], onDelete: Cascade)
9749
+ candidates VenueMatchCandidate[]
9750
+
9751
+ @@index([bashEventId, createdAt])
9752
+ @@index([hostUserId])
9753
+ }
9754
+
9755
+ /// Every scored candidate in a run (not just the top 3 shown).
9756
+ model VenueMatchCandidate {
9757
+ id String @id @default(cuid())
9758
+ runId String
9759
+ rank Int
9760
+ score Int
9761
+ /// `VenueMatchScoreBreakdown` — per-factor score + applied weight.
9762
+ scoreBreakdown Json
9763
+ /// Host-facing reason strings ("2.1 mi away", "Fits 25 guests").
9764
+ reasons Json
9765
+ source VenueMatchCandidateSource
9766
+ serviceId String?
9767
+ googlePlaceId String?
9768
+ /// `VenueCandidateDisplay` — snapshot so GET never re-hits Google.
9769
+ displaySnapshot Json
9770
+ hostAction VenueMatchHostAction @default(None)
9771
+ hostActionAt DateTime?
9772
+
9773
+ run VenueMatchRun @relation(fields: [runId], references: [id], onDelete: Cascade)
9774
+ service Service? @relation(fields: [serviceId], references: [id], onDelete: SetNull)
9775
+
9776
+ @@index([runId, rank])
9777
+ @@index([googlePlaceId])
9778
+ @@index([serviceId])
9779
+ }
9780
+
9781
+ /// ToS-compatible place data (Outscraper CSV/API imports). The ONLY allowed
9782
+ /// contact-data source for VenueSupplyLead — Google Place fields are never
9783
+ /// copied here (see GooglePlaceCache for the ephemeral display tier).
9784
+ model ExternalPlaceCatalog {
9785
+ id String @id @default(cuid())
9786
+ googlePlaceId String? @unique
9787
+ phoneE164 String?
9788
+ name String
9789
+ fullAddress String? @db.Text
9790
+ city String?
9791
+ state String?
9792
+ postalCode String?
9793
+ latitude Float?
9794
+ longitude Float?
9795
+ rating Float?
9796
+ reviewCount Int?
9797
+ category String?
9798
+ website String? @db.Text
9799
+ source ExternalPlaceSource @default(OutscraperCsv)
9800
+ /// Scraped/ file stem or Outscraper query that produced this row.
9801
+ sourceKey String?
9802
+ importedAt DateTime @default(now())
9803
+ refreshedAt DateTime?
9804
+
9805
+ supplyLeads VenueSupplyLead[]
9806
+
9807
+ @@index([phoneE164])
9808
+ @@index([city, state])
9809
+ }
9810
+
9811
+ /// Off-platform venue prospect for the sales pipeline. Contact fields come
9812
+ /// from ExternalPlaceCatalog (denormalized name/phone kept for fast lists).
9813
+ model VenueSupplyLead {
9814
+ id String @id @default(cuid())
9815
+ catalogId String?
9816
+ googlePlaceId String? @unique
9817
+ /// Set when the venue converts (publishes a Service on Bash).
9818
+ serviceId String?
9819
+ /// Dedup link to the Vapi dialer lead when phoneE164 matches.
9820
+ outreachLeadId String?
9821
+ name String
9822
+ phoneE164 String?
9823
+ city String?
9824
+ state String?
9825
+ /// Count of DISTINCT bash events that surfaced this venue (top 10).
9826
+ distinctEventCount Int @default(0)
9827
+ firstSurfacedAt DateTime @default(now())
9828
+ lastSurfacedAt DateTime @default(now())
9829
+ stage VenueSupplyLeadStage @default(Surfaced)
9830
+ assignedToUserId String?
9831
+ notes String? @db.Text
9832
+ createdAt DateTime @default(now())
9833
+ updatedAt DateTime @updatedAt
9834
+
9835
+ catalog ExternalPlaceCatalog? @relation(fields: [catalogId], references: [id], onDelete: SetNull)
9836
+ service Service? @relation("SupplyLeadConvertedService", fields: [serviceId], references: [id], onDelete: SetNull)
9837
+ assignedTo User? @relation("SupplyLeadAssignee", fields: [assignedToUserId], references: [id], onDelete: SetNull)
9838
+ surfaceEvents VenueSupplyLeadEvent[]
9839
+
9840
+ @@index([stage, distinctEventCount])
9841
+ @@index([city, state])
9842
+ @@index([phoneE164])
9843
+ @@index([assignedToUserId])
9844
+ }
9845
+
9846
+ /// Join row: which bash surfaced this lead, with occasion context for
9847
+ /// cold-call scripts ("4 hosts searched bachelor party venues near you").
9848
+ model VenueSupplyLeadEvent {
9849
+ id String @id @default(cuid())
9850
+ supplyLeadId String
9851
+ bashEventId String
9852
+ matchCandidateId String?
9853
+ occasion String?
9854
+ guestCountEstimate Int?
9855
+ surfacedAt DateTime @default(now())
9856
+
9857
+ supplyLead VenueSupplyLead @relation(fields: [supplyLeadId], references: [id], onDelete: Cascade)
9858
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
9859
+
9860
+ @@unique([supplyLeadId, bashEventId])
9861
+ @@index([bashEventId])
9862
+ }
9863
+
9864
+ /// Ephemeral Google Place display data (48h TTL). NEVER copied into
9865
+ /// VenueSupplyLead or ExternalPlaceCatalog — Google ToS tier C only.
9866
+ model GooglePlaceCache {
9867
+ googlePlaceId String @id
9868
+ /// Display-only snapshot: name, address, rating, reviewCount, photoUrl, phone, lat/lng.
9869
+ displaySnapshot Json
9870
+ fetchedAt DateTime @default(now())
9871
+ expiresAt DateTime
9872
+
9873
+ @@index([expiresAt])
9874
+ }
@@ -0,0 +1,53 @@
1
+ import type { StorePrintArtwork } from "@prisma/client";
2
+
3
+ import {
4
+ isDarkGarmentColor,
5
+ resolveStorePrintArtwork,
6
+ resolveStorePrintArtworkPath,
7
+ } from "../storePrintArtworkUtils.js";
8
+
9
+ describe("isDarkGarmentColor", () => {
10
+ it("detects common dark garment names", () => {
11
+ expect(isDarkGarmentColor("Black / M")).toBe(true);
12
+ expect(isDarkGarmentColor("navy")).toBe(true);
13
+ expect(isDarkGarmentColor("charcoal heather")).toBe(true);
14
+ expect(isDarkGarmentColor("White / L")).toBe(false);
15
+ expect(isDarkGarmentColor("Heather grey / M")).toBe(false);
16
+ });
17
+
18
+ it("detects dark hex colors", () => {
19
+ expect(isDarkGarmentColor("#000000")).toBe(true);
20
+ expect(isDarkGarmentColor("#ffffff")).toBe(false);
21
+ });
22
+ });
23
+
24
+ describe("resolveStorePrintArtwork", () => {
25
+ it("keeps royal-blue artwork on light garments", () => {
26
+ expect(
27
+ resolveStorePrintArtwork("BashLogoBig" as StorePrintArtwork, {
28
+ garmentColor: "White / M",
29
+ })
30
+ ).toBe("BashLogoBig");
31
+ });
32
+
33
+ it("routes to white knock-out on dark garments", () => {
34
+ expect(
35
+ resolveStorePrintArtwork("BashLogoBig" as StorePrintArtwork, {
36
+ garmentColor: "Black / L",
37
+ })
38
+ ).toBe("BashLogoBigWhite");
39
+ expect(
40
+ resolveStorePrintArtwork("BashLogoLetters" as StorePrintArtwork, {
41
+ garmentColor: "Black / One size",
42
+ })
43
+ ).toBe("BashLogoLettersWhite");
44
+ });
45
+
46
+ it("returns public paths for resolved artwork", () => {
47
+ expect(
48
+ resolveStorePrintArtworkPath("BashLogoBig" as StorePrintArtwork, {
49
+ garmentColor: "Black",
50
+ })
51
+ ).toBe("/bashLogoBigWhite.png");
52
+ });
53
+ });
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  BashEventType,
3
- BracketType,
4
3
  CompetitionType,
5
4
  Contact,
6
5
  DietaryOption,
@@ -1137,9 +1136,10 @@ export const YearsOfExperienceToString: { [key in YearsOfExperienceEnum]: string
1137
1136
  [YearsOfExperience.FivePlusYears]: "More than five years",
1138
1137
  };
1139
1138
 
1139
+ export { BracketType } from "./mirroredPrismaEnums.js";
1140
+
1140
1141
  // Export Prisma enums for use in frontend
1141
1142
  export {
1142
- BracketType,
1143
1143
  CompetitionType,
1144
1144
  EntertainmentServiceType,
1145
1145
  JudgingType,
@@ -1,3 +1,4 @@
1
+ import type { BracketType } from "./mirroredPrismaEnums.js";
1
2
  import {
2
3
  AmountOfGuests,
3
4
  AssociatedBash,
@@ -280,14 +281,23 @@ export interface CompetitionPrizeOfferExt {
280
281
  } | null;
281
282
  }
282
283
 
283
- export interface CompetitionExt extends Competition {
284
- prizes: PrizeExt[];
284
+ /** Competition row + relations; Override keeps new columns visible before consumers refresh Prisma. */
285
+ export type CompetitionExt = Override<
286
+ Competition,
287
+ {
288
+ autoApproveParticipants: boolean;
289
+ participantDisplayMode: string;
290
+ restrictVotingToAttendees: boolean;
291
+ bracketType: BracketType | null;
292
+ prizes: PrizeExt[];
293
+ }
294
+ > & {
285
295
  participants?: CompetitionParticipant[];
286
296
  votes?: CompetitionVote[];
287
297
  entryTicketTier?: TicketTier | null;
288
298
  sponsor?: CompetitionSponsor[];
289
299
  prizeOffers?: CompetitionPrizeOfferExt[];
290
- }
300
+ };
291
301
 
292
302
  /** One row in BashEvent.merchandiseItems (host wizard; informational catalog) */
293
303
  export type BashEventHostMerchItem = {
@@ -356,12 +366,15 @@ export type BashEventHiredServicePublic = {
356
366
  }>;
357
367
  };
358
368
 
359
- export interface BashEventExt extends Omit<
360
- BashEvent,
361
- | "ideaExpiresAt"
362
- | "ideaInterestThreshold"
363
- | "isAutoApprovable"
364
- | "merchandiseItems"
369
+ export interface BashEventExt extends Override<
370
+ Omit<
371
+ BashEvent,
372
+ | "ideaExpiresAt"
373
+ | "ideaInterestThreshold"
374
+ | "isAutoApprovable"
375
+ | "merchandiseItems"
376
+ >,
377
+ { icebreakerPrompt: string | null }
365
378
  > {
366
379
  /** Host-curated merch catalog (wizard); stored as JSON on BashEvent. Null = no merch configured. */
367
380
  merchandiseItems: BashEventMerchCatalog | null;
@@ -984,8 +997,9 @@ export interface ServiceExt extends Service {
984
997
 
985
998
  stripeAccount?: PublicStripeAccount | null;
986
999
 
987
- latitude?: number;
988
- longitude?: number;
1000
+ /** Geo anchor — explicit on ext type for consumers before Prisma client catches up */
1001
+ latitude: number | null;
1002
+ longitude: number | null;
989
1003
 
990
1004
  // availableDateTimes?: Availability[];
991
1005