@mohasinac/appkit 3.2.10 → 3.2.12
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/_internal/server/features/auth/firebase-identity.d.ts +2 -1
- package/dist/_internal/server/features/checkout/actions.js +11 -9
- package/dist/_internal/server/features/grouped/data.js +11 -3
- package/dist/_internal/server/jobs/core/cleanupRtdbEvents.js +4 -1
- package/dist/_internal/shared/listing-types/action-tracker.js +2 -1
- package/dist/features/blog/api/[slug]/route.js +2 -1
- package/dist/features/homepage/lib/live-stats.js +7 -5
- package/dist/features/scams/actions/scam-actions.js +2 -1
- package/dist/features/scams/repository/scammer.repository.js +10 -3
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { JsonObject } from "../../../../schemas/types";
|
|
1
2
|
type IdentityOperation = "signInWithPassword" | "signInWithCustomToken";
|
|
2
3
|
/**
|
|
3
4
|
* Calls the Firebase Identity Toolkit REST API with one retry for transient
|
|
@@ -8,5 +9,5 @@ type IdentityOperation = "signInWithPassword" | "signInWithCustomToken";
|
|
|
8
9
|
* into a session cookie. Centralised here so the URL, Content-Type, and retry
|
|
9
10
|
* config are in one place.
|
|
10
11
|
*/
|
|
11
|
-
export declare function callFirebaseIdentityToolkit<T extends
|
|
12
|
+
export declare function callFirebaseIdentityToolkit<T extends JsonObject>(operation: IdentityOperation, body: JsonObject, apiKey: string): Promise<T>;
|
|
12
13
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeError } from "../../../../errors/normalize";
|
|
2
|
+
import { safeFireAndForget } from "../../../../utils/safe-fire-forget";
|
|
2
3
|
/**
|
|
3
4
|
* Checkout server actions (appkit).
|
|
4
5
|
*
|
|
@@ -29,6 +30,7 @@ import { formatShippingAddress } from "./data";
|
|
|
29
30
|
import { enforceMaxPerUserForCart, computePrizeRevealDeadline, } from "./prize-bundle-gates";
|
|
30
31
|
import { getListingRule, runSyncPreflight, } from "../../../shared/checkout/rules";
|
|
31
32
|
import { cartIsDigitalOnly } from "../../../shared/listing-types/cart-shipping";
|
|
33
|
+
const AUDIT_LOG_FAIL_MSG = "checkout: audit log failed (non-critical)";
|
|
32
34
|
/**
|
|
33
35
|
* SB-UNI-N — Atomically claim the next available digital code for a confirmed
|
|
34
36
|
* order. Pre-fetches a candidate code outside the transaction then locks and
|
|
@@ -264,7 +266,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
264
266
|
if (!addressId) {
|
|
265
267
|
failedCheckoutRepository
|
|
266
268
|
.logCheckout(uid, "address_not_found", "Address required for physical cart", { addressId: "", paymentMethod })
|
|
267
|
-
.catch(
|
|
269
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: err instanceof Error ? err.message : String(err) }); });
|
|
268
270
|
throw new NotFoundError(ERROR_MESSAGES.CHECKOUT.ADDRESS_REQUIRED);
|
|
269
271
|
}
|
|
270
272
|
const addressDoc = await unitOfWork.addresses.findById(addressId);
|
|
@@ -275,7 +277,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
275
277
|
if (!resolvedAddress) {
|
|
276
278
|
failedCheckoutRepository
|
|
277
279
|
.logCheckout(uid, "address_not_found", "Address not found", { addressId, paymentMethod })
|
|
278
|
-
.catch(
|
|
280
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: err instanceof Error ? err.message : String(err) }); });
|
|
279
281
|
throw new NotFoundError(ERROR_MESSAGES.CHECKOUT.ADDRESS_REQUIRED);
|
|
280
282
|
}
|
|
281
283
|
shippingAddress = formatShippingAddress(resolvedAddress);
|
|
@@ -414,7 +416,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
414
416
|
addressId,
|
|
415
417
|
paymentMethod,
|
|
416
418
|
})
|
|
417
|
-
.catch(
|
|
419
|
+
.catch((logErr) => { void normalizeError(logErr); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: logErr instanceof Error ? logErr.message : String(logErr) }); });
|
|
418
420
|
throw err;
|
|
419
421
|
}
|
|
420
422
|
const { available, unavailable, emailOtpUsed } = stockResult;
|
|
@@ -425,7 +427,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
425
427
|
paymentMethod,
|
|
426
428
|
cartItemCount: cartItems.length,
|
|
427
429
|
})
|
|
428
|
-
.catch(
|
|
430
|
+
.catch((logErr) => { void normalizeError(logErr); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: logErr instanceof Error ? logErr.message : String(logErr) }); });
|
|
429
431
|
throw new ValidationError(ERROR_MESSAGES.CHECKOUT.INSUFFICIENT_STOCK);
|
|
430
432
|
}
|
|
431
433
|
const appliedCoupons = cart.appliedCoupons ?? [];
|
|
@@ -669,7 +671,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
669
671
|
gatewayPaymentId: razorpay_payment_id,
|
|
670
672
|
addressId,
|
|
671
673
|
})
|
|
672
|
-
.catch(
|
|
674
|
+
.catch((logErr) => { void normalizeError(logErr); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: logErr instanceof Error ? logErr.message : String(logErr) }); });
|
|
673
675
|
throw new ValidationError(ERROR_MESSAGES.CHECKOUT.PAYMENT_FAILED);
|
|
674
676
|
}
|
|
675
677
|
const cart = await unitOfWork.carts.getOrCreate(uid);
|
|
@@ -711,7 +713,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
711
713
|
gatewayPaymentId: razorpay_payment_id,
|
|
712
714
|
addressId,
|
|
713
715
|
})
|
|
714
|
-
.catch(
|
|
716
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: err instanceof Error ? err.message : String(err) }); });
|
|
715
717
|
throw new ApiError(403, "Order verification required. Please complete OTP verification and retry.");
|
|
716
718
|
}
|
|
717
719
|
}
|
|
@@ -762,7 +764,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
762
764
|
gatewayPaymentId: razorpay_payment_id,
|
|
763
765
|
addressId,
|
|
764
766
|
})
|
|
765
|
-
.catch(
|
|
767
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: err instanceof Error ? err.message : String(err) }); });
|
|
766
768
|
throw new ValidationError(exists
|
|
767
769
|
? ERROR_MESSAGES.CHECKOUT.INSUFFICIENT_STOCK
|
|
768
770
|
: ERROR_MESSAGES.CHECKOUT.PRODUCT_UNAVAILABLE);
|
|
@@ -790,7 +792,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
790
792
|
amountRs: paidAmountRs,
|
|
791
793
|
addressId,
|
|
792
794
|
})
|
|
793
|
-
.catch(
|
|
795
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: err instanceof Error ? err.message : String(err) }); });
|
|
794
796
|
throw new ValidationError(ERROR_MESSAGES.CHECKOUT.PAYMENT_FAILED);
|
|
795
797
|
}
|
|
796
798
|
}
|
|
@@ -996,7 +998,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
996
998
|
});
|
|
997
999
|
if (!isDigitalCartRazorpay && addressId) {
|
|
998
1000
|
const otpRefForDelete = consentOtpRef(db, uid, addressId);
|
|
999
|
-
otpRefForDelete.delete()
|
|
1001
|
+
safeFireAndForget(otpRefForDelete.delete(), "checkout: delete OTP consent ref after Razorpay payment");
|
|
1000
1002
|
}
|
|
1001
1003
|
if (emailsToSend.length > 0) {
|
|
1002
1004
|
Promise.all(emailsToSend.map((e) => sendOrderConfirmationEmail(e))).catch((err) => serverLogger.error("Order confirmation email error:", err));
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { cache } from "react";
|
|
2
2
|
import { getAdminDb } from "../../../../providers/db-firebase";
|
|
3
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
4
|
+
import { serverLogger } from "../../../../monitoring/server-logger";
|
|
3
5
|
import { GROUPED_LISTINGS_COLLECTION, } from "../../../../features/grouped/schemas/firestore";
|
|
4
6
|
import { productRepository } from "../../../../repositories";
|
|
5
7
|
import { PRODUCT_FIELDS, CATEGORY_FIELDS } from "../../../../constants/field-names";
|
|
@@ -55,7 +57,9 @@ export const getGroupedListingForDetail = cache(async (slug) => {
|
|
|
55
57
|
}
|
|
56
58
|
return mapDoc(snap.docs[0]);
|
|
57
59
|
}
|
|
58
|
-
catch {
|
|
60
|
+
catch (err) {
|
|
61
|
+
void normalizeError(err);
|
|
62
|
+
serverLogger.warn("grouped-data: getGroupedListingForDetail failed — returning null", { slug, error: err instanceof Error ? err.message : String(err) });
|
|
59
63
|
return null;
|
|
60
64
|
}
|
|
61
65
|
});
|
|
@@ -93,7 +97,9 @@ export async function listGroupedListings(params = {}) {
|
|
|
93
97
|
.get();
|
|
94
98
|
return snap.docs.map(mapDoc);
|
|
95
99
|
}
|
|
96
|
-
catch {
|
|
100
|
+
catch (err) {
|
|
101
|
+
void normalizeError(err);
|
|
102
|
+
serverLogger.warn("grouped-data: listGroupedListings failed — returning empty", { params, error: err instanceof Error ? err.message : String(err) });
|
|
97
103
|
return [];
|
|
98
104
|
}
|
|
99
105
|
}
|
|
@@ -118,7 +124,9 @@ export async function listSitemapGroupedListings() {
|
|
|
118
124
|
};
|
|
119
125
|
});
|
|
120
126
|
}
|
|
121
|
-
catch {
|
|
127
|
+
catch (err) {
|
|
128
|
+
void normalizeError(err);
|
|
129
|
+
serverLogger.warn("grouped-data: listSitemapGroupedListings failed — returning empty", { error: err instanceof Error ? err.message : String(err) });
|
|
122
130
|
return [];
|
|
123
131
|
}
|
|
124
132
|
}
|
|
@@ -17,7 +17,10 @@ export async function runCleanupRtdbEvents(ctx) {
|
|
|
17
17
|
if (staleAuthIds.length > 0) {
|
|
18
18
|
await Promise.all(staleAuthIds.flatMap((id) => [
|
|
19
19
|
rtdb.ref(`auth_events/${id}`).remove(),
|
|
20
|
-
auth.deleteUser(`auth_event_${id}`).catch(
|
|
20
|
+
auth.deleteUser(`auth_event_${id}`).catch((err) => {
|
|
21
|
+
void normalizeError(err);
|
|
22
|
+
ctx.logger.warn("cleanupRtdbEvents: deleteUser skipped — may already be absent", { userId: `auth_event_${id}`, error: err instanceof Error ? err.message : String(err) });
|
|
23
|
+
}),
|
|
21
24
|
]));
|
|
22
25
|
ctx.logger.info("Auth events removed", { count: staleAuthIds.length });
|
|
23
26
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
1
2
|
function defaultSink(event) {
|
|
2
3
|
if (typeof window === "undefined") {
|
|
3
4
|
// SSR / Node — best-effort console log; production servers can swap
|
|
@@ -32,7 +33,7 @@ export const actionTracker = {
|
|
|
32
33
|
const result = currentSink(event);
|
|
33
34
|
// Swallow promise rejections so the caller path is never affected.
|
|
34
35
|
if (result && typeof result.catch === "function") {
|
|
35
|
-
result.catch(
|
|
36
|
+
result.catch((err) => { void normalizeError(err); });
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
catch {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { NextResponse } from "next/server.js";
|
|
12
12
|
import { blogRepository } from "../../repository/blog.repository";
|
|
13
13
|
import { normalizeError } from "../../../../errors/normalize";
|
|
14
|
+
import { safeFireAndForget } from "../../../../utils/safe-fire-forget";
|
|
14
15
|
function toBlogPost(doc) {
|
|
15
16
|
return {
|
|
16
17
|
...doc,
|
|
@@ -28,7 +29,7 @@ export async function GET(_request, context) {
|
|
|
28
29
|
return NextResponse.json({ success: false, error: "Blog post not found" }, { status: 404 });
|
|
29
30
|
}
|
|
30
31
|
// Increment view count fire-and-forget — must not block response
|
|
31
|
-
blogRepository.incrementViews(post.id)
|
|
32
|
+
safeFireAndForget(blogRepository.incrementViews(post.id), "blog: incrementViews");
|
|
32
33
|
// Related posts: same category, latest 3, excluding current
|
|
33
34
|
const related = (await blogRepository.findRelated(post.category, post.id, 3)).map(toBlogPost);
|
|
34
35
|
const body = { post: toBlogPost(post), related };
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import { ALLOWED_LIVE_COLLECTIONS } from "../schemas/firestore";
|
|
9
9
|
import { productRepository, storeRepository, userRepository, reviewRepository, orderRepository, } from "../../../repositories";
|
|
10
10
|
import { getAdminDb, getFirestoreCount } from "../../../providers/db-firebase";
|
|
11
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
12
|
+
import { serverLogger } from "../../../monitoring/server-logger";
|
|
11
13
|
/**
|
|
12
14
|
* Fetch live stats for all requested metrics.
|
|
13
15
|
* Preset metrics are deduped so each Firestore query runs at most once.
|
|
@@ -27,22 +29,22 @@ export async function fetchLiveStats(requests) {
|
|
|
27
29
|
if (neededPresets.has("total_listings")) {
|
|
28
30
|
tasks.push(productRepository.count()
|
|
29
31
|
.then((n) => { resolvedPresets.total_listings = String(n); })
|
|
30
|
-
.catch(
|
|
32
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn("live-stats: total_listings query failed — stat omitted", { error: err instanceof Error ? err.message : String(err) }); }));
|
|
31
33
|
}
|
|
32
34
|
if (neededPresets.has("verified_sellers")) {
|
|
33
35
|
tasks.push(storeRepository.count()
|
|
34
36
|
.then((n) => { resolvedPresets.verified_sellers = String(n); })
|
|
35
|
-
.catch(
|
|
37
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn("live-stats: verified_sellers query failed — stat omitted", { error: err instanceof Error ? err.message : String(err) }); }));
|
|
36
38
|
}
|
|
37
39
|
if (neededPresets.has("total_buyers")) {
|
|
38
40
|
tasks.push(userRepository.countByRole("user")
|
|
39
41
|
.then((n) => { resolvedPresets.total_buyers = String(n); })
|
|
40
|
-
.catch(
|
|
42
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn("live-stats: total_buyers query failed — stat omitted", { error: err instanceof Error ? err.message : String(err) }); }));
|
|
41
43
|
}
|
|
42
44
|
if (neededPresets.has("total_orders")) {
|
|
43
45
|
tasks.push(orderRepository.count()
|
|
44
46
|
.then((n) => { resolvedPresets.total_orders = String(n); })
|
|
45
|
-
.catch(
|
|
47
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn("live-stats: total_orders query failed — stat omitted", { error: err instanceof Error ? err.message : String(err) }); }));
|
|
46
48
|
}
|
|
47
49
|
if (neededPresets.has("total_reviews") || neededPresets.has("platform_rating")) {
|
|
48
50
|
tasks.push(reviewRepository.findAll()
|
|
@@ -57,7 +59,7 @@ export async function fetchLiveStats(requests) {
|
|
|
57
59
|
resolvedPresets.platform_rating = avg.toFixed(1) + "★";
|
|
58
60
|
}
|
|
59
61
|
})
|
|
60
|
-
.catch(
|
|
62
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn("live-stats: reviews/rating query failed — stat omitted", { error: err instanceof Error ? err.message : String(err) }); }));
|
|
61
63
|
}
|
|
62
64
|
// --- Collection-query metrics ------------------------------------------------
|
|
63
65
|
const queryRequests = requests.filter((r) => r.source === "live-collection" && r.collectionQuery !== undefined);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
|
|
3
3
|
import { scammerRepository } from "../repository/scammer.repository";
|
|
4
|
+
import { safeFireAndForget } from "../../../utils/safe-fire-forget";
|
|
4
5
|
/**
|
|
5
6
|
* List verified scammer profiles for the public registry page.
|
|
6
7
|
* Accepts sieve-style searchParams from Next.js page props.
|
|
@@ -43,7 +44,7 @@ export async function getPublicScammerById(id) {
|
|
|
43
44
|
if (!doc || doc.status !== "verified")
|
|
44
45
|
return null;
|
|
45
46
|
// Increment views — non-blocking.
|
|
46
|
-
scammerRepository.incrementViews(doc.id)
|
|
47
|
+
safeFireAndForget(scammerRepository.incrementViews(doc.id), "scam: incrementViews");
|
|
47
48
|
return doc;
|
|
48
49
|
}
|
|
49
50
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DatabaseError } from "../../../errors";
|
|
2
2
|
import { BaseRepository, prepareForFirestore, } from "../../../providers/db-firebase";
|
|
3
3
|
import { normalizeError } from "../../../errors/normalize";
|
|
4
|
+
import { serverLogger } from "../../../monitoring/server-logger";
|
|
4
5
|
import { increment } from "../../../contracts/field-ops";
|
|
5
6
|
import { SCAMMER_COLLECTION, SCAMMER_FIELDS, SCAMMER_INCIDENTS_SUBCOLLECTION, SCAMMER_COMMENTS_SUBCOLLECTION, DEFAULT_SCAMMER_DATA, } from "../schemas/firestore";
|
|
6
7
|
class ScammerRepository extends BaseRepository {
|
|
@@ -116,7 +117,9 @@ class ScammerRepository extends BaseRepository {
|
|
|
116
117
|
.get();
|
|
117
118
|
return snap.docs.map((d) => this.mapDoc(d));
|
|
118
119
|
}
|
|
119
|
-
catch {
|
|
120
|
+
catch (err) {
|
|
121
|
+
void normalizeError(err);
|
|
122
|
+
serverLogger.warn("scammer-repo: listPublicIncidents failed — returning empty", { scammerId, error: err instanceof Error ? err.message : String(err) });
|
|
120
123
|
return [];
|
|
121
124
|
}
|
|
122
125
|
}
|
|
@@ -132,7 +135,9 @@ class ScammerRepository extends BaseRepository {
|
|
|
132
135
|
.get();
|
|
133
136
|
return snap.docs.map((d) => this.mapDoc(d));
|
|
134
137
|
}
|
|
135
|
-
catch {
|
|
138
|
+
catch (err) {
|
|
139
|
+
void normalizeError(err);
|
|
140
|
+
serverLogger.warn("scammer-repo: listPublicComments failed — returning empty", { scammerId, error: err instanceof Error ? err.message : String(err) });
|
|
136
141
|
return [];
|
|
137
142
|
}
|
|
138
143
|
}
|
|
@@ -143,7 +148,9 @@ class ScammerRepository extends BaseRepository {
|
|
|
143
148
|
const results = await Promise.all(ids.slice(0, 5).map((id) => this.findById(id).catch(() => null)));
|
|
144
149
|
return results.filter((d) => d !== null && d.status === "verified");
|
|
145
150
|
}
|
|
146
|
-
catch {
|
|
151
|
+
catch (err) {
|
|
152
|
+
void normalizeError(err);
|
|
153
|
+
serverLogger.warn("scammer-repo: findManyById failed — returning empty", { ids, error: err instanceof Error ? err.message : String(err) });
|
|
147
154
|
return [];
|
|
148
155
|
}
|
|
149
156
|
}
|