@bash-app/bash-common 30.225.0 → 30.226.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 (109) hide show
  1. package/dist/__tests__/agePolicy.test.d.ts +2 -0
  2. package/dist/__tests__/agePolicy.test.d.ts.map +1 -0
  3. package/dist/__tests__/agePolicy.test.js +203 -0
  4. package/dist/__tests__/agePolicy.test.js.map +1 -0
  5. package/dist/__tests__/getDisplayGender.test.d.ts +2 -0
  6. package/dist/__tests__/getDisplayGender.test.d.ts.map +1 -0
  7. package/dist/__tests__/getDisplayGender.test.js +34 -0
  8. package/dist/__tests__/getDisplayGender.test.js.map +1 -0
  9. package/dist/__tests__/statusEnums.test.d.ts +2 -0
  10. package/dist/__tests__/statusEnums.test.d.ts.map +1 -0
  11. package/dist/__tests__/statusEnums.test.js +24 -0
  12. package/dist/__tests__/statusEnums.test.js.map +1 -0
  13. package/dist/aiApproval.d.ts +64 -0
  14. package/dist/aiApproval.d.ts.map +1 -0
  15. package/dist/aiApproval.js +54 -0
  16. package/dist/aiApproval.js.map +1 -0
  17. package/dist/definitions.d.ts +44 -2
  18. package/dist/definitions.d.ts.map +1 -1
  19. package/dist/definitions.js +106 -2
  20. package/dist/definitions.js.map +1 -1
  21. package/dist/extendedSchemas.d.ts +237 -104
  22. package/dist/extendedSchemas.d.ts.map +1 -1
  23. package/dist/extendedSchemas.js +12 -2
  24. package/dist/extendedSchemas.js.map +1 -1
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/legalTemplates.d.ts +1 -1
  30. package/dist/legalTemplates.js +1 -1
  31. package/dist/utils/__tests__/badgeUtils.test.d.ts +2 -0
  32. package/dist/utils/__tests__/badgeUtils.test.d.ts.map +1 -0
  33. package/dist/utils/__tests__/badgeUtils.test.js +157 -0
  34. package/dist/utils/__tests__/badgeUtils.test.js.map +1 -0
  35. package/dist/utils/__tests__/bashPointsPaymentUtils.test.d.ts +2 -0
  36. package/dist/utils/__tests__/bashPointsPaymentUtils.test.d.ts.map +1 -0
  37. package/dist/utils/__tests__/bashPointsPaymentUtils.test.js +114 -0
  38. package/dist/utils/__tests__/bashPointsPaymentUtils.test.js.map +1 -0
  39. package/dist/utils/__tests__/contentFilterUtils.test.d.ts +2 -0
  40. package/dist/utils/__tests__/contentFilterUtils.test.d.ts.map +1 -0
  41. package/dist/utils/__tests__/contentFilterUtils.test.js +443 -0
  42. package/dist/utils/__tests__/contentFilterUtils.test.js.map +1 -0
  43. package/dist/utils/__tests__/displayDateUtils.test.d.ts +2 -0
  44. package/dist/utils/__tests__/displayDateUtils.test.d.ts.map +1 -0
  45. package/dist/utils/__tests__/displayDateUtils.test.js +105 -0
  46. package/dist/utils/__tests__/displayDateUtils.test.js.map +1 -0
  47. package/dist/utils/__tests__/getBackendHost.test.d.ts +2 -0
  48. package/dist/utils/__tests__/getBackendHost.test.d.ts.map +1 -0
  49. package/dist/utils/__tests__/getBackendHost.test.js +104 -0
  50. package/dist/utils/__tests__/getBackendHost.test.js.map +1 -0
  51. package/dist/utils/__tests__/getFrontendHost.test.d.ts +2 -0
  52. package/dist/utils/__tests__/getFrontendHost.test.d.ts.map +1 -0
  53. package/dist/utils/__tests__/getFrontendHost.test.js +207 -0
  54. package/dist/utils/__tests__/getFrontendHost.test.js.map +1 -0
  55. package/dist/utils/__tests__/promoCodesUtils.test.d.ts +2 -0
  56. package/dist/utils/__tests__/promoCodesUtils.test.d.ts.map +1 -0
  57. package/dist/utils/__tests__/promoCodesUtils.test.js +183 -0
  58. package/dist/utils/__tests__/promoCodesUtils.test.js.map +1 -0
  59. package/dist/utils/__tests__/qrCodeUtils.test.d.ts +2 -0
  60. package/dist/utils/__tests__/qrCodeUtils.test.d.ts.map +1 -0
  61. package/dist/utils/__tests__/qrCodeUtils.test.js +99 -0
  62. package/dist/utils/__tests__/qrCodeUtils.test.js.map +1 -0
  63. package/dist/utils/__tests__/stripeAccountUtils.test.d.ts +2 -0
  64. package/dist/utils/__tests__/stripeAccountUtils.test.d.ts.map +1 -0
  65. package/dist/utils/__tests__/stripeAccountUtils.test.js +75 -0
  66. package/dist/utils/__tests__/stripeAccountUtils.test.js.map +1 -0
  67. package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.d.ts +2 -0
  68. package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.d.ts.map +1 -0
  69. package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.js +233 -0
  70. package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.js.map +1 -0
  71. package/dist/utils/blog/blogCommentDbUtils.d.ts +161 -0
  72. package/dist/utils/blog/blogCommentDbUtils.d.ts.map +1 -0
  73. package/dist/utils/blog/blogCommentDbUtils.js +111 -0
  74. package/dist/utils/blog/blogCommentDbUtils.js.map +1 -0
  75. package/dist/utils/blog/blogDbUtils.d.ts +10 -0
  76. package/dist/utils/blog/blogDbUtils.d.ts.map +1 -1
  77. package/dist/utils/blog/blogDbUtils.js +71 -0
  78. package/dist/utils/blog/blogDbUtils.js.map +1 -1
  79. package/dist/utils/urlUtils.d.ts +10 -0
  80. package/dist/utils/urlUtils.d.ts.map +1 -1
  81. package/dist/utils/urlUtils.js +24 -5
  82. package/dist/utils/urlUtils.js.map +1 -1
  83. package/dist/utils/userUtils.d.ts +32 -0
  84. package/dist/utils/userUtils.d.ts.map +1 -1
  85. package/dist/utils/userUtils.js +27 -0
  86. package/dist/utils/userUtils.js.map +1 -1
  87. package/package.json +1 -1
  88. package/prisma/schema.prisma +927 -845
  89. package/src/__tests__/agePolicy.test.ts +257 -0
  90. package/src/__tests__/getDisplayGender.test.ts +50 -0
  91. package/src/__tests__/statusEnums.test.ts +36 -0
  92. package/src/definitions.ts +138 -1
  93. package/src/extendedSchemas.ts +41 -2
  94. package/src/index.ts +1 -0
  95. package/src/legalTemplates.ts +1 -1
  96. package/src/utils/__tests__/badgeUtils.test.ts +191 -0
  97. package/src/utils/__tests__/bashPointsPaymentUtils.test.ts +153 -0
  98. package/src/utils/__tests__/contentFilterUtils.test.ts +547 -0
  99. package/src/utils/__tests__/displayDateUtils.test.ts +113 -0
  100. package/src/utils/__tests__/getBackendHost.test.ts +128 -0
  101. package/src/utils/__tests__/getFrontendHost.test.ts +232 -0
  102. package/src/utils/__tests__/promoCodesUtils.test.ts +237 -0
  103. package/src/utils/__tests__/qrCodeUtils.test.ts +143 -0
  104. package/src/utils/__tests__/stripeAccountUtils.test.ts +93 -0
  105. package/src/utils/blog/__tests__/getRelatedBlogPosts.test.ts +271 -0
  106. package/src/utils/blog/blogCommentDbUtils.ts +161 -0
  107. package/src/utils/blog/blogDbUtils.ts +95 -1
  108. package/src/utils/urlUtils.ts +27 -5
  109. package/src/utils/userUtils.ts +40 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qrCodeUtils.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/qrCodeUtils.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stripeAccountUtils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stripeAccountUtils.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/stripeAccountUtils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * stripeAccountUtils — the gate that decides whether a host can accept money.
