@bash-app/bash-common 30.226.0 → 30.227.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 (49) hide show
  1. package/dist/aiApproval.d.ts +25 -0
  2. package/dist/aiApproval.d.ts.map +1 -1
  3. package/dist/aiApproval.js +32 -4
  4. package/dist/aiApproval.js.map +1 -1
  5. package/dist/extendedSchemas.d.ts +43 -1
  6. package/dist/extendedSchemas.d.ts.map +1 -1
  7. package/dist/extendedSchemas.js +22 -0
  8. package/dist/extendedSchemas.js.map +1 -1
  9. package/dist/index.d.ts +4 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/utils/__tests__/flyerUtils.test.d.ts +2 -0
  14. package/dist/utils/__tests__/flyerUtils.test.d.ts.map +1 -0
  15. package/dist/utils/__tests__/flyerUtils.test.js +49 -0
  16. package/dist/utils/__tests__/flyerUtils.test.js.map +1 -0
  17. package/dist/utils/flyerUtils.d.ts +42 -0
  18. package/dist/utils/flyerUtils.d.ts.map +1 -0
  19. package/dist/utils/flyerUtils.js +104 -0
  20. package/dist/utils/flyerUtils.js.map +1 -0
  21. package/package.json +1 -1
  22. package/prisma/schema.prisma +319 -50
  23. package/src/aiApproval.ts +118 -0
  24. package/src/extendedSchemas.ts +45 -1
  25. package/src/index.ts +14 -0
  26. package/src/utils/__tests__/flyerUtils.test.ts +70 -0
  27. package/src/utils/flyerUtils.ts +137 -0
  28. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.d.ts +0 -6
  29. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.d.ts.map +0 -1
  30. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.js +0 -104
  31. package/dist/utils/__tests__/cancellationPolicyRefundResolver.test.js.map +0 -1
  32. package/dist/utils/service/serviceRateDBUtils.d.ts +0 -1
  33. package/dist/utils/service/serviceRateDBUtils.d.ts.map +0 -1
  34. package/dist/utils/service/serviceRateDBUtils.js +0 -159
  35. package/dist/utils/service/serviceRateDBUtils.js.map +0 -1
  36. package/prisma/migrations/add_bash_availability.sql +0 -53
  37. package/prisma/migrations/add_bashcash_pricing_to_ticket_tier.sql +0 -15
  38. package/prisma/migrations/add_bashpoints_purchase_tracking.sql +0 -33
  39. package/prisma/migrations/add_event_group_allow_auto_join.sql +0 -17
  40. package/prisma/migrations/add_group_member_status_not_going_maybe.sql +0 -5
  41. package/prisma/migrations/add_groups_and_momentum.sql +0 -135
  42. package/prisma/migrations/add_groups_momentum_phase0_4.sql +0 -200
  43. package/prisma/migrations/add_new_feature_tables.sql +0 -140
  44. package/prisma/migrations/add_pricing_type_enum.sql +0 -88
  45. package/prisma/migrations/add_tier_privacy_and_fee_handling.sql +0 -29
  46. package/prisma/migrations/diagnostic_bashcash_columns.sql +0 -157
  47. package/prisma/migrations/fix_bashcash_referral_code_schema_mismatch.sql +0 -81
  48. package/prisma/migrations/rename_bashcash_to_bashpoints.sql +0 -183
  49. package/prisma/migrations/rename_bashcredits_to_bashpoints.sql +0 -96
@@ -79,7 +79,13 @@ import {
79
79
  VendorPreferences,
80
80
  VendorSocialMediaExpectation,
81
81
  Venue,
82
- VolunteerService
82
+ VolunteerService,
83
+ FlyerCampaign,
84
+ FlyerMailingRoute,
85
+ FlyerArtwork,
86
+ FlyerCampaignSponsor,
87
+ FlyerConversion,
88
+ PostalCarrierRoute,
83
89
  } from "@prisma/client";
84
90
  import { BlogStatus, SERVICE_LINK_DATA_TO_INCLUDE } from "./definitions.js";
