@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.
- package/dist/__tests__/agePolicy.test.d.ts +2 -0
- package/dist/__tests__/agePolicy.test.d.ts.map +1 -0
- package/dist/__tests__/agePolicy.test.js +203 -0
- package/dist/__tests__/agePolicy.test.js.map +1 -0
- package/dist/__tests__/getDisplayGender.test.d.ts +2 -0
- package/dist/__tests__/getDisplayGender.test.d.ts.map +1 -0
- package/dist/__tests__/getDisplayGender.test.js +34 -0
- package/dist/__tests__/getDisplayGender.test.js.map +1 -0
- package/dist/__tests__/statusEnums.test.d.ts +2 -0
- package/dist/__tests__/statusEnums.test.d.ts.map +1 -0
- package/dist/__tests__/statusEnums.test.js +24 -0
- package/dist/__tests__/statusEnums.test.js.map +1 -0
- package/dist/aiApproval.d.ts +64 -0
- package/dist/aiApproval.d.ts.map +1 -0
- package/dist/aiApproval.js +54 -0
- package/dist/aiApproval.js.map +1 -0
- package/dist/definitions.d.ts +44 -2
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +106 -2
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +237 -104
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js +12 -2
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/legalTemplates.d.ts +1 -1
- package/dist/legalTemplates.js +1 -1
- package/dist/utils/__tests__/badgeUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/badgeUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/badgeUtils.test.js +157 -0
- package/dist/utils/__tests__/badgeUtils.test.js.map +1 -0
- package/dist/utils/__tests__/bashPointsPaymentUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/bashPointsPaymentUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/bashPointsPaymentUtils.test.js +114 -0
- package/dist/utils/__tests__/bashPointsPaymentUtils.test.js.map +1 -0
- package/dist/utils/__tests__/contentFilterUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/contentFilterUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/contentFilterUtils.test.js +443 -0
- package/dist/utils/__tests__/contentFilterUtils.test.js.map +1 -0
- package/dist/utils/__tests__/displayDateUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/displayDateUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/displayDateUtils.test.js +105 -0
- package/dist/utils/__tests__/displayDateUtils.test.js.map +1 -0
- package/dist/utils/__tests__/getBackendHost.test.d.ts +2 -0
- package/dist/utils/__tests__/getBackendHost.test.d.ts.map +1 -0
- package/dist/utils/__tests__/getBackendHost.test.js +104 -0
- package/dist/utils/__tests__/getBackendHost.test.js.map +1 -0
- package/dist/utils/__tests__/getFrontendHost.test.d.ts +2 -0
- package/dist/utils/__tests__/getFrontendHost.test.d.ts.map +1 -0
- package/dist/utils/__tests__/getFrontendHost.test.js +207 -0
- package/dist/utils/__tests__/getFrontendHost.test.js.map +1 -0
- package/dist/utils/__tests__/promoCodesUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/promoCodesUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/promoCodesUtils.test.js +183 -0
- package/dist/utils/__tests__/promoCodesUtils.test.js.map +1 -0
- package/dist/utils/__tests__/qrCodeUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/qrCodeUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/qrCodeUtils.test.js +99 -0
- package/dist/utils/__tests__/qrCodeUtils.test.js.map +1 -0
- package/dist/utils/__tests__/stripeAccountUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/stripeAccountUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/stripeAccountUtils.test.js +75 -0
- package/dist/utils/__tests__/stripeAccountUtils.test.js.map +1 -0
- package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.d.ts +2 -0
- package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.d.ts.map +1 -0
- package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.js +233 -0
- package/dist/utils/blog/__tests__/getRelatedBlogPosts.test.js.map +1 -0
- package/dist/utils/blog/blogCommentDbUtils.d.ts +161 -0
- package/dist/utils/blog/blogCommentDbUtils.d.ts.map +1 -0
- package/dist/utils/blog/blogCommentDbUtils.js +111 -0
- package/dist/utils/blog/blogCommentDbUtils.js.map +1 -0
- package/dist/utils/blog/blogDbUtils.d.ts +10 -0
- package/dist/utils/blog/blogDbUtils.d.ts.map +1 -1
- package/dist/utils/blog/blogDbUtils.js +71 -0
- package/dist/utils/blog/blogDbUtils.js.map +1 -1
- package/dist/utils/urlUtils.d.ts +10 -0
- package/dist/utils/urlUtils.d.ts.map +1 -1
- package/dist/utils/urlUtils.js +24 -5
- package/dist/utils/urlUtils.js.map +1 -1
- package/dist/utils/userUtils.d.ts +32 -0
- package/dist/utils/userUtils.d.ts.map +1 -1
- package/dist/utils/userUtils.js +27 -0
- package/dist/utils/userUtils.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +927 -845
- package/src/__tests__/agePolicy.test.ts +257 -0
- package/src/__tests__/getDisplayGender.test.ts +50 -0
- package/src/__tests__/statusEnums.test.ts +36 -0
- package/src/definitions.ts +138 -1
- package/src/extendedSchemas.ts +41 -2
- package/src/index.ts +1 -0
- package/src/legalTemplates.ts +1 -1
- package/src/utils/__tests__/badgeUtils.test.ts +191 -0
- package/src/utils/__tests__/bashPointsPaymentUtils.test.ts +153 -0
- package/src/utils/__tests__/contentFilterUtils.test.ts +547 -0
- package/src/utils/__tests__/displayDateUtils.test.ts +113 -0
- package/src/utils/__tests__/getBackendHost.test.ts +128 -0
- package/src/utils/__tests__/getFrontendHost.test.ts +232 -0
- package/src/utils/__tests__/promoCodesUtils.test.ts +237 -0
- package/src/utils/__tests__/qrCodeUtils.test.ts +143 -0
- package/src/utils/__tests__/stripeAccountUtils.test.ts +93 -0
- package/src/utils/blog/__tests__/getRelatedBlogPosts.test.ts +271 -0
- package/src/utils/blog/blogCommentDbUtils.ts +161 -0
- package/src/utils/blog/blogDbUtils.ts +95 -1
- package/src/utils/urlUtils.ts +27 -5
- package/src/utils/userUtils.ts +40 -0
|
@@ -345,4 +345,98 @@ export async function getRecentBlogPosts(
|
|
|
345
345
|
});
|
|
346
346
|
|
|
347
347
|
return posts as BlogPostExt[];
|
|
348
|
-
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export interface RelatedBlogPost {
|
|
351
|
+
id: string;
|
|
352
|
+
slug: string | null;
|
|
353
|
+
title: string;
|
|
354
|
+
excerpt: string | null;
|
|
355
|
+
coverImage: string | null;
|
|
356
|
+
readTime: number | null;
|
|
357
|
+
publishedAt: Date | null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Score candidate posts by (1) tag overlap with the source post, (2) shared
|
|
361
|
+
// category, then (3) recency. Drives the "Related posts" module that internal-
|
|
362
|
+
// links blog detail pages -- a major SEO lever per the backlink/SEO plan.
|
|
363
|
+
// Falls back to recent same-category posts when the source has no tags.
|
|
364
|
+
export async function getRelatedBlogPosts(
|
|
365
|
+
prisma: PrismaClient,
|
|
366
|
+
postId: string,
|
|
367
|
+
limit: number = 4
|
|
368
|
+
): Promise<RelatedBlogPost[]> {
|
|
369
|
+
const source = await prisma.blogPost.findUnique({
|
|
370
|
+
where: { id: postId },
|
|
371
|
+
select: {
|
|
372
|
+
id: true,
|
|
373
|
+
categoryId: true,
|
|
374
|
+
tags: { select: { id: true } },
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
if (!source) {
|
|
378
|
+
return [];
|
|
379
|
+
}
|
|
380
|
+
const tagIds = source.tags.map((t) => t.id);
|
|
381
|
+
|
|
382
|
+
const orClauses: Prisma.BlogPostWhereInput[] = [];
|
|
383
|
+
if (tagIds.length > 0) {
|
|
384
|
+
orClauses.push({ tags: { some: { id: { in: tagIds } } } });
|
|
385
|
+
}
|
|
386
|
+
if (source.categoryId) {
|
|
387
|
+
orClauses.push({ categoryId: source.categoryId });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const where: Prisma.BlogPostWhereInput = {
|
|
391
|
+
id: { not: postId },
|
|
392
|
+
status: BlogStatus.Published,
|
|
393
|
+
publishedAt: { not: null },
|
|
394
|
+
...(orClauses.length > 0 ? { OR: orClauses } : {}),
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const candidates = await prisma.blogPost.findMany({
|
|
398
|
+
where,
|
|
399
|
+
select: {
|
|
400
|
+
id: true,
|
|
401
|
+
slug: true,
|
|
402
|
+
title: true,
|
|
403
|
+
excerpt: true,
|
|
404
|
+
coverImage: true,
|
|
405
|
+
readTime: true,
|
|
406
|
+
publishedAt: true,
|
|
407
|
+
categoryId: true,
|
|
408
|
+
tags: { select: { id: true } },
|
|
409
|
+
},
|
|
410
|
+
orderBy: { publishedAt: 'desc' },
|
|
411
|
+
take: limit * 4,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const scored = candidates.map((c) => {
|
|
415
|
+
const sharedTagCount = c.tags.filter((t) => tagIds.includes(t.id)).length;
|
|
416
|
+
const sharedCategory =
|
|
417
|
+
source.categoryId && c.categoryId === source.categoryId ? 1 : 0;
|
|
418
|
+
const recency = c.publishedAt ? c.publishedAt.getTime() : 0;
|
|
419
|
+
return { c, sharedTagCount, sharedCategory, recency };
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
scored.sort((a, b) => {
|
|
423
|
+
if (b.sharedTagCount !== a.sharedTagCount) {
|
|
424
|
+
return b.sharedTagCount - a.sharedTagCount;
|
|
425
|
+
}
|
|
426
|
+
if (b.sharedCategory !== a.sharedCategory) {
|
|
427
|
+
return b.sharedCategory - a.sharedCategory;
|
|
428
|
+
}
|
|
429
|
+
return b.recency - a.recency;
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
return scored.slice(0, limit).map(({ c }) => ({
|
|
433
|
+
id: c.id,
|
|
434
|
+
slug: c.slug,
|
|
435
|
+
title: c.title,
|
|
436
|
+
excerpt: c.excerpt,
|
|
437
|
+
coverImage: c.coverImage,
|
|
438
|
+
readTime: c.readTime,
|
|
439
|
+
publishedAt: c.publishedAt,
|
|
440
|
+
}));
|
|
441
|
+
}
|
|
442
|
+
|
package/src/utils/urlUtils.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isProduction } from "./apiUtils.js";
|
|
2
1
|
import { BASH_DETAIL_URL, STRIPE_CHECKOUT_TIMEOUT_MS, STRIPE_REDIRECT_TIMEOUT_MS } from "../definitions.js";
|
|
3
2
|
import { generateBashDetailUrl } from "./slugUtils.js";
|
|
4
3
|
|
|
@@ -17,11 +16,34 @@ function isCapacitorNativePlatform(): boolean {
|
|
|
17
16
|
return cap?.isNativePlatform?.() === true;
|
|
18
17
|
}
|
|
19
18
|
|
|
19
|
+
/** True when the current page is being served from a developer's local machine (not ngrok, not deployed). */
|
|
20
|
+
function isLocalDevHostname(hostname: string): boolean {
|
|
21
|
+
return (
|
|
22
|
+
hostname === "localhost" ||
|
|
23
|
+
hostname === "127.0.0.1" ||
|
|
24
|
+
hostname.startsWith("192.168.")
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Canonical frontend origin for share links, embeds, QR codes, etc.
|
|
30
|
+
*
|
|
31
|
+
* Browser-hostname-driven (NOT env-var driven) so the answer is correct in any
|
|
32
|
+
* deployed environment — including ones where `process.env.ENVIRONMENT` /
|
|
33
|
+
* `BASH_ENV` aren't inlined into the bundle (Vite only inlines REACT_APP_* /
|
|
34
|
+
* VITE_* / NODE_ENV / PUBLIC_URL). Previously this depended on `isProduction()`
|
|
35
|
+
* and produced URLs like `https://bash.community:3000/bash/...` in production,
|
|
36
|
+
* which broke Facebook/iMessage previews because `:3000` isn't publicly reachable.
|
|
37
|
+
*/
|
|
20
38
|
export function getFrontendHost(): string {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
if (typeof window === "undefined") {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
const { protocol, hostname, host, port } = window.location;
|
|
43
|
+
if (isLocalDevHostname(hostname)) {
|
|
44
|
+
return `${protocol}//${hostname}:${port || "3000"}`;
|
|
45
|
+
}
|
|
46
|
+
return `${protocol}//${host}`;
|
|
25
47
|
}
|
|
26
48
|
|
|
27
49
|
export function getBackendHost(): string {
|
package/src/utils/userUtils.ts
CHANGED
|
@@ -1 +1,41 @@
|
|
|
1
|
+
import { Gender } from "@prisma/client";
|
|
2
|
+
|
|
1
3
|
export const MAX_USER_NAME_CHANGES = 3 as const;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Minimum shape needed by `getDisplayGender` — accepts any object that has the
|
|
7
|
+
* two relevant columns (e.g. `User`, `UserExt`, `PublicUser`, or a partial
|
|
8
|
+
* select). The free-text `genderIdentity` is the source of truth for *display*
|
|
9
|
+
* because it is user-self-described and may not map to a `Gender` enum value
|
|
10
|
+
* (e.g. "Non-binary", "Genderfluid").
|
|
11
|
+
*/
|
|
12
|
+
export interface GenderDisplayUser {
|
|
13
|
+
gender?: Gender | null;
|
|
14
|
+
genderIdentity?: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the gender label that should be **displayed in the UI** (profile
|
|
19
|
+
* header, badges, organizer cards, etc.) for a given user.
|
|
20
|
+
*
|
|
21
|
+
* Precedence:
|
|
22
|
+
* 1. `genderIdentity` (free-text self-description) when present and
|
|
23
|
+
* non-blank — wins over the enum so the user sees their own words.
|
|
24
|
+
* 2. `gender` (enum) when present.
|
|
25
|
+
* 3. `null` when neither is set.
|
|
26
|
+
*
|
|
27
|
+
* IMPORTANT: This helper is for **display only**. Analytics, demographics
|
|
28
|
+
* cohorts, target-audience matching, and anything that needs a stable
|
|
29
|
+
* categorical value should keep reading `user.gender` directly. Adding a
|
|
30
|
+
* comment at those sites (e.g. "intentional: analytics uses raw enum") makes
|
|
31
|
+
* the divergence explicit.
|
|
32
|
+
*
|
|
33
|
+
* @returns the user-facing string (free-text or enum value), or `null` if no
|
|
34
|
+
* gender info has been provided.
|
|
35
|
+
*/
|
|
36
|
+
export function getDisplayGender(user: GenderDisplayUser): string | null {
|
|
37
|
+
const trimmedIdentity = user.genderIdentity?.trim();
|
|
38
|
+
if (trimmedIdentity) return trimmedIdentity;
|
|
39
|
+
if (user.gender) return user.gender;
|
|
40
|
+
return null;
|
|
41
|
+
}
|