@axova/shared 1.0.2 → 1.1.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 (68) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +2 -0
  3. package/dist/lib/db.d.ts +34406 -1
  4. package/dist/lib/db.js +21 -1
  5. package/dist/middleware/storeOwnership.js +22 -3
  6. package/dist/middleware/storeValidationMiddleware.js +16 -39
  7. package/dist/schemas/admin/admin-schema.d.ts +2 -2
  8. package/dist/schemas/ai-moderation/ai-moderation-schema.d.ts +6 -6
  9. package/dist/schemas/common/common-schemas.d.ts +71 -71
  10. package/dist/schemas/compliance/compliance-schema.d.ts +20 -20
  11. package/dist/schemas/compliance/kyc-schema.d.ts +8 -8
  12. package/dist/schemas/customer/customer-schema.d.ts +18 -18
  13. package/dist/schemas/index.d.ts +28 -0
  14. package/dist/schemas/index.js +134 -3
  15. package/dist/schemas/inventory/inventory-tables.d.ts +188 -188
  16. package/dist/schemas/inventory/lot-tables.d.ts +102 -102
  17. package/dist/schemas/order/cart-schema.d.ts +2865 -0
  18. package/dist/schemas/order/cart-schema.js +396 -0
  19. package/dist/schemas/order/order-schema.d.ts +19 -19
  20. package/dist/schemas/order/order-schema.js +8 -2
  21. package/dist/schemas/product/discount-schema.d.ts +3 -3
  22. package/dist/schemas/product/product-schema.d.ts +3 -3
  23. package/dist/schemas/store/store-audit-schema.d.ts +20 -20
  24. package/dist/schemas/store/store-schema.d.ts +182 -2
  25. package/dist/schemas/store/store-schema.js +19 -0
  26. package/dist/schemas/store/storefront-config-schema.d.ts +434 -823
  27. package/dist/schemas/store/storefront-config-schema.js +35 -62
  28. package/dist/utils/subdomain.d.ts +1 -1
  29. package/dist/utils/subdomain.js +10 -15
  30. package/package.json +1 -1
  31. package/src/configs/index.ts +654 -654
  32. package/src/index.ts +26 -23
  33. package/src/interfaces/customer-events.ts +106 -106
  34. package/src/interfaces/inventory-events.ts +545 -545
  35. package/src/interfaces/inventory-types.ts +1004 -1004
  36. package/src/interfaces/order-events.ts +381 -381
  37. package/src/lib/auditLogger.ts +1117 -1117
  38. package/src/lib/authOrganization.ts +153 -153
  39. package/src/lib/db.ts +84 -64
  40. package/src/middleware/serviceAuth.ts +328 -328
  41. package/src/middleware/storeOwnership.ts +199 -181
  42. package/src/middleware/storeValidationMiddleware.ts +17 -50
  43. package/src/middleware/userAuth.ts +248 -248
  44. package/src/schemas/admin/admin-schema.ts +208 -208
  45. package/src/schemas/ai-moderation/ai-moderation-schema.ts +180 -180
  46. package/src/schemas/common/common-schemas.ts +108 -108
  47. package/src/schemas/compliance/compliance-schema.ts +927 -0
  48. package/src/schemas/compliance/kyc-schema.ts +649 -0
  49. package/src/schemas/customer/customer-schema.ts +576 -0
  50. package/src/schemas/index.ts +202 -3
  51. package/src/schemas/inventory/inventory-tables.ts +1927 -0
  52. package/src/schemas/inventory/lot-tables.ts +799 -0
  53. package/src/schemas/order/cart-schema.ts +652 -0
  54. package/src/schemas/order/order-schema.ts +1406 -0
  55. package/src/schemas/product/discount-relations.ts +44 -0
  56. package/src/schemas/product/discount-schema.ts +464 -0
  57. package/src/schemas/product/product-relations.ts +187 -0
  58. package/src/schemas/product/product-schema.ts +955 -0
  59. package/src/schemas/store/ethiopian_business_api.md.resolved +212 -0
  60. package/src/schemas/store/store-audit-schema.ts +1257 -0
  61. package/src/schemas/store/store-schema.ts +682 -0
  62. package/src/schemas/store/store-settings-schema.ts +231 -0
  63. package/src/schemas/store/storefront-config-schema.ts +382 -0
  64. package/src/schemas/types.ts +67 -67
  65. package/src/types/events.ts +646 -646
  66. package/src/utils/errorHandler.ts +44 -44
  67. package/src/utils/subdomain.ts +19 -23
  68. package/tsconfig.json +21 -21