85
91
  import {
@@ -1463,3 +1469,41 @@ export interface BlogTagFormData {
1463
1469
  slug?: string;
1464
1470
  color?: string;
1465
1471
  }
1472
+
1473
+ // --- Flyer Blast (EDDM campaigns) ---
1474
+ export const FLYER_CAMPAIGN_WITH_RELATIONS_INCLUDE = {
1475
+ bashEvent: {
1476
+ select: {
1477
+ id: true,
1478
+ title: true,
1479
+ slug: true,
1480
+ coverPhoto: true,
1481
+ themeColor: true,
1482
+ startDateTime: true,
1483
+ endDateTime: true,
1484
+ location: true,
1485
+ street: true,
1486
+ city: true,
1487
+ state: true,
1488
+ zipCode: true,
1489
+ },
1490
+ },
1491
+ routes: true,
1492
+ sponsors: true,
1493
+ artworkVersions: { orderBy: { version: "desc" as const }, take: 3 },
1494
+ } satisfies Prisma.FlyerCampaignInclude;
1495
+
1496
+ export type FlyerCampaignExt = Prisma.FlyerCampaignGetPayload<{
1497
+ include: typeof FLYER_CAMPAIGN_WITH_RELATIONS_INCLUDE;
1498
+ }>;
1499
+
1500
+ export interface FlyerAnalyticsSummary {
1501
+ campaignId: string;
1502
+ qrScans: number;
1503
+ sponsorQrScans: number;
1504
+ rsvps: number;
1505
+ ticketPurchases: number;
1506
+ byRoute: { crrtCode: string; zipCode: string; scans: number }[];
1507
+ }
1508
+
1509
+ export type PostalCarrierRouteExt = PostalCarrierRoute;
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export * from "./extendedSchemas.js";
7
7
  export * from "./partnerStoreTypes.js";
8
8
  export * from "./bashFeedTypes.js";
9
9
  export * from "./membershipDefinitions.js";
10
+ export * from "./aiApproval.js";
10
11
 
11
12
  // Re-export ALL Prisma enums as values (usable as runtime constants, e.g. ServiceTypes.Entertainment)
12
13
  // Excludes enums already re-exported by definitions.ts and membershipDefinitions.ts:
@@ -16,6 +17,7 @@ export * from "./membershipDefinitions.js";
16
17
  export {
17
18
  $Enums,
18
19
  AISpecialistFormat,
20
+ AiApprovalVerdict,
19
21
  AVTechnicianFormat,
20
22
  AgeRange,
21
23
  AmbashadorApplicationStatus,
@@ -84,6 +86,11 @@ export {
84
86
  EventTemplateCategory,
85
87
  ExhibitorBookingStatus,
86
88
  FinancingSubType,
89
+ FlyerCampaignStatus,
90
+ FlyerConversionType,
91
+ FlyerFulfillmentType,
92
+ FlyerSponsorSlot,
93
+ FlyerTargetingMode,
87
94
  FloralFormat,
88
95
  FoodAndBeverageSubType,
89
96
  FoodCartStandFormat,
@@ -222,7 +229,13 @@ export type {
222
229
  DocumentID,
223
230
  EventTask,
224
231
  Exhibitor,
232
+ FlyerArtwork,
233
+ FlyerCampaign,
234
+ FlyerCampaignSponsor,
235
+ FlyerConversion,
236
+ FlyerMailingRoute,
225
237
  GoogleReview,
238
+ PostalCarrierRoute,
226
239
  Invitation,
227
240
  Link,
228
241
  Media,
@@ -296,6 +309,7 @@ export * from "./utils/blog/blogDbUtils.js";
296
309
  export * from "./utils/blog/blogCommentDbUtils.js";
297
310
  export * from "./utils/blogUtils.js";
298
311
  export * from "./utils/entityUtils.js";
312
+ export * from "./utils/flyerUtils.js";
299
313
  export * from "./utils/generalDateTimeUtils.js";
300
314
  export * from "./utils/luxonUtils.js";
301
315
  export * from "./utils/mediaClientDefaults.js";
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from "@jest/globals";
2
+ import {
3
+ isEddmFlatSizeCompliant,
4
+ quoteFlyerTierForHouseholds,
5
+ computeHostTotalAfterSponsors,
6
+ FLYER_PRICING_TIERS,
7
+ USPS_EDDM_RETAIL_MIN_MAILPIECES,
8
+ flyerQrScanPath,
9
+ generateFlyerCampaignSlug,
10
+ distanceMiles,
11
+ } from "../flyerUtils.js";
12
+
13
+ describe("flyerUtils", () => {
14
+ it("isEddmFlatSizeCompliant accepts 6.25 x 11", () => {
15
+ expect(isEddmFlatSizeCompliant(6.25, 11)).toBe(true);
16
+ });
17
+
18
+ it("isEddmFlatSizeCompliant rejects too-small flats", () => {
19
+ expect(isEddmFlatSizeCompliant(4, 6)).toBe(false);
20
+ });
21
+
22
+ it("quoteFlyerTierForHouseholds returns null below minimum", () => {
23
+ expect(quoteFlyerTierForHouseholds(50)).toBeNull();
24
+ });
25
+
26
+ it("quoteFlyerTierForHouseholds returns Starter for 200 homes", () => {
27
+ const q = quoteFlyerTierForHouseholds(200);
28
+ expect(q?.tierId).toBe("starter");
29
+ expect(q?.hostPriceCents).toBe(FLYER_PRICING_TIERS[0].hostPriceCents);
30
+ });
31
+
32
+ it("computeHostTotalAfterSponsors never goes negative", () => {
33
+ expect(computeHostTotalAfterSponsors(32900, 50000)).toBe(0);
34
+ });
35
+
36
+ it("min mailpieces matches starter tier floor", () => {
37
+ expect(USPS_EDDM_RETAIL_MIN_MAILPIECES).toBe(
38
+ FLYER_PRICING_TIERS[0].minHouseholds
39
+ );
40
+ });
41
+
42
+ it("flyerQrScanPath encodes campaign slug for query", () => {
43
+ expect(flyerQrScanPath("abc")).toBe("/api/flyer/qr-scan?c=abc");
44
+ expect(flyerQrScanPath("a&b=c")).toBe(
45
+ "/api/flyer/qr-scan?c=" + encodeURIComponent("a&b=c")
46
+ );
47
+ });
48
+
49
+ it("generateFlyerCampaignSlug yields fb prefix, fixed length, allowed charset", () => {
50
+ const rnd = jest.spyOn(Math, "random").mockReturnValue(0);
51
+ try {
52
+ const slug = generateFlyerCampaignSlug();
53
+ expect(slug.startsWith("fb")).toBe(true);
54
+ expect(slug).toHaveLength(14);
55
+ expect(slug.slice(2)).toMatch(/^[23456789abcdefghjkmnpqrstuvwxyz]{12}$/);
56
+ } finally {
57
+ rnd.mockRestore();
58
+ }
59
+ });
60
+
61
+ it("distanceMiles is ~0 for identical points", () => {
62
+ expect(distanceMiles(40.7, -112, 40.7, -112)).toBe(0);
63
+ });
64
+
65
+ it("distanceMiles SLC to Provo is in plausible range (~35–50 mi)", () => {
66
+ const mi = distanceMiles(40.7608, -111.891, 40.2338, -111.6585);
67
+ expect(mi).toBeGreaterThan(30);
68
+ expect(mi).toBeLessThan(55);
69
+ });
70
+ });
@@ -0,0 +1,137 @@
1
+ /**
2
+ * USPS EDDM flat minimum: 6.125" × 11" (one dimension must be at least 11").
3
+ * Product standard used by Flyer Blast: 6.25" × 11" portrait.
4
+ */
5
+ export const FLYER_EDDM_WIDTH_IN = 6.25;
6
+ export const FLYER_EDDM_HEIGHT_IN = 11;
7
+ export const FLYER_EDDM_MIN_WIDTH_IN = 6.125;
8
+ export const FLYER_EDDM_MIN_HEIGHT_IN = 11;
9
+
10
+ /** USPS EDDM Retail per-piece postage (2026 — verify periodically). */
11
+ export const USPS_EDDM_RETAIL_PER_PIECE_CENTS = 24.7;
12
+
13
+ /**
14
+ * USPS EDDM Retail minimum mailpieces per mailing (saturation).
15
+ * Aligns with starter tier lower bound in `FLYER_PRICING_TIERS`.
16
+ */
17
+ export const USPS_EDDM_RETAIL_MIN_MAILPIECES = 200;
18
+
19
+ export type FlyerPricingTierId = "starter" | "neighborhood" | "district" | "metro";
20
+
21
+ export interface FlyerTierQuote {
22
+ tierId: FlyerPricingTierId;
23
+ label: string;
24
+ minHouseholds: number;
25
+ maxHouseholds: number;
26
+ hostPriceCents: number;
27
+ }
28
+
29
+ export const FLYER_PRICING_TIERS: FlyerTierQuote[] = [
30
+ {
31
+ tierId: "starter",
32
+ label: "Starter",
33
+ minHouseholds: 200,
34
+ maxHouseholds: 499,
35
+ hostPriceCents: 14900,
36
+ },
37
+ {
38
+ tierId: "neighborhood",
39
+ label: "Neighborhood",
40
+ minHouseholds: 500,
41
+ maxHouseholds: 1499,
42
+ hostPriceCents: 32900,
43
+ },
44
+ {
45
+ tierId: "district",
46
+ label: "District",
47
+ minHouseholds: 1500,
48
+ maxHouseholds: 3999,
49
+ hostPriceCents: 69900,
50
+ },
51
+ {
52
+ tierId: "metro",
53
+ label: "Metro",
54
+ minHouseholds: 4000,
55
+ maxHouseholds: 9999,
56
+ hostPriceCents: 139900,
57
+ },
58
+ ];
59
+
60
+ /** Default sponsor slot list prices (Utah / intermountain — cents). */
61
+ export const FLYER_SPONSOR_DEFAULT_FEATURED_CENTS = 10000;
62
+ export const FLYER_SPONSOR_DEFAULT_SUPPORTING_CENTS = 5000;
63
+
64
+ export function isEddmFlatSizeCompliant(widthIn: number, heightIn: number): boolean {
65
+ const long = Math.max(widthIn, heightIn);
66
+ const short = Math.min(widthIn, heightIn);
67
+ return long >= FLYER_EDDM_MIN_HEIGHT_IN && short >= FLYER_EDDM_MIN_WIDTH_IN;
68
+ }
69
+
70
+ /**
71
+ * Map total selected households to a pricing tier. Returns null if below minimum (200 for EDDM Retail).
72
+ */
73
+ export function quoteFlyerTierForHouseholds(
74
+ totalHouseholds: number
75
+ ): FlyerTierQuote | null {
76
+ if (totalHouseholds < FLYER_PRICING_TIERS[0].minHouseholds) {
77
+ return null;
78
+ }
79
+ for (const tier of FLYER_PRICING_TIERS) {
80
+ if (
81
+ totalHouseholds >= tier.minHouseholds &&
82
+ totalHouseholds <= tier.maxHouseholds
83
+ ) {
84
+ return tier;
85
+ }
86
+ }
87
+ // Above metro max — use metro as ceiling for MVP
88
+ const last = FLYER_PRICING_TIERS[FLYER_PRICING_TIERS.length - 1];
89
+ if (totalHouseholds > last.maxHouseholds) {
90
+ return last;
91
+ }
92
+ return null;
93
+ }
94
+
95
+ export function computeHostTotalAfterSponsors(
96
+ printPostageCostCents: number,
97
+ sponsorRevenueCents: number
98
+ ): number {
99
+ return Math.max(0, printPostageCostCents - sponsorRevenueCents);
100
+ }
101
+
102
+ const EARTH_RADIUS_MI = 3958.8;
103
+
104
+ /** Haversine distance in miles between two WGS84 points. */
105
+ export function distanceMiles(
106
+ lat1: number,
107
+ lng1: number,
108
+ lat2: number,
109
+ lng2: number
110
+ ): number {
111
+ const rlat1 = (lat1 * Math.PI) / 180;
112
+ const rlat2 = (lat2 * Math.PI) / 180;
113
+ const dLat = rlat2 - rlat1;
114
+ const dLng = ((lng2 - lng1) * Math.PI) / 180;
115
+ const a =
116
+ Math.sin(dLat / 2) * Math.sin(dLat / 2) +
117
+ Math.cos(rlat1) * Math.cos(rlat2) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
118
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
119
+ return EARTH_RADIUS_MI * c;
120
+ }
121
+
122
+ /** URL path for the public QR scan endpoint (prepend API origin in clients). */
123
+ export function flyerQrScanPath(campaignQrSlug: string): string {
124
+ return `/api/flyer/qr-scan?c=${encodeURIComponent(campaignQrSlug)}`;
125
+ }
126
+
127
+ /**
128
+ * Short slug for campaign QR (avoid ambiguous chars); unique enforced in DB.
129
+ */
130
+ export function generateFlyerCampaignSlug(): string {
131
+ const alphabet = "23456789abcdefghjkmnpqrstuvwxyz";
132
+ let out = "fb";
133
+ for (let i = 0; i < 12; i++) {
134
+ out += alphabet[Math.floor(Math.random() * alphabet.length)];
135
+ }
136
+ return out;
137
+ }
@@ -1,6 +0,0 @@
1
- /**
2
- * Tests for cancellation refund resolution (single source of truth:
3
- * SERVICE_CANCELLATION_POLICY_DATA).
4
- */
5
- export {};
6
- //# sourceMappingURL=cancellationPolicyRefundResolver.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cancellationPolicyRefundResolver.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/cancellationPolicyRefundResolver.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -1,104 +0,0 @@
1
- /**
2
- * Tests for cancellation refund resolution (single source of truth:
3
- * SERVICE_CANCELLATION_POLICY_DATA).
4
- */
5
- import { ServiceCancellationPolicy } from "@prisma/client";
6
- import { cancellationRuleThresholdHours, getMatchingCancellationRefundRule, hoursUntilEventStart, resolveCancellationRefundFraction, } from "../service/cancellationPolicyRefundResolver.js";
7
- describe("cancellationPolicyRefundResolver", () => {
8
- const fixedNow = new Date("2026-01-01T12:00:00.000Z");
9
- describe("hoursUntilEventStart", () => {
10
- test("returns 0 when event is in the past", () => {
11
- expect(hoursUntilEventStart(new Date("2025-12-01T12:00:00.000Z"), fixedNow)).toBe(0);
12
- });
13
- test("returns positive hours until start", () => {
14
- const start = new Date("2026-01-03T12:00:00.000Z");
15
- expect(hoursUntilEventStart(start, fixedNow)).toBe(48);
16
- });
17
- });
18
- describe("cancellationRuleThresholdHours", () => {
19
- test("converts days to hours", () => {
20
- expect(cancellationRuleThresholdHours({ days: 7, refundPercentage: 100 })).toBe(168);
21
- });
22
- test("uses hours when set", () => {
23
- expect(cancellationRuleThresholdHours({ hours: 24, refundPercentage: 50 })).toBe(24);
24
- });
25
- });
26
- describe("VendorStandard (14d full, 3d partial)", () => {
27
- test("20 days before: 100%", () => {
28
- const eventStart = new Date("2026-01-21T12:00:00.000Z");
29
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorStandard, {
30
- eventStart,
31
- now: fixedNow,
32
- })).toBe(1);
33
- });
34
- test("10 days before: 50%", () => {
35
- const eventStart = new Date("2026-01-11T12:00:00.000Z");
36
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorStandard, {
37
- eventStart,
38
- now: fixedNow,
39
- })).toBe(0.5);
40
- });
41
- test("2 days before: 0%", () => {
42
- const eventStart = new Date("2026-01-03T12:00:00.000Z");
43
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorStandard, {
44
- eventStart,
45
- now: fixedNow,
46
- })).toBe(0);
47
- });
48
- });
49
- describe("VendorFlexible (7d full, 24h partial)", () => {
50
- test("8 days before: 100%", () => {
51
- const eventStart = new Date("2026-01-09T12:00:00.000Z");
52
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorFlexible, {
53
- eventStart,
54
- now: fixedNow,
55
- })).toBe(1);
56
- });
57
- test("5 days before: 50%", () => {
58
- const eventStart = new Date("2026-01-06T12:00:00.000Z");
59
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorFlexible, {
60
- eventStart,
61
- now: fixedNow,
62
- })).toBe(0.5);
63
- });
64
- test("12 hours before: 0%", () => {
65
- const eventStart = new Date("2026-01-02T00:00:00.000Z");
66
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorFlexible, {
67
- eventStart,
68
- now: fixedNow,
69
- })).toBe(0);
70
- });
71
- test("6d 23h before (~167h): still 50% tier, not full 7d", () => {
72
- const eventStart = new Date("2026-01-08T11:00:00.000Z");
73
- expect(hoursUntilEventStart(eventStart, fixedNow)).toBeCloseTo(167, 0);
74
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VendorFlexible, {
75
- eventStart,
76
- now: fixedNow,
77
- })).toBe(0.5);
78
- });
79
- });
80
- describe("VeryFlexible (24h full only)", () => {
81
- test("2 days before: 100%", () => {
82
- const eventStart = new Date("2026-01-03T12:00:00.000Z");
83
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VeryFlexible, {
84
- eventStart,
85
- now: fixedNow,
86
- })).toBe(1);
87
- });
88
- test("12 hours before: 0%", () => {
89
- const eventStart = new Date("2026-01-02T00:00:00.000Z");
90
- expect(resolveCancellationRefundFraction(ServiceCancellationPolicy.VeryFlexible, {
91
- eventStart,
92
- now: fixedNow,
93
- })).toBe(0);
94
- });
95
- });
96
- describe("getMatchingCancellationRefundRule", () => {
97
- test("returns the winning tier rule object", () => {
98
- const eventStart = new Date("2026-01-11T12:00:00.000Z");
99
- const rule = getMatchingCancellationRefundRule(ServiceCancellationPolicy.VendorStandard, { eventStart, now: fixedNow });
100
- expect(rule).toEqual({ days: 3, refundPercentage: 50 });
101
- });
102
- });
103
- });
104
- //# sourceMappingURL=cancellationPolicyRefundResolver.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cancellationPolicyRefundResolver.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/cancellationPolicyRefundResolver.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- //# sourceMappingURL=serviceRateDBUtils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serviceRateDBUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/service/serviceRateDBUtils.ts"],"names":[],"mappings":""}
@@ -1,159 +0,0 @@
1
- "use strict";
2
- // import { ServiceRate } from "@prisma/client";
3
- // import { ApiResult } from "../../definitions";
4
- // import { ServiceSpecialRatesExt } from "../../extendedSchemas";
5
- // import { convertDollarsToCents, convertCentsToDollars } from "../paymentUtils";
6
- // export function specialRateConvertToDb(
7
- // specialRate: Partial<ServiceSpecialRatesExt>
8
- // ): Partial<ServiceSpecialRatesExt> {
9
- // return specialRateConvertRatesToDB(
10
- // specialRateConvertDateStringsToDb(structuredClone(specialRate))
11
- // );
12
- // }
13
- // export function specialRateConvertFromDb(
14
- // specialRate: ServiceSpecialRatesExt
15
- // ): ServiceSpecialRatesExt {
16
- // // console.log(`specialRateConvertFromDb`);
17
- // return specialRateConvertRatesFromDB(
18
- // specialRateConvertDateStringsFromDb(specialRate)
19
- // );
20
- // }
21
- // function specialRateConvertDateStringsToDb(
22
- // specialRate: Partial<ServiceSpecialRatesExt>
23
- // ): Partial<ServiceSpecialRatesExt> {
24
- // // const oldStart = new Date(specialRate.startDate!);
25
- // // const oldEnd = new Date(specialRate.endDate!);
26
- // // specialRate.startDate = dateTimeLocalToUTCDate(specialRate.startDate!);
27
- // // specialRate.endDate = dateTimeLocalToUTCDate(specialRate.endDate!);
28
- // // specialRate.startDate = normalizeDate(specialRate.startDate!);
29
- // // specialRate.endDate = normalizeDate(specialRate.endDate!);
30
- // // const localStart = dateTimeLocalFromUTCDate(new Date(specialRate.startDate!));
31
- // // const localEnd = dateTimeLocalFromUTCDate(new Date(specialRate.endDate!));
32
- // // console.log(
33
- // // `specialRateConvertDateStringsToDb oldStart: ${oldStart?.toString()}`
34
- // // );
35
- // // console.log(
36
- // // `specialRateConvertDateStringsToDb oldEnd: ${oldEnd?.toString()}`
37
- // // );
38
- // // console.log(
39
- // // `specialRateConvertDateStringsToDb specialRate.startDate: ${specialRate.startDate?.toString()}`
40
- // // );
41
- // // console.log(
42
- // // `specialRateConvertDateStringsToDb specialRate.endDate: ${specialRate.endDate?.toString()}`
43
- // // );
44
- // // console.log(
45
- // // `specialRateConvertDateStringsToDb localStart: ${localStart?.toString()}`
46
- // // );
47
- // // console.log(
48
- // // `specialRateConvertDateStringsToDb localEnd: ${localEnd?.toString()}`
49
- // // );
50
- // // const a = 3 + 4;
51
- // // specialRate.startDate = specialRate.startDate.toISOString();
52
- // // specialRate.endDate = specialRate.endDate.toISOString();
53
- // // return specialRate;
54
- // return specialRate;
55
- // }
56
- // function specialRateConvertDateStringsFromDb(
57
- // specialRate: ServiceSpecialRatesExt
58
- // ): ServiceSpecialRatesExt {
59
- // // specialRate.startDate = localDateToUTC(new Date(specialRate.startDate));
60
- // // specialRate.endDate = localDateToUTC(new Date(specialRate.endDate));
61
- // //this shouldnt even be necessary as javascript dates are always stored in UTC timezone (they lack timezone information)
62
- // // specialRate.startDate = ensureDateTimeIsLocalDateTime(
63
- // // new Date(specialRate.startDate)
64
- // // ).toDate();
65
- // // specialRate.endDate = ensureDateTimeIsLocalDateTime(
66
- // // new Date(specialRate.endDate)
67
- // // ).toDate();
68
- // specialRate.startDate = new Date(specialRate.startDate);
69
- // specialRate.endDate = new Date(specialRate.endDate);
70
- // // const oldStart = new Date(specialRate.startDate!);
71
- // // const oldEnd = new Date(specialRate.endDate!);
72
- // // specialRate.startDate = dateTimeLocalFromUTCDate(
73
- // // new Date(specialRate.startDate)
74
- // // );
75
- // // specialRate.endDate = dateTimeLocalFromUTCDate(new Date(specialRate.endDate));
76
- // // // specialRate.startDate = ensureDateTimeIsLocalDateTime(
77
- // // // new Date(specialRate.startDate)
78
- // // // ).toDate();
79
- // // // specialRate.endDate = ensureDateTimeIsLocalDateTime(
80
- // // // new Date(specialRate.endDate)
81
- // // // ).toDate();
82
- // // // specialRate.startDate = localDateToUTC(new Date(specialRate.startDate));
83
- // // // specialRate.endDate = localDateToUTC(new Date(specialRate.endDate));
84
- // // const date2 = normalizeDate(specialRate.startDate)!;
85
- // // const date3 = normalizeDate(specialRate.endDate)!;
86
- // // const a = 3 + 4;
87
- // return specialRate;
88
- // }
89
- // function specialRateConvertRatesToDB(
90
- // specialRate: Partial<ServiceSpecialRatesExt>
91
- // ): Partial<ServiceSpecialRatesExt> {
92
- // if (specialRate.serviceRate) {
93
- // specialRate.serviceRate = generalRatesConvertRatesToDB(
94
- // specialRate.serviceRate
95
- // ) as ServiceRate;
96
- // }
97
- // return specialRate;
98
- // }
99
- // function specialRateConvertRatesFromDB(
100
- // specialRate: ServiceSpecialRatesExt
101
- // ): ServiceSpecialRatesExt {
102
- // if (specialRate.serviceRate) {
103
- // specialRate.serviceRate = generalRatesConvertRatesFromDB(
104
- // specialRate.serviceRate
105
- // );
106
- // }
107
- // return specialRate;
108
- // }
109
- // export function generalRatesConvertRatesToDB(
110
- // generalRate: ServiceRate
111
- // ): ServiceRate {
112
- // return generalRatesConvertRatesToDBHelper(structuredClone(generalRate));
113
- // }
114
- // export function generalRatesConvertRatesToDBHelper(
115
- // generalRate: ServiceRate
116
- // ): ServiceRate {
117
- // // console.log(
118
- // // `generalRatesConvertRatesToDBHelper A: ${JSON.stringify(generalRate)}`
119
- // // );
120
- // if (generalRate?.dailyRate) {
121
- // generalRate.dailyRate = convertDollarsToCents(generalRate.dailyRate);
122
- // }
123
- // if (generalRate?.hourlyRate) {
124
- // generalRate.hourlyRate = convertDollarsToCents(generalRate.hourlyRate);
125
- // }
126
- // if (generalRate?.cleaningFeePerBooking) {
127
- // generalRate.cleaningFeePerBooking = convertDollarsToCents(
128
- // generalRate.cleaningFeePerBooking
129
- // );
130
- // }
131
- // // console.log(
132
- // // `generalRatesConvertRatesToDBHelper B: ${JSON.stringify(generalRate)}`
133
- // // );
134
- // return generalRate;
135
- // }
136
- // export function generalRatesConvertRatesFromDB(
137
- // generalRate: ServiceRate
138
- // ): ServiceRate {
139
- // // console.log(
140
- // // `generalRatesConvertRatesFromDB A: ${JSON.stringify(generalRate)}`
141
- // // );
142
- // // console.log(`specialRateConvertRatesFromDB`);
143
- // if (generalRate?.dailyRate) {
144
- // generalRate.dailyRate = convertCentsToDollars(generalRate.dailyRate);
145
- // }
146
- // if (generalRate?.hourlyRate) {
147
- // generalRate.hourlyRate = convertCentsToDollars(generalRate.hourlyRate);
148
- // }
149
- // if (generalRate?.cleaningFeePerBooking) {
150
- // generalRate.cleaningFeePerBooking = convertCentsToDollars(
151
- // generalRate.cleaningFeePerBooking
152
- // );
153
- // }
154
- // // console.log(
155
- // // `generalRatesConvertRatesFromDB B: ${JSON.stringify(generalRate)}`
156
- // // );
157
- // return generalRate;
158
- // }
159
- //# sourceMappingURL=serviceRateDBUtils.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serviceRateDBUtils.js","sourceRoot":"","sources":["../../../src/utils/service/serviceRateDBUtils.ts"],"names":[],"mappings":";AAAA,gDAAgD;AAChD,iDAAiD;AACjD,kEAAkE;AAClE,kFAAkF;AAElF,0CAA0C;AAC1C,iDAAiD;AACjD,uCAAuC;AACvC,wCAAwC;AACxC,sEAAsE;AACtE,OAAO;AACP,IAAI;AAEJ,4CAA4C;AAC5C,wCAAwC;AACxC,8BAA8B;AAC9B,gDAAgD;AAChD,0CAA0C;AAC1C,uDAAuD;AACvD,OAAO;AACP,IAAI;AAEJ,8CAA8C;AAC9C,iDAAiD;AACjD,uCAAuC;AACvC,0DAA0D;AAC1D,sDAAsD;AAEtD,+EAA+E;AAC/E,2EAA2E;AAC3E,sEAAsE;AACtE,kEAAkE;AAElE,sFAAsF;AACtF,kFAAkF;AAElF,oBAAoB;AACpB,+EAA+E;AAC/E,UAAU;AACV,oBAAoB;AACpB,2EAA2E;AAC3E,UAAU;AACV,oBAAoB;AACpB,yGAAyG;AACzG,UAAU;AACV,oBAAoB;AACpB,qGAAqG;AACrG,UAAU;AACV,oBAAoB;AACpB,mFAAmF;AACnF,UAAU;AACV,oBAAoB;AACpB,+EAA+E;AAC/E,UAAU;AAEV,wBAAwB;AAExB,oEAAoE;AACpE,gEAAgE;AAChE,2BAA2B;AAC3B,wBAAwB;AACxB,IAAI;AAEJ,gDAAgD;AAChD,wCAAwC;AACxC,8BAA8B;AAC9B,gFAAgF;AAChF,4EAA4E;AAE5E,6HAA6H;AAC7H,8DAA8D;AAC9D,yCAAyC;AACzC,mBAAmB;AACnB,4DAA4D;AAC5D,uCAAuC;AACvC,mBAAmB;AAEnB,6DAA6D;AAC7D,yDAAyD;AAEzD,0DAA0D;AAC1D,sDAAsD;AAEtD,yDAAyD;AACzD,yCAAyC;AACzC,UAAU;AACV,sFAAsF;AAEtF,iEAAiE;AACjE,4CAA4C;AAC5C,sBAAsB;AACtB,+DAA+D;AAC/D,0CAA0C;AAC1C,sBAAsB;AAEtB,mFAAmF;AACnF,+EAA+E;AAE/E,4DAA4D;AAC5D,0DAA0D;AAE1D,wBAAwB;AACxB,wBAAwB;AACxB,IAAI;AAEJ,wCAAwC;AACxC,iDAAiD;AACjD,uCAAuC;AACvC,mCAAmC;AACnC,8DAA8D;AAC9D,gCAAgC;AAChC,wBAAwB;AACxB,MAAM;AACN,wBAAwB;AACxB,IAAI;AAEJ,0CAA0C;AAC1C,wCAAwC;AACxC,8BAA8B;AAC9B,mCAAmC;AACnC,gEAAgE;AAChE,gCAAgC;AAChC,SAAS;AACT,MAAM;AACN,wBAAwB;AACxB,IAAI;AAEJ,gDAAgD;AAChD,6BAA6B;AAC7B,mBAAmB;AACnB,6EAA6E;AAC7E,IAAI;AAEJ,sDAAsD;AACtD,6BAA6B;AAC7B,mBAAmB;AACnB,sBAAsB;AACtB,kFAAkF;AAClF,YAAY;AACZ,kCAAkC;AAClC,4EAA4E;AAC5E,MAAM;AACN,mCAAmC;AACnC,8EAA8E;AAC9E,MAAM;AACN,8CAA8C;AAC9C,iEAAiE;AACjE,0CAA0C;AAC1C,SAAS;AACT,MAAM;AACN,sBAAsB;AACtB,kFAAkF;AAClF,YAAY;AACZ,wBAAwB;AACxB,IAAI;AAEJ,kDAAkD;AAClD,6BAA6B;AAC7B,mBAAmB;AACnB,sBAAsB;AACtB,8EAA8E;AAC9E,YAAY;AACZ,qDAAqD;AACrD,kCAAkC;AAClC,4EAA4E;AAC5E,MAAM;AACN,mCAAmC;AACnC,8EAA8E;AAC9E,MAAM;AACN,8CAA8C;AAC9C,iEAAiE;AACjE,0CAA0C;AAC1C,SAAS;AACT,MAAM;AACN,sBAAsB;AACtB,8EAA8E;AAC9E,YAAY;AACZ,wBAAwB;AACxB,IAAI"}
@@ -1,53 +0,0 @@
1
- -- Migration: Create BashAvailability table and BashAvailabilityWindow enum
2
- -- Required for the "Who's going out?" / LookingForSomethingSection feature.
3
- -- Run this against QA and prod before (or alongside) deploying the API code.
4
-
5
- DO $$ BEGIN
6
-
7
- -- 1. Create the enum type if it doesn't exist
8
- IF NOT EXISTS (
9
- SELECT 1 FROM pg_type WHERE typname = 'BashAvailabilityWindow'
10
- ) THEN
11
- CREATE TYPE "BashAvailabilityWindow" AS ENUM (
12
- 'Now',
13
- 'Tonight',
14
- 'ThisWeekend',
15
- 'Custom'
16
- );
17
- RAISE NOTICE 'Created enum BashAvailabilityWindow';
18
- ELSE
19
- RAISE NOTICE 'Enum BashAvailabilityWindow already exists — skipping';
20
- END IF;
21
-
22
- -- 2. Create the BashAvailability table if it doesn't exist
23
- IF NOT EXISTS (
24
- SELECT 1 FROM information_schema.tables
25
- WHERE table_name = 'BashAvailability'
26
- ) THEN
27
- CREATE TABLE "BashAvailability" (
28
- "id" TEXT NOT NULL,
29
- "userId" TEXT NOT NULL,
30
- "window" "BashAvailabilityWindow" NOT NULL,
31
- "city" TEXT NOT NULL,
32
- "state" TEXT,
33
- "customAt" TIMESTAMP(3),
34
- "expiresAt" TIMESTAMP(3) NOT NULL,
35
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT NOW(),
36
- "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT NOW(),
37
-
38
- CONSTRAINT "BashAvailability_pkey" PRIMARY KEY ("id"),
39
- CONSTRAINT "BashAvailability_userId_key" UNIQUE ("userId"),
40
- CONSTRAINT "BashAvailability_userId_fkey"
41
- FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE
42
- );
43
-
44
- -- Composite index used by GET /availability?city=...&window=...
45
- CREATE INDEX "BashAvailability_city_window_expiresAt_idx"
46
- ON "BashAvailability" ("city", "window", "expiresAt");
47
-
48
- RAISE NOTICE 'Created table BashAvailability';
49
- ELSE
50
- RAISE NOTICE 'Table BashAvailability already exists — skipping';
51
- END IF;
52
-
53
- END $$;