@bash-app/bash-common 30.308.0 → 30.310.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 (63) hide show
  1. package/dist/__tests__/competitionValidation.test.d.ts +2 -0
  2. package/dist/__tests__/competitionValidation.test.d.ts.map +1 -0
  3. package/dist/__tests__/competitionValidation.test.js +28 -0
  4. package/dist/__tests__/competitionValidation.test.js.map +1 -0
  5. package/dist/__tests__/definitionsHelpers.test.js +8 -1
  6. package/dist/__tests__/definitionsHelpers.test.js.map +1 -1
  7. package/dist/__tests__/membershipDefinitions.test.js +5 -0
  8. package/dist/__tests__/membershipDefinitions.test.js.map +1 -1
  9. package/dist/__tests__/sideQuestIdeas.test.js +5 -1
  10. package/dist/__tests__/sideQuestIdeas.test.js.map +1 -1
  11. package/dist/__tests__/sideQuestTypes.test.d.ts +2 -0
  12. package/dist/__tests__/sideQuestTypes.test.d.ts.map +1 -0
  13. package/dist/__tests__/sideQuestTypes.test.js +29 -0
  14. package/dist/__tests__/sideQuestTypes.test.js.map +1 -0
  15. package/dist/__tests__/statusEnums.test.js +7 -0
  16. package/dist/__tests__/statusEnums.test.js.map +1 -1
  17. package/dist/competitionValidation.d.ts +11 -2
  18. package/dist/competitionValidation.d.ts.map +1 -1
  19. package/dist/competitionValidation.js +40 -2
  20. package/dist/competitionValidation.js.map +1 -1
  21. package/dist/definitions.d.ts +7 -0
  22. package/dist/definitions.d.ts.map +1 -1
  23. package/dist/definitions.js +10 -0
  24. package/dist/definitions.js.map +1 -1
  25. package/dist/extendedSchemas.d.ts +51 -11
  26. package/dist/extendedSchemas.d.ts.map +1 -1
  27. package/dist/extendedSchemas.js +5 -0
  28. package/dist/extendedSchemas.js.map +1 -1
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/membershipDefinitions.d.ts +2 -0
  34. package/dist/membershipDefinitions.d.ts.map +1 -1
  35. package/dist/membershipDefinitions.js +3 -0
  36. package/dist/membershipDefinitions.js.map +1 -1
  37. package/dist/mirroredPrismaEnums.d.ts +4 -0
  38. package/dist/mirroredPrismaEnums.d.ts.map +1 -1
  39. package/dist/mirroredPrismaEnums.js +4 -0
  40. package/dist/mirroredPrismaEnums.js.map +1 -1
  41. package/dist/sideQuestIdeas.d.ts.map +1 -1
  42. package/dist/sideQuestIdeas.js +2 -2
  43. package/dist/sideQuestIdeas.js.map +1 -1
  44. package/dist/sideQuestTypes.d.ts +116 -0
  45. package/dist/sideQuestTypes.d.ts.map +1 -0
  46. package/dist/sideQuestTypes.js +67 -0
  47. package/dist/sideQuestTypes.js.map +1 -0
  48. package/package.json +1 -1
  49. package/prisma/schema.prisma +111 -0
  50. package/src/__tests__/competitionValidation.test.ts +43 -0
  51. package/src/__tests__/definitionsHelpers.test.ts +16 -0
  52. package/src/__tests__/membershipDefinitions.test.ts +18 -0
  53. package/src/__tests__/sideQuestIdeas.test.ts +11 -1
  54. package/src/__tests__/sideQuestTypes.test.ts +44 -0
  55. package/src/__tests__/statusEnums.test.ts +7 -0
  56. package/src/competitionValidation.ts +56 -2
  57. package/src/definitions.ts +13 -0
  58. package/src/extendedSchemas.ts +23 -11
  59. package/src/index.ts +1 -0
  60. package/src/membershipDefinitions.ts +3 -0
  61. package/src/mirroredPrismaEnums.ts +4 -0
  62. package/src/sideQuestIdeas.ts +2 -2
  63. package/src/sideQuestTypes.ts +190 -0