@@ -1,181 +1,199 @@
1
- import type { FastifyReply, FastifyRequest } from "fastify";
2
- import {
3
- createServiceAuthenticator,
4
- getServiceAuthConfigFromEnv,
5
- } from "./serviceAuth";
6
-
7
- export interface StoreOwnerMiddlewareOptions {
8
- requireActive?: boolean;
9
- allowParameterStoreId?: boolean;
10
- storeIdParamName?: string;
11
- allowAdminOverride?: boolean;
12
- overrideRoles?: string[];
13
- overridePermissions?: string[];
14
- }
15
-
16
- /**
17
- * Factory that returns a middleware ensuring the current user owns the target store.
18
- * - Uses shared store validation under the hood for existence/active checks
19
- * - Supports optional admin/permission overrides (e.g. SUPER_ADMIN or * permissions)
20
- */
21
- export function requireStoreOwner(options: StoreOwnerMiddlewareOptions = {}) {
22
- const {
23
- requireActive = true,
24
- allowParameterStoreId = true,
25
- storeIdParamName = "storeId",
26
- allowAdminOverride = true,
27
- overrideRoles = ["SUPER_ADMIN"],
28
- overridePermissions = ["*", "store:read:all", "store:write:all"],
29
- } = options;
30
-
31
- return async (request: FastifyRequest, reply: FastifyReply) => {
32
- const user = (request as any).user as
33
- | {
34
- userId: string;
35
- role?: string;
36
- permissions?: string[];
37
- }
38
- | undefined;
39
-
40
- // Extract storeId from params/query/body/user.context
41
- let storeId: string | undefined;
42
- const params = (request.params || {}) as Record<string, any>;
43
- const query = (request.query || {}) as Record<string, any>;
44
- const body = (request.body || {}) as Record<string, any>;
45
- if (allowParameterStoreId && typeof params[storeIdParamName] === "string") {
46
- storeId = params[storeIdParamName] as string;
47
- }
48
- if (!storeId && typeof query.storeId === "string")
49
- storeId = query.storeId as string;
50
- if (!storeId && typeof body.storeId === "string")
51
- storeId = body.storeId as string;
52
- if (!storeId && typeof (request as any).user?.storeId === "string") {
53
- storeId = (request as any).user?.storeId as string;
54
- }
55
-
56
- if (!storeId) {
57
- return reply.status(400).send({
58
- error: "Store ID Required",
59
- message: `Store ID must be provided in URL (:${storeIdParamName}), query, or body`,
60
- code: "STORE_ID_MISSING",
61
- });
62
- }
63
-
64
- // Admin/permission overrides
65
- const hasRoleOverride = !!user?.role && overrideRoles.includes(user.role);
66
- const hasPermissionOverride = !!user?.permissions?.some((p) =>
67
- overridePermissions.includes(p),
68
- );
69
- if (allowAdminOverride && (hasRoleOverride || hasPermissionOverride)) {
70
- // Optionally attach minimal store info by fetching; if it fails, still allow
71
- await attachStoreInfoIfPossible(request, storeId);
72
- return;
73
- }
74
-
75
- // Fetch store details from Store Service internal route
76
- try {
77
- const store = await fetchStoreById(storeId);
78
-
79
- if (!store) {
80
- return reply.status(404).send({
81
- error: "Store Not Found",
82
- message: `Store with ID ${storeId} does not exist`,
83
- code: "STORE_NOT_FOUND",
84
- });
85
- }
86
-
87
- // requireActive check
88
- if (requireActive && store.isActive === false) {
89
- return reply.status(403).send({
90
- error: "Store Inactive",
91
- message: `Store ${storeId} is currently inactive`,
92
- code: "STORE_INACTIVE",
93
- });
94
- }
95
-
96
- // Ownership check
97
- if (!user?.userId || store.userId !== user.userId) {
98
- return reply.status(403).send({
99
- error: "Store Access Denied",
100
- message: "You do not have permission to access this store",
101
- code: "STORE_ACCESS_DENIED",
102
- });
103
- }
104
-
105
- // Attach store info to request for downstream handlers
106
- (request as any).storeInfo = {
107
- storeId: store.id,
108
- storeName: store.storeName,
109
- isActive: !!store.isActive,
110
- userId: store.userId,
111
- };
112
- } catch (error) {
113
- console.error("Store ownership validation error:", error);
114
- return reply.status(500).send({
115
- error: "Internal Server Error",
116
- message: "Failed to validate store ownership",
117
- code: "STORE_OWNERSHIP_VALIDATION_ERROR",
118
- });
119
- }
120
- };
121
- }
122
-
123
- // Fetches store by ID from the Store Service internal endpoint
124
- async function fetchStoreById(storeId: string): Promise<{
125
- id: string;
126
- storeName: string;
127
- isActive?: boolean;
128
- userId: string;
129
- } | null> {
130
- const baseUrl = process.env.STORE_SERVICE_URL || "http://localhost:3001";
131
- const url = `${baseUrl}/internal/stores/${encodeURIComponent(storeId)}`;
132
-
133
- let token: string | undefined;
134
- try {
135
- const cfg = getServiceAuthConfigFromEnv();
136
- token = createServiceAuthenticator(cfg).generateServiceToken(
137
- process.env.SERVICE_NAME || "inventory-core-service",
138
- ["store:read"],
139
- );
140
- } catch {
141
- // Development fallback tokens accepted by store service
142
- const isDev = process.env.NODE_ENV !== "production";
143
- if (isDev) token = "inventory-dev-access";
144
- }
145
-
146
- const headers: Record<string, string> = {
147
- "Content-Type": "application/json",
148
- };
149
- if (token) headers["x-service-token"] = token;
150
-
151
- try {
152
- const res = await fetch(url, { method: "GET", headers });
153
- if (res.ok) {
154
- const data = (await res.json()) as any;
155
- const store = data?.store || data?.data?.store || null;
156
- if (store) return store;
157
- }
158
- } catch (error) {
159
- console.error('Failed to fetch store from store service:', error);
160
- }
161
- return null;
162
- }
163
-
164
- async function attachStoreInfoIfPossible(
165
- request: FastifyRequest,
166
- storeId: string,
167
- ) {
168
- try {
169
- const store = await fetchStoreById(storeId);
170
- if (store) {
171
- (request as any).storeInfo = {
172
- storeId: store.id,
173
- storeName: store.storeName,
174
- isActive: !!store.isActive,
175
- userId: store.userId,
176
- };
177
- }
178
- } catch {
179
- // ignore
180
- }
181
- }
1
+ import { eq } from "drizzle-orm";
2
+ import type { FastifyReply, FastifyRequest } from "fastify";
3
+ import { db } from "../lib/db";
4
+ import { stores } from "../schemas/store/store-schema";
5
+ import {
6
+ createServiceAuthenticator,
7
+ getServiceAuthConfigFromEnv,
8
+ } from "./serviceAuth";
9
+
10
+ export interface StoreOwnerMiddlewareOptions {
11
+ requireActive?: boolean;
12
+ allowParameterStoreId?: boolean;
13
+ storeIdParamName?: string;
14
+ allowAdminOverride?: boolean;
15
+ overrideRoles?: string[];
16
+ overridePermissions?: string[];
17
+ }
18
+
19
+ /**
20
+ * Factory that returns a middleware ensuring the current user owns the target store.
21
+ * - Uses shared store validation under the hood for existence/active checks
22
+ * - Supports optional admin/permission overrides (e.g. SUPER_ADMIN or * permissions)
23
+ */
24
+ export function requireStoreOwner(options: StoreOwnerMiddlewareOptions = {}) {
25
+ const {
26
+ requireActive = true,
27
+ allowParameterStoreId = true,
28
+ storeIdParamName = "storeId",
29
+ allowAdminOverride = true,
30
+ overrideRoles = ["SUPER_ADMIN"],
31
+ overridePermissions = ["*", "store:read:all", "store:write:all"],
32
+ } = options;
33
+
34
+ return async (request: FastifyRequest, reply: FastifyReply) => {
35
+ const user = (request as any).user as
36
+ | {
37
+ userId: string;
38
+ role?: string;
39
+ permissions?: string[];
40
+ }
41
+ | undefined;
42
+
43
+ // Extract storeId from params/query/body/user.context
44
+ let storeId: string | undefined;
45
+ const params = (request.params || {}) as Record<string, any>;
46
+ const query = (request.query || {}) as Record<string, any>;
47
+ const body = (request.body || {}) as Record<string, any>;
48
+ if (allowParameterStoreId && typeof params[storeIdParamName] === "string") {
49
+ storeId = params[storeIdParamName] as string;
50
+ }
51
+ if (!storeId && typeof query.storeId === "string")
52
+ storeId = query.storeId as string;
53
+ if (!storeId && typeof body.storeId === "string")
54
+ storeId = body.storeId as string;
55
+ if (!storeId && typeof (request as any).user?.storeId === "string") {
56
+ storeId = (request as any).user?.storeId as string;
57
+ }
58
+
59
+ if (!storeId) {
60
+ return reply.status(400).send({
61
+ error: "Store ID Required",
62
+ message: `Store ID must be provided in URL (:${storeIdParamName}), query, or body`,
63
+ code: "STORE_ID_MISSING",
64
+ });
65
+ }
66
+
67
+ // Admin/permission overrides
68
+ const hasRoleOverride = !!user?.role && overrideRoles.includes(user.role);
69
+ const hasPermissionOverride = !!user?.permissions?.some((p) =>
70
+ overridePermissions.includes(p),
71
+ );
72
+ if (allowAdminOverride && (hasRoleOverride || hasPermissionOverride)) {
73
+ // Optionally attach minimal store info by fetching; if it fails, still allow
74
+ await attachStoreInfoIfPossible(request, storeId);
75
+ return;
76
+ }
77
+
78
+ // Fetch store details from Store Service internal route
79
+ try {
80
+ const store = await fetchStoreById(storeId);
81
+
82
+ if (!store) {
83
+ return reply.status(404).send({
84
+ error: "Store Not Found",
85
+ message: `Store with ID ${storeId} does not exist`,
86
+ code: "STORE_NOT_FOUND",
87
+ });
88
+ }
89
+
90
+ // requireActive check
91
+ if (requireActive && store.isActive === false) {
92
+ return reply.status(403).send({
93
+ error: "Store Inactive",
94
+ message: `Store ${storeId} is currently inactive`,
95
+ code: "STORE_INACTIVE",
96
+ });
97
+ }
98
+
99
+ // Ownership check
100
+ if (!user?.userId || store.userId !== user.userId) {
101
+ return reply.status(403).send({
102
+ error: "Store Access Denied",
103
+ message: "You do not have permission to access this store",
104
+ code: "STORE_ACCESS_DENIED",
105
+ });
106
+ }
107
+
108
+ // Attach store info to request for downstream handlers
109
+ (request as any).storeInfo = {
110
+ storeId: store.id,
111
+ storeName: store.storeName,
112
+ isActive: !!store.isActive,
113
+ userId: store.userId,
114
+ };
115
+ } catch (error) {
116
+ console.error("Store ownership validation error:", error);
117
+ return reply.status(500).send({
118
+ error: "Internal Server Error",
119
+ message: "Failed to validate store ownership",
120
+ code: "STORE_OWNERSHIP_VALIDATION_ERROR",
121
+ });
122
+ }
123
+ };
124
+ }
125
+
126
+ // Fetches store by ID from the Store Service internal endpoint
127
+ async function fetchStoreById(storeId: string): Promise<{
128
+ id: string;
129
+ storeName: string;
130
+ isActive?: boolean;
131
+ userId: string;
132
+ } | null> {
133
+ const baseUrl = process.env.STORE_SERVICE_URL || "http://localhost:3001";
134
+ const url = `${baseUrl}/internal/stores/${encodeURIComponent(storeId)}`;
135
+
136
+ let token: string | undefined;
137
+ try {
138
+ const cfg = getServiceAuthConfigFromEnv();
139
+ token = createServiceAuthenticator(cfg).generateServiceToken(
140
+ process.env.SERVICE_NAME || "inventory-core-service",
141
+ ["store:read"],
142
+ );
143
+ } catch {
144
+ // Development fallback tokens accepted by store service
145
+ const isDev = process.env.NODE_ENV !== "production";
146
+ if (isDev) token = "inventory-dev-access";
147
+ }
148
+
149
+ const headers: Record<string, string> = {
150
+ "Content-Type": "application/json",
151
+ };
152
+ if (token) headers["x-service-token"] = token;
153
+
154
+ try {
155
+ const res = await fetch(url, { method: "GET", headers });
156
+ if (res.ok) {
157
+ const data = (await res.json()) as any;
158
+ const store = data?.store || data?.data?.store || null;
159
+ if (store) return store;
160
+ }
161
+ } catch {
162
+ // ignore network error and try DB fallback when possible
163
+ }
164
+ // DB fallback (primarily for in-process store-service)
165
+ try {
166
+ const result = await db
167
+ .select({
168
+ id: stores.id,
169
+ storeName: stores.storeName,
170
+ isActive: stores.isActive,
171
+ userId: stores.userId,
172
+ })
173
+ .from(stores)
174
+ .where(eq(stores.id, storeId))
175
+ .limit(1);
176
+ return result[0] || null;
177
+ } catch {
178
+ return null;
179
+ }
180
+ }
181
+
182
+ async function attachStoreInfoIfPossible(
183
+ request: FastifyRequest,
184
+ storeId: string,
185
+ ) {
186
+ try {
187
+ const store = await fetchStoreById(storeId);
188
+ if (store) {
189
+ (request as any).storeInfo = {
190
+ storeId: store.id,
191
+ storeName: store.storeName,
192
+ isActive: !!store.isActive,
193
+ userId: store.userId,
194
+ };
195
+ }
196
+ } catch {
197
+ // ignore
198
+ }
199
+ }
@@ -1,8 +1,7 @@
1
+ import { eq } from "drizzle-orm";
1
2
  import { FastifyReply, FastifyRequest } from "fastify";
