@instockng/api-client 1.0.6 → 1.0.8
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/apps/backend/src/generated/zod/index.d.ts +1114 -0
- package/dist/apps/backend/src/generated/zod/index.js +670 -0
- package/dist/apps/backend/src/http-app.d.ts +40 -0
- package/dist/apps/backend/src/http-app.js +134 -0
- package/dist/apps/backend/src/lib/brand-response.d.ts +14 -0
- package/dist/apps/backend/src/lib/brand-response.js +8 -0
- package/dist/apps/backend/src/lib/cart-helpers.d.ts +282 -0
- package/dist/apps/backend/src/lib/cart-helpers.js +121 -0
- package/dist/apps/backend/src/lib/cart-recovery.d.ts +30 -0
- package/dist/apps/backend/src/lib/cart-recovery.js +147 -0
- package/dist/apps/backend/src/lib/cart-response.d.ts +121 -0
- package/dist/apps/backend/src/lib/cart-response.js +150 -0
- package/dist/apps/backend/src/lib/clerk.d.ts +18 -0
- package/dist/apps/backend/src/lib/clerk.js +190 -0
- package/dist/apps/backend/src/lib/delivery-zone-response.d.ts +64 -0
- package/dist/apps/backend/src/lib/delivery-zone-response.js +24 -0
- package/dist/apps/backend/src/lib/discount-code-response.d.ts +42 -0
- package/dist/apps/backend/src/lib/discount-code-response.js +19 -0
- package/dist/apps/backend/src/lib/discount.d.ts +20 -0
- package/dist/apps/backend/src/lib/discount.js +35 -0
- package/dist/apps/backend/src/lib/inventory.d.ts +26 -0
- package/dist/apps/backend/src/lib/inventory.js +160 -0
- package/dist/apps/backend/src/lib/meta-capi.d.ts +53 -0
- package/dist/apps/backend/src/lib/meta-capi.js +151 -0
- package/dist/apps/backend/src/lib/openapi.d.ts +36 -0
- package/dist/apps/backend/src/lib/openapi.js +69 -0
- package/dist/apps/backend/src/lib/order-recovery.d.ts +459 -0
- package/dist/apps/backend/src/lib/order-recovery.js +378 -0
- package/dist/apps/backend/src/lib/order-response.d.ts +138 -0
- package/dist/apps/backend/src/lib/order-response.js +61 -0
- package/dist/apps/backend/src/lib/pricing.d.ts +39 -0
- package/dist/apps/backend/src/lib/pricing.js +62 -0
- package/dist/apps/backend/src/lib/prisma.d.ts +9 -0
- package/dist/apps/backend/src/lib/prisma.js +30 -0
- package/dist/apps/backend/src/lib/product-response.d.ts +82 -0
- package/dist/apps/backend/src/lib/product-response.js +29 -0
- package/dist/apps/backend/src/lib/sentry.d.ts +48 -0
- package/dist/apps/backend/src/lib/sentry.js +180 -0
- package/dist/apps/backend/src/lib/utils.d.ts +32 -0
- package/dist/apps/backend/src/lib/utils.js +63 -0
- package/dist/apps/backend/src/middleware/clerk-auth.d.ts +8 -0
- package/dist/apps/backend/src/middleware/clerk-auth.js +89 -0
- package/dist/apps/backend/src/middleware/cors.d.ts +8 -0
- package/dist/apps/backend/src/middleware/cors.js +11 -0
- package/dist/apps/backend/src/notifications/producers/meta-capi-producer.d.ts +62 -0
- package/dist/apps/backend/src/notifications/producers/meta-capi-producer.js +180 -0
- package/dist/apps/backend/src/notifications/producers/order-notification.d.ts +9 -0
- package/dist/apps/backend/src/notifications/producers/order-notification.js +18 -0
- package/dist/apps/backend/src/notifications/producers/prospect-recovery-notification.d.ts +10 -0
- package/dist/apps/backend/src/notifications/producers/prospect-recovery-notification.js +11 -0
- package/dist/apps/backend/src/routes/admin/abandoned-carts.d.ts +605 -0
- package/dist/apps/backend/src/routes/admin/abandoned-carts.js +194 -0
- package/dist/apps/backend/src/routes/admin/brands.d.ts +175 -0
- package/dist/apps/backend/src/routes/admin/brands.js +118 -0
- package/dist/apps/backend/src/routes/admin/customers.d.ts +308 -0
- package/dist/apps/backend/src/routes/admin/customers.js +39 -0
- package/dist/apps/backend/src/routes/admin/delivery-zones.d.ts +446 -0
- package/dist/apps/backend/src/routes/admin/delivery-zones.js +300 -0
- package/dist/apps/backend/src/routes/admin/discount-codes.d.ts +478 -0
- package/dist/apps/backend/src/routes/admin/discount-codes.js +418 -0
- package/dist/apps/backend/src/routes/admin/inventory.d.ts +273 -0
- package/dist/apps/backend/src/routes/admin/inventory.js +199 -0
- package/dist/apps/backend/src/routes/admin/orders.d.ts +1780 -0
- package/dist/apps/backend/src/routes/admin/orders.js +552 -0
- package/dist/apps/backend/src/routes/admin/products.d.ts +860 -0
- package/dist/apps/backend/src/routes/admin/products.js +126 -0
- package/dist/apps/backend/src/routes/admin/stats.d.ts +290 -0
- package/dist/apps/backend/src/routes/admin/stats.js +55 -0
- package/dist/apps/backend/src/routes/admin/variants.d.ts +239 -0
- package/dist/apps/backend/src/routes/admin/variants.js +197 -0
- package/dist/apps/backend/src/routes/admin/warehouses.d.ts +373 -0
- package/dist/apps/backend/src/routes/admin/warehouses.js +123 -0
- package/dist/apps/backend/src/routes/public/brands.d.ts +40 -0
- package/dist/apps/backend/src/routes/public/brands.js +38 -0
- package/dist/apps/backend/src/routes/public/carts.d.ts +2657 -0
- package/dist/apps/backend/src/routes/public/carts.js +778 -0
- package/dist/apps/backend/src/routes/public/delivery-zones.d.ts +37 -0
- package/dist/apps/backend/src/routes/public/delivery-zones.js +64 -0
- package/dist/apps/backend/src/routes/public/orders.d.ts +609 -0
- package/dist/apps/backend/src/routes/public/orders.js +184 -0
- package/dist/apps/backend/src/routes/public/products.d.ts +449 -0
- package/dist/apps/backend/src/routes/public/products.js +133 -0
- package/dist/apps/backend/src/types/index.d.ts +43 -0
- package/dist/apps/backend/src/types/index.js +2 -0
- package/dist/apps/backend/src/validators/brand.d.ts +17 -0
- package/dist/apps/backend/src/validators/brand.js +15 -0
- package/dist/apps/backend/src/validators/delivery-zone.d.ts +35 -0
- package/dist/apps/backend/src/validators/delivery-zone.js +55 -0
- package/dist/apps/backend/src/validators/discount-code.d.ts +74 -0
- package/dist/apps/backend/src/validators/discount-code.js +50 -0
- package/dist/apps/backend/src/validators/inventory.d.ts +20 -0
- package/dist/apps/backend/src/validators/inventory.js +15 -0
- package/dist/apps/backend/src/validators/order.d.ts +58 -0
- package/dist/apps/backend/src/validators/order.js +62 -0
- package/dist/apps/backend/src/validators/product.d.ts +18 -0
- package/dist/apps/backend/src/validators/product.js +19 -0
- package/dist/apps/backend/src/validators/variant.d.ts +19 -0
- package/dist/apps/backend/src/validators/variant.js +19 -0
- package/dist/apps/backend/src/validators/warehouse.d.ts +15 -0
- package/dist/apps/backend/src/validators/warehouse.js +15 -0
- package/dist/fetchers/orders.d.ts +258 -1
- package/dist/hooks/admin/orders.d.ts +285 -3
- package/dist/hooks/admin/orders.js +7 -4
- package/dist/hooks/public/orders.d.ts +258 -1
- package/dist/packages/api-client/src/backend-types.d.ts +10 -0
- package/dist/packages/api-client/src/backend-types.js +10 -0
- package/dist/packages/api-client/src/client.d.ts +20 -0
- package/dist/packages/api-client/src/client.js +40 -0
- package/dist/packages/api-client/src/enum-types.d.ts +8 -0
- package/dist/packages/api-client/src/enum-types.js +5 -0
- package/dist/packages/api-client/src/fetchers/brands.d.ts +25 -0
- package/dist/packages/api-client/src/fetchers/brands.js +26 -0
- package/dist/packages/api-client/src/fetchers/carts.d.ts +2337 -0
- package/dist/packages/api-client/src/fetchers/carts.js +174 -0
- package/dist/packages/api-client/src/fetchers/delivery-zones.d.ts +30 -0
- package/dist/packages/api-client/src/fetchers/delivery-zones.js +26 -0
- package/dist/packages/api-client/src/fetchers/index.d.ts +22 -0
- package/dist/packages/api-client/src/fetchers/index.js +22 -0
- package/dist/packages/api-client/src/fetchers/orders.d.ts +544 -0
- package/dist/packages/api-client/src/fetchers/orders.js +44 -0
- package/dist/packages/api-client/src/fetchers/products.d.ts +386 -0
- package/dist/packages/api-client/src/fetchers/products.js +42 -0
- package/dist/packages/api-client/src/hooks/admin/abandoned-carts.d.ts +535 -0
- package/dist/packages/api-client/src/hooks/admin/abandoned-carts.js +83 -0
- package/dist/packages/api-client/src/hooks/admin/brands.d.ts +79 -0
- package/dist/packages/api-client/src/hooks/admin/brands.js +108 -0
- package/dist/packages/api-client/src/hooks/admin/customers.d.ts +280 -0
- package/dist/packages/api-client/src/hooks/admin/customers.js +26 -0
- package/dist/packages/api-client/src/hooks/admin/delivery-zones.d.ts +278 -0
- package/dist/packages/api-client/src/hooks/admin/delivery-zones.js +176 -0
- package/dist/packages/api-client/src/hooks/admin/discount-codes.d.ts +299 -0
- package/dist/packages/api-client/src/hooks/admin/discount-codes.js +165 -0
- package/dist/packages/api-client/src/hooks/admin/index.d.ts +16 -0
- package/dist/packages/api-client/src/hooks/admin/index.js +16 -0
- package/dist/packages/api-client/src/hooks/admin/inventory.d.ts +224 -0
- package/dist/packages/api-client/src/hooks/admin/inventory.js +107 -0
- package/dist/packages/api-client/src/hooks/admin/orders.d.ts +1674 -0
- package/dist/packages/api-client/src/hooks/admin/orders.js +178 -0
- package/dist/packages/api-client/src/hooks/admin/products.d.ts +374 -0
- package/dist/packages/api-client/src/hooks/admin/products.js +89 -0
- package/dist/packages/api-client/src/hooks/admin/stats.d.ts +279 -0
- package/dist/packages/api-client/src/hooks/admin/stats.js +25 -0
- package/dist/packages/api-client/src/hooks/admin/variants.d.ts +115 -0
- package/dist/packages/api-client/src/hooks/admin/variants.js +127 -0
- package/dist/packages/api-client/src/hooks/admin/warehouses.d.ts +277 -0
- package/dist/packages/api-client/src/hooks/admin/warehouses.js +108 -0
- package/dist/packages/api-client/src/hooks/public/brands.d.ts +33 -0
- package/dist/packages/api-client/src/hooks/public/brands.js +30 -0
- package/dist/packages/api-client/src/hooks/public/carts.d.ts +2407 -0
- package/dist/packages/api-client/src/hooks/public/carts.js +213 -0
- package/dist/packages/api-client/src/hooks/public/delivery-zones.d.ts +36 -0
- package/dist/packages/api-client/src/hooks/public/delivery-zones.js +28 -0
- package/dist/packages/api-client/src/hooks/public/index.d.ts +10 -0
- package/dist/packages/api-client/src/hooks/public/index.js +10 -0
- package/dist/packages/api-client/src/hooks/public/orders.d.ts +563 -0
- package/dist/packages/api-client/src/hooks/public/orders.js +50 -0
- package/dist/packages/api-client/src/hooks/public/products.d.ts +398 -0
- package/dist/packages/api-client/src/hooks/public/products.js +47 -0
- package/dist/packages/api-client/src/hooks/use-query-unwrapped.d.ts +20 -0
- package/dist/packages/api-client/src/hooks/use-query-unwrapped.js +22 -0
- package/dist/packages/api-client/src/hooks/useApiConfig.d.ts +12 -0
- package/dist/packages/api-client/src/hooks/useApiConfig.js +14 -0
- package/dist/packages/api-client/src/index.d.ts +20 -0
- package/dist/packages/api-client/src/index.js +25 -0
- package/dist/packages/api-client/src/provider.d.ts +36 -0
- package/dist/packages/api-client/src/provider.js +54 -0
- package/dist/packages/api-client/src/rpc-client.d.ts +9639 -0
- package/dist/packages/api-client/src/rpc-client.js +78 -0
- package/dist/packages/api-client/src/rpc-types.d.ts +76 -0
- package/dist/packages/api-client/src/rpc-types.js +7 -0
- package/dist/packages/api-client/src/types.d.ts +34 -0
- package/dist/packages/api-client/src/types.js +16 -0
- package/dist/packages/api-client/src/utils/query-keys.d.ts +106 -0
- package/dist/packages/api-client/src/utils/query-keys.js +108 -0
- package/dist/rpc-client.d.ts +891 -319
- package/dist/utils/query-keys.d.ts +1 -1
- package/dist/utils/query-keys.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PrismaClient } from '@prisma/client';
|
|
2
|
+
import { PrismaNeon } from '@prisma/adapter-neon';
|
|
3
|
+
import { neonConfig } from '@neondatabase/serverless';
|
|
4
|
+
// Configure Neon for WebSocket connections globally (required for Cloudflare Workers)
|
|
5
|
+
// @ts-ignore-next-line
|
|
6
|
+
neonConfig.webSocketConstructor = WebSocket;
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new Prisma client for each request.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT: In Cloudflare Workers, we CANNOT use a singleton pattern because
|
|
11
|
+
* I/O objects (like database connections) cannot be shared across different requests.
|
|
12
|
+
* Each request must create its own Prisma instance.
|
|
13
|
+
*/
|
|
14
|
+
export function getPrismaClient(databaseUrl) {
|
|
15
|
+
if (!databaseUrl) {
|
|
16
|
+
throw new Error('DATABASE_URL is required but not provided');
|
|
17
|
+
}
|
|
18
|
+
// Prisma 6.6.0+ changed the Driver Adapters API
|
|
19
|
+
// The adapter now takes connectionString directly, not a Pool
|
|
20
|
+
const adapter = new PrismaNeon({ connectionString: databaseUrl });
|
|
21
|
+
// Create a new Prisma Client for this request
|
|
22
|
+
const prisma = new PrismaClient({
|
|
23
|
+
adapter,
|
|
24
|
+
log: process.env.NODE_ENV === 'development' ? ['error', 'warn'] : ['error'],
|
|
25
|
+
});
|
|
26
|
+
return prisma;
|
|
27
|
+
}
|
|
28
|
+
// Note: Prisma v6 removed $use middleware API
|
|
29
|
+
// Soft delete logic is now handled directly in queries (where: { deletedAt: null })
|
|
30
|
+
// For soft deletes, use: prisma.model.update({ where: { id }, data: { deletedAt: new Date() } })
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Prisma } from '@prisma/client';
|
|
2
|
+
export declare const PRODUCT_INCLUDE_FULL: {
|
|
3
|
+
brand: boolean;
|
|
4
|
+
variants: {
|
|
5
|
+
where: {
|
|
6
|
+
deletedAt: any;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export declare const PRODUCT_INCLUDE_FULL_PUBLIC: {
|
|
11
|
+
brand: boolean;
|
|
12
|
+
variants: {
|
|
13
|
+
where: {
|
|
14
|
+
isActive: boolean;
|
|
15
|
+
deletedAt: any;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export type ProductDBResponse = Prisma.ProductGetPayload<{
|
|
20
|
+
include: {
|
|
21
|
+
brand: true;
|
|
22
|
+
variants: true;
|
|
23
|
+
};
|
|
24
|
+
}>;
|
|
25
|
+
export type ProductVariantDBResponse = Prisma.ProductVariantGetPayload<{
|
|
26
|
+
include: {
|
|
27
|
+
product: false;
|
|
28
|
+
};
|
|
29
|
+
}>;
|
|
30
|
+
export declare function formatProductResponse(product: ProductDBResponse): {
|
|
31
|
+
brand: {
|
|
32
|
+
createdAt: string;
|
|
33
|
+
updatedAt: string;
|
|
34
|
+
deletedAt: string;
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
slug: string;
|
|
38
|
+
logoUrl: string | null;
|
|
39
|
+
siteUrl: string;
|
|
40
|
+
domain: string;
|
|
41
|
+
metaPixelId: string | null;
|
|
42
|
+
};
|
|
43
|
+
variants: {
|
|
44
|
+
createdAt: string;
|
|
45
|
+
updatedAt: string;
|
|
46
|
+
price: number;
|
|
47
|
+
deletedAt: string;
|
|
48
|
+
id: string;
|
|
49
|
+
name: string | null;
|
|
50
|
+
isActive: boolean;
|
|
51
|
+
thumbnailUrl: string | null;
|
|
52
|
+
productId: string;
|
|
53
|
+
sku: string;
|
|
54
|
+
trackInventory: boolean;
|
|
55
|
+
lowStockThreshold: number | null;
|
|
56
|
+
}[];
|
|
57
|
+
id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
slug: string;
|
|
60
|
+
createdAt: Date;
|
|
61
|
+
updatedAt: Date;
|
|
62
|
+
deletedAt: Date | null;
|
|
63
|
+
brandId: string;
|
|
64
|
+
isActive: boolean;
|
|
65
|
+
description: string | null;
|
|
66
|
+
thumbnailUrl: string | null;
|
|
67
|
+
quantityDiscounts: Prisma.JsonValue | null;
|
|
68
|
+
};
|
|
69
|
+
export declare function formatProductVariantResponse(variant: ProductVariantDBResponse): {
|
|
70
|
+
createdAt: string;
|
|
71
|
+
updatedAt: string;
|
|
72
|
+
price: number;
|
|
73
|
+
deletedAt: string;
|
|
74
|
+
id: string;
|
|
75
|
+
name: string | null;
|
|
76
|
+
isActive: boolean;
|
|
77
|
+
thumbnailUrl: string | null;
|
|
78
|
+
productId: string;
|
|
79
|
+
sku: string;
|
|
80
|
+
trackInventory: boolean;
|
|
81
|
+
lowStockThreshold: number | null;
|
|
82
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { formatBrandResponse } from './brand-response';
|
|
2
|
+
export const PRODUCT_INCLUDE_FULL = {
|
|
3
|
+
brand: true,
|
|
4
|
+
variants: {
|
|
5
|
+
where: { deletedAt: null },
|
|
6
|
+
},
|
|
7
|
+
};
|
|
8
|
+
export const PRODUCT_INCLUDE_FULL_PUBLIC = {
|
|
9
|
+
brand: true,
|
|
10
|
+
variants: {
|
|
11
|
+
where: { isActive: true, deletedAt: null },
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export function formatProductResponse(product) {
|
|
15
|
+
return {
|
|
16
|
+
...product,
|
|
17
|
+
brand: formatBrandResponse(product.brand),
|
|
18
|
+
variants: product.variants.map(formatProductVariantResponse),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function formatProductVariantResponse(variant) {
|
|
22
|
+
return {
|
|
23
|
+
...variant,
|
|
24
|
+
createdAt: variant.createdAt.toISOString(),
|
|
25
|
+
updatedAt: variant.updatedAt.toISOString(),
|
|
26
|
+
price: parseFloat(variant.price.toString()),
|
|
27
|
+
deletedAt: variant.deletedAt ? variant.deletedAt.toISOString() : null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentry error tracking for Cloudflare Workers
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for capturing errors, exceptions, and messages
|
|
5
|
+
* with proper context and sampling rates.
|
|
6
|
+
*
|
|
7
|
+
* Sentry is initialized via withSentry() wrapper in index.ts
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from 'hono';
|
|
10
|
+
/**
|
|
11
|
+
* Capture an exception with request context
|
|
12
|
+
*/
|
|
13
|
+
export declare function captureException(error: Error | unknown, context?: {
|
|
14
|
+
level?: 'fatal' | 'error' | 'warning';
|
|
15
|
+
tags?: Record<string, string>;
|
|
16
|
+
extra?: Record<string, any>;
|
|
17
|
+
fingerprint?: string[];
|
|
18
|
+
honoContext?: Context<any>;
|
|
19
|
+
}): void;
|
|
20
|
+
/**
|
|
21
|
+
* Capture a message with context (for non-exception events)
|
|
22
|
+
*/
|
|
23
|
+
export declare function captureMessage(message: string, context?: {
|
|
24
|
+
level?: 'fatal' | 'error' | 'warning' | 'info' | 'debug';
|
|
25
|
+
tags?: Record<string, string>;
|
|
26
|
+
extra?: Record<string, any>;
|
|
27
|
+
sampleRate?: number;
|
|
28
|
+
}): void;
|
|
29
|
+
/**
|
|
30
|
+
* Add breadcrumb for tracing event flow
|
|
31
|
+
*/
|
|
32
|
+
export declare function addBreadcrumb(message: string, data?: Record<string, any>, category?: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Wrap a function with Sentry error capturing
|
|
35
|
+
*/
|
|
36
|
+
export declare function withSentry<T extends (...args: any[]) => any>(fn: T, context?: {
|
|
37
|
+
tags?: Record<string, string>;
|
|
38
|
+
extra?: Record<string, any>;
|
|
39
|
+
}): T;
|
|
40
|
+
/**
|
|
41
|
+
* Helper to determine if an error should be tracked in Sentry
|
|
42
|
+
* based on its characteristics (e.g., status code, error code)
|
|
43
|
+
*/
|
|
44
|
+
export declare function shouldTrackError(statusCode: number, errorCode?: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Get sample rate for different error types
|
|
47
|
+
*/
|
|
48
|
+
export declare function getSampleRate(errorCode: string): number;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentry error tracking for Cloudflare Workers
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for capturing errors, exceptions, and messages
|
|
5
|
+
* with proper context and sampling rates.
|
|
6
|
+
*
|
|
7
|
+
* Sentry is initialized via withSentry() wrapper in index.ts
|
|
8
|
+
*/
|
|
9
|
+
import * as Sentry from '@sentry/cloudflare';
|
|
10
|
+
/**
|
|
11
|
+
* Capture an exception with request context
|
|
12
|
+
*/
|
|
13
|
+
export function captureException(error, context) {
|
|
14
|
+
Sentry.withScope((scope) => {
|
|
15
|
+
// Set severity level
|
|
16
|
+
if (context?.level) {
|
|
17
|
+
scope.setLevel(context.level);
|
|
18
|
+
}
|
|
19
|
+
// Add tags for filtering/grouping
|
|
20
|
+
if (context?.tags) {
|
|
21
|
+
Object.entries(context.tags).forEach(([key, value]) => {
|
|
22
|
+
scope.setTag(key, value);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
// Add extra context data
|
|
26
|
+
if (context?.extra) {
|
|
27
|
+
Object.entries(context.extra).forEach(([key, value]) => {
|
|
28
|
+
scope.setExtra(key, value);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// Custom fingerprint for grouping
|
|
32
|
+
if (context?.fingerprint) {
|
|
33
|
+
scope.setFingerprint(context.fingerprint);
|
|
34
|
+
}
|
|
35
|
+
// Add request context from Hono if available
|
|
36
|
+
if (context?.honoContext) {
|
|
37
|
+
const c = context.honoContext;
|
|
38
|
+
const req = c.req;
|
|
39
|
+
// Set request context
|
|
40
|
+
scope.setContext('request', {
|
|
41
|
+
url: req.url,
|
|
42
|
+
method: req.method,
|
|
43
|
+
headers: sanitizeHeaders(req.header()),
|
|
44
|
+
});
|
|
45
|
+
// Add user context if authenticated
|
|
46
|
+
const user = c.get('user');
|
|
47
|
+
if (user) {
|
|
48
|
+
scope.setUser({
|
|
49
|
+
id: user.id,
|
|
50
|
+
email: user.email,
|
|
51
|
+
username: user.name,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// Add brand context if available
|
|
55
|
+
const brandSlug = req.param('brandSlug') || req.query('brandSlug');
|
|
56
|
+
if (brandSlug) {
|
|
57
|
+
scope.setTag('brand', brandSlug);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Capture the exception
|
|
61
|
+
Sentry.captureException(error);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Capture a message with context (for non-exception events)
|
|
66
|
+
*/
|
|
67
|
+
export function captureMessage(message, context) {
|
|
68
|
+
// Apply sampling if specified
|
|
69
|
+
if (context?.sampleRate !== undefined && Math.random() > context.sampleRate) {
|
|
70
|
+
return; // Skip this capture based on sample rate
|
|
71
|
+
}
|
|
72
|
+
Sentry.withScope((scope) => {
|
|
73
|
+
if (context?.level) {
|
|
74
|
+
scope.setLevel(context.level);
|
|
75
|
+
}
|
|
76
|
+
if (context?.tags) {
|
|
77
|
+
Object.entries(context.tags).forEach(([key, value]) => {
|
|
78
|
+
scope.setTag(key, value);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (context?.extra) {
|
|
82
|
+
Object.entries(context.extra).forEach(([key, value]) => {
|
|
83
|
+
scope.setExtra(key, value);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
Sentry.captureMessage(message, context?.level || 'info');
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Add breadcrumb for tracing event flow
|
|
91
|
+
*/
|
|
92
|
+
export function addBreadcrumb(message, data, category) {
|
|
93
|
+
Sentry.addBreadcrumb({
|
|
94
|
+
message,
|
|
95
|
+
data,
|
|
96
|
+
category: category || 'default',
|
|
97
|
+
level: 'info',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Wrap a function with Sentry error capturing
|
|
102
|
+
*/
|
|
103
|
+
export function withSentry(fn, context) {
|
|
104
|
+
return ((...args) => {
|
|
105
|
+
try {
|
|
106
|
+
const result = fn(...args);
|
|
107
|
+
// Handle async functions
|
|
108
|
+
if (result instanceof Promise) {
|
|
109
|
+
return result.catch((error) => {
|
|
110
|
+
captureException(error, context);
|
|
111
|
+
throw error;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
captureException(error, context);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Sanitize headers to remove sensitive data
|
|
124
|
+
*/
|
|
125
|
+
function sanitizeHeaders(headers) {
|
|
126
|
+
const sanitized = {};
|
|
127
|
+
const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key'];
|
|
128
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
129
|
+
if (sensitiveHeaders.includes(key.toLowerCase())) {
|
|
130
|
+
sanitized[key] = '[REDACTED]';
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
sanitized[key] = value;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return sanitized;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Helper to determine if an error should be tracked in Sentry
|
|
140
|
+
* based on its characteristics (e.g., status code, error code)
|
|
141
|
+
*/
|
|
142
|
+
export function shouldTrackError(statusCode, errorCode) {
|
|
143
|
+
// Always track 500 errors
|
|
144
|
+
if (statusCode >= 500) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
// Don't track expected client errors
|
|
148
|
+
const expectedErrors = ['UNAUTHORIZED', 'NOT_FOUND'];
|
|
149
|
+
if (errorCode && expectedErrors.includes(errorCode)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
// Track other 4xx errors with sampling
|
|
153
|
+
return statusCode >= 400;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get sample rate for different error types
|
|
157
|
+
*/
|
|
158
|
+
export function getSampleRate(errorCode) {
|
|
159
|
+
const sampleRates = {
|
|
160
|
+
// High-volume, expected errors - low sample rate
|
|
161
|
+
CART_EXPIRED: 0.1,
|
|
162
|
+
NOT_FOUND: 0.05,
|
|
163
|
+
CART_NOT_FOUND: 0.05,
|
|
164
|
+
ORDER_NOT_FOUND: 0.05,
|
|
165
|
+
VARIANT_NOT_FOUND: 0.05,
|
|
166
|
+
// Medium-volume errors - moderate sample rate
|
|
167
|
+
INVALID_DISCOUNT_CODE: 0.2,
|
|
168
|
+
MIN_PURCHASE_NOT_MET: 0.15,
|
|
169
|
+
PAYMENT_METHOD_NOT_AVAILABLE: 0.25,
|
|
170
|
+
INVALID_DELIVERY_ZONE: 0.3,
|
|
171
|
+
PRECONDITION_FAILED: 0.5,
|
|
172
|
+
// Low-volume errors - higher sample rate
|
|
173
|
+
BAD_REQUEST: 0.1,
|
|
174
|
+
CODE_EXISTS: 1.0,
|
|
175
|
+
STATE_EXISTS: 1.0,
|
|
176
|
+
// Default for unknown errors
|
|
177
|
+
DEFAULT: 1.0,
|
|
178
|
+
};
|
|
179
|
+
return sampleRates[errorCode] ?? sampleRates.DEFAULT;
|
|
180
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Decimal } from '@prisma/client/runtime/library';
|
|
2
|
+
export { WHATSAPP_HELP_NUMBER, WHATSAPP_HELP_NUMBER_FORMATTED, WHATSAPP_HELP_LINK } from '@oms/shared';
|
|
3
|
+
/**
|
|
4
|
+
* Convert Decimal to number for JSON responses
|
|
5
|
+
*/
|
|
6
|
+
export declare function toNumber(value: number | Decimal): number;
|
|
7
|
+
/**
|
|
8
|
+
* Round to 2 decimal places (for monetary values in Naira)
|
|
9
|
+
*/
|
|
10
|
+
export declare function round(value: number): number;
|
|
11
|
+
export declare function formatCurrency(amount: number | string | Decimal | null | undefined): string;
|
|
12
|
+
export declare function formatDate(date: Date | string): string;
|
|
13
|
+
export interface PaginationParams {
|
|
14
|
+
page?: number;
|
|
15
|
+
limit?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface PaginatedResponse<T> {
|
|
18
|
+
data: T[];
|
|
19
|
+
pagination: {
|
|
20
|
+
page: number;
|
|
21
|
+
limit: number;
|
|
22
|
+
total: number;
|
|
23
|
+
totalPages: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare function getPaginationParams(params: PaginationParams): {
|
|
27
|
+
page: number;
|
|
28
|
+
limit: number;
|
|
29
|
+
skip: number;
|
|
30
|
+
};
|
|
31
|
+
export declare function createPaginatedResponse<T>(data: T[], total: number, page: number, limit: number): PaginatedResponse<T>;
|
|
32
|
+
export declare function generateSlug(text: string): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Re-export shared constants
|
|
2
|
+
export { WHATSAPP_HELP_NUMBER, WHATSAPP_HELP_NUMBER_FORMATTED, WHATSAPP_HELP_LINK } from '@oms/shared';
|
|
3
|
+
/**
|
|
4
|
+
* Convert Decimal to number for JSON responses
|
|
5
|
+
*/
|
|
6
|
+
export function toNumber(value) {
|
|
7
|
+
if (typeof value === 'number')
|
|
8
|
+
return value;
|
|
9
|
+
return parseFloat(value.toString());
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Round to 2 decimal places (for monetary values in Naira)
|
|
13
|
+
*/
|
|
14
|
+
export function round(value) {
|
|
15
|
+
return Math.round(value * 100) / 100;
|
|
16
|
+
}
|
|
17
|
+
export function formatCurrency(amount) {
|
|
18
|
+
if (amount === null || amount === undefined) {
|
|
19
|
+
return '₦0.00';
|
|
20
|
+
}
|
|
21
|
+
const numericAmount = typeof amount === 'string' ? parseFloat(amount) : toNumber(amount) ?? 0;
|
|
22
|
+
if (isNaN(numericAmount)) {
|
|
23
|
+
return '₦0.00';
|
|
24
|
+
}
|
|
25
|
+
return new Intl.NumberFormat('en-US', {
|
|
26
|
+
style: 'currency',
|
|
27
|
+
currency: 'NGN',
|
|
28
|
+
}).format(numericAmount);
|
|
29
|
+
}
|
|
30
|
+
export function formatDate(date) {
|
|
31
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
32
|
+
return d.toLocaleDateString('en-US', {
|
|
33
|
+
year: 'numeric',
|
|
34
|
+
month: 'long',
|
|
35
|
+
day: 'numeric',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function getPaginationParams(params) {
|
|
39
|
+
const page = Math.max(1, params.page || 1);
|
|
40
|
+
const limit = Math.min(100, Math.max(1, params.limit || 20));
|
|
41
|
+
const skip = (page - 1) * limit;
|
|
42
|
+
return { page, limit, skip };
|
|
43
|
+
}
|
|
44
|
+
export function createPaginatedResponse(data, total, page, limit) {
|
|
45
|
+
return {
|
|
46
|
+
data,
|
|
47
|
+
pagination: {
|
|
48
|
+
page,
|
|
49
|
+
limit,
|
|
50
|
+
total,
|
|
51
|
+
totalPages: Math.ceil(total / limit),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Generate a URL-friendly slug from a string
|
|
56
|
+
export function generateSlug(text) {
|
|
57
|
+
return text
|
|
58
|
+
.toLowerCase()
|
|
59
|
+
.trim()
|
|
60
|
+
.replace(/[^\w\s-]/g, '') // Remove non-word chars (except spaces and hyphens)
|
|
61
|
+
.replace(/[\s_-]+/g, '-') // Replace spaces, underscores, and multiple hyphens with single hyphen
|
|
62
|
+
.replace(/^-+|-+$/g, ''); // Remove leading/trailing hyphens
|
|
63
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { verifyClerkToken, extractBearerToken } from '../lib/clerk';
|
|
2
|
+
import { getPrismaClient } from '../lib/prisma';
|
|
3
|
+
export async function clerkAuthMiddleware(c, next) {
|
|
4
|
+
const authHeader = c.req.header('Authorization');
|
|
5
|
+
const token = extractBearerToken(authHeader);
|
|
6
|
+
console.log('Authenticating request with token');
|
|
7
|
+
if (!token) {
|
|
8
|
+
return c.json({
|
|
9
|
+
success: false,
|
|
10
|
+
error: {
|
|
11
|
+
code: 'UNAUTHORIZED',
|
|
12
|
+
message: 'Missing authentication token',
|
|
13
|
+
},
|
|
14
|
+
}, 401);
|
|
15
|
+
}
|
|
16
|
+
const clerkUser = await verifyClerkToken(token, c.env.CLERK_PUBLISHABLE_KEY);
|
|
17
|
+
console.log(clerkUser);
|
|
18
|
+
if (!clerkUser) {
|
|
19
|
+
return c.json({
|
|
20
|
+
success: false,
|
|
21
|
+
error: {
|
|
22
|
+
code: 'UNAUTHORIZED',
|
|
23
|
+
message: 'Invalid authentication token',
|
|
24
|
+
},
|
|
25
|
+
}, 401);
|
|
26
|
+
}
|
|
27
|
+
// Ensure user exists in database (auto-provisioning)
|
|
28
|
+
const prisma = getPrismaClient(c.env.DATABASE_URL);
|
|
29
|
+
// First check if user exists
|
|
30
|
+
let dbUser = await prisma.user.findUnique({
|
|
31
|
+
where: { clerkId: clerkUser.id },
|
|
32
|
+
});
|
|
33
|
+
// If user doesn't exist, fetch their details from Clerk API and create them
|
|
34
|
+
if (!dbUser) {
|
|
35
|
+
console.log('User not found in database, fetching details from Clerk API');
|
|
36
|
+
try {
|
|
37
|
+
const userResponse = await fetch(`https://api.clerk.com/v1/users/${clerkUser.id}`, {
|
|
38
|
+
headers: {
|
|
39
|
+
Authorization: `Bearer ${c.env.CLERK_SECRET_KEY}`,
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
if (userResponse.ok) {
|
|
44
|
+
const userData = await userResponse.json();
|
|
45
|
+
console.log('Fetched user data from Clerk API');
|
|
46
|
+
const email = userData.email_addresses?.[0]?.email_address || userData.primary_email_address || `${clerkUser.id}@unknown.clerk`;
|
|
47
|
+
const name = userData.first_name && userData.last_name
|
|
48
|
+
? `${userData.first_name} ${userData.last_name}`.trim()
|
|
49
|
+
: userData.first_name || userData.last_name || null;
|
|
50
|
+
dbUser = await prisma.user.create({
|
|
51
|
+
data: {
|
|
52
|
+
clerkId: clerkUser.id,
|
|
53
|
+
email,
|
|
54
|
+
name,
|
|
55
|
+
role: 'admin',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.error('Failed to fetch user from Clerk API:', userResponse.status);
|
|
61
|
+
// Create user with minimal info
|
|
62
|
+
dbUser = await prisma.user.create({
|
|
63
|
+
data: {
|
|
64
|
+
clerkId: clerkUser.id,
|
|
65
|
+
email: `${clerkUser.id}@unknown.clerk`,
|
|
66
|
+
name: null,
|
|
67
|
+
role: 'admin',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error('Error fetching user from Clerk API:', error);
|
|
74
|
+
// Create user with minimal info
|
|
75
|
+
dbUser = await prisma.user.create({
|
|
76
|
+
data: {
|
|
77
|
+
clerkId: clerkUser.id,
|
|
78
|
+
email: `${clerkUser.id}@unknown.clerk`,
|
|
79
|
+
name: null,
|
|
80
|
+
role: 'admin',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Store both Clerk user and database user in context for later use
|
|
86
|
+
c.set('user', clerkUser);
|
|
87
|
+
c.set('dbUser', dbUser);
|
|
88
|
+
await next();
|
|
89
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { HonoRequest, Next } from 'hono';
|
|
2
|
+
import { Env } from '../types';
|
|
3
|
+
export declare function corsMiddleware(c: {
|
|
4
|
+
env: Env;
|
|
5
|
+
req: HonoRequest;
|
|
6
|
+
header: (name: string, value: string) => void;
|
|
7
|
+
body: (body: any, status: number) => Response;
|
|
8
|
+
}, next: Next): Promise<Response>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export async function corsMiddleware(c, next) {
|
|
2
|
+
c.header('Access-Control-Allow-Origin', '*');
|
|
3
|
+
c.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
|
4
|
+
c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
|
|
5
|
+
c.header('Access-Control-Allow-Credentials', 'true');
|
|
6
|
+
// Handle preflight requests
|
|
7
|
+
if (c.req.method === 'OPTIONS') {
|
|
8
|
+
return c.body(null, 204);
|
|
9
|
+
}
|
|
10
|
+
await next();
|
|
11
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta Conversions API Event Producer
|
|
3
|
+
*
|
|
4
|
+
* Queues Meta CAPI events for processing by the queue consumer.
|
|
5
|
+
*/
|
|
6
|
+
import { Env } from '../../types';
|
|
7
|
+
import { OrderDBResponse } from '../../lib/order-response';
|
|
8
|
+
/**
|
|
9
|
+
* Queue an AddToCart event
|
|
10
|
+
*/
|
|
11
|
+
export declare function enqueueAddToCartEvent(env: Env, data: {
|
|
12
|
+
cartId: string;
|
|
13
|
+
itemId: string;
|
|
14
|
+
pixelId: string | null;
|
|
15
|
+
brandSiteUrl: string;
|
|
16
|
+
productName: string;
|
|
17
|
+
productSlug: string;
|
|
18
|
+
sku: string;
|
|
19
|
+
price: number;
|
|
20
|
+
quantity: number;
|
|
21
|
+
customerEmail?: string | null;
|
|
22
|
+
customerPhone?: string | null;
|
|
23
|
+
clientIpAddress?: string;
|
|
24
|
+
clientUserAgent?: string;
|
|
25
|
+
fbc?: string;
|
|
26
|
+
fbp?: string;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Queue an InitiateCheckout event
|
|
30
|
+
*/
|
|
31
|
+
export declare function enqueueInitiateCheckoutEvent(env: Env, data: {
|
|
32
|
+
cartId: string;
|
|
33
|
+
pixelId: string | null;
|
|
34
|
+
brandSiteUrl: string;
|
|
35
|
+
cartTotal: number;
|
|
36
|
+
itemCount: number;
|
|
37
|
+
customerEmail?: string | null;
|
|
38
|
+
customerPhone?: string | null;
|
|
39
|
+
clientIpAddress?: string;
|
|
40
|
+
clientUserAgent?: string;
|
|
41
|
+
fbc?: string;
|
|
42
|
+
fbp?: string;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Queue a Purchase event from an order
|
|
46
|
+
*
|
|
47
|
+
* For orders with multiple products, uses the brand's homepage
|
|
48
|
+
* since there's no single product page to reference.
|
|
49
|
+
*/
|
|
50
|
+
export declare function enqueuePurchaseEvent(env: Env, order: OrderDBResponse, options?: {
|
|
51
|
+
clientIpAddress?: string;
|
|
52
|
+
clientUserAgent?: string;
|
|
53
|
+
fbc?: string;
|
|
54
|
+
fbp?: string;
|
|
55
|
+
}): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Queue a ConfirmedPurchase event when order is delivered & paid
|
|
58
|
+
*
|
|
59
|
+
* This event tracks successful order completion (delivered + paid),
|
|
60
|
+
* which is a higher value conversion signal than initial purchase.
|
|
61
|
+
*/
|
|
62
|
+
export declare function enqueueConfirmedPurchaseEvent(env: Env, order: OrderDBResponse): Promise<void>;
|