@bash-app/bash-common 30.142.0 → 30.143.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bash-app/bash-common",
3
- "version": "30.142.0",
3
+ "version": "30.143.0",
4
4
  "description": "Common data and scripts to use on the frontend and backend",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -555,6 +555,7 @@ model Invitation {
555
555
  model BashEvent {
556
556
  id String @id @default(cuid())
557
557
  source BashEventSource @default(Bash)
558
+ sourcePartnerId String? // When source=Partner, e.g. 'jamwith', 'mixlog'
558
559
  title String
559
560
  slug String? @unique
560
561
  creatorId String
@@ -1374,6 +1375,7 @@ model User {
1374
1375
  isSuperUser Boolean @default(false)
1375
1376
  isSuspended Boolean @default(false)
1376
1377
  intent UserIntent?
1378
+ signupSource String? // e.g. 'jamwith', 'mixlog', 'organic' for attribution
1377
1379
  googleCalendarAccess String?
1378
1380
 
1379
1381
  // Google OAuth tokens for API access (Contacts, Calendar, etc.)
@@ -1391,6 +1393,15 @@ model User {
1391
1393
  uploadedImage String?
1392
1394
  uploadedImageUpdatedAt DateTime? // When manually uploaded image was last updated
1393
1395
  dob DateTime?
1396
+ showBirthdayOnProfile Boolean @default(false) // If true, show month/day on public profile
1397
+ // Part B: Birthday Rewards full product
1398
+ birthdayRewardsOptIn Boolean @default(false)
1399
+ birthdayRewardsOptInAt DateTime?
1400
+ birthdayLocationId String? // FK UserLocation (birthday home base)
1401
+ birthdayMaxDistanceMiles Int @default(25)
1402
+ birthdayNoPurchaseOnly Boolean @default(false)
1403
+ birthdayCategories String[] @default([]) // e.g. restaurant, coffee, dessert
1404
+ idDocumentUrl String? // Optional: secure ID upload for verification
1394
1405
  gender Gender?
1395
1406
  sex Sex?
1396
1407
  organization String?
@@ -1478,6 +1489,15 @@ model User {
1478
1489
  auditLogs AuditLog[] @relation("UserAuditLogs")
1479
1490
  auditLogsPerformed AuditLog[] @relation("PerformedByUser")
1480
1491
  consents UserConsent[]
1492
+ birthdayOfferOptIns UserBirthdayOfferOptIn[]
1493
+ birthdayOfferDeliveries BirthdayOfferDelivery[]
1494
+ birthdayBoostGrants BirthdayBoostGrant[]
1495
+ birthdayRewardRedemptions BirthdayRewardRedemption[]
1496
+ birthdayDealSuggestions BirthdayDealSuggestion[]
1497
+ userLocations UserLocation[]
1498
+ birthdayLocation UserLocation? @relation("UserBirthdayLocation", fields: [birthdayLocationId], references: [id], onDelete: SetNull)
1499
+ offerClaims OfferClaim[]
1500
+ birthdayPlans BirthdayPlan[]
1481
1501
 
1482
1502
  associatedBashes AssociatedBash[]
1483
1503
  associatedServices AssociatedService[]
@@ -3610,6 +3630,8 @@ enum BashEventSource {
3610
3630
  LinkedIn
3611
3631
  Meetup
3612
3632
  Facebook
3633
+ JamWith
3634
+ Partner // Generic: use sourcePartnerId for partner key (e.g. 'mixlog', custom integrations)
3613
3635
  }
3614
3636
 
3615
3637
  enum TicketStatus {
@@ -5516,6 +5538,7 @@ enum CreditSourceType {
5516
5538
  FirstFavoriteBash // First bash added to favorites
5517
5539
  FirstShareBash // First time sharing a bash event
5518
5540
  FirstInviteToBash // First invite to someone else's bash
5541
+ BirthdayReward // Annual BashPoints boost on user's birthday
5519
5542
  }
5520
5543
 
5521
5544
  enum PendingGiftStatus {
@@ -5577,6 +5600,325 @@ enum ConsentType {
5577
5600
  Analytics // Optional: Google Analytics, Mixpanel, etc.
5578
5601
  Marketing // Optional: Promotional emails
5579
5602
  ThirdPartySharing // Optional: Share data with partners
5603
+ BirthdayOffers // Optional: Receive birthday offers from partner companies
5604
+ }
5605
+
5606
+ // Birthday offers from partner companies: users opt in once, we send minimal PII to partner APIs
5607
+ model BirthdayOfferPartner {
5608
+ id String @id @default(cuid())
5609
+ name String
5610
+ slug String @unique
5611
+ description String? @db.Text
5612
+ apiEndpoint String // Partner API URL; API keys live in env (BIRTHDAY_PARTNER_<SLUG>_API_KEY)
5613
+ config Json? // Partner-specific: e.g. sendFullDob (bool), extra fields
5614
+ requiresPhone Boolean @default(false) // Part B: when true, share User.phone with partner
5615
+ isActive Boolean @default(true)
5616
+ createdAt DateTime @default(now())
5617
+ updatedAt DateTime @updatedAt
5618
+ optIns UserBirthdayOfferOptIn[]
5619
+ deliveries BirthdayOfferDelivery[]
5620
+ catalogEntries BirthdayFreebieCatalog[]
5621
+
5622
+ @@index([isActive])
5623
+ }
5624
+
5625
+ model UserBirthdayOfferOptIn {
5626
+ id String @id @default(cuid())
5627
+ userId String
5628
+ partnerId String
5629
+ optedInAt DateTime @default(now())
5630
+ lastSentAt DateTime? // Last time we sent this user to this partner (for idempotency)
5631
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
5632
+ partner BirthdayOfferPartner @relation(fields: [partnerId], references: [id], onDelete: Cascade)
5633
+
5634
+ @@unique([userId, partnerId])
5635
+ @@index([partnerId])
5636
+ @@index([userId])
5637
+ }
5638
+
5639
+ model BirthdayOfferDelivery {
5640
+ id String @id @default(cuid())
5641
+ userId String
5642
+ partnerId String
5643
+ birthdayYear Int // Year when the send counted (for idempotency: one send per user/partner/year)
5644
+ sentAt DateTime @default(now())
5645
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
5646
+ partner BirthdayOfferPartner @relation(fields: [partnerId], references: [id], onDelete: Cascade)
5647
+
5648
+ @@unique([userId, partnerId, birthdayYear])
5649
+ @@index([partnerId])
5650
+ @@index([userId])
5651
+ }
5652
+
5653
+ // Birthday Rewards focused scope (v1): annual BashPoints boost idempotency
5654
+ model BirthdayBoostGrant {
5655
+ id String @id @default(cuid())
5656
+ userId String
5657
+ birthdayYear Int // Year for which boost was granted
5658
+ grantedAt DateTime @default(now())
5659
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
5660
+
5661
+ @@unique([userId, birthdayYear])
5662
+ @@index([userId])
5663
+ @@index([birthdayYear])
5664
+ }
5665
+
5666
+ // User-recorded places where they picked up a birthday reward (with optional rating)
5667
+ model BirthdayRewardRedemption {
5668
+ id String @id @default(cuid())
5669
+ userId String
5670
+ placeName String // e.g. "Freddy's", "Starbucks"
5671
+ redeemedAt DateTime @default(now())
5672
+ birthdayYear Int
5673
+ notes String? @db.Text
5674
+ difficultyScore Int? // 1-10
5675
+ qualityScore Int? // 1-10
5676
+ ratingComment String? @db.Text
5677
+ ratedAt DateTime?
5678
+ offerId String? // Part B: optional FK to structured BirthdayOffer
5679
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
5680
+ offer BirthdayOffer? @relation(fields: [offerId], references: [id], onDelete: SetNull)
5681
+
5682
+ @@index([userId])
5683
+ @@index([userId, birthdayYear])
5684
+ @@index([offerId])
5685
+ }
5686
+
5687
+ enum BirthdayDealSuggestionStatus {
5688
+ Pending
5689
+ Approved
5690
+ Rejected
5691
+ }
5692
+
5693
+ // User-suggested birthday deals (admin can approve and add to catalog later)
5694
+ model BirthdayDealSuggestion {
5695
+ id String @id @default(cuid())
5696
+ userId String
5697
+ brandOrPlaceName String
5698
+ dealDescription String @db.Text
5699
+ sourceWhereHeard String? @db.Text
5700
+ linkUrl String? @db.Text
5701
+ status BirthdayDealSuggestionStatus @default(Pending)
5702
+ createdAt DateTime @default(now())
5703
+ reviewedAt DateTime?
5704
+ reviewedById String?
5705
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
5706
+ catalogEntry BirthdayFreebieCatalog?
5707
+
5708
+ @@index([userId])
5709
+ @@index([status])
5710
+ }
5711
+
5712
+ // Public SEO catalog for birthday freebie pages (programmatic by city/state)
5713
+ enum BirthdayFreebieCategory {
5714
+ NoPurchaseRequired
5715
+ PurchaseRequired
5716
+ }
5717
+
5718
+ enum BirthdayFreebieExpirationType {
5719
+ DayOf
5720
+ Valid30Days
5721
+ }
5722
+
5723
+ enum BirthdayFreebieSource {
5724
+ Partner
5725
+ Suggestion
5726
+ Manual
5727
+ }
5728
+
5729
+ enum BirthdayFreebieSector {
5730
+ CoffeeAndBeverages
5731
+ FoodAndRestaurants
5732
+ DessertsAndIceCream
5733
+ BeautyAndRetail
5734
+ Entertainment
5735
+ Other
5736
+ }
5737
+
5738
+ model BirthdayFreebieCatalog {
5739
+ id String @id @default(cuid())
5740
+ brandOrPlaceName String
5741
+ description String? @db.Text
5742
+ logoUrl String? @db.Text
5743
+ sector BirthdayFreebieSector @default(Other)
5744
+ category BirthdayFreebieCategory
5745
+ expirationType BirthdayFreebieExpirationType
5746
+ lastVerifiedAt DateTime
5747
+ requiresAdvanceSignup Boolean? @default(false)
5748
+ source BirthdayFreebieSource
5749
+ partnerId String?
5750
+ suggestionId String? @unique // one-to-one: at most one catalog entry per suggestion
5751
+ stateSlug String
5752
+ citySlug String?
5753
+ isActive Boolean @default(true)
5754
+ createdAt DateTime @default(now())
5755
+ updatedAt DateTime @updatedAt
5756
+
5757
+ partner BirthdayOfferPartner? @relation(fields: [partnerId], references: [id], onDelete: SetNull)
5758
+ suggestion BirthdayDealSuggestion? @relation(fields: [suggestionId], references: [id], onDelete: SetNull)
5759
+
5760
+ @@index([stateSlug, citySlug])
5761
+ @@index([isActive])
5762
+ @@index([sector])
5763
+ }
5764
+
5765
+ // Part B: User saved locations (birthday home base, etc.)
5766
+ model UserLocation {
5767
+ id String @id @default(cuid())
5768
+ userId String
5769
+ label String? // e.g. "Home", "Work"
5770
+ address1 String
5771
+ address2 String?
5772
+ city String
5773
+ state String
5774
+ zip String
5775
+ country String? @default("US")
5776
+ lat Float?
5777
+ lng Float?
5778
+ isDefault Boolean? @default(false)
5779
+ createdAt DateTime @default(now())
5780
+ updatedAt DateTime @updatedAt
5781
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
5782
+ usersWithBirthdayLocation User[] @relation("UserBirthdayLocation")
5783
+
5784
+ @@index([userId])
5785
+ }
5786
+
5787
+ // Part B: Structured birthday offers (catalog)
5788
+ enum RedemptionMethod {
5789
+ APP
5790
+ EMAIL
5791
+ IN_STORE
5792
+ ONLINE
5793
+ }
5794
+
5795
+ enum BirthdayOfferExpirationType {
5796
+ DAY_OF // End of birthday day
5797
+ DAYS_FROM_BDAY
5798
+ END_OF_MONTH
5799
+ VARIABLE
5800
+ }
5801
+
5802
+ enum BirthdayOfferSourceType {
5803
+ BRAND_SITE
5804
+ USER_REPORTED
5805
+ PARTNER
5806
+ }
5807
+
5808
+ enum OfferClaimStatus {
5809
+ CLICKED
5810
+ SAVED
5811
+ CLAIMED
5812
+ REDEEMED
5813
+ EXPIRED
5814
+ }
5815
+
5816
+ model Brand {
5817
+ id String @id @default(cuid())
5818
+ name String
5819
+ websiteUrl String? @db.Text
5820
+ logoUrl String? @db.Text
5821
+ categoryDefault String? // restaurant, coffee, dessert, retail, entertainment
5822
+ isNational Boolean @default(true)
5823
+ createdAt DateTime @default(now())
5824
+ updatedAt DateTime @updatedAt
5825
+ offers BirthdayOffer[]
5826
+ locations BrandLocation[]
5827
+
5828
+ @@index([isNational])
5829
+ }
5830
+
5831
+ model BirthdayOffer {
5832
+ id String @id @default(cuid())
5833
+ brandId String
5834
+ title String // e.g. "Free burger reward"
5835
+ descriptionShort String? @db.Text
5836
+ purchaseRequired Boolean @default(false)
5837
+ signupRequired Boolean @default(false)
5838
+ appRequired Boolean @default(false)
5839
+ requiresPhone Boolean @default(false)
5840
+ redemptionMethod RedemptionMethod
5841
+ expirationType BirthdayOfferExpirationType
5842
+ expirationDays Int? // when expirationType = DAYS_FROM_BDAY
5843
+ windowStartOffsetDays Int @default(0)
5844
+ termsUrl String? @db.Text
5845
+ termsNotes String? @db.Text
5846
+ sourceType BirthdayOfferSourceType
5847
+ lastVerifiedAt DateTime?
5848
+ isActive Boolean @default(true)
5849
+ priorityScore Int @default(0)
5850
+ featuredUntil DateTime?
5851
+ createdAt DateTime @default(now())
5852
+ updatedAt DateTime @updatedAt
5853
+ brand Brand @relation(fields: [brandId], references: [id], onDelete: Cascade)
5854
+ claims OfferClaim[]
5855
+ redemptions BirthdayRewardRedemption[]
5856
+
5857
+ @@index([brandId])
5858
+ @@index([isActive])
5859
+ @@index([sourceType])
5860
+ }
5861
+
5862
+ model BrandLocation {
5863
+ id String @id @default(cuid())
5864
+ brandId String
5865
+ address1 String
5866
+ address2 String?
5867
+ city String
5868
+ state String
5869
+ zip String
5870
+ lat Float
5871
+ lng Float
5872
+ isActive Boolean @default(true)
5873
+ createdAt DateTime @default(now())
5874
+ updatedAt DateTime @updatedAt
5875
+ brand Brand @relation(fields: [brandId], references: [id], onDelete: Cascade)
5876
+
5877
+ @@index([brandId])
5878
+ @@index([isActive])
5879
+ }
5880
+
5881
+ model OfferClaim {
5882
+ id String @id @default(cuid())
5883
+ userId String
5884
+ birthdayOfferId String
5885
+ claimedAt DateTime @default(now())
5886
+ birthdayYear Int
5887
+ status OfferClaimStatus
5888
+ meta Json?
5889
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
5890
+ birthdayOffer BirthdayOffer @relation(fields: [birthdayOfferId], references: [id], onDelete: Cascade)
5891
+
5892
+ @@index([userId])
5893
+ @@index([birthdayOfferId])
5894
+ @@index([userId, birthdayYear])
5895
+ }
5896
+
5897
+ model BirthdayPlan {
5898
+ id String @id @default(cuid())
5899
+ userId String
5900
+ birthdayYear Int
5901
+ generatedAt DateTime @default(now())
5902
+ preferencesSnapshot Json // distance, filters, optimizeFor
5903
+ items Json // ordered stops: plannedDate, brandId, offerId, locationId?, distanceFromHome, deadline, purchaseRequired
5904
+ validFrom DateTime
5905
+ validTo DateTime
5906
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
5907
+
5908
+ @@index([userId])
5909
+ @@index([userId, birthdayYear])
5910
+ }
5911
+
5912
+ // Part B: Geocoding cache to avoid repeated API calls (birthday rewards, UserLocation, etc.)
5913
+ model GeocodeCache {
5914
+ id String @id @default(cuid())
5915
+ addressQuery String @unique // Normalized query string (trimmed, collapsed whitespace)
5916
+ lat Float
5917
+ lng Float
5918
+ normalizedAddress String @db.Text // formatted_address from provider
5919
+ createdAt DateTime @default(now())
5920
+
5921
+ @@index([addressQuery])
5580
5922
  }
5581
5923
 
5582
5924
  model AuditLog {