2
- import {
3
- createServiceAuthenticator,
4
- getServiceAuthConfigFromEnv,
5
- } from "./serviceAuth";
3
+ import { db } from "../lib/db";
4
+ import { stores } from "../schemas/store/store-schema";
6
5
 
7
6
  // Store cache to reduce database calls
8
7
  const storeCache = new Map<
@@ -144,10 +143,19 @@ export const validateStoreMiddleware = (
144
143
  }
145
144
  }
146
145
 
147
- // Validate store exists and get details from store service API
148
- const fetchedStore = await fetchStoreById(storeId);
149
-
150
- if (!fetchedStore) {
146
+ // Validate store exists and get details from database
147
+ const store = await db
148
+ .select({
149
+ id: stores.id,
150
+ storeName: stores.storeName,
151
+ isActive: stores.isActive,
152
+ userId: stores.userId,
153
+ })
154
+ .from(stores)
155
+ .where(eq(stores.id, storeId))
156
+ .limit(1);
157
+
158
+ if (store.length === 0) {
151
159
  // Cache negative result
152
160
  storeCache.set(storeId, { isValid: false, timestamp: Date.now() });
153
161
 
@@ -158,7 +166,7 @@ export const validateStoreMiddleware = (
158
166
  });
159
167
  }
160
168
 