@@ -40,7 +40,7 @@ export const SIDE_QUEST_CATEGORY_META: Record<
40
40
  };
41
41
 
42
42
  /** Phase 1 host-selectable quest types */
43
- export const SIDE_QUEST_PHASE1_TYPES: SideQuestTypeId[] = ["Qr", "Code"];
43
+ export const SIDE_QUEST_PHASE1_TYPES: SideQuestTypeId[] = ["Qr", "Code", "Vote"];
44
44
 
45
45
  export const SIDE_QUEST_TYPE_META: Record<
46
46
  SideQuestTypeId,
@@ -79,7 +79,7 @@ export const SIDE_QUEST_TYPE_META: Record<
79
79
  Vote: {
80
80
  label: "Vote Quest",
81
81
  description: "Completes after voting in a competition.",
82
- phase1: false,
82
+ phase1: true,
83
83
  },
84
84
  Sponsor: {
85
85
  label: "Sponsor Quest",
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Shared Side Quest / Adventures API shapes and QR deep-link helpers.
3
+ */
4
+
5
+ import { TICKET_DETAILS } from "./definitions.js";
6
+ import type { SideQuestCategoryId, SideQuestTypeId } from "./sideQuestIdeas.js";
7
+
8
+ export interface EventBadgeExt {
9
+ id: string;
10
+ bashEventId: string;
11
+ name: string;
12
+ description: string | null;
13
+ imageUrl: string;
14
+ createdByUserId?: string;
15
+ }
16
+
17
+ export interface SideQuestExt {
18
+ id: string;
19
+ title: string;
20
+ description: string | null;
21
+ sortOrder: number;
22
+ category: SideQuestCategoryId | string;
23
+ questType: SideQuestTypeId | string;
24
+ pointsReward: number;
25
+ raffleEntriesOnComplete: number;
26
+ eventBadgeId: string | null;
27
+ eventBadge?: Pick<
28
+ EventBadgeExt,
29
+ "id" | "name" | "imageUrl" | "description"
30
+ > | null;
31
+ competitionId?: string | null;
32
+ startsAt: string | Date | null;
33
+ endsAt: string | Date | null;
34
+ maxCompletions: number | null;
35
+ isHidden: boolean;
36
+ isLocked?: boolean;
37
+ unlockedAt: string | Date | null;
38
+ sponsorLabel: string | null;
39
+ completed?: boolean;
40
+ code?: string;
41
+ completionCount?: number;
42
+ }
43
+
44
+ export interface SideQuestMilestoneExt {
45
+ id: string;
46
+ requiredCompletions: number;
47
+ bashPointsReward: number | null;
48
+ eventBadgeId: string | null;
49
+ raffleEntries: number | null;
50
+ unlockSideQuestId: string | null;
51
+ sortOrder: number;
52
+ }
53
+
54
+ export interface CommunityQuestGoalExt {
55
+ id?: string;
56
+ targetCompletions: number;
57
+ currentCompletions?: number;
58
+ rewardLabel: string;
59
+ eventBadgeId?: string | null;
60
+ bashPointsReward?: number | null;
61
+ unlockedAt?: string | Date | null;
62
+ }
63
+
64
+ export interface SideQuestLogExt {
65
+ quests: SideQuestExt[];
66
+ availableCount: number;
67
+ completedCount: number;
68
+ hiddenCount: number;
69
+ badgesEarned: EventBadgeExt[];
70
+ eventBadges: EventBadgeExt[];
71
+ raffleEntries: number;
72
+ pointsFromQuests: number;
73
+ community: CommunityQuestGoalExt | null;
74
+ }
75
+
76
+ export interface SideQuestHostBundleExt {
77
+ quests: SideQuestExt[];
78
+ milestones: SideQuestMilestoneExt[];
79
+ community: CommunityQuestGoalExt | null;
80
+ badges: EventBadgeExt[];
81
+ }
82
+
83
+ export interface SideQuestAnalyticsExt {
84
+ uniqueCompleters: number;
85
+ totalCompletions: number;
86
+ raffleEntriesTotal: number;
87
+ community: CommunityQuestGoalExt | null;
88
+ perQuest: Array<{
89
+ questId: string;
90
+ title: string;
91
+ sponsorLabel: string | null;
92
+ completionCount: number;
93
+ }>;
94
+ sponsorRollup: Array<{
95
+ sponsorLabel: string;
96
+ completionCount: number;
97
+ }>;
98
+ recentCompletions: Array<{
99
+ completionId: string;
100
+ questId: string;
101
+ questTitle: string;
102
+ userId: string;
103
+ displayName: string;
104
+ completedAt: string | Date;
105
+ }>;
106
+ }
107
+
108
+ const LEGACY_PREFIX = "bash-side-quest:";
109
+
110
+ /** Absolute or path-only HTTPS deep link for booth QR codes. */
111
+ export function buildSideQuestDeepLink(params: {
112
+ bashEventId: string;
113
+ questId: string;
114
+ code: string;
115
+ /** Origin without trailing slash, e.g. https://bash.community */
116
+ frontendOrigin?: string;
117
+ slug?: string | null;
118
+ }): string {
119
+ const code = params.code.trim().toUpperCase().replace(/\s+/g, "");
120
+ const qs = new URLSearchParams({
121
+ quest: params.questId,
122
+ code,
123
+ });
124
+ const path = `${TICKET_DETAILS}/${params.bashEventId}?${qs.toString()}`;
125
+ const origin = (params.frontendOrigin ?? "").replace(/\/$/, "");
126
+ return origin ? `${origin}${path}` : path;
127
+ }
128
+
129
+ /** Legacy opaque payload (still accepted when pasted). */
130
+ export function buildSideQuestLegacyPayload(
131
+ bashEventId: string,
132
+ questId: string,
133
+ code: string
134
+ ): string {
135
+ const normalized = code.trim().toUpperCase().replace(/\s+/g, "");
136
+ return `${LEGACY_PREFIX}${bashEventId}:${questId}:${normalized}`;
137
+ }
138
+
139
+ export type ParsedSideQuestScan = {
140
+ bashEventId?: string;
141
+ questId?: string;
142
+ code: string;
143
+ };
144
+
145
+ /**
146
+ * Parse a scanned/pasted value into quest id + code.
147
+ * Supports HTTPS ticket-details URLs and legacy `bash-side-quest:` payloads.
148
+ */
149
+ export function parseSideQuestScanPayload(raw: string): ParsedSideQuestScan | null {
150
+ const trimmed = raw.trim();
151
+ if (!trimmed) return null;
152
+
153
+ if (trimmed.startsWith(LEGACY_PREFIX)) {
154
+ const rest = trimmed.slice(LEGACY_PREFIX.length);
155
+ const parts = rest.split(":");
156
+ if (parts.length >= 3) {
157
+ const [bashEventId, questId, ...codeParts] = parts;
158
+ const code = codeParts.join(":").trim().toUpperCase();
159
+ if (code) return { bashEventId, questId, code };
160
+ }
161
+ return null;
162
+ }
163
+
164
+ try {
165
+ const url = trimmed.includes("://")
166
+ ? new URL(trimmed)
167
+ : new URL(trimmed, "https://bash.community");
168
+ const questId = url.searchParams.get("quest") ?? undefined;
169
+ const codeParam = url.searchParams.get("code");
170
+ if (codeParam) {
171
+ return {
172
+ questId: questId || undefined,
173
+ code: codeParam.trim().toUpperCase().replace(/\s+/g, ""),
174
+ bashEventId: url.pathname.includes("/ticket-details/")
175
+ ? url.pathname.split("/ticket-details/")[1]?.split(/[/?#]/)[0]
176
+ : undefined,
177
+ };
178
+ }
179
+ } catch {
180
+ // not a URL — treat as raw code
181
+ }
182
+
183
+ // Plain code (4–6 chars)
184
+ const plain = trimmed.toUpperCase().replace(/\s+/g, "");
185
+ if (/^[A-Z0-9]{4,8}$/.test(plain)) {
186
+ return { code: plain };
187
+ }
188
+
189
+ return null;
190
+ }