@mohasinac/appkit 4.42.4 → 4.42.5
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.
|
@@ -13,8 +13,8 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
13
13
|
priceRupees: z.ZodEffects<z.ZodString, string, string>;
|
|
14
14
|
description: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
15
15
|
ruleType: z.ZodEnum<["static", "dynamic"]>;
|
|
16
|
-
productIds: z.ZodArray<z.ZodString, "many"
|
|
17
|
-
dynamicRule: z.ZodObject<{
|
|
16
|
+
productIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
17
|
+
dynamicRule: z.ZodOptional<z.ZodObject<{
|
|
18
18
|
type: z.ZodLiteral<"dynamic">;
|
|
19
19
|
filter: z.ZodObject<{
|
|
20
20
|
categorySlug: z.ZodOptional<z.ZodString>;
|
|
@@ -54,17 +54,20 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
54
54
|
};
|
|
55
55
|
limit: number;
|
|
56
56
|
orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
|
|
57
|
-
}
|
|
57
|
+
}>>;
|
|
58
58
|
coverImage: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
59
59
|
brandSlug: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
60
60
|
isActive: z.ZodBoolean;
|
|
61
61
|
}, "strip", z.ZodTypeAny, {
|
|
62
62
|
name: string;
|
|
63
63
|
isActive: boolean;
|
|
64
|
-
productIds: string[];
|
|
65
64
|
priceRupees: string;
|
|
66
65
|
ruleType: "static" | "dynamic";
|
|
67
|
-
|
|
66
|
+
brandSlug?: string | undefined;
|
|
67
|
+
description?: string | undefined;
|
|
68
|
+
coverImage?: string | undefined;
|
|
69
|
+
productIds?: string[] | undefined;
|
|
70
|
+
dynamicRule?: {
|
|
68
71
|
type: "dynamic";
|
|
69
72
|
filter: {
|
|
70
73
|
listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
|
|
@@ -74,17 +77,17 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
74
77
|
};
|
|
75
78
|
limit: number;
|
|
76
79
|
orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
|
|
77
|
-
};
|
|
78
|
-
brandSlug?: string | undefined;
|
|
79
|
-
description?: string | undefined;
|
|
80
|
-
coverImage?: string | undefined;
|
|
80
|
+
} | undefined;
|
|
81
81
|
}, {
|
|
82
82
|
name: string;
|
|
83
83
|
isActive: boolean;
|
|
84
|
-
productIds: string[];
|
|
85
84
|
priceRupees: string;
|
|
86
85
|
ruleType: "static" | "dynamic";
|
|
87
|
-
|
|
86
|
+
brandSlug?: string | undefined;
|
|
87
|
+
description?: string | undefined;
|
|
88
|
+
coverImage?: string | undefined;
|
|
89
|
+
productIds?: string[] | undefined;
|
|
90
|
+
dynamicRule?: {
|
|
88
91
|
type: "dynamic";
|
|
89
92
|
filter: {
|
|
90
93
|
listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
|
|
@@ -94,17 +97,17 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
94
97
|
};
|
|
95
98
|
limit: number;
|
|
96
99
|
orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
|
|
97
|
-
};
|
|
98
|
-
brandSlug?: string | undefined;
|
|
99
|
-
description?: string | undefined;
|
|
100
|
-
coverImage?: string | undefined;
|
|
100
|
+
} | undefined;
|
|
101
101
|
}>, {
|
|
102
102
|
name: string;
|
|
103
103
|
isActive: boolean;
|
|
104
|
-
productIds: string[];
|
|
105
104
|
priceRupees: string;
|
|
106
105
|
ruleType: "static" | "dynamic";
|
|
107
|
-
|
|
106
|
+
brandSlug?: string | undefined;
|
|
107
|
+
description?: string | undefined;
|
|
108
|
+
coverImage?: string | undefined;
|
|
109
|
+
productIds?: string[] | undefined;
|
|
110
|
+
dynamicRule?: {
|
|
108
111
|
type: "dynamic";
|
|
109
112
|
filter: {
|
|
110
113
|
listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
|
|
@@ -114,17 +117,17 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
114
117
|
};
|
|
115
118
|
limit: number;
|
|
116
119
|
orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
|
|
117
|
-
};
|
|
118
|
-
brandSlug?: string | undefined;
|
|
119
|
-
description?: string | undefined;
|
|
120
|
-
coverImage?: string | undefined;
|
|
120
|
+
} | undefined;
|
|
121
121
|
}, {
|
|
122
122
|
name: string;
|
|
123
123
|
isActive: boolean;
|
|
124
|
-
productIds: string[];
|
|
125
124
|
priceRupees: string;
|
|
126
125
|
ruleType: "static" | "dynamic";
|
|
127
|
-
|
|
126
|
+
brandSlug?: string | undefined;
|
|
127
|
+
description?: string | undefined;
|
|
128
|
+
coverImage?: string | undefined;
|
|
129
|
+
productIds?: string[] | undefined;
|
|
130
|
+
dynamicRule?: {
|
|
128
131
|
type: "dynamic";
|
|
129
132
|
filter: {
|
|
130
133
|
listingType?: "standard" | "pre-order" | "prize-draw" | undefined;
|
|
@@ -134,9 +137,6 @@ export declare const bundleFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
134
137
|
};
|
|
135
138
|
limit: number;
|
|
136
139
|
orderBy?: "price-asc" | "price-desc" | "createdAt-desc" | undefined;
|
|
137
|
-
};
|
|
138
|
-
brandSlug?: string | undefined;
|
|
139
|
-
description?: string | undefined;
|
|
140
|
-
coverImage?: string | undefined;
|
|
140
|
+
} | undefined;
|
|
141
141
|
}>;
|
|
142
142
|
export type BundleFormValues = z.infer<typeof bundleFormSchema>;
|
|
@@ -95,12 +95,12 @@ export const bundleFormSchema = z
|
|
|
95
95
|
* because an empty picker on a DYNAMIC bundle is correct — the resolver
|
|
96
96
|
* fills the mirror. A flat `.min(3)` would block every dynamic save.
|
|
97
97
|
*/
|
|
98
|
-
productIds: annotate(z.array(z.string()), {
|
|
98
|
+
productIds: annotate(z.array(z.string()).optional(), {
|
|
99
99
|
section: "members", order: 2, row: "full", kind: "list",
|
|
100
100
|
label: `${BUNDLE_COPY.adminEditor.ruleTypeStatic} (${BUNDLE_MIN_ITEMS}–${BUNDLE_MAX_ITEMS})`,
|
|
101
101
|
when: (v) => v.ruleType !== "dynamic",
|
|
102
102
|
}),
|
|
103
|
-
dynamicRule: annotate(dynamicRuleSchema, {
|
|
103
|
+
dynamicRule: annotate(dynamicRuleSchema.optional(), {
|
|
104
104
|
section: "members", order: 3, row: "full",
|
|
105
105
|
label: BUNDLE_COPY.adminEditor.dynamic.title,
|
|
106
106
|
help: BUNDLE_COPY.adminEditor.dynamic.hint,
|
|
@@ -130,20 +130,44 @@ export const bundleFormSchema = z
|
|
|
130
130
|
* them — a two-member bundle saved, and `listBundleMembers` rendered it as
|
|
131
131
|
* a "bundle" of two.
|
|
132
132
|
*/
|
|
133
|
+
/*
|
|
134
|
+
* 🛑 Both member sources are `.optional()` because a hidden control is not
|
|
135
|
+
* SENT, and the two are mutually hidden by `ruleType`. Declaring either as
|
|
136
|
+
* required made the branch that hides it impossible to save: a static
|
|
137
|
+
* bundle submitted no `dynamicRule`, zod raised `invalid_type`, and
|
|
138
|
+
* `zodErrorMap` rendered it as "Bundle members: This field is required" on
|
|
139
|
+
* a form whose picker visibly held three products. Admin bundle CREATION
|
|
140
|
+
* was impossible in both directions. Found by
|
|
141
|
+
* `money-flows/blockers--admin` → `cross-store-group-refused`, which could
|
|
142
|
+
* not reach the cross-store guard it exists to test.
|
|
143
|
+
*
|
|
144
|
+
* Requiredness therefore lives HERE, per branch, where `ruleType` is known.
|
|
145
|
+
*/
|
|
146
|
+
if (v.ruleType === "dynamic") {
|
|
147
|
+
if (!v.dynamicRule) {
|
|
148
|
+
ctx.addIssue({
|
|
149
|
+
code: z.ZodIssueCode.custom,
|
|
150
|
+
path: ["dynamicRule"],
|
|
151
|
+
message: "Describe the query that resolves this bundle's members.",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
133
156
|
if (v.ruleType !== "static")
|
|
134
157
|
return;
|
|
135
|
-
|
|
158
|
+
const productIds = v.productIds ?? [];
|
|
159
|
+
if (productIds.length < BUNDLE_MIN_ITEMS) {
|
|
136
160
|
ctx.addIssue({
|
|
137
161
|
code: z.ZodIssueCode.custom,
|
|
138
162
|
path: ["productIds"],
|
|
139
|
-
message: `Select at least ${BUNDLE_MIN_ITEMS} products (currently ${
|
|
163
|
+
message: `Select at least ${BUNDLE_MIN_ITEMS} products (currently ${productIds.length}).`,
|
|
140
164
|
});
|
|
141
165
|
}
|
|
142
|
-
else if (
|
|
166
|
+
else if (productIds.length > BUNDLE_MAX_ITEMS) {
|
|
143
167
|
ctx.addIssue({
|
|
144
168
|
code: z.ZodIssueCode.custom,
|
|
145
169
|
path: ["productIds"],
|
|
146
|
-
message: `Maximum ${BUNDLE_MAX_ITEMS} products allowed (currently ${
|
|
170
|
+
message: `Maximum ${BUNDLE_MAX_ITEMS} products allowed (currently ${productIds.length}).`,
|
|
147
171
|
});
|
|
148
172
|
}
|
|
149
173
|
});
|
|
@@ -114,7 +114,31 @@ class EventRepository extends BaseRepository {
|
|
|
114
114
|
createdAt: now,
|
|
115
115
|
updatedAt: now,
|
|
116
116
|
});
|
|
117
|
-
|
|
117
|
+
/*
|
|
118
|
+
* 🛑 This used to be `.add(data)` — a raw write that ran NEITHER half of
|
|
119
|
+
* the repository's own contract, and broke two things at once:
|
|
120
|
+
*
|
|
121
|
+
* 1. `applyWriteHooks` never fired, so `buildSearchTxtFor` never wrote
|
|
122
|
+
* SEARCH_TXT — and `list()` filters on
|
|
123
|
+
* `where(SEARCH_TXT, "array-contains", head)`. Every event created
|
|
124
|
+
* through the UI was therefore invisible to admin event search,
|
|
125
|
+
* permanently, while seeded events (whose tokens the seeder writes)
|
|
126
|
+
* searched fine. The hook's docstring above claims it is derived on
|
|
127
|
+
* every write path; it was not derived on this one.
|
|
128
|
+
* 2. `.add()` mints a Firestore auto-id, so the document id was e.g.
|
|
129
|
+
* `SJHlHFAs5BHMLQwH8MdM` while every seeded event uses its slug.
|
|
130
|
+
* Events are "pure slugs (id === slug)" per the slug table, and the
|
|
131
|
+
* public URL and any slug lookup depend on it.
|
|
132
|
+
*
|
|
133
|
+
* `.create()` rather than `.set()`: it REJECTS an existing id, so a
|
|
134
|
+
* duplicate title surfaces as a conflict instead of silently
|
|
135
|
+
* overwriting someone else's event.
|
|
136
|
+
*
|
|
137
|
+
* Found by tester run-3 batch 98, page-wiring/reachability--admin →
|
|
138
|
+
* lottery-can-be-created-without-seeding. Root Cause #9's shape.
|
|
139
|
+
*/
|
|
140
|
+
const ref = this.getCollection().doc(slug);
|
|
141
|
+
await ref.create(this.applyWriteHooks(data));
|
|
118
142
|
const created = await ref.get();
|
|
119
143
|
serverLogger.info("Event created", { eventId: ref.id, type: input.type });
|
|
120
144
|
return this.mapDoc(created);
|
|
@@ -553,4 +553,45 @@ export const usersSeedData = [
|
|
|
553
553
|
createdAt: daysAgo(60),
|
|
554
554
|
updatedAt: daysAgo(2),
|
|
555
555
|
},
|
|
556
|
+
/*
|
|
557
|
+
* ── Disabled buyer: the ONLY account with `disabled: true` ──
|
|
558
|
+
*
|
|
559
|
+
* 🛑 This persona exists so that cases about a banned/disabled login are
|
|
560
|
+
* testable WITHOUT disabling a real one. Measured 2026-10-01: of the 19
|
|
561
|
+
* seeded users, `disabled: true` occurred ZERO times, so every such case was
|
|
562
|
+
* permanently unrunnable — `users` is PRESERVE-tier (never wiped, never
|
|
563
|
+
* restored, merge-write on reseed), making "disable someone to test it" the
|
|
564
|
+
* one piece of permanent damage a test run can do.
|
|
565
|
+
*
|
|
566
|
+
* Nothing but a sign-in attempt should ever touch it: no orders, no store,
|
|
567
|
+
* no public profile. Keep `disabled: true` — flipping it to false silently
|
|
568
|
+
* removes the only fixture these cases have.
|
|
569
|
+
*
|
|
570
|
+
* Needed by money-flows/blockers--guest → banned-account-blocked, and the
|
|
571
|
+
* soft-ban cases declined for the same reason. Same class as Root Cause #90:
|
|
572
|
+
* a case reports "nothing here" because its fixture was never seeded.
|
|
573
|
+
*/
|
|
574
|
+
{
|
|
575
|
+
uid: "user-qa-disabled",
|
|
576
|
+
email: "qa.disabled@letitrip.in",
|
|
577
|
+
phoneNumber: `${_ph}9999900099`,
|
|
578
|
+
phoneVerified: false,
|
|
579
|
+
displayName: "QA Disabled Account",
|
|
580
|
+
role: USER_FIELDS.ROLE_VALUES.USER,
|
|
581
|
+
emailVerified: true,
|
|
582
|
+
disabled: true,
|
|
583
|
+
publicProfile: {
|
|
584
|
+
isPublic: false,
|
|
585
|
+
showEmail: false,
|
|
586
|
+
showPhone: false,
|
|
587
|
+
showOrders: false,
|
|
588
|
+
showWishlist: false,
|
|
589
|
+
bio: "Fixture account — deliberately disabled so ban/disabled-login cases are testable.",
|
|
590
|
+
location: "Indore, Madhya Pradesh",
|
|
591
|
+
},
|
|
592
|
+
stats: { totalOrders: 0, auctionsWon: 0, itemsSold: 0, reviewsCount: 0 },
|
|
593
|
+
metadata: { lastSignInTime: daysAgo(30), creationTime: daysAgo(90).toISOString(), loginCount: 1 },
|
|
594
|
+
createdAt: daysAgo(90),
|
|
595
|
+
updatedAt: daysAgo(30),
|
|
596
|
+
},
|
|
556
597
|
];
|