161
- const storeData = fetchedStore;
169
+ const storeData = store[0];
162
170
 
163
171
  // Check if store is active when required
164
172
  if (requireActive && !storeData.isActive) {
@@ -237,44 +245,3 @@ export const requireOwnedStore = validateStoreMiddleware({
237
245
  export const requireAnyStore = validateStoreMiddleware({
238
246
  requireActive: false,
239
247
  });
240
-
241
- // Helper function to fetch store from store service
242
- async function fetchStoreById(storeId: string): Promise<{
243
- id: string;
244
- storeName: string;
245
- isActive: boolean;
246
- userId: string;
247
- } | null> {
248
- const baseUrl = process.env.STORE_SERVICE_URL || "http://localhost:3001";
249
- const url = `${baseUrl}/internal/stores/${encodeURIComponent(storeId)}`;
250
-
251
- let token: string | undefined;
252
- try {
253
- const cfg = getServiceAuthConfigFromEnv();
254
- token = createServiceAuthenticator(cfg).generateServiceToken(
255
- process.env.SERVICE_NAME || "shared-middleware",
256
- ["store:read"],
257
- );
258
- } catch {
259
- // Development fallback tokens accepted by store service
260
- const isDev = process.env.NODE_ENV !== "production";
261
- if (isDev) token = "dev-access";
262
- }
263
-
264
- const headers: Record<string, string> = {
265
- "Content-Type": "application/json",
266
- };
267
- if (token) headers["x-service-token"] = token;
268
-
269
- try {
270
- const res = await fetch(url, { method: "GET", headers });
271
- if (res.ok) {
272
- const data = (await res.json()) as any;
273
- const store = data?.store || data?.data?.store || null;
274
- if (store) return store;
275
- }
276
- } catch (error) {
277
- console.error('Failed to fetch store from store service:', error);
278
- }
279
- return null;
280
- }