3
+ *
4
+ * `stripeAccountFullyLinked` returns `true` ONLY when ALL FOUR of these are true:
5
+ * - linkingStatus is non-null
6
+ * - hasOutstandingRequirements === false
7
+ * - isOnboarded === true
8
+ * - paymentsEnabled === true
9
+ * - taxMonitoringEnabled === true
10
+ *
11
+ * This is the single check used everywhere we decide "can this host get paid?"
12
+ * Returning `true` when any one is false leads to checkouts that can't actually
13
+ * disburse money. We pin all 5 axes individually so a future refactor that
14
+ * accidentally drops one will fail loudly.
15
+ */
16
+ import { stripeAccountFullyLinked } from "../stripeAccountUtils.js";
17
+ const fullyLinked = {
18
+ isOnboarded: true,
19
+ hasOutstandingRequirements: false,
20
+ paymentsEnabled: true,
21
+ taxMonitoringEnabled: true,
22
+ };
23
+ describe("stripeAccountFullyLinked", () => {
24
+ describe("nullish guard", () => {
25
+ it("returns false for null", () => {
26
+ expect(stripeAccountFullyLinked(null)).toBe(false);
27
+ });
28
+ it("returns false for undefined", () => {
29
+ expect(stripeAccountFullyLinked(undefined)).toBe(false);
30
+ });
31
+ });
32
+ describe("happy path", () => {
33
+ it("returns true when ALL four flags are correct", () => {
34
+ expect(stripeAccountFullyLinked(fullyLinked)).toBe(true);
35
+ });
36
+ });
37
+ describe("each individual gate must be satisfied", () => {
38
+ it("returns false when isOnboarded is false", () => {
39
+ expect(stripeAccountFullyLinked({ ...fullyLinked, isOnboarded: false })).toBe(false);
40
+ });
41
+ it("returns false when hasOutstandingRequirements is true", () => {
42
+ expect(stripeAccountFullyLinked({
43
+ ...fullyLinked,
44
+ hasOutstandingRequirements: true,
45
+ })).toBe(false);
46
+ });
47
+ it("returns false when paymentsEnabled is false", () => {
48
+ expect(stripeAccountFullyLinked({ ...fullyLinked, paymentsEnabled: false })).toBe(false);
49
+ });
50
+ it("returns false when taxMonitoringEnabled is false", () => {
51
+ expect(stripeAccountFullyLinked({
52
+ ...fullyLinked,
53
+ taxMonitoringEnabled: false,
54
+ })).toBe(false);
55
+ });
56
+ });
57
+ describe("multi-flag failures (any combination = false)", () => {
58
+ it("returns false when payments AND tax monitoring are both disabled", () => {
59
+ expect(stripeAccountFullyLinked({
60
+ ...fullyLinked,
61
+ paymentsEnabled: false,
62
+ taxMonitoringEnabled: false,
63
+ })).toBe(false);
64
+ });
65
+ it("returns false when EVERY flag is in the wrong state", () => {
66
+ expect(stripeAccountFullyLinked({
67
+ isOnboarded: false,
68
+ hasOutstandingRequirements: true,
69
+ paymentsEnabled: false,
70
+ taxMonitoringEnabled: false,
71
+ })).toBe(false);
72
+ });
73
+ });
74
+ });
75
+ //# sourceMappingURL=stripeAccountUtils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stripeAccountUtils.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/stripeAccountUtils.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAGpE,MAAM,WAAW,GAAwB;IACvC,WAAW,EAAE,IAAI;IACjB,0BAA0B,EAAE,KAAK;IACjC,eAAe,EAAE,IAAI;IACrB,oBAAoB,EAAE,IAAI;CAC3B,CAAC;AAEF,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;YAChC,MAAM,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;YACrC,MAAM,CAAC,wBAAwB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,CAAC,wBAAwB,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACtD,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,CACJ,wBAAwB,CAAC,EAAE,GAAG,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CACjE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;YAC/D,MAAM,CACJ,wBAAwB,CAAC;gBACvB,GAAG,WAAW;gBACd,0BAA0B,EAAE,IAAI;aACjC,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,MAAM,CACJ,wBAAwB,CAAC,EAAE,GAAG,WAAW,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CACrE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;YAC1D,MAAM,CACJ,wBAAwB,CAAC;gBACvB,GAAG,WAAW;gBACd,oBAAoB,EAAE,KAAK;aAC5B,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,+CAA+C,EAAE,GAAG,EAAE;QAC7D,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;YAC1E,MAAM,CACJ,wBAAwB,CAAC;gBACvB,GAAG,WAAW;gBACd,eAAe,EAAE,KAAK;gBACtB,oBAAoB,EAAE,KAAK;aAC5B,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;YAC7D,MAAM,CACJ,wBAAwB,CAAC;gBACvB,WAAW,EAAE,KAAK;gBAClB,0BAA0B,EAAE,IAAI;gBAChC,eAAe,EAAE,KAAK;gBACtB,oBAAoB,EAAE,KAAK;aAC5B,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=getRelatedBlogPosts.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRelatedBlogPosts.test.d.ts","sourceRoot":"","sources":["../../../../src/utils/blog/__tests__/getRelatedBlogPosts.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,233 @@
1
+ // Tests for getRelatedBlogPosts -- the tag-overlap-first scoring algorithm
2
+ // that powers the "Related reading" rail under every blog detail page. The
3
+ // scoring function is the SEO-critical surface (controls the internal-link
4
+ // graph) so we exercise every branch of the priority sort:
5
+ // 1. tag overlap count (descending)
6
+ // 2. shared category (1/0)
7
+ // 3. recency (most recent first)
8
+ //
9
+ // Plus the edge cases:
10
+ // - source post does not exist -> []
11
+ // - source has no tags + no category -> still returns recent published posts
12
+ // - limit cap
13
+ // - publishedAt null on a candidate is treated as oldest
14
+ import { BlogStatus } from "@prisma/client";
15
+ import { getRelatedBlogPosts } from "../blogDbUtils.js";
16
+ function makePrismaMock(opts) {
17
+ const findUnique = jest.fn().mockResolvedValue(opts.source);
18
+ const findMany = jest.fn().mockResolvedValue(opts.candidates);
19
+ return {
20
+ blogPost: { findUnique, findMany },
21
+ __findUnique: findUnique,
22
+ __findMany: findMany,
23
+ };
24
+ }
25
+ function candidate(overrides = {}) {
26
+ return {
27
+ id: "c-default",
28
+ slug: "c-default",
29
+ title: "Default Candidate",
30
+ excerpt: "default",
31
+ coverImage: null,
32
+ readTime: 5,
33
+ publishedAt: new Date("2026-01-01T00:00:00Z"),
34
+ categoryId: null,
35
+ tags: [],
36
+ ...overrides,
37
+ };
38
+ }
39
+ describe("getRelatedBlogPosts", () => {
40
+ it("returns [] when the source post does not exist", async () => {
41
+ const prisma = makePrismaMock({ source: null, candidates: [] });
42
+ const out = await getRelatedBlogPosts(prisma, "missing-id");
43
+ expect(out).toEqual([]);
44
+ expect(prisma.__findMany).not.toHaveBeenCalled();
45
+ });
46
+ it("ranks candidates by shared tag count (descending)", async () => {
47
+ const source = {
48
+ id: "src",
49
+ categoryId: null,
50
+ tags: [{ id: "t1" }, { id: "t2" }, { id: "t3" }],
51
+ };
52
+ const candidates = [
53
+ candidate({
54
+ id: "low",
55
+ title: "One tag in common",
56
+ tags: [{ id: "t1" }],
57
+ publishedAt: new Date("2026-03-01"),
58
+ }),
59
+ candidate({
60
+ id: "high",
61
+ title: "Three tags in common",
62
+ tags: [{ id: "t1" }, { id: "t2" }, { id: "t3" }],
63
+ publishedAt: new Date("2026-01-01"),
64
+ }),
65
+ candidate({
66
+ id: "mid",
67
+ title: "Two tags in common",
68
+ tags: [{ id: "t1" }, { id: "t2" }],
69
+ publishedAt: new Date("2026-02-01"),
70
+ }),
71
+ ];
72
+ const prisma = makePrismaMock({ source, candidates });
73
+ const out = await getRelatedBlogPosts(prisma, "src", 4);
74
+ expect(out.map((p) => p.id)).toEqual(["high", "mid", "low"]);
75
+ });
76
+ it("uses shared category as the tiebreaker when tag counts tie", async () => {
77
+ const source = {
78
+ id: "src",
79
+ categoryId: "cat-1",
80
+ tags: [{ id: "t1" }],
81
+ };
82
+ const candidates = [
83
+ candidate({
84
+ id: "without-category",
85
+ tags: [{ id: "t1" }],
86
+ categoryId: "cat-other",
87
+ publishedAt: new Date("2026-03-01"),
88
+ }),
89
+ candidate({
90
+ id: "with-category",
91
+ tags: [{ id: "t1" }],
92
+ categoryId: "cat-1",
93
+ publishedAt: new Date("2026-01-01"),
94
+ }),
95
+ ];
96
+ const prisma = makePrismaMock({ source, candidates });
97
+ const out = await getRelatedBlogPosts(prisma, "src", 4);
98
+ expect(out.map((p) => p.id)).toEqual(["with-category", "without-category"]);
99
+ });
100
+ it("falls back to recency when both tag count and category tie", async () => {
101
+ const source = {
102
+ id: "src",
103
+ categoryId: "cat-1",
104
+ tags: [{ id: "t1" }],
105
+ };
106
+ const candidates = [
107
+ candidate({
108
+ id: "older",
109
+ tags: [{ id: "t1" }],
110
+ categoryId: "cat-1",
111
+ publishedAt: new Date("2026-01-01"),
112
+ }),
113
+ candidate({
114
+ id: "newer",
115
+ tags: [{ id: "t1" }],
116
+ categoryId: "cat-1",
117
+ publishedAt: new Date("2026-04-01"),
118
+ }),
119
+ ];
120
+ const prisma = makePrismaMock({ source, candidates });
121
+ const out = await getRelatedBlogPosts(prisma, "src", 4);
122
+ expect(out.map((p) => p.id)).toEqual(["newer", "older"]);
123
+ });
124
+ it("treats null publishedAt as the oldest possible candidate", async () => {
125
+ const source = {
126
+ id: "src",
127
+ categoryId: null,
128
+ tags: [{ id: "t1" }],
129
+ };
130
+ const candidates = [
131
+ candidate({
132
+ id: "no-date",
133
+ tags: [{ id: "t1" }],
134
+ publishedAt: null,
135
+ }),
136
+ candidate({
137
+ id: "dated",
138
+ tags: [{ id: "t1" }],
139
+ publishedAt: new Date("2026-01-01"),
140
+ }),
141
+ ];
142
+ const prisma = makePrismaMock({ source, candidates });
143
+ const out = await getRelatedBlogPosts(prisma, "src", 4);
144
+ expect(out.map((p) => p.id)).toEqual(["dated", "no-date"]);
145
+ });
146
+ it("respects the limit parameter", async () => {
147
+ const source = {
148
+ id: "src",
149
+ categoryId: null,
150
+ tags: [{ id: "t1" }],
151
+ };
152
+ const candidates = Array.from({ length: 8 }, (_, i) => candidate({
153
+ id: `c${i}`,
154
+ title: `Candidate ${i}`,
155
+ tags: [{ id: "t1" }],
156
+ publishedAt: new Date(2026, 0, i + 1),
157
+ }));
158
+ const prisma = makePrismaMock({ source, candidates });
159
+ const out = await getRelatedBlogPosts(prisma, "src", 3);
160
+ expect(out).toHaveLength(3);
161
+ });
162
+ it("defaults the limit to 4 when not provided", async () => {
163
+ const source = {
164
+ id: "src",
165
+ categoryId: null,
166
+ tags: [{ id: "t1" }],
167
+ };
168
+ const candidates = Array.from({ length: 10 }, (_, i) => candidate({ id: `c${i}`, tags: [{ id: "t1" }] }));
169
+ const prisma = makePrismaMock({ source, candidates });
170
+ const out = await getRelatedBlogPosts(prisma, "src");
171
+ expect(out).toHaveLength(4);
172
+ });
173
+ it("queries by tag-overlap OR shared category, excluding the source", async () => {
174
+ const source = {
175
+ id: "src",
176
+ categoryId: "cat-1",
177
+ tags: [{ id: "t1" }, { id: "t2" }],
178
+ };
179
+ const prisma = makePrismaMock({ source, candidates: [] });
180
+ await getRelatedBlogPosts(prisma, "src", 5);
181
+ const where = prisma.__findMany.mock.calls[0][0].where;
182
+ expect(where.id).toEqual({ not: "src" });
183
+ expect(where.status).toBe(BlogStatus.Published);
184
+ expect(where.publishedAt).toEqual({ not: null });
185
+ expect(where.OR).toEqual([
186
+ { tags: { some: { id: { in: ["t1", "t2"] } } } },
187
+ { categoryId: "cat-1" },
188
+ ]);
189
+ // candidate pool size is limit * 4 to give the scorer headroom
190
+ expect(prisma.__findMany.mock.calls[0][0].take).toBe(20);
191
+ });
192
+ it("omits the OR clause entirely when source has no tags and no category", async () => {
193
+ const source = { id: "src", categoryId: null, tags: [] };
194
+ const prisma = makePrismaMock({ source, candidates: [] });
195
+ await getRelatedBlogPosts(prisma, "src");
196
+ const where = prisma.__findMany.mock.calls[0][0].where;
197
+ expect(where.OR).toBeUndefined();
198
+ });
199
+ it("returns the trimmed RelatedBlogPost shape (no internal scoring fields)", async () => {
200
+ const source = {
201
+ id: "src",
202
+ categoryId: null,
203
+ tags: [{ id: "t1" }],
204
+ };
205
+ const candidates = [
206
+ candidate({
207
+ id: "c1",
208
+ slug: "c1-slug",
209
+ title: "C1",
210
+ excerpt: "ex",
211
+ coverImage: "img.png",
212
+ readTime: 7,
213
+ publishedAt: new Date("2026-01-01"),
214
+ tags: [{ id: "t1" }],
215
+ categoryId: "cat-1",
216
+ }),
217
+ ];
218
+ const prisma = makePrismaMock({ source, candidates });
219
+ const out = await getRelatedBlogPosts(prisma, "src", 4);
220
+ expect(out[0]).toEqual({
221
+ id: "c1",
222
+ slug: "c1-slug",
223
+ title: "C1",
224
+ excerpt: "ex",
225
+ coverImage: "img.png",
226
+ readTime: 7,
227
+ publishedAt: new Date("2026-01-01"),
228
+ });
229
+ expect(Object.keys(out[0])).not.toContain("categoryId");
230
+ expect(Object.keys(out[0])).not.toContain("tags");
231
+ });
232
+ });
233
+ //# sourceMappingURL=getRelatedBlogPosts.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRelatedBlogPosts.test.js","sourceRoot":"","sources":["../../../../src/utils/blog/__tests__/getRelatedBlogPosts.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,161 @@
1
+ import { PrismaClient, BlogComment, CommentStatus } from '@prisma/client';
2
+ import { PublicUser } from '../../extendedSchemas.js';
3
+ export declare const BLOG_COMMENT_INCLUDE: {
4
+ author: {
5
+ select: {
6
+ id: true;
7
+ username: true;
8
+ email: true;
9
+ givenName: true;
10
+ familyName: true;
11
+ image: true;
12
+ imageUpdatedAt: true;
13
+ uploadedImage: true;
14
+ uploadedImageUpdatedAt: true;
15
+ isSuperUser: true;
16
+ socialMediaProfiles: true;
17
+ city: true;
18
+ state: true;
19
+ createdOn: true;
20
+ dob: true;
21
+ showAgeOnProfile: true;
22
+ showGenderOnProfile: true;
23
+ showVerifiedBadgesOnProfile: true;
24
+ schoolName: true;
25
+ documentIDId: true;
26
+ idVerified: true;
27
+ paymentVerified: true;
28
+ reviews: true;
29
+ contacts: true;
30
+ accepted: true;
31
+ boughtTicket: true;
32
+ noPay: true;
33
+ supportedEvent: true;
34
+ aboutMe: true;
35
+ levelBadge: true;
36
+ temporaryBadges: true;
37
+ artistsToSee: true;
38
+ membershipTier: true;
39
+ isAmbashador: true;
40
+ isBashInsider: true;
41
+ scoutTier: true;
42
+ isFirstInCity: true;
43
+ paymentHandle: true;
44
+ bashPointsBalance: true;
45
+ bashPointsEarnedLifetime: true;
46
+ hostRating: true;
47
+ totalRatings: true;
48
+ totalEventsHosted: true;
49
+ isSuspended: true;
50
+ userSubscription: {
51
+ select: {
52
+ type: true;
53
+ status: true;
54
+ ownerId: true;
55
+ subscriptionCount: true;
56
+ listedCount: true;
57
+ };
58
+ };
59
+ };
60
+ };
61
+ replies: {
62
+ where: {
63
+ status: "Published";
64
+ };
65
+ orderBy: {
66
+ createdAt: "asc";
67
+ };
68
+ include: {
69
+ author: {
70
+ select: {
71
+ id: true;
72
+ username: true;
73
+ email: true;
74
+ givenName: true;
75
+ familyName: true;
76
+ image: true;
77
+ imageUpdatedAt: true;
78
+ uploadedImage: true;
79
+ uploadedImageUpdatedAt: true;
80
+ isSuperUser: true;
81
+ socialMediaProfiles: true;
82
+ city: true;
83
+ state: true;
84
+ createdOn: true;
85
+ dob: true;
86
+ showAgeOnProfile: true;
87
+ showGenderOnProfile: true;
88
+ showVerifiedBadgesOnProfile: true;
89
+ schoolName: true;
90
+ documentIDId: true;
91
+ idVerified: true;
92
+ paymentVerified: true;
93
+ reviews: true;
94
+ contacts: true;
95
+ accepted: true;
96
+ boughtTicket: true;
97
+ noPay: true;
98
+ supportedEvent: true;
99
+ aboutMe: true;
100
+ levelBadge: true;
101
+ temporaryBadges: true;
102
+ artistsToSee: true;
103
+ membershipTier: true;
104
+ isAmbashador: true;
105
+ isBashInsider: true;
106
+ scoutTier: true;
107
+ isFirstInCity: true;
108
+ paymentHandle: true;
109
+ bashPointsBalance: true;
110
+ bashPointsEarnedLifetime: true;
111
+ hostRating: true;
112
+ totalRatings: true;
113
+ totalEventsHosted: true;
114
+ isSuspended: true;
115
+ userSubscription: {
116
+ select: {
117
+ type: true;
118
+ status: true;
119
+ ownerId: true;
120
+ subscriptionCount: true;
121
+ listedCount: true;
122
+ };
123
+ };
124
+ };
125
+ };
126
+ };
127
+ };
128
+ };
129
+ export interface BlogCommentWithAuthor extends BlogComment {
130
+ author: PublicUser;
131
+ replies?: (BlogComment & {
132
+ author: PublicUser;
133
+ })[];
134
+ }
135
+ export declare function validateCommentContent(content: string): {
136
+ ok: true;
137
+ } | {
138
+ ok: false;
139
+ reason: string;
140
+ };
141
+ export declare function getBlogCommentsForPost(prisma: PrismaClient, postId: string): Promise<BlogCommentWithAuthor[]>;
142
+ export declare function getBlogCommentsByStatus(prisma: PrismaClient, status: CommentStatus | CommentStatus[], options?: {
143
+ limit?: number;
144
+ offset?: number;
145
+ }): Promise<BlogCommentWithAuthor[]>;
146
+ export declare function getBlogCommentById(prisma: PrismaClient, id: string): Promise<BlogCommentWithAuthor | null>;
147
+ export declare function createBlogComment(prisma: PrismaClient, data: {
148
+ postId: string;
149
+ authorId: string;
150
+ parentId?: string | null;
151
+ content: string;
152
+ status: CommentStatus;
153
+ }): Promise<BlogCommentWithAuthor>;
154
+ export declare function updateBlogCommentContent(prisma: PrismaClient, id: string, data: {
155
+ content: string;
156
+ status?: CommentStatus;
157
+ }): Promise<BlogCommentWithAuthor>;
158
+ export declare function setBlogCommentStatus(prisma: PrismaClient, id: string, status: CommentStatus): Promise<BlogCommentWithAuthor>;
159
+ export declare function softDeleteBlogComment(prisma: PrismaClient, id: string): Promise<BlogCommentWithAuthor>;
160
+ export declare function countRecentCommentsByAuthor(prisma: PrismaClient, authorId: string, windowMs: number): Promise<number>;
161
+ //# sourceMappingURL=blogCommentDbUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blogCommentDbUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/blog/blogCommentDbUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAU,MAAM,gBAAgB,CAAC;AAClF,OAAO,EAAiC,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAIrF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaI,CAAC;AAEtC,MAAM,WAAW,qBAAsB,SAAQ,WAAW;IACxD,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,WAAW,GAAG;QAAE,MAAM,EAAE,UAAU,CAAA;KAAE,CAAC,EAAE,CAAC;CACpD;AAKD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CASpG;AAGD,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAWlC;AAGD,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,aAAa,GAAG,aAAa,EAAE,EACvC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAChD,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAWlC;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,YAAY,EACpB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAKvC;AAMD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE;IACJ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,aAAa,CAAC;CACvB,GACA,OAAO,CAAC,qBAAqB,CAAC,CAWhC;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,YAAY,EACpB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,aAAa,CAAA;CAAE,GAChD,OAAO,CAAC,qBAAqB,CAAC,CAShC;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,YAAY,EACpB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,qBAAqB,CAAC,CAMhC;AAKD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,YAAY,EACpB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,qBAAqB,CAAC,CAEhC;AAED,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAQjB"}
@@ -0,0 +1,111 @@
1
+ import { CommentStatus } from '@prisma/client';
2
+ import { FRONT_END_USER_DATA_TO_SELECT } from '../../extendedSchemas.js';
3
+ // Shape returned by every helper below. Mirrors INVITATION_DATA_TO_INCLUDE
4
+ // pattern so consumers never have to `as`-cast.
5
+ export const BLOG_COMMENT_INCLUDE = {
6
+ author: {
7
+ select: FRONT_END_USER_DATA_TO_SELECT,
8
+ },
9
+ replies: {
10
+ where: { status: CommentStatus.Published },
11
+ orderBy: { createdAt: 'asc' },
12
+ include: {
13
+ author: {
14
+ select: FRONT_END_USER_DATA_TO_SELECT,
15
+ },
16
+ },
17
+ },
18
+ };
19
+ const MAX_COMMENT_LENGTH = 4000;
20
+ const MIN_COMMENT_LENGTH = 1;
21
+ export function validateCommentContent(content) {
22
+ const trimmed = content.trim();
23
+ if (trimmed.length < MIN_COMMENT_LENGTH) {
24
+ return { ok: false, reason: 'Comment cannot be empty.' };
25
+ }
26
+ if (trimmed.length > MAX_COMMENT_LENGTH) {
27
+ return { ok: false, reason: `Comment is too long (max ${MAX_COMMENT_LENGTH} characters).` };
28
+ }
29
+ return { ok: true };
30
+ }
31
+ // Public list: only Published comments + their Published replies.
32
+ export async function getBlogCommentsForPost(prisma, postId) {
33
+ const comments = await prisma.blogComment.findMany({
34
+ where: {
35
+ postId,
36
+ parentId: null,
37
+ status: CommentStatus.Published,
38
+ },
39
+ include: BLOG_COMMENT_INCLUDE,
40
+ orderBy: { createdAt: 'desc' },
41
+ });
42
+ return comments;
43
+ }
44
+ // Admin list: any status, used by the moderation queue.
45
+ export async function getBlogCommentsByStatus(prisma, status, options = {}) {
46
+ const { limit = 50, offset = 0 } = options;
47
+ const statusFilter = Array.isArray(status) ? { in: status } : status;
48
+ const comments = await prisma.blogComment.findMany({
49
+ where: { status: statusFilter },
50
+ include: BLOG_COMMENT_INCLUDE,
51
+ orderBy: { createdAt: 'desc' },
52
+ skip: offset,
53
+ take: limit,
54
+ });
55
+ return comments;
56
+ }
57
+ export async function getBlogCommentById(prisma, id) {
58
+ return prisma.blogComment.findUnique({
59
+ where: { id },
60
+ include: BLOG_COMMENT_INCLUDE,
61
+ });
62
+ }
63
+ // Caller (the API endpoint) is responsible for running AI moderation BEFORE
64
+ // calling this and choosing the resulting `status` (Published vs Pending).
65
+ // Keeping moderation in the API layer avoids dragging the OpenAI dependency
66
+ // into bash-common.
67
+ export async function createBlogComment(prisma, data) {
68
+ return prisma.blogComment.create({
69
+ data: {
70
+ postId: data.postId,
71
+ authorId: data.authorId,
72
+ parentId: data.parentId ?? null,
73
+ content: data.content.trim(),
74
+ status: data.status,
75
+ },
76
+ include: BLOG_COMMENT_INCLUDE,
77
+ });
78
+ }
79
+ export async function updateBlogCommentContent(prisma, id, data) {
80
+ return prisma.blogComment.update({
81
+ where: { id },
82
+ data: {
83
+ content: data.content.trim(),
84
+ ...(data.status ? { status: data.status } : {}),
85
+ },
86
+ include: BLOG_COMMENT_INCLUDE,
87
+ });
88
+ }
89
+ export async function setBlogCommentStatus(prisma, id, status) {
90
+ return prisma.blogComment.update({
91
+ where: { id },
92
+ data: { status },
93
+ include: BLOG_COMMENT_INCLUDE,
94
+ });
95
+ }
96
+ // Soft delete: preserves the row (and any child replies) so threading stays
97
+ // intact, but hides it from public reads. Use `setBlogCommentStatus` with
98
+ // `Trash` rather than a hard delete unless an admin explicitly removes it.
99
+ export async function softDeleteBlogComment(prisma, id) {
100
+ return setBlogCommentStatus(prisma, id, CommentStatus.Trash);
101
+ }
102
+ export async function countRecentCommentsByAuthor(prisma, authorId, windowMs) {
103
+ const since = new Date(Date.now() - windowMs);
104
+ return prisma.blogComment.count({
105
+ where: {
106
+ authorId,
107
+ createdAt: { gte: since },
108
+ },
109
+ });
110
+ }
111
+ //# sourceMappingURL=blogCommentDbUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blogCommentDbUtils.js","sourceRoot":"","sources":["../../../src/utils/blog/blogCommentDbUtils.ts"],"names":[],"mappings":"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"}
@@ -45,4 +45,14 @@ export declare function connectOrCreateTags(prisma: PrismaClient, tagNames: stri
45
45
  }[]>;
46
46
  export declare function getPopularBlogPosts(prisma: PrismaClient, limit?: number): Promise<BlogPostExt[]>;
47
47
  export declare function getRecentBlogPosts(prisma: PrismaClient, limit?: number): Promise<BlogPostExt[]>;
48
+ export interface RelatedBlogPost {
49
+ id: string;
50
+ slug: string | null;
51
+ title: string;
52
+ excerpt: string | null;
53
+ coverImage: string | null;
54
+ readTime: number | null;
55
+ publishedAt: Date | null;
56
+ }
57
+ export declare function getRelatedBlogPosts(prisma: PrismaClient, postId: string, limit?: number): Promise<RelatedBlogPost[]>;
48
58
  //# sourceMappingURL=blogDbUtils.d.ts.map