From 8fc603db0ab912e97c613599b09621ef0263d263 Mon Sep 17 00:00:00 2001 From: shafi54 <108669266+shafi-aviz@users.noreply.github.com> Date: Sat, 7 Mar 2026 16:24:24 +0530 Subject: [PATCH] enh --- AGENTS.md | 6 - apps/backend/assets/signed-url-cache.json | 2 +- apps/backend/index.ts | 18 +- .../admin-apis/apis}/av-router.ts | 6 +- .../apis}/product-tags.controller.ts | 12 +- .../admin-apis/apis}/product.controller.ts | 14 +- .../admin-apis/apis}/product.router.ts | 4 +- .../admin-apis/apis}/tag.router.ts | 4 +- .../apis/admin-apis/dataAccessors/demo.txt | 0 .../apis}/common-product.controller.ts | 6 +- .../apis}/common-product.router.ts | 2 +- .../common-apis/apis}/common.router.ts | 2 +- .../apis/common-apis/dataAccessors/demo.txt | 0 apps/backend/src/db/db_index.ts | 2 +- apps/backend/src/db/porter.ts | 6 +- apps/backend/src/db/seed.ts | 8 +- apps/backend/src/db/types.ts | 4 +- apps/backend/src/jobs/jobs-index.ts | 2 +- .../src/jobs/payment-status-checker.ts | 6 +- apps/backend/src/lib/automatedJobs.ts | 8 +- apps/backend/src/lib/axios.ts | 2 +- apps/backend/src/lib/const-store.ts | 8 +- apps/backend/src/lib/delete-image.ts | 6 +- apps/backend/src/lib/delete-orders.ts | 4 +- apps/backend/src/lib/event-queue.ts | 2 +- apps/backend/src/lib/expo-service.ts | 2 +- apps/backend/src/lib/init.ts | 10 +- apps/backend/src/lib/notif-job.ts | 8 +- apps/backend/src/lib/notif-service.ts | 8 +- apps/backend/src/lib/otp-utils.ts | 4 +- apps/backend/src/lib/payments-utils.ts | 6 +- apps/backend/src/lib/post-order-handler.ts | 8 +- apps/backend/src/lib/redis-client.ts | 2 +- apps/backend/src/lib/roles-manager.ts | 2 +- apps/backend/src/lib/s3-client.ts | 10 +- apps/backend/src/lib/telegram-service.ts | 2 +- apps/backend/src/main-router.ts | 14 +- .../backend/src/middleware/auth.middleware.ts | 6 +- apps/backend/src/middleware/auth.ts | 2 +- apps/backend/src/middleware/staff-auth.ts | 6 +- .../src/services/user/order-service.ts | 4 +- .../src/services/user/product-service.ts | 4 +- apps/backend/src/stores/banner-store.ts | 14 +- apps/backend/src/stores/product-store.ts | 10 +- apps/backend/src/stores/product-tag-store.ts | 12 +- apps/backend/src/stores/slot-store.ts | 8 +- apps/backend/src/stores/store-initializer.ts | 12 +- .../src/stores/user-negativity-store.ts | 6 +- .../src/trpc/admin-apis/admin-trpc-index.ts | 34 - .../admin-apis/apis}/address.ts | 6 +- .../apis/admin-apis/apis/admin-trpc-index.ts | 35 + .../admin-apis/apis}/banner.ts | 14 +- .../admin-apis/apis}/cancelled-orders.ts | 6 +- .../admin-apis/apis}/complaint.ts | 8 +- .../admin-apis/apis}/const.ts | 10 +- .../admin-apis/apis}/coupon.ts | 6 +- .../admin-apis/apis}/order.ts | 14 +- .../admin-apis/apis}/payments.ts | 10 +- .../admin-apis/apis}/product.ts | 20 +- .../admin-apis/apis}/slots.ts | 16 +- .../admin-apis/apis}/staff-user.ts | 8 +- .../admin-apis/apis}/store.ts | 12 +- .../admin-apis/apis}/user.ts | 14 +- .../admin-apis/apis}/vendor-snippets.ts | 8 +- .../apis/admin-apis/dataAccessors/demo.txt | 0 .../common-apis/common-trpc-index.ts | 18 +- .../src/trpc/{ => apis}/common-apis/common.ts | 12 +- .../src/trpc/apis/user-apis/apis/address.ts | 0 .../src/trpc/apis/user-apis/apis/auth.ts | 0 .../src/trpc/apis/user-apis/apis/banners.ts | 0 .../src/trpc/apis/user-apis/apis/cart.ts | 0 .../src/trpc/apis/user-apis/apis/complaint.ts | 0 .../src/trpc/apis/user-apis/apis/coupon.ts | 0 .../trpc/apis/user-apis/apis/file-upload.ts | 0 .../src/trpc/apis/user-apis/apis/order.ts | 0 .../src/trpc/apis/user-apis/apis/payments.ts | 0 .../src/trpc/apis/user-apis/apis/product.ts | 0 .../src/trpc/apis/user-apis/apis/slots.ts | 0 .../src/trpc/apis/user-apis/apis/stores.ts | 0 .../src/trpc/apis/user-apis/apis/tags.ts | 0 .../apis/user-apis/apis/user-trpc-index.ts | 5 + .../src/trpc/apis/user-apis/apis/user.ts | 0 .../apis/user-apis/dataAccessors/demo.txt | 0 apps/backend/src/trpc/router.ts | 8 +- apps/backend/src/trpc/user-apis/address.ts | 194 ---- apps/backend/src/trpc/user-apis/auth.ts | 447 -------- apps/backend/src/trpc/user-apis/banners.ts | 38 - apps/backend/src/trpc/user-apis/cart.ts | 244 ----- apps/backend/src/trpc/user-apis/complaint.ts | 63 -- apps/backend/src/trpc/user-apis/coupon.ts | 296 ------ .../backend/src/trpc/user-apis/file-upload.ts | 55 - apps/backend/src/trpc/user-apis/order.ts | 989 ------------------ apps/backend/src/trpc/user-apis/payments.ts | 159 --- apps/backend/src/trpc/user-apis/product.ts | 265 ----- apps/backend/src/trpc/user-apis/slots.ts | 88 -- apps/backend/src/trpc/user-apis/stores.ts | 143 --- apps/backend/src/trpc/user-apis/tags.ts | 28 - .../src/trpc/user-apis/user-trpc-index.ts | 34 - apps/backend/src/trpc/user-apis/user.ts | 170 --- apps/backend/src/uv-apis/auth.controller.ts | 16 +- apps/backend/src/uv-apis/auth.router.ts | 6 +- .../src/uv-apis/user-rest.controller.ts | 10 +- apps/backend/src/uv-apis/uv-router.ts | 8 +- apps/backend/src/v1-router.ts | 6 +- apps/backend/tsconfig.json | 117 +-- progress.md | 9 - 106 files changed, 309 insertions(+), 3634 deletions(-) rename apps/backend/src/{admin-apis => apis/admin-apis/apis}/av-router.ts (60%) rename apps/backend/src/{admin-apis => apis/admin-apis/apis}/product-tags.controller.ts (94%) rename apps/backend/src/{admin-apis => apis/admin-apis/apis}/product.controller.ts (95%) rename apps/backend/src/{admin-apis => apis/admin-apis/apis}/product.router.ts (61%) rename apps/backend/src/{admin-apis => apis/admin-apis/apis}/tag.router.ts (76%) create mode 100644 apps/backend/src/apis/admin-apis/dataAccessors/demo.txt rename apps/backend/src/{common-apis => apis/common-apis/apis}/common-product.controller.ts (96%) rename apps/backend/src/{common-apis => apis/common-apis/apis}/common-product.router.ts (65%) rename apps/backend/src/{common-apis => apis/common-apis/apis}/common.router.ts (66%) create mode 100644 apps/backend/src/apis/common-apis/dataAccessors/demo.txt delete mode 100644 apps/backend/src/trpc/admin-apis/admin-trpc-index.ts rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/address.ts (86%) create mode 100644 apps/backend/src/trpc/apis/admin-apis/apis/admin-trpc-index.ts rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/banner.ts (94%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/cancelled-orders.ts (97%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/complaint.ts (91%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/const.ts (84%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/coupon.ts (99%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/order.ts (98%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/payments.ts (94%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/product.ts (96%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/slots.ts (97%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/staff-user.ts (97%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/store.ts (94%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/user.ts (97%) rename apps/backend/src/trpc/{admin-apis => apis/admin-apis/apis}/vendor-snippets.ts (98%) create mode 100644 apps/backend/src/trpc/apis/admin-apis/dataAccessors/demo.txt rename apps/backend/src/trpc/{ => apis}/common-apis/common-trpc-index.ts (88%) rename apps/backend/src/trpc/{ => apis}/common-apis/common.ts (92%) create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/address.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/auth.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/banners.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/cart.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/complaint.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/coupon.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/file-upload.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/order.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/payments.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/product.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/slots.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/stores.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/tags.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/user-trpc-index.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/apis/user.ts create mode 100644 apps/backend/src/trpc/apis/user-apis/dataAccessors/demo.txt delete mode 100644 apps/backend/src/trpc/user-apis/address.ts delete mode 100644 apps/backend/src/trpc/user-apis/auth.ts delete mode 100644 apps/backend/src/trpc/user-apis/banners.ts delete mode 100644 apps/backend/src/trpc/user-apis/cart.ts delete mode 100644 apps/backend/src/trpc/user-apis/complaint.ts delete mode 100644 apps/backend/src/trpc/user-apis/coupon.ts delete mode 100644 apps/backend/src/trpc/user-apis/file-upload.ts delete mode 100644 apps/backend/src/trpc/user-apis/order.ts delete mode 100644 apps/backend/src/trpc/user-apis/payments.ts delete mode 100644 apps/backend/src/trpc/user-apis/product.ts delete mode 100644 apps/backend/src/trpc/user-apis/slots.ts delete mode 100644 apps/backend/src/trpc/user-apis/stores.ts delete mode 100644 apps/backend/src/trpc/user-apis/tags.ts delete mode 100644 apps/backend/src/trpc/user-apis/user-trpc-index.ts delete mode 100644 apps/backend/src/trpc/user-apis/user.ts diff --git a/AGENTS.md b/AGENTS.md index 1b04b96..f5cca2c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,6 @@ # Agent Instructions for Meat Farmer Monorepo ## Important instructions -- Don't try to build the code or run or compile it. Just make changes and leave the rest for the user. -- Don't run any drizzle migrations. User will handle it. ## Code Style Guidelines @@ -47,7 +45,3 @@ react-native. They are available in the common-ui as MyText, MyTextInput, MyTouc - Apps: `user-ui`, `admin-ui`, `inspiration-ui`, `inspiration-backend` - Database: Drizzle ORM with PostgreSQL -## Important Notes -- **Do not run build, compile, or migration commands** - These should be handled manually by developers -- Avoid running `npm run build`, `tsc`, `drizzle-kit generate`, or similar compilation/migration commands -- Don't do anything with git. Don't do git add or git commit. That will be managed entirely by the user \ No newline at end of file diff --git a/apps/backend/assets/signed-url-cache.json b/apps/backend/assets/signed-url-cache.json index c0091eb..6639511 100644 --- a/apps/backend/assets/signed-url-cache.json +++ b/apps/backend/assets/signed-url-cache.json @@ -1 +1 @@ -{"originalToSigned":{"store-images/1770281046297.jpg":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1770281046297.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1afe9039f361ce70fcc3a68de32993e58b756ab08bd9d87d6e3d34fbb4148328&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366337},"tags/1763835253683-c9c3e293-0bef-4c58-a976-dd49c050cd36.jpeg":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1763835253683-c9c3e293-0bef-4c58-a976-dd49c050cd36.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=fac0e97b2338016ba72643c42de92a5e9cf5f0bfcf11dd2aeecf5dc0d34171ba&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366455},"store-images/1770429593455.jpg":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1770429593455.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=19df483133aa5f52ec0dda46d8af54d599414a25267ff4ef42e0dd8d53ea556a&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366580},"product-images/1768640403001-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768640403001-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=22ff3140d62afd7d78c1de47c43ea9561dc37c696ed942d6cda546daea56b4ba&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768653009851-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768653009851-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1ea3a780cfb67a292d1913fb4730ab4ceb658b3a1353d61ccb7ec28c7881789d&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768651774899-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768651774899-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=7eb5559007236039f2373e85a12ea3336bc84a12720c5dc7acd1e7da9fed02c0&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768646932088-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768646932088-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=b201180d952587fba2041db1ab7d248938ff471409bf637c3023351c3e8dec03&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768641635898-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768641635898-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=e3bfb85e5ef369bc869ffdb18ac16c92126f7f617030f0c88d23273f7ec6d7f4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768658094891-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768658094891-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=55e090998386c164fb3d40d30bac108bb881fb817f581c9a0cb8c1195faceb14&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768638795991-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768638795991-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=62079d0123da0cc99538e01d256ecaec20a46bc7c490e70109c8dc6cb3497844&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768647119198-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768647119198-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=5ca6bf1cd13d0fcc0c1847e53d0f88a3e22b41580a2546db1606d10002f5a459&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768641988607-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768641988607-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=d3941a2d3b5b2d250bb2fbd8df1c0bf0299ab78f59d19b6a1bbfce96018e0a83&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768643354251-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768643354251-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=b7ca2eace445623b1da7f9b155fa5685615717df6471af7714c076c602eaee16&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768646744949-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768646744949-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=673533173e374a6057fb78654f49030ea0869861efc4d6fcc6568cb05bac999c&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768646295695-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768646295695-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=87c8c3c9c2f583b7eafac0925082de94cdbafc996451df8e24131123375ac508&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768640645444-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768640645444-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4bdcca828f2d81b9c56b0be7fd6ff14e2bbd4fd478c4895e3d6ae07bd7055dbf&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768653997561-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768653997561-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=dcac82b1d3508324583e14ce1cb7b9006ac3258980ef822340b5db59977921be&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768650709484-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768650709484-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c78462779a019391cc2253f80cfab714d4c1466d830c81e471d67eb5c395b430&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768654475850-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768654475850-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=38e48c0c02ee28acea91ce06cd16df3293e600b62be43641717b54349b884d73&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768640009078-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768640009078-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=e7346c6079cec1e9aa443ea24f74467689ad50b5eb2d4c13b36c4434ab748615&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768642455479-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768642455479-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=77ccb358f02eda1e341306a33377d8c4e31ab95b0eddbe64fe6cbbe1cc4eac05&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768650081183-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768650081183-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=830c4b84713bc3f0af5a2d36516fd1ca147b811c6e720a8cb512ba2e9e395eff&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768651334320-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768651334320-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=b1e4bfecc10abfd57fedf0ad0a0201225331fdc2c1dfad05cae9e5477145aa08&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768654740988-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768654740988-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=fdcf19b3c730401c98c312e926ace19a7a4a93826488c745f28e27151935c993&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768642724520-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768642724520-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=5353efc7f6d4e381e4a3cb5738155f654eb65aa7640f665b6c809e33df67f4eb&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768640936747-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768640936747-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=34b70f89b1abf3c581118cc89effe858bd4214f6973378beb4efa54f80c23f24&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769576743718-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769576743718-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=a3aca0d129c327461fd6b2b1c163d3def4f202465e4ba6faf5da3b3a9f9f41a3&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768649454994-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768649454994-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=83865ab9ad30e2803ba642d948b5bc2731dc5901725eb72bd5464ec1c0732315&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768639705530-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768639705530-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ceef4980ddfef281c31aa271ac98f7137278ccea61e075531ba3e2899abd9aa7&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768639090932-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768639090932-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=d536c5eee29ef13fa9a9c487b82bf2e7b794ba8c0916695ff60fbc4b4a0f6dae&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768718685357-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768718685357-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=fdbfe6176d4b0eecc0c8630ca8d1c04bc3822dcb887858a78a62ffbc99a4dfd2&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768649838027-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768649838027-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=41cc73d90b50bfdbd2550b624edf87108b232e3650be221fb97e337d155697a1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768652061701-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768652061701-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4efbeb16f23b5998f5fc3ecaa71c2eb44604522a5615aa8e13ce1946469681b4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768643138910-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768643138910-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=15530ba2a02b75b4f0b082533ce9d53e1980b2fed50a0416cc4e3a577c8d1655&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1770212631435-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1770212631435-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=fbb426393c22d489aaa06634eb30b50d18eb100397c8009925a8c8236626ec71&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768832690994-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768832690994-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ee6bce96cb9172d5eb6d128d70dfd71fdca8b0a98d02fc25c057e5f889883b7e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769149204037-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769149204037-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=55339163d26219cd2519d811d86e32c8a0e6d3ab34465d71564b59b818ee07b2&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769063467386-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769063467386-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=18d4d07579696d11ac2ed288ce806e575abdc52eabaed2adc5254c24695e97d0&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768817786453-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768817786453-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4c5c9750575d706bb03d57ec68e000848172c7c2f2b17624de9c9b2d838bf05e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768817786455-1":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768817786455-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c50bc8f6ff6c5a63b163e2552d4572eff204ef421fc8cb4d6207dccb61f715bd&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769063660493-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769063660493-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=75f00f60ae50ade6842679a5865e933e2262d95351fdcfd23bee04177a6c894b&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768834996932-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768834996932-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=19951d815bd36fa683ceaddbb1924b43b509caa5a40601d3eb019c0c97a75572&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1771411526889-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1771411526889-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=5a32126e04ff78965fceeec9b25a810a2a2f5165cbca646042aa726e51f3ec97&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768710760133-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768710760133-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=03c31becedffaa2849585bad814e3c2968342163c8385dd41142e3fd11bcbcc3&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768639707515-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768639707515-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=7a5ecb2c24aa449e47fb73fd35142820703de74af5db24abb1b8061e8c437b32&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768713206145-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768713206145-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=3e43f4e1c6560a9b0137034788c355406393ed789422e511c097f473c5b930fd&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768639645423-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768639645423-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=19cd20af98e25ea68f7a06a3e4a45a18bac0441f3c544f017038b45c9d6e216f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768822893380-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768822893380-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4dc3dcbbceecb7581b96d0b56d62c8d609c879d58f9400b59a162f969c65c47c&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1766406273176-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1766406273176-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ac106fe61ae85e1f81d770e65cea6e711a1ef0cb0ab5430969e0af30e22066c8&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1766406273178-1":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1766406273178-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=d4f546c91e24b6de7e764312953a4a7935270648fa96ae0237bebb55ed2dcdb1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768710099841-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768710099841-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=803decbec1c2ca16ca93f1d1649f7dd1f1405decf3bc743ccaccad55c08c6b01&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768710434326-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768710434326-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c9f325ce1af6f6d9fb975a3f63386485a07ddf48f5cdd6a21aea6d56d9af934f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768639292540-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768639292540-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=3bcfe4299a2827519117d0c86842dd9e6db7ea3c9deeb0dc86575e5d67ece4e1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768640233068-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768640233068-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4242dc446b833310ddcb89c582828016e145f98b39396d707a82196b0f9f4904&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768713537512-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768713537512-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=71cce60b4e73bf010f116268ade2cceb0e71859d2bd2e7636c6ead10249a169f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1766636589779-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1766636589779-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=25ed7cedcbdab26129ad788de1a3e26bebef612fdc990bb6c1bc3f72b0733292&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768823985921-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768823985921-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1a8d451f64098c76394b6d130d76ce355f6aa0e0e21a43b7af311863337803b4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769241234003-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769241234003-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4a430d6dbf42d085f304e301f5462f848541ead11e588583532a938b97ba10ac&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768711261174-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768711261174-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=7911ac884fdb9abbb58b5f565bd821d7c0ce04bdb693caf95ef773c26751a7ed&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768726783083-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768726783083-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=05bb6a2de955559e877fd90d3cc5946ca43eabad442aa00b1b21817b929952d1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768712093829-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768712093829-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c35b059a1d4d8adff0a4a68d1c756593a683f734affbf8840a72fcfcb58a99a6&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768727264220-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768727264220-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c7a66902ee1bff1f9516a121f5a53409223ea4486bbc119e53fe2cb36a69c879&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768711919135-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768711919135-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4a5c195c92b3f4ac14ac833dd7903997557561fe711f0a0534ea1c8d6decfc22&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769356702593-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769356702593-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=0294dee84238e303590f6a8f1c62ce93e58f9a856042c74d59d860ced1d226d5&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769394457296-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769394457296-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=9762ceec436c4bb0b467827090a67e76182323a4c16e20227d0349d4659f6a15&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768709567040-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768709567040-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=b2d7d718e03cfb7128ae72c65b5ed07e831b059f65edba0a7267e23ad2a1eb39&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769396724595-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769396724595-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=beff46d37a490c876f8ce508451edbad12e464a020f221c15c616bbe52729894&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768713886512-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768713886512-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=f59babd14e553d9df7e81caca9c3fc81e0533670b539fdf775b66be4717741d7&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769397372526-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769397372526-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=85f1f67d8940353f933947d971d68869f9f82c38f3f8559e84d4d63176c44491&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769574792720-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769574792720-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ce6694fa782c6a01602c29afd4461b59ec6042d01211e289fba8eb89ddc75617&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769398103853-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769398103853-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=e480bf672bde2550ad5257205ca9ff5aedd39d3519e49e440b2d7861010e7c7f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769351976737-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769351976737-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=fe15b2fe5151eabf845def222d934b026bc3ba94226d402cac008629da528be8&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768712490280-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768712490280-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=38436fad38a100ed16dde7d89311342ea9bed03b1844d42707317adb89342cb8&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769398710713-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769398710713-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ed7dc21e8bc641ce14063597dcd96a0ee5598d6c2f5267029d3af9c16c8127b5&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769396046557-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769396046557-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4df8eb070918b867acd409055d1280fb99aa9317bf3c8cb78451e380bcf5e675&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769395184980-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769395184980-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=af279d603ad5e06c26639392608972be1470710045550630bd29bea4de6402d6&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769399617573-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769399617573-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=a688a5aa30ddac19532573578282ea6abc6e9a2ead4a7e3812dd79d6c63bc44c&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769571810861-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769571810861-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=5ccb50ccfc1247a77ab0454184c9635468a27526f8bd3d5fb69dec977ccc5f93&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769577010969-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769577010969-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c51836b98f61cccb559cc1c890907e20c73d8ffbba11082e1d649c6ce0937827&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769571978680-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769571978680-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=6c4e3d4d3f657170fd9e9831ffe3f50d372179ee98b63ee4db74270be26dfdef&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1771249922306-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1771249922306-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1c5a2a4d98150e156e7ac00464bd842cab27aa545b68e60b4a7386393d6964bd&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1771647812442-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1771647812442-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=834aa795bad44671bb7a4dc6621563c55ee00cce066ea4cbf6e5cc45b97153cc&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768834170995-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768834170995-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=9f2802242259f4330df40a89fda28880e28cbc6ae48c5430762935ddba267dd1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1767636561867-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1767636561867-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=158daf212f8255c9a838a066674b680f6af437e1b98d4e0fa5a3e13ac7fdd8ad&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769151216776-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769151216776-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1898e5394606a2393f354d3f2d41c659a9314a2078271d674b6f385a0811f8b4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1767636171845-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1767636171845-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=f693aa231fe13f2ac04e3f4168413a8c8b3030b7ef9ece21afcaaf2c444c9528&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1770051959669-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1770051959669-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=0e47673cc13fa2fc996cbb3b8b6d640c9e4caa57a5367c06ff0fe8ab0385c5f9&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1770051810386-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1770051810386-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=d6804624376f8b27159dd56da1bc290ba3e56f488866828254c4be9b5d62075b&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768475599800-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768475599800-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=0aea2472625965c4e464d54fb9780245955038beea492e872544b8d0b5672f62&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768390245969-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768390245969-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4691361cf32401e074053beb6bd8ac7b888c074a6abf52c97774dcd8b4a45e41&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768484106870-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768484106870-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=90bbbb25f4055b1f14e5393af00b4570f61388bca4fc6bb29d087c825aaee9ab&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1767637606890-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1767637606890-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=f3ff12d163a9d58e0aabb5b3f6c7ff738011206b3838fe6e31d765750e5e31b3&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768393778962-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768393778962-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=507e9100e921861a8a2f0da0dffe82beefa26f347ccd52d48f7b2cc38f95888d&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769063336125-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769063336125-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=8e1dc36bfacf06d11f1e608299dfa7394647e52c0afbc01324cfca750583dfe4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1767635527063-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1767635527063-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=605ee823795a36df29e6c4c9b6f265529b06369a3179168f81f782a4d56bae47&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768307047191-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768307047191-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=0758307523ffc725e04d412ec7bf04acf02998ab5c2352e1c488691de1afb8a8&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768539930897-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768539930897-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=3ba8d607be7e0de04682a774dd2523afa2988e7050d2e55ce17753a2db87a9e6&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768539930899-1":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768539930899-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=73779d31b73506ea79fe9a131c4ed0a8aaf876e47f299dd3bf054d217cbd804d&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768397780093-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768397780093-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=dd0b96e3d0159e9a863efde0baa0d40535d77336b64e473adf913de5a78496ec&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768479562478-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768479562478-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=85ff0374e5145f291274286ada2adf3b5f90ff57c005047cbd1c230bb24787c5&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768413754684-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768413754684-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=acc54a5b75eea8b6da6a88d5f5b3b884a0f6d1838fc4ec4c3acbafdc36adb590&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768478127128-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768478127128-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1961a5b79ac8bcd54d95dc2bb3c2d7bfe1c0fb37cce9e5a764ffec0dcaf3df09&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768478472220-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768478472220-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=452db93c01a8ef15ec6c3928c4eda349ebf9560b10d49042a97c5aedf71fe4dc&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768477813647-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768477813647-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=068775da0d0664f51c206e632da2203c63f0efc06f95925e12fb9d945c37d0f2&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1768477813648-1":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768477813648-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ebe921e9ab353b7ec0f53bb72f5cd21d779aba2551766b61dd00cead94da69b5&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"product-images/1769356039418-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769356039418-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ffd8869d991db6dbc77e4f85c5359131e9400cab2985e424dba2216c041aa7b3&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366780},"tags/1763835293899-43b3fbe1-9b5b-441c-b4d4-d1691c3f02f3.webp":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1763835293899-43b3fbe1-9b5b-441c-b4d4-d1691c3f02f3.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=19163d99c28d9168460ae7acce81ee0704cb11363d423b80b0a768fa2ca46d03&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824366786},"tags/1768709725124-ebf421c5-ad52-49a9-b65c-1de008110b8a.png":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1768709725124-ebf421c5-ad52-49a9-b65c-1de008110b8a.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191347Z&X-Amz-Expires=259200&X-Amz-Signature=f49e81af96cd7e8b2479b16308843540de8a84abd741c9a6499247e967e1e77e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824367426},"tags/1770321659633-1763869265110-e22b6d94-dac9-499f-babb-1e944d90b01a.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260205%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260205T195535Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3D917db15bcc60cab7ac5cd5e49d85d13a960fe77b4a5e327dd449048870494cf9%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770321659633-1763869265110-e22b6d94-dac9-499f-babb-1e944d90b01a.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260205%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260205T195535Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253D917db15bcc60cab7ac5cd5e49d85d13a960fe77b4a5e327dd449048870494cf9%2526X-Amz-SignedHeaders%253Dhost%2526x-amz-checksum-mode%253DENABLED%2526x-id%253DGetObject?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191348Z&X-Amz-Expires=259200&X-Amz-Signature=9752ae6754548661e6c602cfb0875aa01f3f4abf790f99899840a98620f438f4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824368652},"tags/1770323410499-1763869436182-bf82f7b4-a1f3-4113-985b-96311b7a910e.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260205%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260205T202804Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3Dea436390b277935d843cae6b5cfa62aeed5799cb4a962ab31a0be4b132ca4b30%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770323410499-1763869436182-bf82f7b4-a1f3-4113-985b-96311b7a910e.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260205%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260205T202804Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253Dea436390b277935d843cae6b5cfa62aeed5799cb4a962ab31a0be4b132ca4b30%2526X-Amz-SignedHeaders%253Dhost%2526x-amz-checksum-mode%253DENABLED%2526x-id%253DGetObject?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191349Z&X-Amz-Expires=259200&X-Amz-Signature=a01a73bb0535284e3206085dd02c5d2d85cfee33d786936d60ad3d0b5ac9b4ce&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824369468},"tags/1770323560823-fd0ec463-bed0-474e-aa14-dc6480ce36af.jpeg":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770323560823-fd0ec463-bed0-474e-aa14-dc6480ce36af.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191350Z&X-Amz-Expires=259200&X-Amz-Signature=6ee19f7224fa753b7b6d95b36385f4c4b8ac9ab946287828ae5276e21031e121&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824370107},"product-images/1769149707440-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769149707440-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191356Z&X-Amz-Expires=259200&X-Amz-Signature=829d1ace553b80c12ca0c1af158af7c5a9935ae8bdce7dd32c0ec4c97b062980&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824376846},"product-images/1769151844424-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769151844424-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191359Z&X-Amz-Expires=259200&X-Amz-Signature=bfd8ed947c20b20355e242b35ff767117adbf656be3d864a27c7bf5cb712eef4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824379340},"product-images/1769353802270-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769353802270-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191401Z&X-Amz-Expires=259200&X-Amz-Signature=e329a998b308f5a426f853545e0889dd85c501591f0d9be90e50e2e2c3f946cc&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824381392},"product-images/1764500702143-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1764500702143-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191403Z&X-Amz-Expires=259200&X-Amz-Signature=a52f20c4901c80301abbe23d718c923f02a28f03b42370068594541d1a8ce046&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824383049},"product-images/1764500702144-1":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1764500702144-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191403Z&X-Amz-Expires=259200&X-Amz-Signature=bb7320a492a9a6edf0ff17cfc1e440af5c77797349b5f9e671300fe3b3ca8218&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824383049},"product-images/1769150471654-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769150471654-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191405Z&X-Amz-Expires=259200&X-Amz-Signature=ab20e1546cf02b25edf05cedfbb60c26e43b240b857b37b8a1ca9143b563b727&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824385699},"product-images/1769150471655-1":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769150471655-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191405Z&X-Amz-Expires=259200&X-Amz-Signature=a1c61968b958c6d1e2e8cb95c0b2b9b5de5f121dfe6ededd743af50664624935&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824385699},"product-images/1769571126092-0":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769571126092-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191407Z&X-Amz-Expires=259200&X-Amz-Signature=813ef83d697135fdfbcd485163ba5c5c55d73ab96db2070eb1cd1963151c98e2&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772824387845},"profile-images/1766160314135-1000000018.jpg":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/profile-images/1766160314135-1000000018.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131847Z&X-Amz-Expires=259200&X-Amz-Signature=80f34e33b4b221089a4112daee9e78dfd248da67256af197b2d5cb1281cef630&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772889467117},"store-images/1766052073748.png":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766052073748.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=0c5171b3f9c8b125df68ca2b6deae2caf0779b0f556200d52a40ece0e35e8ded&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772889470671},"store-images/1766051618139.png":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766051618139.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=28ccd00f0782d5891077b2d65912c0f81b2bbb6db7eaa6d9aa8c198d78e2f51f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772889470671},"store-images/1766053828604.png":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766053828604.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=88e8085143f3ba11a2f48ae86916f26a7e745c4fe932601e775147a7f40ba4d1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772889470671}},"signedToOriginal":{"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1770281046297.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1afe9039f361ce70fcc3a68de32993e58b756ab08bd9d87d6e3d34fbb4148328&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1770281046297.jpg","expiresAt":1772824366337},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1763835253683-c9c3e293-0bef-4c58-a976-dd49c050cd36.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=fac0e97b2338016ba72643c42de92a5e9cf5f0bfcf11dd2aeecf5dc0d34171ba&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1763835253683-c9c3e293-0bef-4c58-a976-dd49c050cd36.jpeg","expiresAt":1772824366455},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1770429593455.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=19df483133aa5f52ec0dda46d8af54d599414a25267ff4ef42e0dd8d53ea556a&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1770429593455.jpg","expiresAt":1772824366580},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768640403001-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=22ff3140d62afd7d78c1de47c43ea9561dc37c696ed942d6cda546daea56b4ba&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768640403001-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768653009851-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1ea3a780cfb67a292d1913fb4730ab4ceb658b3a1353d61ccb7ec28c7881789d&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768653009851-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768651774899-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=7eb5559007236039f2373e85a12ea3336bc84a12720c5dc7acd1e7da9fed02c0&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768651774899-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768646932088-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=b201180d952587fba2041db1ab7d248938ff471409bf637c3023351c3e8dec03&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768646932088-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768641635898-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=e3bfb85e5ef369bc869ffdb18ac16c92126f7f617030f0c88d23273f7ec6d7f4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768641635898-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768658094891-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=55e090998386c164fb3d40d30bac108bb881fb817f581c9a0cb8c1195faceb14&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768658094891-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768638795991-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=62079d0123da0cc99538e01d256ecaec20a46bc7c490e70109c8dc6cb3497844&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768638795991-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768647119198-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=5ca6bf1cd13d0fcc0c1847e53d0f88a3e22b41580a2546db1606d10002f5a459&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768647119198-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768641988607-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=d3941a2d3b5b2d250bb2fbd8df1c0bf0299ab78f59d19b6a1bbfce96018e0a83&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768641988607-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768643354251-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=b7ca2eace445623b1da7f9b155fa5685615717df6471af7714c076c602eaee16&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768643354251-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768646744949-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=673533173e374a6057fb78654f49030ea0869861efc4d6fcc6568cb05bac999c&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768646744949-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768646295695-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=87c8c3c9c2f583b7eafac0925082de94cdbafc996451df8e24131123375ac508&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768646295695-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768640645444-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4bdcca828f2d81b9c56b0be7fd6ff14e2bbd4fd478c4895e3d6ae07bd7055dbf&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768640645444-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768653997561-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=dcac82b1d3508324583e14ce1cb7b9006ac3258980ef822340b5db59977921be&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768653997561-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768650709484-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c78462779a019391cc2253f80cfab714d4c1466d830c81e471d67eb5c395b430&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768650709484-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768654475850-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=38e48c0c02ee28acea91ce06cd16df3293e600b62be43641717b54349b884d73&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768654475850-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768640009078-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=e7346c6079cec1e9aa443ea24f74467689ad50b5eb2d4c13b36c4434ab748615&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768640009078-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768642455479-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=77ccb358f02eda1e341306a33377d8c4e31ab95b0eddbe64fe6cbbe1cc4eac05&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768642455479-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768650081183-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=830c4b84713bc3f0af5a2d36516fd1ca147b811c6e720a8cb512ba2e9e395eff&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768650081183-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768651334320-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=b1e4bfecc10abfd57fedf0ad0a0201225331fdc2c1dfad05cae9e5477145aa08&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768651334320-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768654740988-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=fdcf19b3c730401c98c312e926ace19a7a4a93826488c745f28e27151935c993&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768654740988-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768642724520-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=5353efc7f6d4e381e4a3cb5738155f654eb65aa7640f665b6c809e33df67f4eb&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768642724520-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768640936747-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=34b70f89b1abf3c581118cc89effe858bd4214f6973378beb4efa54f80c23f24&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768640936747-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769576743718-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=a3aca0d129c327461fd6b2b1c163d3def4f202465e4ba6faf5da3b3a9f9f41a3&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769576743718-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768649454994-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=83865ab9ad30e2803ba642d948b5bc2731dc5901725eb72bd5464ec1c0732315&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768649454994-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768639705530-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ceef4980ddfef281c31aa271ac98f7137278ccea61e075531ba3e2899abd9aa7&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768639705530-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768639090932-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=d536c5eee29ef13fa9a9c487b82bf2e7b794ba8c0916695ff60fbc4b4a0f6dae&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768639090932-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768718685357-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=fdbfe6176d4b0eecc0c8630ca8d1c04bc3822dcb887858a78a62ffbc99a4dfd2&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768718685357-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768649838027-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=41cc73d90b50bfdbd2550b624edf87108b232e3650be221fb97e337d155697a1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768649838027-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768652061701-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4efbeb16f23b5998f5fc3ecaa71c2eb44604522a5615aa8e13ce1946469681b4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768652061701-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768643138910-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=15530ba2a02b75b4f0b082533ce9d53e1980b2fed50a0416cc4e3a577c8d1655&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768643138910-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1770212631435-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=fbb426393c22d489aaa06634eb30b50d18eb100397c8009925a8c8236626ec71&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1770212631435-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768832690994-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ee6bce96cb9172d5eb6d128d70dfd71fdca8b0a98d02fc25c057e5f889883b7e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768832690994-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769149204037-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=55339163d26219cd2519d811d86e32c8a0e6d3ab34465d71564b59b818ee07b2&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769149204037-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769063467386-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=18d4d07579696d11ac2ed288ce806e575abdc52eabaed2adc5254c24695e97d0&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769063467386-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768817786453-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4c5c9750575d706bb03d57ec68e000848172c7c2f2b17624de9c9b2d838bf05e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768817786453-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768817786455-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c50bc8f6ff6c5a63b163e2552d4572eff204ef421fc8cb4d6207dccb61f715bd&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768817786455-1","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769063660493-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=75f00f60ae50ade6842679a5865e933e2262d95351fdcfd23bee04177a6c894b&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769063660493-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768834996932-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=19951d815bd36fa683ceaddbb1924b43b509caa5a40601d3eb019c0c97a75572&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768834996932-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1771411526889-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=5a32126e04ff78965fceeec9b25a810a2a2f5165cbca646042aa726e51f3ec97&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1771411526889-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768710760133-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=03c31becedffaa2849585bad814e3c2968342163c8385dd41142e3fd11bcbcc3&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768710760133-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768639707515-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=7a5ecb2c24aa449e47fb73fd35142820703de74af5db24abb1b8061e8c437b32&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768639707515-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768713206145-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=3e43f4e1c6560a9b0137034788c355406393ed789422e511c097f473c5b930fd&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768713206145-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768639645423-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=19cd20af98e25ea68f7a06a3e4a45a18bac0441f3c544f017038b45c9d6e216f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768639645423-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768822893380-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4dc3dcbbceecb7581b96d0b56d62c8d609c879d58f9400b59a162f969c65c47c&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768822893380-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1766406273176-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ac106fe61ae85e1f81d770e65cea6e711a1ef0cb0ab5430969e0af30e22066c8&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1766406273176-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1766406273178-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=d4f546c91e24b6de7e764312953a4a7935270648fa96ae0237bebb55ed2dcdb1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1766406273178-1","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768710099841-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=803decbec1c2ca16ca93f1d1649f7dd1f1405decf3bc743ccaccad55c08c6b01&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768710099841-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768710434326-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c9f325ce1af6f6d9fb975a3f63386485a07ddf48f5cdd6a21aea6d56d9af934f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768710434326-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768639292540-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=3bcfe4299a2827519117d0c86842dd9e6db7ea3c9deeb0dc86575e5d67ece4e1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768639292540-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768640233068-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4242dc446b833310ddcb89c582828016e145f98b39396d707a82196b0f9f4904&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768640233068-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768713537512-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=71cce60b4e73bf010f116268ade2cceb0e71859d2bd2e7636c6ead10249a169f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768713537512-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1766636589779-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=25ed7cedcbdab26129ad788de1a3e26bebef612fdc990bb6c1bc3f72b0733292&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1766636589779-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768823985921-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1a8d451f64098c76394b6d130d76ce355f6aa0e0e21a43b7af311863337803b4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768823985921-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769241234003-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4a430d6dbf42d085f304e301f5462f848541ead11e588583532a938b97ba10ac&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769241234003-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768711261174-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=7911ac884fdb9abbb58b5f565bd821d7c0ce04bdb693caf95ef773c26751a7ed&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768711261174-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768726783083-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=05bb6a2de955559e877fd90d3cc5946ca43eabad442aa00b1b21817b929952d1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768726783083-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768712093829-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c35b059a1d4d8adff0a4a68d1c756593a683f734affbf8840a72fcfcb58a99a6&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768712093829-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768727264220-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c7a66902ee1bff1f9516a121f5a53409223ea4486bbc119e53fe2cb36a69c879&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768727264220-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768711919135-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4a5c195c92b3f4ac14ac833dd7903997557561fe711f0a0534ea1c8d6decfc22&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768711919135-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769356702593-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=0294dee84238e303590f6a8f1c62ce93e58f9a856042c74d59d860ced1d226d5&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769356702593-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769394457296-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=9762ceec436c4bb0b467827090a67e76182323a4c16e20227d0349d4659f6a15&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769394457296-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768709567040-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=b2d7d718e03cfb7128ae72c65b5ed07e831b059f65edba0a7267e23ad2a1eb39&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768709567040-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769396724595-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=beff46d37a490c876f8ce508451edbad12e464a020f221c15c616bbe52729894&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769396724595-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768713886512-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=f59babd14e553d9df7e81caca9c3fc81e0533670b539fdf775b66be4717741d7&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768713886512-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769397372526-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=85f1f67d8940353f933947d971d68869f9f82c38f3f8559e84d4d63176c44491&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769397372526-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769574792720-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ce6694fa782c6a01602c29afd4461b59ec6042d01211e289fba8eb89ddc75617&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769574792720-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769398103853-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=e480bf672bde2550ad5257205ca9ff5aedd39d3519e49e440b2d7861010e7c7f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769398103853-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769351976737-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=fe15b2fe5151eabf845def222d934b026bc3ba94226d402cac008629da528be8&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769351976737-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768712490280-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=38436fad38a100ed16dde7d89311342ea9bed03b1844d42707317adb89342cb8&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768712490280-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769398710713-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ed7dc21e8bc641ce14063597dcd96a0ee5598d6c2f5267029d3af9c16c8127b5&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769398710713-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769396046557-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4df8eb070918b867acd409055d1280fb99aa9317bf3c8cb78451e380bcf5e675&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769396046557-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769395184980-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=af279d603ad5e06c26639392608972be1470710045550630bd29bea4de6402d6&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769395184980-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769399617573-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=a688a5aa30ddac19532573578282ea6abc6e9a2ead4a7e3812dd79d6c63bc44c&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769399617573-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769571810861-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=5ccb50ccfc1247a77ab0454184c9635468a27526f8bd3d5fb69dec977ccc5f93&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769571810861-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769577010969-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=c51836b98f61cccb559cc1c890907e20c73d8ffbba11082e1d649c6ce0937827&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769577010969-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769571978680-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=6c4e3d4d3f657170fd9e9831ffe3f50d372179ee98b63ee4db74270be26dfdef&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769571978680-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1771249922306-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1c5a2a4d98150e156e7ac00464bd842cab27aa545b68e60b4a7386393d6964bd&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1771249922306-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1771647812442-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=834aa795bad44671bb7a4dc6621563c55ee00cce066ea4cbf6e5cc45b97153cc&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1771647812442-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768834170995-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=9f2802242259f4330df40a89fda28880e28cbc6ae48c5430762935ddba267dd1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768834170995-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1767636561867-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=158daf212f8255c9a838a066674b680f6af437e1b98d4e0fa5a3e13ac7fdd8ad&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1767636561867-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769151216776-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1898e5394606a2393f354d3f2d41c659a9314a2078271d674b6f385a0811f8b4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769151216776-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1767636171845-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=f693aa231fe13f2ac04e3f4168413a8c8b3030b7ef9ece21afcaaf2c444c9528&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1767636171845-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1770051959669-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=0e47673cc13fa2fc996cbb3b8b6d640c9e4caa57a5367c06ff0fe8ab0385c5f9&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1770051959669-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1770051810386-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=d6804624376f8b27159dd56da1bc290ba3e56f488866828254c4be9b5d62075b&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1770051810386-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768475599800-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=0aea2472625965c4e464d54fb9780245955038beea492e872544b8d0b5672f62&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768475599800-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768390245969-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=4691361cf32401e074053beb6bd8ac7b888c074a6abf52c97774dcd8b4a45e41&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768390245969-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768484106870-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=90bbbb25f4055b1f14e5393af00b4570f61388bca4fc6bb29d087c825aaee9ab&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768484106870-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1767637606890-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=f3ff12d163a9d58e0aabb5b3f6c7ff738011206b3838fe6e31d765750e5e31b3&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1767637606890-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768393778962-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=507e9100e921861a8a2f0da0dffe82beefa26f347ccd52d48f7b2cc38f95888d&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768393778962-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769063336125-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=8e1dc36bfacf06d11f1e608299dfa7394647e52c0afbc01324cfca750583dfe4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769063336125-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1767635527063-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=605ee823795a36df29e6c4c9b6f265529b06369a3179168f81f782a4d56bae47&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1767635527063-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768307047191-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=0758307523ffc725e04d412ec7bf04acf02998ab5c2352e1c488691de1afb8a8&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768307047191-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768539930897-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=3ba8d607be7e0de04682a774dd2523afa2988e7050d2e55ce17753a2db87a9e6&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768539930897-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768539930899-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=73779d31b73506ea79fe9a131c4ed0a8aaf876e47f299dd3bf054d217cbd804d&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768539930899-1","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768397780093-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=dd0b96e3d0159e9a863efde0baa0d40535d77336b64e473adf913de5a78496ec&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768397780093-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768479562478-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=85ff0374e5145f291274286ada2adf3b5f90ff57c005047cbd1c230bb24787c5&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768479562478-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768413754684-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=acc54a5b75eea8b6da6a88d5f5b3b884a0f6d1838fc4ec4c3acbafdc36adb590&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768413754684-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768478127128-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=1961a5b79ac8bcd54d95dc2bb3c2d7bfe1c0fb37cce9e5a764ffec0dcaf3df09&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768478127128-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768478472220-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=452db93c01a8ef15ec6c3928c4eda349ebf9560b10d49042a97c5aedf71fe4dc&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768478472220-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768477813647-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=068775da0d0664f51c206e632da2203c63f0efc06f95925e12fb9d945c37d0f2&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768477813647-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1768477813648-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ebe921e9ab353b7ec0f53bb72f5cd21d779aba2551766b61dd00cead94da69b5&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1768477813648-1","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769356039418-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=ffd8869d991db6dbc77e4f85c5359131e9400cab2985e424dba2216c041aa7b3&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769356039418-0","expiresAt":1772824366780},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1763835293899-43b3fbe1-9b5b-441c-b4d4-d1691c3f02f3.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191346Z&X-Amz-Expires=259200&X-Amz-Signature=19163d99c28d9168460ae7acce81ee0704cb11363d423b80b0a768fa2ca46d03&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1763835293899-43b3fbe1-9b5b-441c-b4d4-d1691c3f02f3.webp","expiresAt":1772824366786},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1768709725124-ebf421c5-ad52-49a9-b65c-1de008110b8a.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191347Z&X-Amz-Expires=259200&X-Amz-Signature=f49e81af96cd7e8b2479b16308843540de8a84abd741c9a6499247e967e1e77e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1768709725124-ebf421c5-ad52-49a9-b65c-1de008110b8a.png","expiresAt":1772824367426},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770321659633-1763869265110-e22b6d94-dac9-499f-babb-1e944d90b01a.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260205%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260205T195535Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253D917db15bcc60cab7ac5cd5e49d85d13a960fe77b4a5e327dd449048870494cf9%2526X-Amz-SignedHeaders%253Dhost%2526x-amz-checksum-mode%253DENABLED%2526x-id%253DGetObject?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191348Z&X-Amz-Expires=259200&X-Amz-Signature=9752ae6754548661e6c602cfb0875aa01f3f4abf790f99899840a98620f438f4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1770321659633-1763869265110-e22b6d94-dac9-499f-babb-1e944d90b01a.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260205%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260205T195535Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3D917db15bcc60cab7ac5cd5e49d85d13a960fe77b4a5e327dd449048870494cf9%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject","expiresAt":1772824368652},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770323410499-1763869436182-bf82f7b4-a1f3-4113-985b-96311b7a910e.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260205%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260205T202804Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253Dea436390b277935d843cae6b5cfa62aeed5799cb4a962ab31a0be4b132ca4b30%2526X-Amz-SignedHeaders%253Dhost%2526x-amz-checksum-mode%253DENABLED%2526x-id%253DGetObject?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191349Z&X-Amz-Expires=259200&X-Amz-Signature=a01a73bb0535284e3206085dd02c5d2d85cfee33d786936d60ad3d0b5ac9b4ce&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1770323410499-1763869436182-bf82f7b4-a1f3-4113-985b-96311b7a910e.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260205%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260205T202804Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3Dea436390b277935d843cae6b5cfa62aeed5799cb4a962ab31a0be4b132ca4b30%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject","expiresAt":1772824369468},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770323560823-fd0ec463-bed0-474e-aa14-dc6480ce36af.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191350Z&X-Amz-Expires=259200&X-Amz-Signature=6ee19f7224fa753b7b6d95b36385f4c4b8ac9ab946287828ae5276e21031e121&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1770323560823-fd0ec463-bed0-474e-aa14-dc6480ce36af.jpeg","expiresAt":1772824370107},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769149707440-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191356Z&X-Amz-Expires=259200&X-Amz-Signature=829d1ace553b80c12ca0c1af158af7c5a9935ae8bdce7dd32c0ec4c97b062980&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769149707440-0","expiresAt":1772824376846},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769151844424-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191359Z&X-Amz-Expires=259200&X-Amz-Signature=bfd8ed947c20b20355e242b35ff767117adbf656be3d864a27c7bf5cb712eef4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769151844424-0","expiresAt":1772824379340},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769353802270-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191401Z&X-Amz-Expires=259200&X-Amz-Signature=e329a998b308f5a426f853545e0889dd85c501591f0d9be90e50e2e2c3f946cc&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769353802270-0","expiresAt":1772824381392},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1764500702143-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191403Z&X-Amz-Expires=259200&X-Amz-Signature=a52f20c4901c80301abbe23d718c923f02a28f03b42370068594541d1a8ce046&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1764500702143-0","expiresAt":1772824383049},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1764500702144-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191403Z&X-Amz-Expires=259200&X-Amz-Signature=bb7320a492a9a6edf0ff17cfc1e440af5c77797349b5f9e671300fe3b3ca8218&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1764500702144-1","expiresAt":1772824383049},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769150471654-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191405Z&X-Amz-Expires=259200&X-Amz-Signature=ab20e1546cf02b25edf05cedfbb60c26e43b240b857b37b8a1ca9143b563b727&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769150471654-0","expiresAt":1772824385699},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769150471655-1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191405Z&X-Amz-Expires=259200&X-Amz-Signature=a1c61968b958c6d1e2e8cb95c0b2b9b5de5f121dfe6ededd743af50664624935&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769150471655-1","expiresAt":1772824385699},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/product-images/1769571126092-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260303%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260303T191407Z&X-Amz-Expires=259200&X-Amz-Signature=813ef83d697135fdfbcd485163ba5c5c55d73ab96db2070eb1cd1963151c98e2&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"product-images/1769571126092-0","expiresAt":1772824387845},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/profile-images/1766160314135-1000000018.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131847Z&X-Amz-Expires=259200&X-Amz-Signature=80f34e33b4b221089a4112daee9e78dfd248da67256af197b2d5cb1281cef630&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"profile-images/1766160314135-1000000018.jpg","expiresAt":1772889467117},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766052073748.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=0c5171b3f9c8b125df68ca2b6deae2caf0779b0f556200d52a40ece0e35e8ded&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1766052073748.png","expiresAt":1772889470671},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766051618139.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=28ccd00f0782d5891077b2d65912c0f81b2bbb6db7eaa6d9aa8c198d78e2f51f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1766051618139.png","expiresAt":1772889470671},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766053828604.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=88e8085143f3ba11a2f48ae86916f26a7e745c4fe932601e775147a7f40ba4d1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1766053828604.png","expiresAt":1772889470671}}} \ No newline at end of file +{"originalToSigned":{"profile-images/1766160314135-1000000018.jpg":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/profile-images/1766160314135-1000000018.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131847Z&X-Amz-Expires=259200&X-Amz-Signature=80f34e33b4b221089a4112daee9e78dfd248da67256af197b2d5cb1281cef630&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772889467117},"store-images/1766052073748.png":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766052073748.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=0c5171b3f9c8b125df68ca2b6deae2caf0779b0f556200d52a40ece0e35e8ded&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772889470671},"store-images/1766051618139.png":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766051618139.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=28ccd00f0782d5891077b2d65912c0f81b2bbb6db7eaa6d9aa8c198d78e2f51f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772889470671},"store-images/1766053828604.png":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766053828604.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=88e8085143f3ba11a2f48ae86916f26a7e745c4fe932601e775147a7f40ba4d1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1772889470671},"tags/1763835253683-c9c3e293-0bef-4c58-a976-dd49c050cd36.jpeg":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1763835253683-c9c3e293-0bef-4c58-a976-dd49c050cd36.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105258Z&X-Amz-Expires=259200&X-Amz-Signature=a4e4226aa30b5f0fde27efca0f139c00df5d3e13d08b104c4aee12afda1f7498&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773139918864},"tags/1763835293899-43b3fbe1-9b5b-441c-b4d4-d1691c3f02f3.webp":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1763835293899-43b3fbe1-9b5b-441c-b4d4-d1691c3f02f3.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105259Z&X-Amz-Expires=259200&X-Amz-Signature=bf52ada07a10aef2f03249480efc7c918389e8711c31dec38c7bae4b922ddfd6&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773139919079},"tags/1768709725124-ebf421c5-ad52-49a9-b65c-1de008110b8a.png":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1768709725124-ebf421c5-ad52-49a9-b65c-1de008110b8a.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105259Z&X-Amz-Expires=259200&X-Amz-Signature=3e175d60bbaba792d9dd5d60d4ebad6ad395e9f5f7a00f469e6d74dccec68d59&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773139919523},"tags/1770321659633-1763869265110-e22b6d94-dac9-499f-babb-1e944d90b01a.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260205%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260205T195535Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3D917db15bcc60cab7ac5cd5e49d85d13a960fe77b4a5e327dd449048870494cf9%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770321659633-1763869265110-e22b6d94-dac9-499f-babb-1e944d90b01a.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260205%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260205T195535Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253D917db15bcc60cab7ac5cd5e49d85d13a960fe77b4a5e327dd449048870494cf9%2526X-Amz-SignedHeaders%253Dhost%2526x-amz-checksum-mode%253DENABLED%2526x-id%253DGetObject?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105259Z&X-Amz-Expires=259200&X-Amz-Signature=5cbc2e1c88c8df749d97c59dfec911cbacc58502fd38e97723ee3627e8934d6e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773139919790},"tags/1770323410499-1763869436182-bf82f7b4-a1f3-4113-985b-96311b7a910e.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260205%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260205T202804Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3Dea436390b277935d843cae6b5cfa62aeed5799cb4a962ab31a0be4b132ca4b30%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770323410499-1763869436182-bf82f7b4-a1f3-4113-985b-96311b7a910e.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260205%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260205T202804Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253Dea436390b277935d843cae6b5cfa62aeed5799cb4a962ab31a0be4b132ca4b30%2526X-Amz-SignedHeaders%253Dhost%2526x-amz-checksum-mode%253DENABLED%2526x-id%253DGetObject?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105300Z&X-Amz-Expires=259200&X-Amz-Signature=aa8adbc434acc3f5c2b8f7a43d300a56df641e21ebefe30510b7e5edbac3e36f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773139920026},"tags/1770323560823-fd0ec463-bed0-474e-aa14-dc6480ce36af.jpeg":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770323560823-fd0ec463-bed0-474e-aa14-dc6480ce36af.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105300Z&X-Amz-Expires=259200&X-Amz-Signature=5064525b43e9119a4ec0c19ca134b84697a814c54121f4bca6d2b28582c2fe01&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773139920248}},"signedToOriginal":{"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/profile-images/1766160314135-1000000018.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131847Z&X-Amz-Expires=259200&X-Amz-Signature=80f34e33b4b221089a4112daee9e78dfd248da67256af197b2d5cb1281cef630&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"profile-images/1766160314135-1000000018.jpg","expiresAt":1772889467117},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766052073748.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=0c5171b3f9c8b125df68ca2b6deae2caf0779b0f556200d52a40ece0e35e8ded&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1766052073748.png","expiresAt":1772889470671},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766051618139.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=28ccd00f0782d5891077b2d65912c0f81b2bbb6db7eaa6d9aa8c198d78e2f51f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1766051618139.png","expiresAt":1772889470671},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/store-images/1766053828604.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260304%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260304T131850Z&X-Amz-Expires=259200&X-Amz-Signature=88e8085143f3ba11a2f48ae86916f26a7e745c4fe932601e775147a7f40ba4d1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1766053828604.png","expiresAt":1772889470671},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1763835253683-c9c3e293-0bef-4c58-a976-dd49c050cd36.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105258Z&X-Amz-Expires=259200&X-Amz-Signature=a4e4226aa30b5f0fde27efca0f139c00df5d3e13d08b104c4aee12afda1f7498&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1763835253683-c9c3e293-0bef-4c58-a976-dd49c050cd36.jpeg","expiresAt":1773139918864},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1763835293899-43b3fbe1-9b5b-441c-b4d4-d1691c3f02f3.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105259Z&X-Amz-Expires=259200&X-Amz-Signature=bf52ada07a10aef2f03249480efc7c918389e8711c31dec38c7bae4b922ddfd6&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1763835293899-43b3fbe1-9b5b-441c-b4d4-d1691c3f02f3.webp","expiresAt":1773139919079},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1768709725124-ebf421c5-ad52-49a9-b65c-1de008110b8a.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105259Z&X-Amz-Expires=259200&X-Amz-Signature=3e175d60bbaba792d9dd5d60d4ebad6ad395e9f5f7a00f469e6d74dccec68d59&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1768709725124-ebf421c5-ad52-49a9-b65c-1de008110b8a.png","expiresAt":1773139919523},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770321659633-1763869265110-e22b6d94-dac9-499f-babb-1e944d90b01a.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260205%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260205T195535Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253D917db15bcc60cab7ac5cd5e49d85d13a960fe77b4a5e327dd449048870494cf9%2526X-Amz-SignedHeaders%253Dhost%2526x-amz-checksum-mode%253DENABLED%2526x-id%253DGetObject?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105259Z&X-Amz-Expires=259200&X-Amz-Signature=5cbc2e1c88c8df749d97c59dfec911cbacc58502fd38e97723ee3627e8934d6e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1770321659633-1763869265110-e22b6d94-dac9-499f-babb-1e944d90b01a.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260205%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260205T195535Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3D917db15bcc60cab7ac5cd5e49d85d13a960fe77b4a5e327dd449048870494cf9%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject","expiresAt":1773139919790},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770323410499-1763869436182-bf82f7b4-a1f3-4113-985b-96311b7a910e.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260205%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260205T202804Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253Dea436390b277935d843cae6b5cfa62aeed5799cb4a962ab31a0be4b132ca4b30%2526X-Amz-SignedHeaders%253Dhost%2526x-amz-checksum-mode%253DENABLED%2526x-id%253DGetObject?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105300Z&X-Amz-Expires=259200&X-Amz-Signature=aa8adbc434acc3f5c2b8f7a43d300a56df641e21ebefe30510b7e5edbac3e36f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1770323410499-1763869436182-bf82f7b4-a1f3-4113-985b-96311b7a910e.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260205%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260205T202804Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3Dea436390b277935d843cae6b5cfa62aeed5799cb4a962ab31a0be4b132ca4b30%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject","expiresAt":1773139920026},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1770323560823-fd0ec463-bed0-474e-aa14-dc6480ce36af.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=8fab47503efb9547b50e4fb317e35cc7%2F20260307%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260307T105300Z&X-Amz-Expires=259200&X-Amz-Signature=5064525b43e9119a4ec0c19ca134b84697a814c54121f4bca6d2b28582c2fe01&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1770323560823-fd0ec463-bed0-474e-aa14-dc6480ce36af.jpeg","expiresAt":1773139920248}}} \ No newline at end of file diff --git a/apps/backend/index.ts b/apps/backend/index.ts index 5f695b3..b450036 100755 --- a/apps/backend/index.ts +++ b/apps/backend/index.ts @@ -5,19 +5,19 @@ import cors from "cors"; import multer from "multer"; import path from "path"; import fs from "fs"; -import { db } from './src/db/db_index'; -import { staffUsers, userDetails } from './src/db/schema'; +import { db } from '@/src/db/db_index'; +import { staffUsers, userDetails } from '@/src/db/schema'; import { eq } from 'drizzle-orm'; -import mainRouter from './src/main-router'; -import initFunc from './src/lib/init'; +import mainRouter from '@/src/main-router'; +import initFunc from '@/src/lib/init'; import { createExpressMiddleware } from '@trpc/server/adapters/express'; -import { appRouter } from './src/trpc/router'; +import { appRouter } from '@/src/trpc/router'; import { TRPCError } from '@trpc/server'; import jwt from 'jsonwebtoken' -import signedUrlCache from 'src/lib/signed-url-cache'; -import { seed } from 'src/db/seed'; -import './src/jobs/jobs-index'; -import { startAutomatedJobs } from './src/lib/automatedJobs'; +import signedUrlCache from '@/src/lib/signed-url-cache'; +import { seed } from '@/src/db/seed'; +import '@/src/jobs/jobs-index'; +import { startAutomatedJobs } from '@/src/lib/automatedJobs'; seed() initFunc() diff --git a/apps/backend/src/admin-apis/av-router.ts b/apps/backend/src/apis/admin-apis/apis/av-router.ts similarity index 60% rename from apps/backend/src/admin-apis/av-router.ts rename to apps/backend/src/apis/admin-apis/apis/av-router.ts index b98e354..732e56f 100755 --- a/apps/backend/src/admin-apis/av-router.ts +++ b/apps/backend/src/apis/admin-apis/apis/av-router.ts @@ -1,7 +1,7 @@ import { Router } from "express"; -import { authenticateStaff } from "../middleware/staff-auth"; -import productRouter from "./product.router"; -import tagRouter from "./tag.router"; +import { authenticateStaff } from "@/src/middleware/staff-auth"; +import productRouter from "@/src/apis/admin-apis/apis/product.router" +import tagRouter from "@/src/apis/admin-apis/apis/tag.router" const router = Router(); diff --git a/apps/backend/src/admin-apis/product-tags.controller.ts b/apps/backend/src/apis/admin-apis/apis/product-tags.controller.ts similarity index 94% rename from apps/backend/src/admin-apis/product-tags.controller.ts rename to apps/backend/src/apis/admin-apis/apis/product-tags.controller.ts index 4466989..7b6a4fe 100644 --- a/apps/backend/src/admin-apis/product-tags.controller.ts +++ b/apps/backend/src/apis/admin-apis/apis/product-tags.controller.ts @@ -1,11 +1,11 @@ import { Request, Response } from "express"; -import { db } from "../db/db_index"; -import { productTagInfo } from "../db/schema"; +import { db } from "@/src/db/db_index"; +import { productTagInfo } from "@/src/db/schema"; import { eq } from "drizzle-orm"; -import { ApiError } from "../lib/api-error"; -import { imageUploadS3, generateSignedUrlFromS3Url } from "../lib/s3-client"; -import { deleteS3Image } from "../lib/delete-image"; -import { initializeAllStores } from '../stores/store-initializer'; +import { ApiError } from "@/src/lib/api-error"; +import { imageUploadS3, generateSignedUrlFromS3Url } from "@/src/lib/s3-client"; +import { deleteS3Image } from "@/src/lib/delete-image"; +import { initializeAllStores } from '@/src/stores/store-initializer'; /** * Create a new product tag diff --git a/apps/backend/src/admin-apis/product.controller.ts b/apps/backend/src/apis/admin-apis/apis/product.controller.ts similarity index 95% rename from apps/backend/src/admin-apis/product.controller.ts rename to apps/backend/src/apis/admin-apis/apis/product.controller.ts index 28c0c42..dcd3471 100644 --- a/apps/backend/src/admin-apis/product.controller.ts +++ b/apps/backend/src/apis/admin-apis/apis/product.controller.ts @@ -1,12 +1,12 @@ import { Request, Response } from "express"; -import { db } from "../db/db_index"; -import { productInfo, units, specialDeals, productTags } from "../db/schema"; +import { db } from "@/src/db/db_index"; +import { productInfo, units, specialDeals, productTags } from "@/src/db/schema"; import { eq, inArray } from "drizzle-orm"; -import { ApiError } from "../lib/api-error"; -import { imageUploadS3, getOriginalUrlFromSignedUrl } from "../lib/s3-client"; -import { deleteS3Image } from "../lib/delete-image"; -import type { SpecialDeal } from "../db/types"; -import { initializeAllStores } from '../stores/store-initializer'; +import { ApiError } from "@/src/lib/api-error"; +import { imageUploadS3, getOriginalUrlFromSignedUrl } from "@/src/lib/s3-client"; +import { deleteS3Image } from "@/src/lib/delete-image"; +import type { SpecialDeal } from "@/src/db/types"; +import { initializeAllStores } from '@/src/stores/store-initializer'; type CreateDeal = { quantity: number; diff --git a/apps/backend/src/admin-apis/product.router.ts b/apps/backend/src/apis/admin-apis/apis/product.router.ts similarity index 61% rename from apps/backend/src/admin-apis/product.router.ts rename to apps/backend/src/apis/admin-apis/apis/product.router.ts index faabd52..fe0bbd1 100644 --- a/apps/backend/src/admin-apis/product.router.ts +++ b/apps/backend/src/apis/admin-apis/apis/product.router.ts @@ -1,6 +1,6 @@ import { Router } from "express"; -import { createProduct, updateProduct } from "./product.controller"; -import uploadHandler from '../lib/upload-handler'; +import { createProduct, updateProduct } from "@/src/apis/admin-apis/apis/product.controller" +import uploadHandler from '@/src/lib/upload-handler'; const router = Router(); diff --git a/apps/backend/src/admin-apis/tag.router.ts b/apps/backend/src/apis/admin-apis/apis/tag.router.ts similarity index 76% rename from apps/backend/src/admin-apis/tag.router.ts rename to apps/backend/src/apis/admin-apis/apis/tag.router.ts index 171ce7f..dbcdb0d 100644 --- a/apps/backend/src/admin-apis/tag.router.ts +++ b/apps/backend/src/apis/admin-apis/apis/tag.router.ts @@ -1,6 +1,6 @@ import { Router } from "express"; -import { createTag, getAllTags, getTagById, updateTag, deleteTag } from "./product-tags.controller"; -import uploadHandler from '../lib/upload-handler'; +import { createTag, getAllTags, getTagById, updateTag, deleteTag } from "@/src/apis/admin-apis/apis/product-tags.controller" +import uploadHandler from '@/src/lib/upload-handler'; const router = Router(); diff --git a/apps/backend/src/apis/admin-apis/dataAccessors/demo.txt b/apps/backend/src/apis/admin-apis/dataAccessors/demo.txt new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/common-apis/common-product.controller.ts b/apps/backend/src/apis/common-apis/apis/common-product.controller.ts similarity index 96% rename from apps/backend/src/common-apis/common-product.controller.ts rename to apps/backend/src/apis/common-apis/apis/common-product.controller.ts index a9b7e7f..93bd682 100644 --- a/apps/backend/src/common-apis/common-product.controller.ts +++ b/apps/backend/src/apis/common-apis/apis/common-product.controller.ts @@ -1,8 +1,8 @@ import { eq, gt, and, sql, inArray } from "drizzle-orm"; import { Request, Response } from "express"; -import { db } from "../db/db_index"; -import { productInfo, units, productSlots, deliverySlotInfo, productTags } from "../db/schema"; -import { scaffoldAssetUrl } from "../lib/s3-client"; +import { db } from "@/src/db/db_index" +import { productInfo, units, productSlots, deliverySlotInfo, productTags } from "@/src/db/schema" +import { scaffoldAssetUrl } from "@/src/lib/s3-client" /** * Get next delivery date for a product diff --git a/apps/backend/src/common-apis/common-product.router.ts b/apps/backend/src/apis/common-apis/apis/common-product.router.ts similarity index 65% rename from apps/backend/src/common-apis/common-product.router.ts rename to apps/backend/src/apis/common-apis/apis/common-product.router.ts index 5d5b60c..90b7655 100644 --- a/apps/backend/src/common-apis/common-product.router.ts +++ b/apps/backend/src/apis/common-apis/apis/common-product.router.ts @@ -1,5 +1,5 @@ import { Router } from "express"; -import { getAllProductsSummary } from "./common-product.controller"; +import { getAllProductsSummary } from "@/src/apis/common-apis/apis/common-product.controller" const router = Router(); diff --git a/apps/backend/src/common-apis/common.router.ts b/apps/backend/src/apis/common-apis/apis/common.router.ts similarity index 66% rename from apps/backend/src/common-apis/common.router.ts rename to apps/backend/src/apis/common-apis/apis/common.router.ts index 7f5a9a9..7277d1f 100644 --- a/apps/backend/src/common-apis/common.router.ts +++ b/apps/backend/src/apis/common-apis/apis/common.router.ts @@ -1,5 +1,5 @@ import { Router } from "express"; -import commonProductsRouter from "./common-product.router"; +import commonProductsRouter from "@/src/apis/common-apis/apis/common-product.router" const router = Router(); diff --git a/apps/backend/src/apis/common-apis/dataAccessors/demo.txt b/apps/backend/src/apis/common-apis/dataAccessors/demo.txt new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/db/db_index.ts b/apps/backend/src/db/db_index.ts index 92cbfbb..05aebab 100755 --- a/apps/backend/src/db/db_index.ts +++ b/apps/backend/src/db/db_index.ts @@ -1,7 +1,7 @@ import { drizzle } from "drizzle-orm/node-postgres" import { migrate } from "drizzle-orm/node-postgres/migrator" import path from "path" -import * as schema from "./schema" +import * as schema from "@/src/db/schema" const db = drizzle({ connection: process.env.DATABASE_URL!, casing: "snake_case", schema: schema }) // const db = drizzle('postgresql://postgres:postgres@localhost:2345/pooler'); diff --git a/apps/backend/src/db/porter.ts b/apps/backend/src/db/porter.ts index 49165ea..4173d6f 100644 --- a/apps/backend/src/db/porter.ts +++ b/apps/backend/src/db/porter.ts @@ -2,13 +2,13 @@ * This was a one time script to change the composition of the signed urls */ -import { db } from './db_index'; +import { db } from '@/src/db/db_index' import { userDetails, productInfo, productTagInfo, complaints -} from './schema'; +} from '@/src/db/schema'; import { eq, not, isNull } from 'drizzle-orm'; const S3_DOMAIN = 'https://s3.sgp.io.cloud.ovh.net'; @@ -122,4 +122,4 @@ runMigration() .catch((error) => { console.error('Process failed:', error); process.exit(1); - }); \ No newline at end of file + }); diff --git a/apps/backend/src/db/seed.ts b/apps/backend/src/db/seed.ts index f844c73..f894534 100644 --- a/apps/backend/src/db/seed.ts +++ b/apps/backend/src/db/seed.ts @@ -1,8 +1,8 @@ -import { db } from "./db_index"; -import { units, productInfo, deliverySlotInfo, productSlots, keyValStore, staffRoles, staffPermissions, staffRolePermissions } from "./schema"; +import { db } from "@/src/db/db_index" +import { units, productInfo, deliverySlotInfo, productSlots, keyValStore, staffRoles, staffPermissions, staffRolePermissions } from "@/src/db/schema" import { eq } from "drizzle-orm"; -import { minOrderValue, deliveryCharge } from '../lib/env-exporter'; -import { CONST_KEYS } from '../lib/const-keys'; +import { minOrderValue, deliveryCharge } from '@/src/lib/env-exporter' +import { CONST_KEYS } from '@/src/lib/const-keys' export async function seed() { console.log("Seeding database..."); diff --git a/apps/backend/src/db/types.ts b/apps/backend/src/db/types.ts index 78c549f..64d42de 100755 --- a/apps/backend/src/db/types.ts +++ b/apps/backend/src/db/types.ts @@ -14,7 +14,7 @@ import type { productCategories, cartItems, coupons, -} from "./schema"; +} from "@/src/db/schema"; export type User = InferSelectModel; export type Address = InferSelectModel; @@ -44,4 +44,4 @@ export type OrderWithItems = Order & { export type CartItemWithProduct = CartItem & { product: ProductInfo; -}; \ No newline at end of file +}; diff --git a/apps/backend/src/jobs/jobs-index.ts b/apps/backend/src/jobs/jobs-index.ts index 1e64d68..107be6f 100644 --- a/apps/backend/src/jobs/jobs-index.ts +++ b/apps/backend/src/jobs/jobs-index.ts @@ -1,5 +1,5 @@ import * as cron from 'node-cron'; -import { checkPendingPayments, checkRefundStatuses } from './payment-status-checker'; +import { checkPendingPayments, checkRefundStatuses } from '@/src/jobs/payment-status-checker' const runCombinedJob = async () => { const start = Date.now(); diff --git a/apps/backend/src/jobs/payment-status-checker.ts b/apps/backend/src/jobs/payment-status-checker.ts index 35f49bf..7c6679b 100644 --- a/apps/backend/src/jobs/payment-status-checker.ts +++ b/apps/backend/src/jobs/payment-status-checker.ts @@ -1,8 +1,8 @@ import * as cron from 'node-cron'; -import { db } from '../db/db_index'; -import { payments, orders, deliverySlotInfo, refunds } from '../db/schema'; +import { db } from '@/src/db/db_index' +import { payments, orders, deliverySlotInfo, refunds } from '@/src/db/schema' import { eq, and, gt, isNotNull } from 'drizzle-orm'; -import { RazorpayPaymentService } from '../lib/payments-utils'; +import { RazorpayPaymentService } from '@/src/lib/payments-utils' interface PendingPaymentRecord { payment: typeof payments.$inferSelect; diff --git a/apps/backend/src/lib/automatedJobs.ts b/apps/backend/src/lib/automatedJobs.ts index a4ac8c9..3142afe 100644 --- a/apps/backend/src/lib/automatedJobs.ts +++ b/apps/backend/src/lib/automatedJobs.ts @@ -1,9 +1,9 @@ import * as cron from 'node-cron'; -import { db } from '../db/db_index'; -import { productInfo, keyValStore } from '../db/schema'; +import { db } from '@/src/db/db_index' +import { productInfo, keyValStore } from '@/src/db/schema' import { inArray, eq } from 'drizzle-orm'; -import { CONST_KEYS } from '../lib/const-keys'; -import { computeConstants } from '../lib/const-store'; +import { CONST_KEYS } from '@/src/lib/const-keys' +import { computeConstants } from '@/src/lib/const-store' const MUTTON_ITEMS = [ diff --git a/apps/backend/src/lib/axios.ts b/apps/backend/src/lib/axios.ts index 636e216..6db2de2 100755 --- a/apps/backend/src/lib/axios.ts +++ b/apps/backend/src/lib/axios.ts @@ -1,5 +1,5 @@ import axiosParent from "axios"; -import { phonePeBaseUrl } from "./env-exporter"; +import { phonePeBaseUrl } from "@/src/lib/env-exporter" export const phonepeAxios = axiosParent.create({ baseURL: phonePeBaseUrl, diff --git a/apps/backend/src/lib/const-store.ts b/apps/backend/src/lib/const-store.ts index 0221ada..c16609e 100644 --- a/apps/backend/src/lib/const-store.ts +++ b/apps/backend/src/lib/const-store.ts @@ -1,7 +1,7 @@ -import { db } from '../db/db_index'; -import { keyValStore } from '../db/schema'; -import redisClient from './redis-client'; -import { CONST_KEYS, CONST_KEYS_ARRAY, type ConstKey } from './const-keys'; +import { db } from '@/src/db/db_index' +import { keyValStore } from '@/src/db/schema' +import redisClient from '@/src/lib/redis-client' +import { CONST_KEYS, CONST_KEYS_ARRAY, type ConstKey } from '@/src/lib/const-keys' const CONST_REDIS_PREFIX = 'const:'; diff --git a/apps/backend/src/lib/delete-image.ts b/apps/backend/src/lib/delete-image.ts index 5289c45..dd6dfdd 100644 --- a/apps/backend/src/lib/delete-image.ts +++ b/apps/backend/src/lib/delete-image.ts @@ -1,7 +1,7 @@ import { eq } from "drizzle-orm"; -import { db } from "../db/db_index"; -import { deleteImageUtil, getOriginalUrlFromSignedUrl } from "./s3-client"; -import { s3Url } from "./env-exporter"; +import { db } from "@/src/db/db_index" +import { deleteImageUtil, getOriginalUrlFromSignedUrl } from "@/src/lib/s3-client" +import { s3Url } from "@/src/lib/env-exporter" function extractS3Key(url: string): string | null { try { diff --git a/apps/backend/src/lib/delete-orders.ts b/apps/backend/src/lib/delete-orders.ts index 57b6d34..4fb9516 100644 --- a/apps/backend/src/lib/delete-orders.ts +++ b/apps/backend/src/lib/delete-orders.ts @@ -1,5 +1,5 @@ -import { db } from '../db/db_index'; -import { orders, orderItems, orderStatus, payments, refunds, couponUsage, complaints } from '../db/schema'; +import { db } from '@/src/db/db_index' +import { orders, orderItems, orderStatus, payments, refunds, couponUsage, complaints } from '@/src/db/schema' import { eq, inArray } from 'drizzle-orm'; /** diff --git a/apps/backend/src/lib/event-queue.ts b/apps/backend/src/lib/event-queue.ts index 13e0d09..9d78222 100644 --- a/apps/backend/src/lib/event-queue.ts +++ b/apps/backend/src/lib/event-queue.ts @@ -1,4 +1,4 @@ -import redisClient from './redis-client'; +import redisClient from '@/src/lib/redis-client' export async function enqueue(queueName: string, eventData: any): Promise { try { diff --git a/apps/backend/src/lib/expo-service.ts b/apps/backend/src/lib/expo-service.ts index 1e87423..fbb784c 100755 --- a/apps/backend/src/lib/expo-service.ts +++ b/apps/backend/src/lib/expo-service.ts @@ -1,6 +1,6 @@ import { Expo } from "expo-server-sdk"; import { title } from "process"; -import { expoAccessToken } from "./env-exporter"; +import { expoAccessToken } from "@/src/lib/env-exporter" const expo = new Expo({ accessToken: expoAccessToken, diff --git a/apps/backend/src/lib/init.ts b/apps/backend/src/lib/init.ts index e5f9455..654b195 100755 --- a/apps/backend/src/lib/init.ts +++ b/apps/backend/src/lib/init.ts @@ -1,8 +1,8 @@ -import './notif-job'; -import { initializeAllStores } from '../stores/store-initializer'; -import { initializeUserNegativityStore } from '../stores/user-negativity-store'; -import { startOrderHandler, startCancellationHandler, publishOrder } from './post-order-handler'; -import { deleteOrders } from './delete-orders'; +import '@/src/lib/notif-job' +import { initializeAllStores } from '@/src/stores/store-initializer' +import { initializeUserNegativityStore } from '@/src/stores/user-negativity-store' +import { startOrderHandler, startCancellationHandler, publishOrder } from '@/src/lib/post-order-handler' +import { deleteOrders } from '@/src/lib/delete-orders' /** * Initialize all application services diff --git a/apps/backend/src/lib/notif-job.ts b/apps/backend/src/lib/notif-job.ts index f534843..c0c18f9 100644 --- a/apps/backend/src/lib/notif-job.ts +++ b/apps/backend/src/lib/notif-job.ts @@ -1,8 +1,8 @@ import { Queue, Worker } from 'bullmq'; import { Expo } from 'expo-server-sdk'; -import { redisUrl } from './env-exporter'; -import { db } from '../db/db_index'; -import { generateSignedUrlFromS3Url } from './s3-client'; +import { redisUrl } from '@/src/lib/env-exporter' +import { db } from '@/src/db/db_index' +import { generateSignedUrlFromS3Url } from '@/src/lib/s3-client' import { NOTIFS_QUEUE, ORDER_PLACED_MESSAGE, @@ -12,7 +12,7 @@ import { ORDER_DELIVERED_MESSAGE, ORDER_CANCELLED_MESSAGE, REFUND_INITIATED_MESSAGE -} from './const-strings'; +} from '@/src/lib/const-strings'; export const notificationQueue = new Queue(NOTIFS_QUEUE, { connection: { url: redisUrl }, diff --git a/apps/backend/src/lib/notif-service.ts b/apps/backend/src/lib/notif-service.ts index 2cb9480..ee1a0d5 100755 --- a/apps/backend/src/lib/notif-service.ts +++ b/apps/backend/src/lib/notif-service.ts @@ -1,6 +1,6 @@ -import { db } from "../db/db_index"; -import { sendPushNotificationsMany } from "./expo-service"; -// import { usersTable, notifCredsTable, notificationTable } from "../db/schema"; +import { db } from "@/src/db/db_index" +import { sendPushNotificationsMany } from "@/src/lib/expo-service" +// import { usersTable, notifCredsTable, notificationTable } from "@/src/db/schema"; import { eq, inArray } from "drizzle-orm"; // Core notification dispatch methods (renamed for clarity) @@ -244,4 +244,4 @@ export const sendNotifToSingleUser = dispatchUserNotification; /** * @deprecated Use notifyNewOffer() or other purpose-specific methods instead */ -export const sendNotifToManyUsers = dispatchBulkNotification; \ No newline at end of file +export const sendNotifToManyUsers = dispatchBulkNotification; diff --git a/apps/backend/src/lib/otp-utils.ts b/apps/backend/src/lib/otp-utils.ts index dc39b45..a35be03 100644 --- a/apps/backend/src/lib/otp-utils.ts +++ b/apps/backend/src/lib/otp-utils.ts @@ -1,5 +1,5 @@ -import { ApiError } from './api-error'; -import { otpSenderAuthToken } from './env-exporter'; +import { ApiError } from '@/src/lib/api-error' +import { otpSenderAuthToken } from '@/src/lib/env-exporter' const otpStore = new Map(); diff --git a/apps/backend/src/lib/payments-utils.ts b/apps/backend/src/lib/payments-utils.ts index 02e7791..e60a03d 100644 --- a/apps/backend/src/lib/payments-utils.ts +++ b/apps/backend/src/lib/payments-utils.ts @@ -1,7 +1,7 @@ import Razorpay from "razorpay"; -import { razorpayId, razorpaySecret } from "./env-exporter"; -import { db } from "../db/db_index"; -import { payments } from "../db/schema"; +import { razorpayId, razorpaySecret } from "@/src/lib/env-exporter" +import { db } from "@/src/db/db_index" +import { payments } from "@/src/db/schema" type Tx = Parameters[0]>[0]; diff --git a/apps/backend/src/lib/post-order-handler.ts b/apps/backend/src/lib/post-order-handler.ts index 6d5de59..62953fa 100644 --- a/apps/backend/src/lib/post-order-handler.ts +++ b/apps/backend/src/lib/post-order-handler.ts @@ -1,7 +1,7 @@ -import { db } from '../db/db_index'; -import { orders, orderStatus } from '../db/schema'; -import redisClient from './redis-client'; -import { sendTelegramMessage } from './telegram-service'; +import { db } from '@/src/db/db_index' +import { orders, orderStatus } from '@/src/db/schema' +import redisClient from '@/src/lib/redis-client' +import { sendTelegramMessage } from '@/src/lib/telegram-service' import { inArray, eq } from 'drizzle-orm'; const ORDER_CHANNEL = 'orders:placed'; diff --git a/apps/backend/src/lib/redis-client.ts b/apps/backend/src/lib/redis-client.ts index 87953e8..cb8f56e 100644 --- a/apps/backend/src/lib/redis-client.ts +++ b/apps/backend/src/lib/redis-client.ts @@ -1,5 +1,5 @@ import { createClient, RedisClientType } from 'redis'; -import { redisUrl } from './env-exporter'; +import { redisUrl } from '@/src/lib/env-exporter' class RedisClient { private client: RedisClientType; diff --git a/apps/backend/src/lib/roles-manager.ts b/apps/backend/src/lib/roles-manager.ts index bdb8057..7242bce 100755 --- a/apps/backend/src/lib/roles-manager.ts +++ b/apps/backend/src/lib/roles-manager.ts @@ -1,4 +1,4 @@ -import { db } from "../db/db_index"; +import { db } from "@/src/db/db_index" /** * Constants for role names to avoid hardcoding and typos diff --git a/apps/backend/src/lib/s3-client.ts b/apps/backend/src/lib/s3-client.ts index cfe933a..7862169 100755 --- a/apps/backend/src/lib/s3-client.ts +++ b/apps/backend/src/lib/s3-client.ts @@ -1,10 +1,10 @@ -// import { s3A, awsBucketName, awsRegion, awsSecretAccessKey } from "../lib/env-exporter" +// import { s3A, awsBucketName, awsRegion, awsSecretAccessKey } from "@/src/lib/env-exporter" import { DeleteObjectCommand, DeleteObjectsCommand, PutObjectCommand, S3Client, GetObjectCommand } from "@aws-sdk/client-s3" import { getSignedUrl } from "@aws-sdk/s3-request-presigner" -import signedUrlCache from "./signed-url-cache" -import { s3AccessKeyId, s3Region, s3Url, s3SecretAccessKey, s3BucketName, assetsDomain } from "./env-exporter"; -import { db } from "../db/db_index"; // Adjust path if needed -import { uploadUrlStatus } from "../db/schema"; +import signedUrlCache from "@/src/lib/signed-url-cache" +import { s3AccessKeyId, s3Region, s3Url, s3SecretAccessKey, s3BucketName, assetsDomain } from "@/src/lib/env-exporter" +import { db } from "@/src/db/db_index"; // Adjust path if needed +import { uploadUrlStatus } from "@/src/db/schema" import { and, eq } from 'drizzle-orm'; const s3Client = new S3Client({ diff --git a/apps/backend/src/lib/telegram-service.ts b/apps/backend/src/lib/telegram-service.ts index 957990f..a3a3e6f 100644 --- a/apps/backend/src/lib/telegram-service.ts +++ b/apps/backend/src/lib/telegram-service.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { isDevMode, telegramBotToken, telegramChatIds } from './env-exporter'; +import { isDevMode, telegramBotToken, telegramChatIds } from '@/src/lib/env-exporter' const BOT_TOKEN = telegramBotToken; const CHAT_IDS = telegramChatIds; diff --git a/apps/backend/src/main-router.ts b/apps/backend/src/main-router.ts index c4df17f..25086e6 100755 --- a/apps/backend/src/main-router.ts +++ b/apps/backend/src/main-router.ts @@ -1,11 +1,11 @@ import { Router, Request, Response, NextFunction } from "express"; -import avRouter from "./admin-apis/av-router"; -import { ApiError } from "./lib/api-error"; -import v1Router from "./v1-router"; -import testController from "./test-controller"; -import { authenticateUser } from "./middleware/auth.middleware"; -import { raiseComplaint } from "./uv-apis/user-rest.controller"; -import uploadHandler from "./lib/upload-handler"; +import avRouter from "@/src/apis/admin-apis/apis/av-router" +import { ApiError } from "@/src/lib/api-error" +import v1Router from "@/src/v1-router" +import testController from "@/src/test-controller" +import { authenticateUser } from "@/src/middleware/auth.middleware" +import { raiseComplaint } from "@/src/uv-apis/user-rest.controller" +import uploadHandler from "@/src/lib/upload-handler" const router = Router(); diff --git a/apps/backend/src/middleware/auth.middleware.ts b/apps/backend/src/middleware/auth.middleware.ts index 3ff1de4..1e381bb 100644 --- a/apps/backend/src/middleware/auth.middleware.ts +++ b/apps/backend/src/middleware/auth.middleware.ts @@ -1,9 +1,9 @@ import { Request, Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; -import { db } from '../db/db_index'; -import { staffUsers, userDetails } from '../db/schema'; +import { db } from '@/src/db/db_index' +import { staffUsers, userDetails } from '@/src/db/schema' import { eq } from 'drizzle-orm'; -import { ApiError } from '../lib/api-error'; +import { ApiError } from '@/src/lib/api-error' interface AuthenticatedRequest extends Request { user?: { diff --git a/apps/backend/src/middleware/auth.ts b/apps/backend/src/middleware/auth.ts index d7a091f..a1a4f3a 100755 --- a/apps/backend/src/middleware/auth.ts +++ b/apps/backend/src/middleware/auth.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; -import { ApiError } from '../lib/api-error'; +import { ApiError } from '@/src/lib/api-error' // Extend the Request interface to include user property declare global { diff --git a/apps/backend/src/middleware/staff-auth.ts b/apps/backend/src/middleware/staff-auth.ts index 471b172..2215f5d 100644 --- a/apps/backend/src/middleware/staff-auth.ts +++ b/apps/backend/src/middleware/staff-auth.ts @@ -1,9 +1,9 @@ import { Request, Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; -import { db } from '../db/db_index'; -import { staffUsers } from '../db/schema'; +import { db } from '@/src/db/db_index' +import { staffUsers } from '@/src/db/schema' import { eq } from 'drizzle-orm'; -import { ApiError } from '../lib/api-error'; +import { ApiError } from '@/src/lib/api-error' // Extend Request interface to include staffUser declare global { diff --git a/apps/backend/src/services/user/order-service.ts b/apps/backend/src/services/user/order-service.ts index 22a54c9..8b2df28 100644 --- a/apps/backend/src/services/user/order-service.ts +++ b/apps/backend/src/services/user/order-service.ts @@ -1,4 +1,4 @@ -import { db } from '../../db/db_index' +import { db } from '@/src/db/db_index' import { orders, orderItems, @@ -13,7 +13,7 @@ import { refunds, units, userDetails, -} from '../../db/schema' +} from '@/src/db/schema' import { eq, and, inArray, desc, gte } from 'drizzle-orm' // ============ User/Auth Queries ============ diff --git a/apps/backend/src/services/user/product-service.ts b/apps/backend/src/services/user/product-service.ts index 77f62fa..0f214d9 100644 --- a/apps/backend/src/services/user/product-service.ts +++ b/apps/backend/src/services/user/product-service.ts @@ -1,5 +1,5 @@ -import { db } from '../../db/db_index' -import { productInfo, units, productSlots, deliverySlotInfo, specialDeals, storeInfo, productReviews, users } from '../../db/schema' +import { db } from '@/src/db/db_index' +import { productInfo, units, productSlots, deliverySlotInfo, specialDeals, storeInfo, productReviews, users } from '@/src/db/schema' import { eq, and, gt, sql, desc } from 'drizzle-orm' /** diff --git a/apps/backend/src/stores/banner-store.ts b/apps/backend/src/stores/banner-store.ts index 3d13523..7fbd6f1 100644 --- a/apps/backend/src/stores/banner-store.ts +++ b/apps/backend/src/stores/banner-store.ts @@ -1,9 +1,13 @@ -// import redisClient from './redis-client'; -import redisClient from 'src/lib/redis-client'; -import { db } from '../db/db_index'; -import { homeBanners } from '../db/schema'; +// import redisClient from '@/src/stores/redis-client'; +import redisClient from '@/src/lib/redis-client'; +import { db } from '@/src/db/db_index' +import { homeBanners } from '@/src/db/schema' import { isNotNull, asc } from 'drizzle-orm'; -import { scaffoldAssetUrl } from 'src/lib/s3-client'; +import { scaffoldAssetUrl } from '@/src/lib/s3-client'; + + + + // Banner Type (matches getBanners return) interface Banner { diff --git a/apps/backend/src/stores/product-store.ts b/apps/backend/src/stores/product-store.ts index 6f845f4..d4cf3a2 100644 --- a/apps/backend/src/stores/product-store.ts +++ b/apps/backend/src/stores/product-store.ts @@ -1,9 +1,9 @@ -// import redisClient from './redis-client'; -import redisClient from 'src/lib/redis-client'; -import { db } from '../db/db_index'; -import { productInfo, units, productSlots, deliverySlotInfo, specialDeals, storeInfo, productTags, productTagInfo } from '../db/schema'; +// import redisClient from '@/src/stores/redis-client'; +import redisClient from '@/src/lib/redis-client'; +import { db } from '@/src/db/db_index' +import { productInfo, units, productSlots, deliverySlotInfo, specialDeals, storeInfo, productTags, productTagInfo } from '@/src/db/schema' import { eq, and, gt, sql } from 'drizzle-orm'; -import { generateSignedUrlsFromS3Urls, scaffoldAssetUrl } from 'src/lib/s3-client'; +import { generateSignedUrlsFromS3Urls, scaffoldAssetUrl } from '@/src/lib/s3-client'; // Uniform Product Type (matches getProductDetails return) interface Product { diff --git a/apps/backend/src/stores/product-tag-store.ts b/apps/backend/src/stores/product-tag-store.ts index 33a7d72..f6a3175 100644 --- a/apps/backend/src/stores/product-tag-store.ts +++ b/apps/backend/src/stores/product-tag-store.ts @@ -1,9 +1,9 @@ -// import redisClient from './redis-client'; -import redisClient from 'src/lib/redis-client'; -import { db } from '../db/db_index'; -import { productTagInfo, productTags } from '../db/schema'; +// import redisClient from '@/src/stores/redis-client'; +import redisClient from '@/src/lib/redis-client'; +import { db } from '@/src/db/db_index' +import { productTagInfo, productTags } from '@/src/db/schema' import { eq, inArray } from 'drizzle-orm'; -import { generateSignedUrlFromS3Url } from 'src/lib/s3-client'; +import { generateSignedUrlFromS3Url } from '@/src/lib/s3-client'; // Tag Type (matches getDashboardTags return) interface Tag { @@ -168,4 +168,4 @@ export async function getTagsByStoreId(storeId: number): Promise { console.error(`Error getting tags for store ${storeId}:`, error); return []; } -} \ No newline at end of file +} diff --git a/apps/backend/src/stores/slot-store.ts b/apps/backend/src/stores/slot-store.ts index 36e897c..3a3ed68 100644 --- a/apps/backend/src/stores/slot-store.ts +++ b/apps/backend/src/stores/slot-store.ts @@ -1,8 +1,8 @@ -import redisClient from 'src/lib/redis-client'; -import { db } from '../db/db_index'; -import { deliverySlotInfo, productSlots, productInfo, units } from '../db/schema'; +import redisClient from '@/src/lib/redis-client'; +import { db } from '@/src/db/db_index' +import { deliverySlotInfo, productSlots, productInfo, units } from '@/src/db/schema' import { eq, and, gt, asc } from 'drizzle-orm'; -import { generateSignedUrlsFromS3Urls, scaffoldAssetUrl } from 'src/lib/s3-client'; +import { generateSignedUrlsFromS3Urls, scaffoldAssetUrl } from '@/src/lib/s3-client'; import dayjs from 'dayjs'; // Define the structure for slot with products diff --git a/apps/backend/src/stores/store-initializer.ts b/apps/backend/src/stores/store-initializer.ts index 19210c0..bb414f8 100644 --- a/apps/backend/src/stores/store-initializer.ts +++ b/apps/backend/src/stores/store-initializer.ts @@ -1,9 +1,9 @@ -import roleManager from '../lib/roles-manager'; -import { computeConstants } from '../lib/const-store'; -import { initializeProducts } from './product-store'; -import { initializeProductTagStore } from './product-tag-store'; -import { initializeSlotStore } from './slot-store'; -import { initializeBannerStore } from './banner-store'; +import roleManager from '@/src/lib/roles-manager' +import { computeConstants } from '@/src/lib/const-store' +import { initializeProducts } from '@/src/stores/product-store' +import { initializeProductTagStore } from '@/src/stores/product-tag-store' +import { initializeSlotStore } from '@/src/stores/slot-store' +import { initializeBannerStore } from '@/src/stores/banner-store' /** * Initialize all application stores diff --git a/apps/backend/src/stores/user-negativity-store.ts b/apps/backend/src/stores/user-negativity-store.ts index d1285b7..d518435 100644 --- a/apps/backend/src/stores/user-negativity-store.ts +++ b/apps/backend/src/stores/user-negativity-store.ts @@ -1,6 +1,6 @@ -import redisClient from 'src/lib/redis-client'; -import { db } from '../db/db_index'; -import { userIncidents } from '../db/schema'; +import redisClient from '@/src/lib/redis-client'; +import { db } from '@/src/db/db_index' +import { userIncidents } from '@/src/db/schema' import { eq, sum } from 'drizzle-orm'; export async function initializeUserNegativityStore(): Promise { diff --git a/apps/backend/src/trpc/admin-apis/admin-trpc-index.ts b/apps/backend/src/trpc/admin-apis/admin-trpc-index.ts deleted file mode 100644 index a2e0571..0000000 --- a/apps/backend/src/trpc/admin-apis/admin-trpc-index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { router } from '../trpc-index'; -import { complaintRouter } from './complaint'; -import { couponRouter } from './coupon'; -import { cancelledOrdersRouter } from './cancelled-orders'; -import { orderRouter } from './order'; -import { vendorSnippetsRouter } from './vendor-snippets'; -import { slotsRouter } from './slots'; -import { productRouter } from './product'; -import { staffUserRouter } from './staff-user'; -import { storeRouter } from './store'; -import { adminPaymentsRouter } from './payments'; -import addressRouter from './address'; -import { bannerRouter } from './banner'; -import { userRouter } from './user'; -import { constRouter } from './const'; - -export const adminRouter = router({ - complaint: complaintRouter, - coupon: couponRouter, - cancelledOrders: cancelledOrdersRouter, - order: orderRouter, - vendorSnippets: vendorSnippetsRouter, - slots: slotsRouter, - product: productRouter, - staffUser: staffUserRouter, - store: storeRouter, - payments: adminPaymentsRouter, - address: addressRouter, - banner: bannerRouter, - user: userRouter, - const: constRouter, -}); - -export type AdminRouter = typeof adminRouter; \ No newline at end of file diff --git a/apps/backend/src/trpc/admin-apis/address.ts b/apps/backend/src/trpc/apis/admin-apis/apis/address.ts similarity index 86% rename from apps/backend/src/trpc/admin-apis/address.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/address.ts index 32f753a..019b895 100644 --- a/apps/backend/src/trpc/admin-apis/address.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/address.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; -import { addressZones, addressAreas } from '../../db/schema'; +import { addressZones, addressAreas } from '@/src/db/schema' import { eq, desc } from 'drizzle-orm'; -import { db } from '../../db/db_index'; -import { router,protectedProcedure } from '../trpc-index'; +import { db } from '@/src/db/db_index' +import { router,protectedProcedure } from '@/src/trpc/trpc-index' const addressRouter = router({ getZones: protectedProcedure.query(async () => { diff --git a/apps/backend/src/trpc/apis/admin-apis/apis/admin-trpc-index.ts b/apps/backend/src/trpc/apis/admin-apis/apis/admin-trpc-index.ts new file mode 100644 index 0000000..3af0292 --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/apis/admin-trpc-index.ts @@ -0,0 +1,35 @@ +// import { router } from '@/src/trpc/trpc-index'; +import { router } from '@/src/trpc/trpc-index' +import { complaintRouter } from '@/src/trpc/apis/admin-apis/apis/complaint' +import { couponRouter } from '@/src/trpc/apis/admin-apis/apis/coupon' +import { cancelledOrdersRouter } from '@/src/trpc/apis/admin-apis/apis/cancelled-orders' +import { orderRouter } from '@/src/trpc/apis/admin-apis/apis/order' +import { vendorSnippetsRouter } from '@/src/trpc/apis/admin-apis/apis/vendor-snippets' +import { slotsRouter } from '@/src/trpc/apis/admin-apis/apis/slots' +import { productRouter } from '@/src/trpc/apis/admin-apis/apis/product' +import { staffUserRouter } from '@/src/trpc/apis/admin-apis/apis/staff-user' +import { storeRouter } from '@/src/trpc/apis/admin-apis/apis/store' +import { adminPaymentsRouter } from '@/src/trpc/apis/admin-apis/apis/payments' +import addressRouter from '@/src/trpc/apis/admin-apis/apis/address' +import { bannerRouter } from '@/src/trpc/apis/admin-apis/apis/banner' +import { userRouter } from '@/src/trpc/apis/admin-apis/apis/user' +import { constRouter } from '@/src/trpc/apis/admin-apis/apis/const' + +export const adminRouter = router({ + complaint: complaintRouter, + coupon: couponRouter, + cancelledOrders: cancelledOrdersRouter, + order: orderRouter, + vendorSnippets: vendorSnippetsRouter, + slots: slotsRouter, + product: productRouter, + staffUser: staffUserRouter, + store: storeRouter, + payments: adminPaymentsRouter, + address: addressRouter, + banner: bannerRouter, + user: userRouter, + const: constRouter, +}); + +export type AdminRouter = typeof adminRouter; diff --git a/apps/backend/src/trpc/admin-apis/banner.ts b/apps/backend/src/trpc/apis/admin-apis/apis/banner.ts similarity index 94% rename from apps/backend/src/trpc/admin-apis/banner.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/banner.ts index 4850ebc..f812430 100644 --- a/apps/backend/src/trpc/admin-apis/banner.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/banner.ts @@ -1,11 +1,11 @@ import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { homeBanners } from '../../db/schema'; +import { db } from '@/src/db/db_index' +import { homeBanners } from '@/src/db/schema' import { eq, and, desc, sql } from 'drizzle-orm'; -import { protectedProcedure, router } from '../trpc-index'; -import { extractKeyFromPresignedUrl, generateSignedUrlFromS3Url } from '../../lib/s3-client'; -import { ApiError } from 'src/lib/api-error'; -import { initializeAllStores } from '../../stores/store-initializer'; +import { protectedProcedure, router } from '@/src/trpc/trpc-index' +import { extractKeyFromPresignedUrl, generateSignedUrlFromS3Url } from '@/src/lib/s3-client' +import { ApiError } from '@/src/lib/api-error'; +import { initializeAllStores } from '@/src/stores/store-initializer' export const bannerRouter = router({ // Get all banners @@ -171,4 +171,4 @@ export const bannerRouter = router({ return { success: true }; }), -}); \ No newline at end of file +}); diff --git a/apps/backend/src/trpc/admin-apis/cancelled-orders.ts b/apps/backend/src/trpc/apis/admin-apis/apis/cancelled-orders.ts similarity index 97% rename from apps/backend/src/trpc/admin-apis/cancelled-orders.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/cancelled-orders.ts index 99bcbfd..14c0b26 100644 --- a/apps/backend/src/trpc/admin-apis/cancelled-orders.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/cancelled-orders.ts @@ -1,7 +1,7 @@ -import { router, protectedProcedure } from '../trpc-index'; +import { router, protectedProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { orders, orderStatus, users, addresses, orderItems, productInfo, units, refunds } from '../../db/schema'; +import { db } from '@/src/db/db_index' +import { orders, orderStatus, users, addresses, orderItems, productInfo, units, refunds } from '@/src/db/schema' import { eq, desc } from 'drizzle-orm'; const updateCancellationReviewSchema = z.object({ diff --git a/apps/backend/src/trpc/admin-apis/complaint.ts b/apps/backend/src/trpc/apis/admin-apis/apis/complaint.ts similarity index 91% rename from apps/backend/src/trpc/admin-apis/complaint.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/complaint.ts index ee3fde4..17d65d4 100644 --- a/apps/backend/src/trpc/admin-apis/complaint.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/complaint.ts @@ -1,9 +1,9 @@ -import { router, protectedProcedure } from '../trpc-index'; +import { router, protectedProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { complaints, users } from '../../db/schema'; +import { db } from '@/src/db/db_index' +import { complaints, users } from '@/src/db/schema' import { eq, desc, lt, and } from 'drizzle-orm'; -import { generateSignedUrlsFromS3Urls } from '../../lib/s3-client'; +import { generateSignedUrlsFromS3Urls } from '@/src/lib/s3-client' export const complaintRouter = router({ getAll: protectedProcedure diff --git a/apps/backend/src/trpc/admin-apis/const.ts b/apps/backend/src/trpc/apis/admin-apis/apis/const.ts similarity index 84% rename from apps/backend/src/trpc/admin-apis/const.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/const.ts index b9f60b4..a426087 100644 --- a/apps/backend/src/trpc/admin-apis/const.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/const.ts @@ -1,9 +1,9 @@ -import { router, protectedProcedure } from '../trpc-index'; +import { router, protectedProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { keyValStore } from '../../db/schema'; -import { computeConstants } from '../../lib/const-store'; -import { CONST_KEYS } from '../../lib/const-keys'; +import { db } from '@/src/db/db_index' +import { keyValStore } from '@/src/db/schema' +import { computeConstants } from '@/src/lib/const-store' +import { CONST_KEYS } from '@/src/lib/const-keys' export const constRouter = router({ getConstants: protectedProcedure diff --git a/apps/backend/src/trpc/admin-apis/coupon.ts b/apps/backend/src/trpc/apis/admin-apis/apis/coupon.ts similarity index 99% rename from apps/backend/src/trpc/admin-apis/coupon.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/coupon.ts index d659fde..4eb3017 100644 --- a/apps/backend/src/trpc/admin-apis/coupon.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/coupon.ts @@ -1,7 +1,7 @@ -import { router, protectedProcedure } from '../trpc-index'; +import { router, protectedProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { coupons, users, staffUsers, orders, couponApplicableUsers, couponApplicableProducts, orderStatus, reservedCoupons } from '../../db/schema'; +import { db } from '@/src/db/db_index' +import { coupons, users, staffUsers, orders, couponApplicableUsers, couponApplicableProducts, orderStatus, reservedCoupons } from '@/src/db/schema' import { eq, and, like, or, inArray, lt } from 'drizzle-orm'; import dayjs from 'dayjs'; diff --git a/apps/backend/src/trpc/admin-apis/order.ts b/apps/backend/src/trpc/apis/admin-apis/apis/order.ts similarity index 98% rename from apps/backend/src/trpc/admin-apis/order.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/order.ts index ba7e1f1..875018e 100644 --- a/apps/backend/src/trpc/admin-apis/order.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/order.ts @@ -1,6 +1,6 @@ -import { router, protectedProcedure } from "../trpc-index"; +import { router, protectedProcedure } from "@/src/trpc/trpc-index" import { z } from "zod"; -import { db } from "../../db/db_index"; +import { db } from "@/src/db/db_index" import { orders, orderItems, @@ -10,17 +10,17 @@ import { refunds, coupons, couponUsage, -} from "../../db/schema"; +} from "@/src/db/schema"; import { eq, and, gte, lt, desc, SQL, inArray } from "drizzle-orm"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; -import { ApiError } from "../../lib/api-error"; +import { ApiError } from "@/src/lib/api-error" import { sendOrderPackagedNotification, sendOrderDeliveredNotification, -} from "../../lib/notif-job"; -import { publishCancellation } from "../../lib/post-order-handler"; -import { getMultipleUserNegativityScores } from "../../stores/user-negativity-store"; +} from "@/src/lib/notif-job"; +import { publishCancellation } from "@/src/lib/post-order-handler" +import { getMultipleUserNegativityScores } from "@/src/stores/user-negativity-store" const updateOrderNotesSchema = z.object({ orderId: z.number(), diff --git a/apps/backend/src/trpc/admin-apis/payments.ts b/apps/backend/src/trpc/apis/admin-apis/apis/payments.ts similarity index 94% rename from apps/backend/src/trpc/admin-apis/payments.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/payments.ts index 23ca16e..51de5fb 100644 --- a/apps/backend/src/trpc/admin-apis/payments.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/payments.ts @@ -1,15 +1,15 @@ -import { router, protectedProcedure } from "../trpc-index"; +import { router, protectedProcedure } from "@/src/trpc/trpc-index" import { z } from "zod"; -import { db } from "../../db/db_index"; +import { db } from "@/src/db/db_index" import { orders, orderStatus, payments, refunds, -} from "../../db/schema"; +} from "@/src/db/schema"; import { and, eq } from "drizzle-orm"; -import { ApiError } from "../../lib/api-error"; -import { RazorpayPaymentService } from "../../lib/payments-utils"; +import { ApiError } from "@/src/lib/api-error" +import { RazorpayPaymentService } from "@/src/lib/payments-utils" const initiateRefundSchema = z .object({ diff --git a/apps/backend/src/trpc/admin-apis/product.ts b/apps/backend/src/trpc/apis/admin-apis/apis/product.ts similarity index 96% rename from apps/backend/src/trpc/admin-apis/product.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/product.ts index 93d87e0..df77159 100644 --- a/apps/backend/src/trpc/admin-apis/product.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/product.ts @@ -1,13 +1,13 @@ -import { router, protectedProcedure } from '../trpc-index'; +import { router, protectedProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { productInfo, units, specialDeals, productSlots, productTags, productReviews, users, productGroupInfo, productGroupMembership } from '../../db/schema'; +import { db } from '@/src/db/db_index' +import { productInfo, units, specialDeals, productSlots, productTags, productReviews, users, productGroupInfo, productGroupMembership } from '@/src/db/schema' import { eq, and, inArray, desc, sql } from 'drizzle-orm'; -import { ApiError } from '../../lib/api-error'; -import { imageUploadS3, generateSignedUrlsFromS3Urls, getOriginalUrlFromSignedUrl, claimUploadUrl } from '../../lib/s3-client'; -import { deleteS3Image } from '../../lib/delete-image'; -import type { SpecialDeal } from '../../db/types'; -import { initializeAllStores } from '../../stores/store-initializer'; +import { ApiError } from '@/src/lib/api-error' +import { imageUploadS3, generateSignedUrlsFromS3Urls, getOriginalUrlFromSignedUrl, claimUploadUrl } from '@/src/lib/s3-client' +import { deleteS3Image } from '@/src/lib/delete-image' +import type { SpecialDeal } from '@/src/db/types' +import { initializeAllStores } from '@/src/stores/store-initializer' type CreateDeal = { quantity: number; @@ -335,7 +335,7 @@ export const productRouter = router({ // Claim upload URLs if (uploadUrls && uploadUrls.length > 0) { - // const { claimUploadUrl } = await import('../../lib/s3-client'); + // const { claimUploadUrl } = await import('@/src/lib/s3-client'); await Promise.all(uploadUrls.map(url => claimUploadUrl(url))); } @@ -531,4 +531,4 @@ export const productRouter = router({ updatedCount: updates.length, }; }), - }); \ No newline at end of file + }); diff --git a/apps/backend/src/trpc/admin-apis/slots.ts b/apps/backend/src/trpc/apis/admin-apis/apis/slots.ts similarity index 97% rename from apps/backend/src/trpc/admin-apis/slots.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/slots.ts index 4f35401..4e9ef18 100644 --- a/apps/backend/src/trpc/admin-apis/slots.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/slots.ts @@ -1,14 +1,14 @@ -import { router, protectedProcedure } from "../trpc-index"; +import { router, protectedProcedure } from "@/src/trpc/trpc-index" import { TRPCError } from "@trpc/server"; import { z } from "zod"; -import { db } from "../../db/db_index"; -import { deliverySlotInfo, productSlots, productInfo, vendorSnippets, productGroupInfo } from "../../db/schema"; +import { db } from "@/src/db/db_index" +import { deliverySlotInfo, productSlots, productInfo, vendorSnippets, productGroupInfo } from "@/src/db/schema" import { eq, inArray, and, desc } from "drizzle-orm"; -import { ApiError } from "../../lib/api-error"; -import { appUrl } from "../../lib/env-exporter"; -import redisClient from "../../lib/redis-client"; -import { getSlotSequenceKey } from "../../lib/redisKeyGetters"; -import { initializeAllStores } from '../../stores/store-initializer'; +import { ApiError } from "@/src/lib/api-error" +import { appUrl } from "@/src/lib/env-exporter" +import redisClient from "@/src/lib/redis-client" +import { getSlotSequenceKey } from "@/src/lib/redisKeyGetters" +import { initializeAllStores } from '@/src/stores/store-initializer' interface CachedDeliverySequence { [userId: string]: number[]; diff --git a/apps/backend/src/trpc/admin-apis/staff-user.ts b/apps/backend/src/trpc/apis/admin-apis/apis/staff-user.ts similarity index 97% rename from apps/backend/src/trpc/admin-apis/staff-user.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/staff-user.ts index c1d0bfd..0a62c21 100644 --- a/apps/backend/src/trpc/admin-apis/staff-user.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/staff-user.ts @@ -1,11 +1,11 @@ -import { router, publicProcedure, protectedProcedure } from '../trpc-index'; +import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { staffUsers, staffRoles, users, userDetails, orders } from '../../db/schema'; +import { db } from '@/src/db/db_index' +import { staffUsers, staffRoles, users, userDetails, orders } from '@/src/db/schema' import { eq, or, ilike, and, lt, desc } from 'drizzle-orm'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; -import { ApiError } from '../../lib/api-error'; +import { ApiError } from '@/src/lib/api-error' export const staffUserRouter = router({ login: publicProcedure diff --git a/apps/backend/src/trpc/admin-apis/store.ts b/apps/backend/src/trpc/apis/admin-apis/apis/store.ts similarity index 94% rename from apps/backend/src/trpc/admin-apis/store.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/store.ts index 61f7a3a..6d1cee2 100644 --- a/apps/backend/src/trpc/admin-apis/store.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/store.ts @@ -1,12 +1,12 @@ -import { router, protectedProcedure } from '../trpc-index'; +import { router, protectedProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { storeInfo, productInfo } from '../../db/schema'; +import { db } from '@/src/db/db_index' +import { storeInfo, productInfo } from '@/src/db/schema' import { eq, inArray } from 'drizzle-orm'; -import { ApiError } from '../../lib/api-error'; - import { extractKeyFromPresignedUrl, deleteImageUtil, generateSignedUrlFromS3Url } from '../../lib/s3-client'; +import { ApiError } from '@/src/lib/api-error' + import { extractKeyFromPresignedUrl, deleteImageUtil, generateSignedUrlFromS3Url } from '@/src/lib/s3-client' import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; -import { initializeAllStores } from '../../stores/store-initializer'; +import { initializeAllStores } from '@/src/stores/store-initializer' export const storeRouter = router({ getStores: protectedProcedure diff --git a/apps/backend/src/trpc/admin-apis/user.ts b/apps/backend/src/trpc/apis/admin-apis/apis/user.ts similarity index 97% rename from apps/backend/src/trpc/admin-apis/user.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/user.ts index 77141ce..2b176b2 100644 --- a/apps/backend/src/trpc/admin-apis/user.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/user.ts @@ -1,11 +1,11 @@ -import { protectedProcedure } from '../trpc-index'; +import { protectedProcedure } from '@/src/trpc/trpc-index'; import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { users, complaints, orders, orderItems, notifCreds, unloggedUserTokens, userDetails, userIncidents } from '../../db/schema'; +import { db } from '@/src/db/db_index'; +import { users, complaints, orders, orderItems, notifCreds, unloggedUserTokens, userDetails, userIncidents } from '@/src/db/schema'; import { eq, sql, desc, asc, count, max, inArray } from 'drizzle-orm'; -import { ApiError } from '../../lib/api-error'; -import { notificationQueue } from '../../lib/notif-job'; -import { recomputeUserNegativityScore } from '../../stores/user-negativity-store'; +import { ApiError } from '@/src/lib/api-error'; +import { notificationQueue } from '@/src/lib/notif-job'; +import { recomputeUserNegativityScore } from '@/src/stores/user-negativity-store'; async function createUserByMobile(mobile: string): Promise { // Clean mobile number (remove non-digits) @@ -212,7 +212,7 @@ export const userRouter = { let orderStatuses: { orderId: number; isDelivered: boolean; isCancelled: boolean }[] = []; if (orderIds.length > 0) { - const { orderStatus } = await import('../../db/schema'); + const { orderStatus } = await import('@/src/db/schema'); orderStatuses = await db .select({ orderId: orderStatus.orderId, diff --git a/apps/backend/src/trpc/admin-apis/vendor-snippets.ts b/apps/backend/src/trpc/apis/admin-apis/apis/vendor-snippets.ts similarity index 98% rename from apps/backend/src/trpc/admin-apis/vendor-snippets.ts rename to apps/backend/src/trpc/apis/admin-apis/apis/vendor-snippets.ts index 80fe66f..4a307d3 100644 --- a/apps/backend/src/trpc/admin-apis/vendor-snippets.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/vendor-snippets.ts @@ -1,10 +1,10 @@ -import { router, publicProcedure, protectedProcedure } from '../trpc-index'; +import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod'; import dayjs from 'dayjs'; -import { db } from '../../db/db_index'; -import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, users, orderStatus } from '../../db/schema'; +import { db } from '@/src/db/db_index' +import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, users, orderStatus } from '@/src/db/schema' import { eq, and, inArray, isNotNull, gt, sql, asc, ne } from 'drizzle-orm'; -import { appUrl } from '../../lib/env-exporter'; +import { appUrl } from '@/src/lib/env-exporter' const createSnippetSchema = z.object({ snippetCode: z.string().min(1, "Snippet code is required"), diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/demo.txt b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/demo.txt new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/common-apis/common-trpc-index.ts b/apps/backend/src/trpc/apis/common-apis/common-trpc-index.ts similarity index 88% rename from apps/backend/src/trpc/common-apis/common-trpc-index.ts rename to apps/backend/src/trpc/apis/common-apis/common-trpc-index.ts index 5891ab6..5be1569 100644 --- a/apps/backend/src/trpc/common-apis/common-trpc-index.ts +++ b/apps/backend/src/trpc/apis/common-apis/common-trpc-index.ts @@ -1,14 +1,14 @@ -import { router, publicProcedure, protectedProcedure } from '../trpc-index'; -import { commonRouter } from './common'; -import { db } from '../../db/db_index'; -import { keyValStore, productInfo, storeInfo } from '../../db/schema'; +import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index' +import { commonRouter } from '@/src/trpc/apis/common-apis/common' +import { db } from '@/src/db/db_index' +import { keyValStore, productInfo, storeInfo } from '@/src/db/schema' import * as turf from '@turf/turf'; import { z } from 'zod'; -import { mbnrGeoJson } from '../../lib/mbnr-geojson'; -import { generateUploadUrl } from '../../lib/s3-client'; -import { ApiError } from '../../lib/api-error'; -import { getAllConstValues } from '../../lib/const-store'; -import { CONST_KEYS } from '../../lib/const-keys'; +import { mbnrGeoJson } from '@/src/lib/mbnr-geojson' +import { generateUploadUrl } from '@/src/lib/s3-client' +import { ApiError } from '@/src/lib/api-error' +import { getAllConstValues } from '@/src/lib/const-store' +import { CONST_KEYS } from '@/src/lib/const-keys' const polygon = turf.polygon(mbnrGeoJson.features[0].geometry.coordinates); diff --git a/apps/backend/src/trpc/common-apis/common.ts b/apps/backend/src/trpc/apis/common-apis/common.ts similarity index 92% rename from apps/backend/src/trpc/common-apis/common.ts rename to apps/backend/src/trpc/apis/common-apis/common.ts index f7650a7..9b63dd3 100644 --- a/apps/backend/src/trpc/common-apis/common.ts +++ b/apps/backend/src/trpc/apis/common-apis/common.ts @@ -1,11 +1,11 @@ -import { router, publicProcedure } from '../trpc-index'; -import { db } from '../../db/db_index'; -import { productInfo, units, productSlots, deliverySlotInfo, storeInfo, productTags, productTagInfo } from '../../db/schema'; +import { router, publicProcedure } from '@/src/trpc/trpc-index' +import { db } from '@/src/db/db_index' +import { productInfo, units, productSlots, deliverySlotInfo, storeInfo, productTags, productTagInfo } from '@/src/db/schema' import { eq, gt, and, sql, inArray } from 'drizzle-orm'; -import { generateSignedUrlsFromS3Urls, generateSignedUrlFromS3Url } from '../../lib/s3-client'; +import { generateSignedUrlsFromS3Urls, generateSignedUrlFromS3Url } from '@/src/lib/s3-client' import { z } from 'zod'; -import { getAllProducts as getAllProductsFromCache } from '../../stores/product-store'; -import { getDashboardTags as getDashboardTagsFromCache } from '../../stores/product-tag-store'; +import { getAllProducts as getAllProductsFromCache } from '@/src/stores/product-store' +import { getDashboardTags as getDashboardTagsFromCache } from '@/src/stores/product-tag-store' import Fuse from 'fuse.js'; export const getNextDeliveryDate = async (productId: number): Promise => { diff --git a/apps/backend/src/trpc/apis/user-apis/apis/address.ts b/apps/backend/src/trpc/apis/user-apis/apis/address.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/auth.ts b/apps/backend/src/trpc/apis/user-apis/apis/auth.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/banners.ts b/apps/backend/src/trpc/apis/user-apis/apis/banners.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/cart.ts b/apps/backend/src/trpc/apis/user-apis/apis/cart.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/complaint.ts b/apps/backend/src/trpc/apis/user-apis/apis/complaint.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/coupon.ts b/apps/backend/src/trpc/apis/user-apis/apis/coupon.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/file-upload.ts b/apps/backend/src/trpc/apis/user-apis/apis/file-upload.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/order.ts b/apps/backend/src/trpc/apis/user-apis/apis/order.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/payments.ts b/apps/backend/src/trpc/apis/user-apis/apis/payments.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/product.ts b/apps/backend/src/trpc/apis/user-apis/apis/product.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/slots.ts b/apps/backend/src/trpc/apis/user-apis/apis/slots.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/stores.ts b/apps/backend/src/trpc/apis/user-apis/apis/stores.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/tags.ts b/apps/backend/src/trpc/apis/user-apis/apis/tags.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/apis/user-trpc-index.ts b/apps/backend/src/trpc/apis/user-apis/apis/user-trpc-index.ts new file mode 100644 index 0000000..d4fb958 --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/apis/user-trpc-index.ts @@ -0,0 +1,5 @@ +import { router } from '@/src/trpc/trpc-index' + +export const userRouter = router({}) + +export type UserRouter = typeof userRouter diff --git a/apps/backend/src/trpc/apis/user-apis/apis/user.ts b/apps/backend/src/trpc/apis/user-apis/apis/user.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/demo.txt b/apps/backend/src/trpc/apis/user-apis/dataAccessors/demo.txt new file mode 100644 index 0000000..e69de29 diff --git a/apps/backend/src/trpc/router.ts b/apps/backend/src/trpc/router.ts index e6471b5..6d357ee 100644 --- a/apps/backend/src/trpc/router.ts +++ b/apps/backend/src/trpc/router.ts @@ -1,8 +1,8 @@ -import { router, publicProcedure } from './trpc-index'; +import { router, publicProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod'; -import { adminRouter } from './admin-apis/admin-trpc-index'; -import { userRouter } from './user-apis/user-trpc-index'; -import { commonApiRouter } from './common-apis/common-trpc-index'; +import { adminRouter } from '@/src/trpc/apis/admin-apis/apis/admin-trpc-index' +import { userRouter } from '@/src/trpc/apis/user-apis/apis/user-trpc-index' +import { commonApiRouter } from '@/src/trpc/apis/common-apis/common-trpc-index' // Create the main app router export const appRouter = router({ diff --git a/apps/backend/src/trpc/user-apis/address.ts b/apps/backend/src/trpc/user-apis/address.ts deleted file mode 100644 index f45c02f..0000000 --- a/apps/backend/src/trpc/user-apis/address.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { router, protectedProcedure } from '../trpc-index'; -import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { addresses, orders, orderStatus, deliverySlotInfo } from '../../db/schema'; -import { eq, and, gte } from 'drizzle-orm'; -import dayjs from 'dayjs'; -import { extractCoordsFromRedirectUrl } from '../../lib/license-util'; - -export const addressRouter = router({ - getDefaultAddress: protectedProcedure - .query(async ({ ctx }) => { - const userId = ctx.user.userId; - - const [defaultAddress] = await db - .select() - .from(addresses) - .where(and(eq(addresses.userId, userId), eq(addresses.isDefault, true))) - .limit(1); - - return { success: true, data: defaultAddress || null }; - }), - - getUserAddresses: protectedProcedure - .query(async ({ ctx }) => { - const userId = ctx.user.userId; - const userAddresses = await db.select().from(addresses).where(eq(addresses.userId, userId)); - return { success: true, data: userAddresses }; - }), - - createAddress: protectedProcedure - .input(z.object({ - name: z.string().min(1, 'Name is required'), - phone: z.string().min(1, 'Phone is required'), - addressLine1: z.string().min(1, 'Address line 1 is required'), - addressLine2: z.string().optional(), - city: z.string().min(1, 'City is required'), - state: z.string().min(1, 'State is required'), - pincode: z.string().min(1, 'Pincode is required'), - isDefault: z.boolean().optional(), - latitude: z.number().optional(), - longitude: z.number().optional(), - googleMapsUrl: z.string().optional(), - })) - .mutation(async ({ input, ctx }) => { - const userId = ctx.user.userId; - const { name, phone, addressLine1, addressLine2, city, state, pincode, isDefault, googleMapsUrl } = input; - - let { latitude, longitude } = input; - - if (googleMapsUrl && latitude === undefined && longitude === undefined) { - const coords = await extractCoordsFromRedirectUrl(googleMapsUrl); - if (coords) { - latitude = Number(coords.latitude); - longitude = Number(coords.longitude); - } - } - - // Validate required fields - if (!name || !phone || !addressLine1 || !city || !state || !pincode) { - throw new Error('Missing required fields'); - } - - // If setting as default, unset other defaults - if (isDefault) { - await db.update(addresses).set({ isDefault: false }).where(eq(addresses.userId, userId)); - } - - const [newAddress] = await db.insert(addresses).values({ - userId, - name, - phone, - addressLine1, - addressLine2, - city, - state, - pincode, - isDefault: isDefault || false, - latitude, - longitude, - googleMapsUrl, - }).returning(); - - return { success: true, data: newAddress }; - }), - - updateAddress: protectedProcedure - .input(z.object({ - id: z.number().int().positive(), - name: z.string().min(1, 'Name is required'), - phone: z.string().min(1, 'Phone is required'), - addressLine1: z.string().min(1, 'Address line 1 is required'), - addressLine2: z.string().optional(), - city: z.string().min(1, 'City is required'), - state: z.string().min(1, 'State is required'), - pincode: z.string().min(1, 'Pincode is required'), - isDefault: z.boolean().optional(), - latitude: z.number().optional(), - longitude: z.number().optional(), - googleMapsUrl: z.string().optional(), - })) - .mutation(async ({ input, ctx }) => { - const userId = ctx.user.userId; - const { id, name, phone, addressLine1, addressLine2, city, state, pincode, isDefault, googleMapsUrl } = input; - - let { latitude, longitude } = input; - - if (googleMapsUrl && latitude === undefined && longitude === undefined) { - const coords = await extractCoordsFromRedirectUrl(googleMapsUrl); - if (coords) { - latitude = Number(coords.latitude); - longitude = Number(coords.longitude); - } - } - - // Check if address exists and belongs to user - const existingAddress = await db.select().from(addresses).where(and(eq(addresses.id, id), eq(addresses.userId, userId))).limit(1); - if (existingAddress.length === 0) { - throw new Error('Address not found'); - } - - // If setting as default, unset other defaults - if (isDefault) { - await db.update(addresses).set({ isDefault: false }).where(eq(addresses.userId, userId)); - } - - const updateData: any = { - name, - phone, - addressLine1, - addressLine2, - city, - state, - pincode, - isDefault: isDefault || false, - googleMapsUrl, - }; - - if (latitude !== undefined) { - updateData.latitude = latitude; - } - if (longitude !== undefined) { - updateData.longitude = longitude; - } - - const [updatedAddress] = await db.update(addresses).set(updateData).where(and(eq(addresses.id, id), eq(addresses.userId, userId))).returning(); - - return { success: true, data: updatedAddress }; - }), - - deleteAddress: protectedProcedure - .input(z.object({ - id: z.number().int().positive(), - })) - .mutation(async ({ input, ctx }) => { - const userId = ctx.user.userId; - const { id } = input; - - // Check if address exists and belongs to user - const existingAddress = await db.select().from(addresses).where(and(eq(addresses.id, id), eq(addresses.userId, userId))).limit(1); - if (existingAddress.length === 0) { - throw new Error('Address not found or does not belong to user'); - } - - // Check if address is attached to any ongoing orders using joins - const ongoingOrders = await db.select({ - order: orders, - status: orderStatus, - slot: deliverySlotInfo - }) - .from(orders) - .innerJoin(orderStatus, eq(orders.id, orderStatus.orderId)) - .innerJoin(deliverySlotInfo, eq(orders.slotId, deliverySlotInfo.id)) - .where(and( - eq(orders.addressId, id), - eq(orderStatus.isCancelled, false), - gte(deliverySlotInfo.deliveryTime, new Date()) - )) - .limit(1); - - if (ongoingOrders.length > 0) { - throw new Error('Address is attached to an ongoing order. Please cancel the order first.'); - } - - // Prevent deletion of default address - if (existingAddress[0].isDefault) { - throw new Error('Cannot delete default address. Please set another address as default first.'); - } - - // Delete the address - await db.delete(addresses).where(and(eq(addresses.id, id), eq(addresses.userId, userId))); - - return { success: true, message: 'Address deleted successfully' }; - }), -}); \ No newline at end of file diff --git a/apps/backend/src/trpc/user-apis/auth.ts b/apps/backend/src/trpc/user-apis/auth.ts deleted file mode 100644 index 2a0dc62..0000000 --- a/apps/backend/src/trpc/user-apis/auth.ts +++ /dev/null @@ -1,447 +0,0 @@ -import { router, publicProcedure, protectedProcedure } from '../trpc-index'; -import { z } from 'zod'; -import bcrypt from 'bcryptjs'; -import jwt from 'jsonwebtoken'; -import { eq } from 'drizzle-orm'; -import { db } from '../../db/db_index'; -import { - users, userCreds, userDetails, addresses, cartItems, complaints, - couponApplicableUsers, couponUsage, notifCreds, notifications, - orderItems, orderStatus, orders, payments, refunds, - productReviews, reservedCoupons -} from '../../db/schema'; -import { generateSignedUrlFromS3Url } from '../../lib/s3-client'; -import { ApiError } from '../../lib/api-error'; -import catchAsync from '../../lib/catch-async'; -import { jwtSecret } from 'src/lib/env-exporter'; -import { sendOtp, verifyOtpUtil, getOtpCreds } from '../../lib/otp-utils'; - -interface LoginRequest { - identifier: string; // email or mobile - password: string; -} - -interface RegisterRequest { - name: string; - email: string; - mobile: string; - password: string; -} - -interface AuthResponse { - token: string; - user: { - id: number; - name?: string | null; - email: string | null; - mobile: string | null; - createdAt: string; - profileImage: string | null; - bio?: string | null; - dateOfBirth?: string | null; - gender?: string | null; - occupation?: string | null; - }; -} - -const generateToken = (userId: number): string => { - const secret = jwtSecret; - if (!secret) { - throw new ApiError('JWT secret not configured', 500); - } - - return jwt.sign({ userId }, secret, { expiresIn: '7d' }); -}; - - - -export const authRouter = router({ - login: publicProcedure - .input(z.object({ - identifier: z.string().min(1, 'Email/mobile is required'), - password: z.string().min(1, 'Password is required'), - })) - .mutation(async ({ input }) => { - const { identifier, password }: LoginRequest = input; - - if (!identifier || !password) { - throw new ApiError('Email/mobile and password are required', 400); - } - - // Find user by email or mobile - const [user] = await db - .select() - .from(users) - .where(eq(users.email, identifier.toLowerCase())) - .limit(1); - - let foundUser = user; - - if (!foundUser) { - // Try mobile if email didn't work - const [userByMobile] = await db - .select() - .from(users) - .where(eq(users.mobile, identifier)) - .limit(1); - foundUser = userByMobile; - } - - if (!foundUser) { - throw new ApiError('Invalid credentials', 401); - } - - // Get user credentials - const [userCredentials] = await db - .select() - .from(userCreds) - .where(eq(userCreds.userId, foundUser.id)) - .limit(1); - - if (!userCredentials) { - throw new ApiError('Account setup incomplete. Please contact support.', 401); - } - - // Get user details for profile image - const [userDetail] = await db - .select() - .from(userDetails) - .where(eq(userDetails.userId, foundUser.id)) - .limit(1); - - // Generate signed URL for profile image if it exists - const profileImageSignedUrl = userDetail?.profileImage - ? await generateSignedUrlFromS3Url(userDetail.profileImage) - : null; - - // Verify password - const isPasswordValid = await bcrypt.compare(password, userCredentials.userPassword); - if (!isPasswordValid) { - throw new ApiError('Invalid credentials', 401); - } - - const token = generateToken(foundUser.id); - - const response: AuthResponse = { - token, - user: { - id: foundUser.id, - name: foundUser.name, - email: foundUser.email, - mobile: foundUser.mobile, - createdAt: foundUser.createdAt.toISOString(), - profileImage: profileImageSignedUrl, - bio: userDetail?.bio || null, - dateOfBirth: userDetail?.dateOfBirth || null, - gender: userDetail?.gender || null, - occupation: userDetail?.occupation || null, - }, - }; - - return { - success: true, - data: response, - }; - }), - - register: publicProcedure - .input(z.object({ - name: z.string().min(1, 'Name is required'), - email: z.string().email('Invalid email format'), - mobile: z.string().min(1, 'Mobile is required'), - password: z.string().min(1, 'Password is required'), - })) - .mutation(async ({ input }) => { - const { name, email, mobile, password }: RegisterRequest = input; - - if (!name || !email || !mobile || !password) { - throw new ApiError('All fields are required', 400); - } - - // Validate email format - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(email)) { - throw new ApiError('Invalid email format', 400); - } - - // Validate mobile format (Indian mobile numbers) - const cleanMobile = mobile.replace(/\D/g, ''); - if (cleanMobile.length !== 10 || !/^[6-9]/.test(cleanMobile)) { - throw new ApiError('Invalid mobile number', 400); - } - - // Check if email already exists - const [existingEmail] = await db - .select() - .from(users) - .where(eq(users.email, email.toLowerCase())) - .limit(1); - - if (existingEmail) { - throw new ApiError('Email already registered', 409); - } - - // Check if mobile already exists - const [existingMobile] = await db - .select() - .from(users) - .where(eq(users.mobile, cleanMobile)) - .limit(1); - - if (existingMobile) { - throw new ApiError('Mobile number already registered', 409); - } - - // Hash password - const hashedPassword = await bcrypt.hash(password, 12); - - // Create user and credentials in a transaction - const newUser = await db.transaction(async (tx) => { - // Create user - const [user] = await tx - .insert(users) - .values({ - name: name.trim(), - email: email.toLowerCase().trim(), - mobile: cleanMobile, - }) - .returning(); - - // Create user credentials - await tx - .insert(userCreds) - .values({ - userId: user.id, - userPassword: hashedPassword, - }); - - return user; - }); - - const token = generateToken(newUser.id); - - const response: AuthResponse = { - token, - user: { - id: newUser.id, - name: newUser.name, - email: newUser.email, - mobile: newUser.mobile, - createdAt: newUser.createdAt.toISOString(), - profileImage: null, - }, - }; - - return { - success: true, - data: response, - }; - }), - - sendOtp: publicProcedure - .input(z.object({ - mobile: z.string(), - })) - .mutation(async ({ input }) => { - - return await sendOtp(input.mobile); - }), - - verifyOtp: publicProcedure - .input(z.object({ - mobile: z.string(), - otp: z.string(), - })) - .mutation(async ({ input }) => { - const verificationId = getOtpCreds(input.mobile); - if (!verificationId) { - throw new ApiError("OTP not sent or expired", 400); - } - const isVerified = await verifyOtpUtil(input.mobile, input.otp, verificationId); - - if (!isVerified) { - throw new ApiError("Invalid OTP", 400); - } - - // Find user - let user = await db.query.users.findFirst({ - where: eq(users.mobile, input.mobile), - }); - - // If user doesn't exist, create one - if (!user) { - const [newUser] = await db - .insert(users) - .values({ - name: null, - email: null, - mobile: input.mobile, - }) - .returning(); - user = newUser; - } - - // Generate JWT - const token = generateToken(user.id); - - return { - success: true, - token, - user: { - id: user.id, - name: user.name, - email: user.email, - mobile: user.mobile, - createdAt: user.createdAt.toISOString(), - profileImage: null, - }, - }; - }), - - updatePassword: protectedProcedure - .input(z.object({ - password: z.string().min(6, 'Password must be at least 6 characters'), - })) - .mutation(async ({ input, ctx }) => { - const userId = ctx.user.userId; - if (!userId) { - throw new ApiError('User not authenticated', 401); - } - - const hashedPassword = await bcrypt.hash(input.password, 10); - - // Insert if not exists, then update if exists - try { - await db.insert(userCreds).values({ - userId: userId, - userPassword: hashedPassword, - }); - // Insert succeeded - new credentials created - } catch (error: any) { - // Insert failed - check if it's a unique constraint violation - if (error.code === '23505') { // PostgreSQL unique constraint violation - // Update existing credentials - await db.update(userCreds).set({ - userPassword: hashedPassword, - }).where(eq(userCreds.userId, userId)); - } else { - // Re-throw if it's a different error - throw error; - } - } - - return { success: true, message: 'Password updated successfully' }; - }), - - getProfile: protectedProcedure - .query(async ({ ctx }) => { - const userId = ctx.user.userId; - - if (!userId) { - throw new ApiError('User not authenticated', 401); - } - - const [user] = await db - .select() - .from(users) - .where(eq(users.id, userId)) - .limit(1); - - if (!user) { - throw new ApiError('User not found', 404); - } - - return { - success: true, - data: { - id: user.id, - name: user.name, - email: user.email, - mobile: user.mobile, - }, - }; - }), - - deleteAccount: protectedProcedure - .input(z.object({ - mobile: z.string().min(10, 'Mobile number is required'), - })) - .mutation(async ({ ctx, input }) => { - const userId = ctx.user.userId; - const { mobile } = input; - - if (!userId) { - throw new ApiError('User not authenticated', 401); - } - - // Double-check: verify user exists and is the authenticated user - const existingUser = await db.query.users.findFirst({ - where: eq(users.id, userId), - columns: { id: true, mobile: true }, - }); - - if (!existingUser) { - throw new ApiError('User not found', 404); - } - - // Additional verification: ensure we're not deleting someone else's data - // The JWT token should already ensure this, but double-checking - if (existingUser.id !== userId) { - throw new ApiError('Unauthorized: Cannot delete another user\'s account', 403); - } - - // Verify mobile number matches user's registered mobile - const cleanInputMobile = mobile.replace(/\D/g, ''); - const cleanUserMobile = existingUser.mobile?.replace(/\D/g, ''); - - if (cleanInputMobile !== cleanUserMobile) { - throw new ApiError('Mobile number does not match your registered number', 400); - } - - // Use transaction for atomic deletion - await db.transaction(async (tx) => { - // Phase 1: Direct references (safe to delete first) - await tx.delete(notifCreds).where(eq(notifCreds.userId, userId)); - await tx.delete(couponApplicableUsers).where(eq(couponApplicableUsers.userId, userId)); - await tx.delete(couponUsage).where(eq(couponUsage.userId, userId)); - await tx.delete(complaints).where(eq(complaints.userId, userId)); - await tx.delete(cartItems).where(eq(cartItems.userId, userId)); - await tx.delete(notifications).where(eq(notifications.userId, userId)); - await tx.delete(productReviews).where(eq(productReviews.userId, userId)); - - // Update reserved coupons (set redeemedBy to null) - await tx.update(reservedCoupons) - .set({ redeemedBy: null }) - .where(eq(reservedCoupons.redeemedBy, userId)); - - // Phase 2: Order dependencies - const userOrders = await tx - .select({ id: orders.id }) - .from(orders) - .where(eq(orders.userId, userId)); - - for (const order of userOrders) { - await tx.delete(orderItems).where(eq(orderItems.orderId, order.id)); - await tx.delete(orderStatus).where(eq(orderStatus.orderId, order.id)); - await tx.delete(payments).where(eq(payments.orderId, order.id)); - await tx.delete(refunds).where(eq(refunds.orderId, order.id)); - // Additional coupon usage entries linked to specific orders - await tx.delete(couponUsage).where(eq(couponUsage.orderId, order.id)); - await tx.delete(complaints).where(eq(complaints.orderId, order.id)); - } - - // Delete orders - await tx.delete(orders).where(eq(orders.userId, userId)); - - // Phase 3: Addresses (now safe since orders are deleted) - await tx.delete(addresses).where(eq(addresses.userId, userId)); - - // Phase 4: Core user data - await tx.delete(userDetails).where(eq(userDetails.userId, userId)); - await tx.delete(userCreds).where(eq(userCreds.userId, userId)); - await tx.delete(users).where(eq(users.id, userId)); - }); - - return { success: true, message: 'Account deleted successfully' }; - }), -}); \ No newline at end of file diff --git a/apps/backend/src/trpc/user-apis/banners.ts b/apps/backend/src/trpc/user-apis/banners.ts deleted file mode 100644 index 9123859..0000000 --- a/apps/backend/src/trpc/user-apis/banners.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { db } from '../../db/db_index'; -import { homeBanners } from '../../db/schema'; -import { publicProcedure, router } from '../trpc-index'; -import { generateSignedUrlFromS3Url } from '../../lib/s3-client'; -import { isNotNull, asc } from 'drizzle-orm'; - -export const bannerRouter = router({ - getBanners: publicProcedure - .query(async () => { - const banners = await db.query.homeBanners.findMany({ - where: isNotNull(homeBanners.serialNum), // Only show assigned banners - orderBy: asc(homeBanners.serialNum), // Order by slot number 1-4 - }); - - // Convert S3 keys to signed URLs for client - const bannersWithSignedUrls = await Promise.all( - banners.map(async (banner) => { - try { - return { - ...banner, - imageUrl: banner.imageUrl ? await generateSignedUrlFromS3Url(banner.imageUrl) : banner.imageUrl, - }; - } catch (error) { - console.error(`Failed to generate signed URL for banner ${banner.id}:`, error); - return { - ...banner, - imageUrl: banner.imageUrl, // Keep original on error - }; - } - }) - ); - - - return { - banners: bannersWithSignedUrls, - }; - }), -}); \ No newline at end of file diff --git a/apps/backend/src/trpc/user-apis/cart.ts b/apps/backend/src/trpc/user-apis/cart.ts deleted file mode 100644 index 196097b..0000000 --- a/apps/backend/src/trpc/user-apis/cart.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { router, protectedProcedure, publicProcedure } from '../trpc-index'; -import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { cartItems, productInfo, units, productSlots, deliverySlotInfo } from '../../db/schema'; -import { eq, and, sql, inArray, gt } from 'drizzle-orm'; -import { ApiError } from '../../lib/api-error'; -import { generateSignedUrlsFromS3Urls, scaffoldAssetUrl } from '../../lib/s3-client'; -import { getProductSlots, getMultipleProductsSlots } from '../../stores/slot-store'; - -interface CartResponse { - items: any[]; - totalItems: number; - totalAmount: number; -} - -const getCartData = async (userId: number): Promise => { - const cartItemsWithProducts = await db - .select({ - cartId: cartItems.id, - productId: productInfo.id, - productName: productInfo.name, - productPrice: productInfo.price, - productImages: productInfo.images, - productQuantity: productInfo.productQuantity, - isOutOfStock: productInfo.isOutOfStock, - unitShortNotation: units.shortNotation, - quantity: cartItems.quantity, - addedAt: cartItems.addedAt, - }) - .from(cartItems) - .innerJoin(productInfo, eq(cartItems.productId, productInfo.id)) - .innerJoin(units, eq(productInfo.unitId, units.id)) - .where(eq(cartItems.userId, userId)); - - // Generate signed URLs for images - const cartWithSignedUrls = await Promise.all( - cartItemsWithProducts.map(async (item) => ({ - id: item.cartId, - productId: item.productId, - quantity: parseFloat(item.quantity), - addedAt: item.addedAt, - product: { - id: item.productId, - name: item.productName, - price: item.productPrice, - productQuantity: item.productQuantity, - unit: item.unitShortNotation, - isOutOfStock: item.isOutOfStock, - images: scaffoldAssetUrl((item.productImages as string[]) || []), - }, - subtotal: parseFloat(item.productPrice.toString()) * parseFloat(item.quantity), - })) - ); - - const totalAmount = cartWithSignedUrls.reduce((sum, item) => sum + item.subtotal, 0); - - return { - items: cartWithSignedUrls, - totalItems: cartWithSignedUrls.length, - totalAmount, - }; -}; - -export const cartRouter = router({ - getCart: protectedProcedure - .query(async ({ ctx }): Promise => { - const userId = ctx.user.userId; - return await getCartData(userId); - }), - - addToCart: protectedProcedure - .input(z.object({ - productId: z.number().int().positive(), - quantity: z.number().int().positive(), - })) - .mutation(async ({ input, ctx }): Promise => { - const userId = ctx.user.userId; - const { productId, quantity } = input; - - // Validate input - if (!productId || !quantity || quantity <= 0) { - throw new ApiError("Product ID and positive quantity required", 400); - } - - // Check if product exists - const product = await db.query.productInfo.findFirst({ - where: eq(productInfo.id, productId), - }); - - if (!product) { - throw new ApiError("Product not found", 404); - } - - // Check if item already exists in cart - const existingItem = await db.query.cartItems.findFirst({ - where: and(eq(cartItems.userId, userId), eq(cartItems.productId, productId)), - }); - - if (existingItem) { - // Update quantity - await db.update(cartItems) - .set({ - quantity: sql`${cartItems.quantity} + ${quantity}`, - }) - .where(eq(cartItems.id, existingItem.id)); - } else { - // Insert new item - await db.insert(cartItems).values({ - userId, - productId, - quantity: quantity.toString(), - }); - } - - // Return updated cart - return await getCartData(userId); - }), - - updateCartItem: protectedProcedure - .input(z.object({ - itemId: z.number().int().positive(), - quantity: z.number().int().min(0), - })) - .mutation(async ({ input, ctx }): Promise => { - const userId = ctx.user.userId; - const { itemId, quantity } = input; - - if (!quantity || quantity <= 0) { - throw new ApiError("Positive quantity required", 400); - } - - const [updatedItem] = await db.update(cartItems) - .set({ quantity: quantity.toString() }) - .where(and( - eq(cartItems.id, itemId), - eq(cartItems.userId, userId) - )) - .returning(); - - if (!updatedItem) { - throw new ApiError("Cart item not found", 404); - } - - // Return updated cart - return await getCartData(userId); - }), - - removeFromCart: protectedProcedure - .input(z.object({ - itemId: z.number().int().positive(), - })) - .mutation(async ({ input, ctx }): Promise => { - const userId = ctx.user.userId; - const { itemId } = input; - - const [deletedItem] = await db.delete(cartItems) - .where(and( - eq(cartItems.id, itemId), - eq(cartItems.userId, userId) - )) - .returning(); - - if (!deletedItem) { - throw new ApiError("Cart item not found", 404); - } - - // Return updated cart - return await getCartData(userId); - }), - - clearCart: protectedProcedure - .mutation(async ({ ctx }) => { - const userId = ctx.user.userId; - - await db.delete(cartItems).where(eq(cartItems.userId, userId)); - - return { - items: [], - totalItems: 0, - totalAmount: 0, - message: "Cart cleared successfully", - }; - }), - - // Original DB-based getCartSlots (commented out) - // getCartSlots: publicProcedure - // .input(z.object({ - // productIds: z.array(z.number().int().positive()) - // })) - // .query(async ({ input }) => { - // const { productIds } = input; - // - // if (productIds.length === 0) { - // return {}; - // } - // - // // Get slots for these products where freeze time is after current time - // const slotsData = await db - // .select({ - // productId: productSlots.productId, - // slotId: deliverySlotInfo.id, - // deliveryTime: deliverySlotInfo.deliveryTime, - // freezeTime: deliverySlotInfo.freezeTime, - // isActive: deliverySlotInfo.isActive, - // }) - // .from(productSlots) - // .innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id)) - // .where(and( - // inArray(productSlots.productId, productIds), - // gt(deliverySlotInfo.freezeTime, sql`NOW()`), - // eq(deliverySlotInfo.isActive, true) - // )); - // - // // Group by productId - // const result: Record = {}; - // slotsData.forEach(slot => { - // if (!result[slot.productId]) { - // result[slot.productId] = []; - // } - // result[slot.productId].push({ - // id: slot.slotId, - // deliveryTime: slot.deliveryTime, - // freezeTime: slot.freezeTime, - // }); - // }); - // - // return result; - // }), - - // Cache-based getCartSlots - getCartSlots: publicProcedure - .input(z.object({ - productIds: z.array(z.number().int().positive()) - })) - .query(async ({ input }) => { - const { productIds } = input; - - if (productIds.length === 0) { - return {}; - } - - return await getMultipleProductsSlots(productIds); - }), -}); diff --git a/apps/backend/src/trpc/user-apis/complaint.ts b/apps/backend/src/trpc/user-apis/complaint.ts deleted file mode 100644 index 284d748..0000000 --- a/apps/backend/src/trpc/user-apis/complaint.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { router, protectedProcedure } from '../trpc-index'; -import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { complaints } from '../../db/schema'; -import { eq } from 'drizzle-orm'; - -export const complaintRouter = router({ - getAll: protectedProcedure - .query(async ({ ctx }) => { - const userId = ctx.user.userId; - - const userComplaints = await db - .select({ - id: complaints.id, - complaintBody: complaints.complaintBody, - response: complaints.response, - isResolved: complaints.isResolved, - createdAt: complaints.createdAt, - orderId: complaints.orderId, - }) - .from(complaints) - .where(eq(complaints.userId, userId)) - .orderBy(complaints.createdAt); - - return { - complaints: userComplaints.map(c => ({ - id: c.id, - complaintBody: c.complaintBody, - response: c.response, - isResolved: c.isResolved, - createdAt: c.createdAt, - orderId: c.orderId, - })), - }; - }), - - raise: protectedProcedure - .input(z.object({ - orderId: z.string().optional(), - complaintBody: z.string().min(1, 'Complaint body is required'), - })) - .mutation(async ({ input, ctx }) => { - const userId = ctx.user.userId; - const { orderId, complaintBody } = input; - - let orderIdNum: number | null = null; - - if (orderId) { - const readableIdMatch = orderId.match(/^ORD(\d+)$/); - if (readableIdMatch) { - orderIdNum = parseInt(readableIdMatch[1]); - } - } - - await db.insert(complaints).values({ - userId, - orderId: orderIdNum, - complaintBody: complaintBody.trim(), - }); - - return { success: true, message: 'Complaint raised successfully' }; - }), -}); \ No newline at end of file diff --git a/apps/backend/src/trpc/user-apis/coupon.ts b/apps/backend/src/trpc/user-apis/coupon.ts deleted file mode 100644 index d3a5555..0000000 --- a/apps/backend/src/trpc/user-apis/coupon.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { router, protectedProcedure } from '../trpc-index'; -import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { coupons, couponUsage, couponApplicableUsers, reservedCoupons, couponApplicableProducts } from '../../db/schema'; -import { eq, and, or, gt, isNull, sql } from 'drizzle-orm'; -import { ApiError } from 'src/lib/api-error'; - -import { users } from '../../db/schema'; - -type CouponWithRelations = typeof coupons.$inferSelect & { - applicableUsers: (typeof couponApplicableUsers.$inferSelect & { user: typeof users.$inferSelect })[]; - usages: typeof couponUsage.$inferSelect[]; -}; - -export interface EligibleCoupon { - id: number; - code: string; - discountType: 'percentage' | 'flat'; - discountValue: number; - maxValue?: number; - minOrder?: number; - description: string; - exclusiveApply?: boolean; - isEligible: boolean; - ineligibilityReason?: string; -} - -const generateCouponDescription = (coupon: any): string => { - let desc = ''; - - if (coupon.discountPercent) { - desc += `${coupon.discountPercent}% off`; - } else if (coupon.flatDiscount) { - desc += `₹${coupon.flatDiscount} off`; - } - - if (coupon.minOrder) { - desc += ` on orders above ₹${coupon.minOrder}`; - } - - if (coupon.maxValue) { - desc += ` (max discount ₹${coupon.maxValue})`; - } - - return desc; -}; - -export interface CouponDisplay { - id: number; - code: string; - discountType: 'percentage' | 'flat'; - discountValue: number; - maxValue?: number; - minOrder?: number; - description: string; - validTill?: Date; - usageCount: number; - maxLimitForUser?: number; - isExpired: boolean; - isUsedUp: boolean; -} - -export const userCouponRouter = router({ - getEligible: protectedProcedure - .query(async ({ ctx }) => { - try { - - const userId = ctx.user.userId; - - // Get all active, non-expired coupons - const allCoupons = await db.query.coupons.findMany({ - where: and( - eq(coupons.isInvalidated, false), - or( - isNull(coupons.validTill), - gt(coupons.validTill, new Date()) - ) - ), - with: { - usages: { - where: eq(couponUsage.userId, userId) - }, - applicableUsers: { - with: { - user: true - } - }, - applicableProducts: { - with: { - product: true - } - }, - } - }); - - // Filter to only coupons applicable to current user - const applicableCoupons = allCoupons.filter(coupon => { - if(!coupon.isUserBased) return true; - const applicableUsers = coupon.applicableUsers || []; - return applicableUsers.some(au => au.userId === userId); - }); - - return { success: true, data: applicableCoupons }; - } - catch(e) { - console.log(e) - throw new ApiError("Unable to get coupons") - } - }), - - getProductCoupons: protectedProcedure - .input(z.object({ productId: z.number().int().positive() })) - .query(async ({ input, ctx }) => { - const userId = ctx.user.userId; - const { productId } = input; - - // Get all active, non-expired coupons - const allCoupons = await db.query.coupons.findMany({ - where: and( - eq(coupons.isInvalidated, false), - or( - isNull(coupons.validTill), - gt(coupons.validTill, new Date()) - ) - ), - with: { - usages: { - where: eq(couponUsage.userId, userId) - }, - applicableUsers: { - with: { - user: true - } - }, - applicableProducts: { - with: { - product: true - } - }, - } - }); - - // Filter to only coupons applicable to current user and product - const applicableCoupons = allCoupons.filter(coupon => { - const applicableUsers = coupon.applicableUsers || []; - const userApplicable = !coupon.isUserBased || applicableUsers.some(au => au.userId === userId); - - const applicableProducts = coupon.applicableProducts || []; - const productApplicable = applicableProducts.length === 0 || applicableProducts.some(ap => ap.productId === productId); - - return userApplicable && productApplicable; - }); - - return { success: true, data: applicableCoupons }; - }), - - getMyCoupons: protectedProcedure - .query(async ({ ctx }) => { - const userId = ctx.user.userId; - - // Get all coupons - const allCoupons = await db.query.coupons.findMany({ - with: { - usages: { - where: eq(couponUsage.userId, userId) - }, - applicableUsers: { - with: { - user: true - } - } - } - }); - - // Filter coupons in JS: not invalidated, applicable to user, and not expired - const applicableCoupons = (allCoupons as CouponWithRelations[]).filter(coupon => { - const isNotInvalidated = !coupon.isInvalidated; - const applicableUsers = coupon.applicableUsers || []; - const isApplicable = coupon.isApplyForAll || applicableUsers.some(au => au.userId === userId); - const isNotExpired = !coupon.validTill || new Date(coupon.validTill) > new Date(); - return isNotInvalidated && isApplicable && isNotExpired; - }); - - // Categorize coupons - const personalCoupons: CouponDisplay[] = []; - const generalCoupons: CouponDisplay[] = []; - - applicableCoupons.forEach(coupon => { - const usageCount = coupon.usages.length; - const isExpired = false; // Already filtered out expired coupons - const isUsedUp = Boolean(coupon.maxLimitForUser && usageCount >= coupon.maxLimitForUser); - - const couponDisplay: CouponDisplay = { - id: coupon.id, - code: coupon.couponCode, - discountType: coupon.discountPercent ? 'percentage' : 'flat', - discountValue: parseFloat(coupon.discountPercent || coupon.flatDiscount || '0'), - maxValue: coupon.maxValue ? parseFloat(coupon.maxValue) : undefined, - minOrder: coupon.minOrder ? parseFloat(coupon.minOrder) : undefined, - description: generateCouponDescription(coupon), - validTill: coupon.validTill ? new Date(coupon.validTill) : undefined, - usageCount, - maxLimitForUser: coupon.maxLimitForUser ? parseInt(coupon.maxLimitForUser.toString()) : undefined, - isExpired, - isUsedUp, - }; - - if ((coupon.applicableUsers || []).some(au => au.userId === userId) && !coupon.isApplyForAll) { - // Personal coupon - personalCoupons.push(couponDisplay); - } else if (coupon.isApplyForAll) { - // General coupon - generalCoupons.push(couponDisplay); - } - }); - - return { - success: true, - data: { - personal: personalCoupons, - general: generalCoupons, - } - }; - }), - - redeemReservedCoupon: protectedProcedure - .input(z.object({ secretCode: z.string() })) - .mutation(async ({ input, ctx }) => { - const userId = ctx.user.userId; - const { secretCode } = input; - - // Find the reserved coupon - const reservedCoupon = await db.query.reservedCoupons.findFirst({ - where: and( - eq(reservedCoupons.secretCode, secretCode.toUpperCase()), - eq(reservedCoupons.isRedeemed, false) - ), - }); - - if (!reservedCoupon) { - throw new ApiError("Invalid or already redeemed coupon code", 400); - } - - // Check if already redeemed by this user (in case of multiple attempts) - if (reservedCoupon.redeemedBy === userId) { - throw new ApiError("You have already redeemed this coupon", 400); - } - - // Create the coupon in the main table - const couponResult = await db.transaction(async (tx) => { - // Insert into coupons - const couponInsert = await tx.insert(coupons).values({ - couponCode: reservedCoupon.couponCode, - isUserBased: true, - discountPercent: reservedCoupon.discountPercent, - flatDiscount: reservedCoupon.flatDiscount, - minOrder: reservedCoupon.minOrder, - productIds: reservedCoupon.productIds, - maxValue: reservedCoupon.maxValue, - isApplyForAll: false, - validTill: reservedCoupon.validTill, - maxLimitForUser: reservedCoupon.maxLimitForUser, - exclusiveApply: reservedCoupon.exclusiveApply, - createdBy: reservedCoupon.createdBy, - }).returning(); - - const coupon = couponInsert[0]; - - // Insert into couponApplicableUsers - await tx.insert(couponApplicableUsers).values({ - couponId: coupon.id, - userId, - }); - - // Copy applicable products - if (reservedCoupon.productIds && Array.isArray(reservedCoupon.productIds) && reservedCoupon.productIds.length > 0) { - // Assuming productIds are the IDs, but wait, in schema, productIds is jsonb, but in relations, couponApplicableProducts has productId - // For simplicity, since reservedCoupons has productIds as jsonb, but to match, perhaps insert into couponApplicableProducts if needed - // But in createReservedCoupon, I inserted applicableProducts into couponApplicableProducts - // So for reserved, perhaps do the same, but since it's jsonb, maybe not. - // For now, skip, as the coupon will have productIds in coupons table. - } - - // Update reserved coupon as redeemed - await tx.update(reservedCoupons).set({ - isRedeemed: true, - redeemedBy: userId, - redeemedAt: new Date(), - }).where(eq(reservedCoupons.id, reservedCoupon.id)); - - return coupon; - }); - - return { success: true, coupon: couponResult }; - }), -}); \ No newline at end of file diff --git a/apps/backend/src/trpc/user-apis/file-upload.ts b/apps/backend/src/trpc/user-apis/file-upload.ts deleted file mode 100644 index 1a94223..0000000 --- a/apps/backend/src/trpc/user-apis/file-upload.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { router, protectedProcedure } from '../trpc-index'; -import { z } from 'zod'; -import { generateUploadUrl } from '../../lib/s3-client'; -import { ApiError } from '../../lib/api-error'; - -export const fileUploadRouter = router({ - generateUploadUrls: protectedProcedure - .input(z.object({ - contextString: z.enum(['review', 'product_info', 'notification']), - mimeTypes: z.array(z.string()), - })) - .mutation(async ({ input }): Promise<{ uploadUrls: string[] }> => { - const { contextString, mimeTypes } = input; - - const uploadUrls: string[] = []; - const keys: string[] = []; - - for (const mimeType of mimeTypes) { - // Generate key based on context and mime type - let folder: string; - if (contextString === 'review') { - folder = 'review-images'; - } else if(contextString === 'product_info') { - folder = 'product-images'; - } - // else if(contextString === 'review_response') { - // folder = 'review-response-images' - // } - else if(contextString === 'notification') { - folder = 'notification-images' - } else { - folder = ''; - } - - const extension = mimeType === 'image/jpeg' ? '.jpg' : - mimeType === 'image/png' ? '.png' : - mimeType === 'image/gif' ? '.gif' : '.jpg'; - const key = `${folder}/${Date.now()}${extension}`; - - try { - const uploadUrl = await generateUploadUrl(key, mimeType); - uploadUrls.push(uploadUrl); - keys.push(key); - - } catch (error) { - console.error('Error generating upload URL:', error); - throw new ApiError('Failed to generate upload URL', 500); - } - } - - return { uploadUrls }; - }), -}); - -export type FileUploadRouter = typeof fileUploadRouter; \ No newline at end of file diff --git a/apps/backend/src/trpc/user-apis/order.ts b/apps/backend/src/trpc/user-apis/order.ts deleted file mode 100644 index 3a4c5c1..0000000 --- a/apps/backend/src/trpc/user-apis/order.ts +++ /dev/null @@ -1,989 +0,0 @@ -import { router, protectedProcedure } from "../trpc-index"; -import { z } from "zod"; -import { db } from "../../db/db_index"; -import { - orders, - orderItems, - orderStatus, - addresses, - productInfo, - paymentInfoTable, - coupons, - couponUsage, - payments, - cartItems, - refunds, - units, - userDetails, -} from "../../db/schema"; -import { eq, and, inArray, desc, gte, lte } from "drizzle-orm"; -import { scaffoldAssetUrl } from "../../lib/s3-client"; -import { ApiError } from "../../lib/api-error"; -import { - sendOrderPlacedNotification, - sendOrderCancelledNotification, -} from "../../lib/notif-job"; -import { RazorpayPaymentService } from "../../lib/payments-utils"; -import { getNextDeliveryDate } from "../common-apis/common"; -import { CONST_KEYS, getConstant, getConstants } from "../../lib/const-store"; -import { publishFormattedOrder, publishCancellation } from "../../lib/post-order-handler"; -import { getSlotById } from "../../stores/slot-store"; - - -const validateAndGetCoupon = async ( - couponId: number | undefined, - userId: number, - totalAmount: number -) => { - if (!couponId) return null; - - const coupon = await db.query.coupons.findFirst({ - where: eq(coupons.id, couponId), - with: { - usages: { where: eq(couponUsage.userId, userId) }, - }, - }); - - if (!coupon) throw new ApiError("Invalid coupon", 400); - if (coupon.isInvalidated) - throw new ApiError("Coupon is no longer valid", 400); - if (coupon.validTill && new Date(coupon.validTill) < new Date()) - throw new ApiError("Coupon has expired", 400); - if ( - coupon.maxLimitForUser && - coupon.usages.length >= coupon.maxLimitForUser - ) - throw new ApiError("Coupon usage limit exceeded", 400); - if ( - coupon.minOrder && - parseFloat(coupon.minOrder.toString()) > totalAmount - ) - throw new ApiError( - "Order amount does not meet coupon minimum requirement", - 400 - ); - - return coupon; -}; - -const applyDiscountToOrder = ( - orderTotal: number, - appliedCoupon: typeof coupons.$inferSelect | null, - proportion: number -) => { - let finalOrderTotal = orderTotal; - // const proportion = totalAmount / orderTotal; - if (appliedCoupon) { - if (appliedCoupon.discountPercent) { - const discount = Math.min( - (orderTotal * - parseFloat(appliedCoupon.discountPercent.toString())) / - 100, - appliedCoupon.maxValue - ? parseFloat(appliedCoupon.maxValue.toString()) * proportion - : Infinity - ); - finalOrderTotal -= discount; - } else if (appliedCoupon.flatDiscount) { - const discount = Math.min( - parseFloat(appliedCoupon.flatDiscount.toString()) * proportion, - appliedCoupon.maxValue - ? parseFloat(appliedCoupon.maxValue.toString()) * proportion - : finalOrderTotal - ); - finalOrderTotal -= discount; - } - } - - // let orderDeliveryCharge = 0; - // if (isFirstOrder && finalOrderTotal < minOrderValue) { - // orderDeliveryCharge = deliveryCharge; - // finalOrderTotal += deliveryCharge; - // } - - - return { finalOrderTotal, orderGroupProportion: proportion }; -}; - -const placeOrderUtil = async (params: { - userId: number; - selectedItems: Array<{ - productId: number; - quantity: number; - slotId: number | null; - }>; - addressId: number; - paymentMethod: "online" | "cod"; - couponId?: number; - userNotes?: string; - isFlash?: boolean; -}) => { - const { - userId, - selectedItems, - addressId, - paymentMethod, - couponId, - userNotes, - } = params; - - const constants = await getConstants([ - CONST_KEYS.minRegularOrderValue, - CONST_KEYS.deliveryCharge, - CONST_KEYS.flashFreeDeliveryThreshold, - CONST_KEYS.flashDeliveryCharge, - ]); - - const isFlashDelivery = params.isFlash; - const minOrderValue = (isFlashDelivery ? constants[CONST_KEYS.flashFreeDeliveryThreshold] : constants[CONST_KEYS.minRegularOrderValue]) || 0; - const deliveryCharge = (isFlashDelivery ? constants[CONST_KEYS.flashDeliveryCharge] : constants[CONST_KEYS.deliveryCharge]) || 0; - - const orderGroupId = `${Date.now()}-${userId}`; - - const address = await db.query.addresses.findFirst({ - where: and(eq(addresses.userId, userId), eq(addresses.id, addressId)), - }); - if (!address) { - throw new ApiError("Invalid address", 400); - } - - const ordersBySlot = new Map< - number | null, - Array<{ - productId: number; - quantity: number; - slotId: number | null; - product: any; - }> - >(); - - for (const item of selectedItems) { - const product = await db.query.productInfo.findFirst({ - where: eq(productInfo.id, item.productId), - }); - if (!product) { - throw new ApiError(`Product ${item.productId} not found`, 400); - } - - if (!ordersBySlot.has(item.slotId)) { - ordersBySlot.set(item.slotId, []); - } - ordersBySlot.get(item.slotId)!.push({ ...item, product }); - } - - if (params.isFlash) { - for (const item of selectedItems) { - const product = await db.query.productInfo.findFirst({ - where: eq(productInfo.id, item.productId), - }); - if (!product?.isFlashAvailable) { - throw new ApiError(`Product ${item.productId} is not available for flash delivery`, 400); - } - } - } - - let totalAmount = 0; - for (const [slotId, items] of ordersBySlot) { - const orderTotal = items.reduce( - (sum, item) => { - const itemPrice = params.isFlash - ? parseFloat((item.product.flashPrice || item.product.price).toString()) - : parseFloat(item.product.price.toString()); - return sum + itemPrice * item.quantity; - }, - 0 - ); - totalAmount += orderTotal; - } - - const appliedCoupon = await validateAndGetCoupon(couponId, userId, totalAmount); - - const expectedDeliveryCharge = - totalAmount < minOrderValue ? deliveryCharge : 0; - - const totalWithDelivery = totalAmount + expectedDeliveryCharge; - - type OrderData = { - order: Omit; - orderItems: Omit[]; - orderStatus: Omit; - }; - - const ordersData: OrderData[] = []; - let isFirstOrder = true; - - for (const [slotId, items] of ordersBySlot) { - const subOrderTotal = items.reduce( - (sum, item) => { - const itemPrice = params.isFlash - ? parseFloat((item.product.flashPrice || item.product.price).toString()) - : parseFloat(item.product.price.toString()); - return sum + itemPrice * item.quantity; - }, - 0 - ); - const subOrderTotalWithDelivery = subOrderTotal + expectedDeliveryCharge; - - const orderGroupProportion = subOrderTotal / totalAmount; - const orderTotalAmount = isFirstOrder ? subOrderTotalWithDelivery : subOrderTotal; - - const { finalOrderTotal: finalOrderAmount } = applyDiscountToOrder( - orderTotalAmount, - appliedCoupon, - orderGroupProportion - ); - - const order: Omit = { - userId, - addressId, - slotId: params.isFlash ? null : slotId, - isCod: paymentMethod === "cod", - isOnlinePayment: paymentMethod === "online", - paymentInfoId: null, - totalAmount: finalOrderAmount.toString(), - deliveryCharge: isFirstOrder ? expectedDeliveryCharge.toString() : "0", - readableId: -1, - userNotes: userNotes || null, - orderGroupId, - orderGroupProportion: orderGroupProportion.toString(), - isFlashDelivery: params.isFlash, - }; - - const orderItemsData: Omit[] = items.map( - (item) => ({ - orderId: 0, - productId: item.productId, - quantity: item.quantity.toString(), - price: params.isFlash - ? item.product.flashPrice || item.product.price - : item.product.price, - discountedPrice: ( - params.isFlash - ? item.product.flashPrice || item.product.price - : item.product.price - ).toString(), - }) - ); - - const orderStatusData: Omit = { - userId, - orderId: 0, - paymentStatus: paymentMethod === "cod" ? "cod" : "pending", - }; - - ordersData.push({ order, orderItems: orderItemsData, orderStatus: orderStatusData }); - isFirstOrder = false; - } - - const createdOrders = await db.transaction(async (tx) => { - let sharedPaymentInfoId: number | null = null; - if (paymentMethod === "online") { - const [paymentInfo] = await tx - .insert(paymentInfoTable) - .values({ - status: "pending", - gateway: "razorpay", - merchantOrderId: `multi_order_${Date.now()}`, - }) - .returning(); - sharedPaymentInfoId = paymentInfo.id; - } - - const ordersToInsert: Omit[] = ordersData.map( - (od) => ({ - ...od.order, - paymentInfoId: sharedPaymentInfoId, - }) - ); - - const insertedOrders = await tx.insert(orders).values(ordersToInsert).returning(); - - const allOrderItems: Omit[] = []; - const allOrderStatuses: Omit[] = []; - - insertedOrders.forEach((order, index) => { - const od = ordersData[index]; - od.orderItems.forEach((item) => { - allOrderItems.push({ ...item, orderId: order.id as number }); - }); - allOrderStatuses.push({ - ...od.orderStatus, - orderId: order.id as number, - }); - }); - - await tx.insert(orderItems).values(allOrderItems); - await tx.insert(orderStatus).values(allOrderStatuses); - - if (paymentMethod === "online" && sharedPaymentInfoId) { - const razorpayOrder = await RazorpayPaymentService.createOrder( - sharedPaymentInfoId, - totalWithDelivery.toString() - ); - await RazorpayPaymentService.insertPaymentRecord( - sharedPaymentInfoId, - razorpayOrder, - tx - ); - } - - return insertedOrders; - }); - - await db.delete(cartItems).where( - and( - eq(cartItems.userId, userId), - inArray( - cartItems.productId, - selectedItems.map((item) => item.productId) - ) - ) - ); - - if (appliedCoupon && createdOrders.length > 0) { - await db.insert(couponUsage).values({ - userId, - couponId: appliedCoupon.id, - orderId: createdOrders[0].id as number, - orderItemId: null, - usedAt: new Date(), - }); - } - - for (const order of createdOrders) { - sendOrderPlacedNotification(userId, order.id.toString()); - } - - await publishFormattedOrder(createdOrders, ordersBySlot); - - return { success: true, data: createdOrders }; -}; - -export const orderRouter = router({ - placeOrder: protectedProcedure - .input( - z.object({ - selectedItems: z.array( - z.object({ - productId: z.number().int().positive(), - quantity: z.number().int().positive(), - slotId: z.union([z.number().int(), z.null()]), - }) - ), - addressId: z.number().int().positive(), - paymentMethod: z.enum(["online", "cod"]), - couponId: z.number().int().positive().optional(), - userNotes: z.string().optional(), - isFlashDelivery: z.boolean().optional().default(false), - }) - ) - .mutation(async ({ input, ctx }) => { - const userId = ctx.user.userId; - - // Check if user is suspended from placing orders - const userDetail = await db.query.userDetails.findFirst({ - where: eq(userDetails.userId, userId), - }); - - if (userDetail?.isSuspended) { - throw new ApiError("Unable to place order", 403); - } - - const { - selectedItems, - addressId, - paymentMethod, - couponId, - userNotes, - isFlashDelivery, - } = input; - - // Check if flash delivery is enabled when placing a flash delivery order - if (isFlashDelivery) { - const isFlashDeliveryEnabled = await getConstant(CONST_KEYS.isFlashDeliveryEnabled); - if (!isFlashDeliveryEnabled) { - throw new ApiError("Flash delivery is currently unavailable. Please opt for scheduled delivery.", 403); - } - } - - // Check if any selected slot is at full capacity (only for regular delivery) - if (!isFlashDelivery) { - const slotIds = [...new Set(selectedItems.filter(i => i.slotId !== null).map(i => i.slotId as number))]; - for (const slotId of slotIds) { - const slot = await getSlotById(slotId); - if (slot?.isCapacityFull) { - throw new ApiError("Selected delivery slot is at full capacity. Please choose another slot.", 403); - } - } - } - - let processedItems = selectedItems; - - // Handle flash delivery slot resolution - if (isFlashDelivery) { - // For flash delivery, set slotId to null (no specific slot assigned) - processedItems = selectedItems.map(item => ({ - ...item, - slotId: null as any, // Type override for flash delivery - })); - } - - return await placeOrderUtil({ - userId, - selectedItems: processedItems, - addressId, - paymentMethod, - couponId, - userNotes, - isFlash: isFlashDelivery, - }); - }), - - getOrders: protectedProcedure - .input( - z - .object({ - page: z.number().min(1).default(1), - pageSize: z.number().min(1).max(50).default(10), - }) - .optional() - ) - .query(async ({ input, ctx }) => { - const { page = 1, pageSize = 10 } = input || {}; - const userId = ctx.user.userId; - const offset = (page - 1) * pageSize; - - // Get total count for pagination - const totalCountResult = await db.$count( - orders, - eq(orders.userId, userId) - ); - const totalCount = totalCountResult; - - const userOrders = await db.query.orders.findMany({ - where: eq(orders.userId, userId), - with: { - orderItems: { - with: { - product: true, - }, - }, - slot: true, - paymentInfo: true, - orderStatus: true, - refunds: true, - }, - orderBy: (orders, { desc }) => [desc(orders.createdAt)], - limit: pageSize, - offset: offset, - }); - - const mappedOrders = await Promise.all( - userOrders.map(async (order) => { - const status = order.orderStatus[0]; - const refund = order.refunds[0]; - - type DeliveryStatus = "cancelled" | "success" | "pending" | "packaged"; - type OrderStatus = "cancelled" | "success"; - - let deliveryStatus: DeliveryStatus; - let orderStatus: OrderStatus; - - const allItemsPackaged = order.orderItems.every( - (item) => item.is_packaged - ); - - if (status?.isCancelled) { - deliveryStatus = "cancelled"; - orderStatus = "cancelled"; - } else if (status?.isDelivered) { - deliveryStatus = "success"; - orderStatus = "success"; - } else if (allItemsPackaged) { - deliveryStatus = "packaged"; - orderStatus = "success"; - } else { - deliveryStatus = "pending"; - orderStatus = "success"; - } - - const paymentMode = order.isCod ? "CoD" : "Online"; - const paymentStatus = status?.paymentStatus || "pending"; - const refundStatus = refund?.refundStatus || "none"; - const refundAmount = refund?.refundAmount - ? parseFloat(refund.refundAmount.toString()) - : null; - - const items = await Promise.all( - order.orderItems.map(async (item) => { - - const signedImages = item.product.images - ? scaffoldAssetUrl( - item.product.images as string[] - ) - : []; - return { - productName: item.product.name, - quantity: parseFloat(item.quantity), - price: parseFloat(item.price.toString()), - discountedPrice: parseFloat( - item.discountedPrice?.toString() || item.price.toString() - ), - amount: - parseFloat(item.price.toString()) * parseFloat(item.quantity), - image: signedImages[0] || null, - }; - }) - ); - - return { - id: order.id, - orderId: `ORD${order.id}`, - orderDate: order.createdAt.toISOString(), - deliveryStatus, - deliveryDate: order.slot?.deliveryTime.toISOString(), - orderStatus, - cancelReason: status?.cancelReason || null, - paymentMode, - totalAmount: Number(order.totalAmount), - deliveryCharge: Number(order.deliveryCharge), - paymentStatus, - refundStatus, - refundAmount, - userNotes: order.userNotes || null, - items, - isFlashDelivery: order.isFlashDelivery, - createdAt: order.createdAt.toISOString(), - }; - }) - ); - - return { - success: true, - data: mappedOrders, - pagination: { - page, - pageSize, - totalCount, - totalPages: Math.ceil(totalCount / pageSize), - }, - }; - }), - - getOrderById: protectedProcedure - .input(z.object({ orderId: z.string() })) - .query(async ({ input, ctx }) => { - const { orderId } = input; - const userId = ctx.user.userId; - - const order = await db.query.orders.findFirst({ - where: and(eq(orders.id, parseInt(orderId)), eq(orders.userId, userId)), - with: { - orderItems: { - with: { - product: true, - }, - }, - slot: true, - paymentInfo: true, - orderStatus: { - with: { - refundCoupon: true, - }, - }, - refunds: true, - }, - }); - - if (!order) { - throw new Error("Order not found"); - } - - // Get coupon usage for this specific order using new orderId field - const couponUsageData = await db.query.couponUsage.findMany({ - where: eq(couponUsage.orderId, order.id), // Use new orderId field - with: { - coupon: true, - }, - }); - - let couponData = null; - if (couponUsageData.length > 0) { - // Calculate total discount from multiple coupons - let totalDiscountAmount = 0; - const orderTotal = parseFloat(order.totalAmount.toString()); - - for (const usage of couponUsageData) { - let discountAmount = 0; - - if (usage.coupon.discountPercent) { - discountAmount = - (orderTotal * - parseFloat(usage.coupon.discountPercent.toString())) / - 100; - } else if (usage.coupon.flatDiscount) { - discountAmount = parseFloat(usage.coupon.flatDiscount.toString()); - } - - // Apply max value limit if set - if ( - usage.coupon.maxValue && - discountAmount > parseFloat(usage.coupon.maxValue.toString()) - ) { - discountAmount = parseFloat(usage.coupon.maxValue.toString()); - } - - totalDiscountAmount += discountAmount; - } - - couponData = { - couponCode: couponUsageData - .map((u) => u.coupon.couponCode) - .join(", "), - couponDescription: `${couponUsageData.length} coupons applied`, - discountAmount: totalDiscountAmount, - }; - } - - const status = order.orderStatus[0]; - const refund = order.refunds[0]; - - type DeliveryStatus = "cancelled" | "success" | "pending" | "packaged"; - type OrderStatus = "cancelled" | "success"; - - let deliveryStatus: DeliveryStatus; - let orderStatus: OrderStatus; - - const allItemsPackaged = order.orderItems.every( - (item) => item.is_packaged - ); - - if (status?.isCancelled) { - deliveryStatus = "cancelled"; - orderStatus = "cancelled"; - } else if (status?.isDelivered) { - deliveryStatus = "success"; - orderStatus = "success"; - } else if (allItemsPackaged) { - deliveryStatus = "packaged"; - orderStatus = "success"; - } else { - deliveryStatus = "pending"; - orderStatus = "success"; - } - - const paymentMode = order.isCod ? "CoD" : "Online"; - const paymentStatus = status?.paymentStatus || "pending"; - const refundStatus = refund?.refundStatus || "none"; - const refundAmount = refund?.refundAmount - ? parseFloat(refund.refundAmount.toString()) - : null; - - const items = await Promise.all( - order.orderItems.map(async (item) => { - const signedImages = item.product.images - ? scaffoldAssetUrl( - item.product.images as string[] - ) - : []; - return { - productName: item.product.name, - quantity: parseFloat(item.quantity), - price: parseFloat(item.price.toString()), - discountedPrice: parseFloat( - item.discountedPrice?.toString() || item.price.toString() - ), - amount: - parseFloat(item.price.toString()) * parseFloat(item.quantity), - image: signedImages[0] || null, - }; - }) - ); - - return { - id: order.id, - orderId: `ORD${order.id}`, - orderDate: order.createdAt.toISOString(), - deliveryStatus, - deliveryDate: order.slot?.deliveryTime.toISOString(), - orderStatus: order.orderStatus, - cancellationStatus: orderStatus, - cancelReason: status?.cancelReason || null, - paymentMode, - paymentStatus, - refundStatus, - refundAmount, - userNotes: order.userNotes || null, - items, - couponCode: couponData?.couponCode || null, - couponDescription: couponData?.couponDescription || null, - discountAmount: couponData?.discountAmount || null, - orderAmount: parseFloat(order.totalAmount.toString()), - isFlashDelivery: order.isFlashDelivery, - createdAt: order.createdAt.toISOString(), - }; - }), - - cancelOrder: protectedProcedure - .input( - z.object({ - // id: z.string().regex(/^ORD\d+$/, "Invalid order ID format"), - id: z.number(), - reason: z.string().min(1, "Cancellation reason is required"), - }) - ) - .mutation(async ({ input, ctx }) => { - try { - const userId = ctx.user.userId; - const { id, reason } = input; - - // Check if order exists and belongs to user - const order = await db.query.orders.findFirst({ - where: eq(orders.id, Number(id)), - with: { - orderStatus: true, - }, - }); - - if (!order) { - console.error("Order not found:", id); - throw new ApiError("Order not found", 404); - } - - if (order.userId !== userId) { - console.error("Order does not belong to user:", { - orderId: id, - orderUserId: order.userId, - requestUserId: userId, - }); - - throw new ApiError("Order not found", 404); - } - - const status = order.orderStatus[0]; - if (!status) { - console.error("Order status not found for order:", id); - throw new ApiError("Order status not found", 400); - } - - if (status.isCancelled) { - console.error("Order is already cancelled:", id); - throw new ApiError("Order is already cancelled", 400); - } - - if (status.isDelivered) { - console.error("Cannot cancel delivered order:", id); - throw new ApiError("Cannot cancel delivered order", 400); - } - - // Perform database operations in transaction - const result = await db.transaction(async (tx) => { - // Update order status - await tx - .update(orderStatus) - .set({ - isCancelled: true, - cancelReason: reason, - cancellationUserNotes: reason, - cancellationReviewed: false, - }) - .where(eq(orderStatus.id, status.id)); - - // Determine refund status based on payment method - const refundStatus = order.isCod ? "na" : "pending"; - - // Insert refund record - await tx.insert(refunds).values({ - orderId: order.id, - refundStatus, - }); - - return { orderId: order.id, userId }; - }); - - // Send notification outside transaction (idempotent operation) - await sendOrderCancelledNotification( - result.userId, - result.orderId.toString() - ); - - // Publish to Redis for Telegram notification - await publishCancellation(result.orderId, 'user', reason); - - return { success: true, message: "Order cancelled successfully" }; - } catch (e) { - console.log(e); - throw new ApiError("failed to cancel order"); - } - }), - - updateUserNotes: protectedProcedure - .input( - z.object({ - id: z.number(), - userNotes: z.string(), - }) - ) - .mutation(async ({ input, ctx }) => { - const userId = ctx.user.userId; - const { id, userNotes } = input; - - // Extract readable ID from orderId (e.g., ORD001 -> 1) - // const readableIdMatch = id.match(/^ORD(\d+)$/); - // if (!readableIdMatch) { - // console.error("Invalid order ID format:", id); - // throw new ApiError("Invalid order ID format", 400); - // } - // const readableId = parseInt(readableIdMatch[1]); - - // Check if order exists and belongs to user - const order = await db.query.orders.findFirst({ - where: eq(orders.id, Number(id)), - with: { - orderStatus: true, - }, - }); - - if (!order) { - console.error("Order not found:", id); - throw new ApiError("Order not found", 404); - } - - if (order.userId !== userId) { - console.error("Order does not belong to user:", { - orderId: id, - orderUserId: order.userId, - requestUserId: userId, - }); - throw new ApiError("Order not found", 404); - } - - const status = order.orderStatus[0]; - if (!status) { - console.error("Order status not found for order:", id); - throw new ApiError("Order status not found", 400); - } - - // Only allow updating notes for orders that are not delivered or cancelled - if (status.isDelivered) { - console.error("Cannot update notes for delivered order:", id); - throw new ApiError("Cannot update notes for delivered order", 400); - } - - if (status.isCancelled) { - console.error("Cannot update notes for cancelled order:", id); - throw new ApiError("Cannot update notes for cancelled order", 400); - } - - // Update user notes - await db - .update(orders) - .set({ - userNotes: userNotes || null, - }) - .where(eq(orders.id, order.id)); - - return { success: true, message: "Notes updated successfully" }; - }), - - getRecentlyOrderedProducts: protectedProcedure - .input( - z - .object({ - limit: z.number().min(1).max(50).default(20), - }) - .optional() - ) - .query(async ({ input, ctx }) => { - const { limit = 20 } = input || {}; - const userId = ctx.user.userId; - - // Get user's recent delivered orders (last 30 days) - const thirtyDaysAgo = new Date(); - thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); - - const recentOrders = await db - .select({ id: orders.id }) - .from(orders) - .innerJoin(orderStatus, eq(orders.id, orderStatus.orderId)) - .where( - and( - eq(orders.userId, userId), - eq(orderStatus.isDelivered, true), - gte(orders.createdAt, thirtyDaysAgo) - ) - ) - .orderBy(desc(orders.createdAt)) - .limit(10); // Get last 10 orders - - if (recentOrders.length === 0) { - return { success: true, products: [] }; - } - - const orderIds = recentOrders.map((order) => order.id); - - // Get unique product IDs from recent orders - const orderItemsResult = await db - .select({ productId: orderItems.productId }) - .from(orderItems) - .where(inArray(orderItems.orderId, orderIds)); - - const productIds = [ - ...new Set(orderItemsResult.map((item) => item.productId)), - ]; - - if (productIds.length === 0) { - return { success: true, products: [] }; - } - - // Get product details - const productsWithUnits = await db - .select({ - id: productInfo.id, - name: productInfo.name, - shortDescription: productInfo.shortDescription, - price: productInfo.price, - images: productInfo.images, - isOutOfStock: productInfo.isOutOfStock, - unitShortNotation: units.shortNotation, - incrementStep: productInfo.incrementStep, - }) - .from(productInfo) - .innerJoin(units, eq(productInfo.unitId, units.id)) - .where( - and( - inArray(productInfo.id, productIds), - eq(productInfo.isSuspended, false) - ) - ) - .orderBy(desc(productInfo.createdAt)) - .limit(limit); - - // Generate signed URLs for product images - const formattedProducts = await Promise.all( - productsWithUnits.map(async (product) => { - const nextDeliveryDate = await getNextDeliveryDate(product.id); - return { - id: product.id, - name: product.name, - shortDescription: product.shortDescription, - price: product.price, - unit: product.unitShortNotation, - incrementStep: product.incrementStep, - isOutOfStock: product.isOutOfStock, - nextDeliveryDate: nextDeliveryDate - ? nextDeliveryDate.toISOString() - : null, - images: scaffoldAssetUrl( - (product.images as string[]) || [] - ), - }; - }) - ); - - return { - success: true, - products: formattedProducts, - }; - }), -}); diff --git a/apps/backend/src/trpc/user-apis/payments.ts b/apps/backend/src/trpc/user-apis/payments.ts deleted file mode 100644 index 851f232..0000000 --- a/apps/backend/src/trpc/user-apis/payments.ts +++ /dev/null @@ -1,159 +0,0 @@ - -import { router, protectedProcedure } from '../trpc-index'; -import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { orders, payments, orderStatus } from '../../db/schema'; -import { eq } from 'drizzle-orm'; -import { ApiError } from '../../lib/api-error'; -import crypto from 'crypto'; -import { razorpayId, razorpaySecret } from "../../lib/env-exporter"; -import { DiskPersistedSet } from "src/lib/disk-persisted-set"; -import { RazorpayPaymentService } from "../../lib/payments-utils"; - - - - -export const paymentRouter = router({ - createRazorpayOrder: protectedProcedure //either create a new payment order or return the existing one - .input(z.object({ - orderId: z.string(), - })) - .mutation(async ({ input, ctx }) => { - const userId = ctx.user.userId; - const { orderId } = input; - - // Validate order exists and belongs to user - const order = await db.query.orders.findFirst({ - where: eq(orders.id, parseInt(orderId)), - }); - - if (!order) { - throw new ApiError("Order not found", 404); - } - - if (order.userId !== userId) { - throw new ApiError("Order does not belong to user", 403); - } - - // Check for existing pending payment - const existingPayment = await db.query.payments.findFirst({ - where: eq(payments.orderId, parseInt(orderId)), - }); - - if (existingPayment && existingPayment.status === 'pending') { - return { - razorpayOrderId: existingPayment.merchantOrderId, - key: razorpayId, - }; - } - - // Create Razorpay order and insert payment record - const razorpayOrder = await RazorpayPaymentService.createOrder(parseInt(orderId), order.totalAmount); - await RazorpayPaymentService.insertPaymentRecord(parseInt(orderId), razorpayOrder); - - return { - razorpayOrderId: razorpayOrder.id, - key: razorpayId, - }; - }), - - - - verifyPayment: protectedProcedure - .input(z.object({ - razorpay_payment_id: z.string(), - razorpay_order_id: z.string(), - razorpay_signature: z.string(), - })) - .mutation(async ({ input, ctx }) => { - const { razorpay_payment_id, razorpay_order_id, razorpay_signature } = input; - - // Verify signature - const expectedSignature = crypto - .createHmac('sha256', razorpaySecret) - .update(razorpay_order_id + '|' + razorpay_payment_id) - .digest('hex'); - - if (expectedSignature !== razorpay_signature) { - throw new ApiError("Invalid payment signature", 400); - } - - // Get current payment record - const currentPayment = await db.query.payments.findFirst({ - where: eq(payments.merchantOrderId, razorpay_order_id), - }); - - if (!currentPayment) { - throw new ApiError("Payment record not found", 404); - } - - // Update payment status and payload - const updatedPayload = { - ...((currentPayment.payload as any) || {}), - payment_id: razorpay_payment_id, - signature: razorpay_signature, - }; - - const [updatedPayment] = await db - .update(payments) - .set({ - status: 'success', - payload: updatedPayload, - }) - .where(eq(payments.merchantOrderId, razorpay_order_id)) - .returning(); - - // Update order status to mark payment as processed - await db - .update(orderStatus) - .set({ - paymentStatus: 'success', - }) - .where(eq(orderStatus.orderId, updatedPayment.orderId)); - - return { - success: true, - message: "Payment verified successfully", - }; - }), - - markPaymentFailed: protectedProcedure - .input(z.object({ - merchantOrderId: z.string(), - })) - .mutation(async ({ input, ctx }) => { - const userId = ctx.user.userId; - const { merchantOrderId } = input; - - // Find payment by merchantOrderId - const payment = await db.query.payments.findFirst({ - where: eq(payments.merchantOrderId, merchantOrderId), - }); - - if (!payment) { - throw new ApiError("Payment not found", 404); - } - - // Check if payment belongs to user's order - const order = await db.query.orders.findFirst({ - where: eq(orders.id, payment.orderId), - }); - - if (!order || order.userId !== userId) { - throw new ApiError("Payment does not belong to user", 403); - } - - // Update payment status to failed - await db - .update(payments) - .set({ status: 'failed' }) - .where(eq(payments.id, payment.id)); - - return { - success: true, - message: "Payment marked as failed", - }; - }), - -}); - diff --git a/apps/backend/src/trpc/user-apis/product.ts b/apps/backend/src/trpc/user-apis/product.ts deleted file mode 100644 index 88dc63f..0000000 --- a/apps/backend/src/trpc/user-apis/product.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { router, publicProcedure, protectedProcedure } from '../trpc-index'; -import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { productInfo, units, productSlots, deliverySlotInfo, specialDeals, storeInfo, productTagInfo, productTags, productReviews, users } from '../../db/schema'; -import { claimUploadUrl, extractKeyFromPresignedUrl, scaffoldAssetUrl } from '../../lib/s3-client'; -import { ApiError } from '../../lib/api-error'; -import { eq, and, gt, sql, inArray, desc } from 'drizzle-orm'; -import { getProductById as getProductByIdFromCache, getAllProducts as getAllProductsFromCache } from '../../stores/product-store'; -import dayjs from 'dayjs'; - -// Uniform Product Type -interface Product { - id: number; - name: string; - shortDescription: string | null; - longDescription: string | null; - price: string; - marketPrice: string | null; - unitNotation: string; - images: string[]; - isOutOfStock: boolean; - store: { id: number; name: string; description: string | null } | null; - incrementStep: number; - productQuantity: number; - isFlashAvailable: boolean; - flashPrice: string | null; - deliverySlots: Array<{ id: number; deliveryTime: Date; freezeTime: Date }>; - specialDeals: Array<{ quantity: string; price: string; validTill: Date }>; -} - -export const productRouter = router({ - getProductDetails: publicProcedure - .input(z.object({ - id: z.string().regex(/^\d+$/, 'Invalid product ID'), - })) - .query(async ({ input }): Promise => { - const { id } = input; - const productId = parseInt(id); - - if (isNaN(productId)) { - throw new Error('Invalid product ID'); - } - - console.log('from the api to get product details') - -// First, try to get the product from Redis cache - const cachedProduct = await getProductByIdFromCache(productId); - - if (cachedProduct) { - // Filter delivery slots to only include those with future freeze times and not at full capacity - const currentTime = new Date(); - const filteredSlots = cachedProduct.deliverySlots.filter(slot => - dayjs(slot.freezeTime).isAfter(currentTime) && !slot.isCapacityFull - ); - - return { - ...cachedProduct, - deliverySlots: filteredSlots - }; - } - - // If not in cache, fetch from database (fallback) - const productData = await db - .select({ - id: productInfo.id, - name: productInfo.name, - shortDescription: productInfo.shortDescription, - longDescription: productInfo.longDescription, - price: productInfo.price, - marketPrice: productInfo.marketPrice, - images: productInfo.images, - isOutOfStock: productInfo.isOutOfStock, - storeId: productInfo.storeId, - unitShortNotation: units.shortNotation, - incrementStep: productInfo.incrementStep, - productQuantity: productInfo.productQuantity, - isFlashAvailable: productInfo.isFlashAvailable, - flashPrice: productInfo.flashPrice, - }) - .from(productInfo) - .innerJoin(units, eq(productInfo.unitId, units.id)) - .where(eq(productInfo.id, productId)) - .limit(1); - - if (productData.length === 0) { - throw new Error('Product not found'); - } - - const product = productData[0]; - - // Fetch store info for this product - const storeData = product.storeId ? await db.query.storeInfo.findFirst({ - where: eq(storeInfo.id, product.storeId), - columns: { id: true, name: true, description: true }, - }) : null; - - // Fetch delivery slots for this product - const deliverySlotsData = await db - .select({ - id: deliverySlotInfo.id, - deliveryTime: deliverySlotInfo.deliveryTime, - freezeTime: deliverySlotInfo.freezeTime, - }) - .from(productSlots) - .innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id)) - .where( - and( - eq(productSlots.productId, productId), - eq(deliverySlotInfo.isActive, true), - eq(deliverySlotInfo.isCapacityFull, false), - gt(deliverySlotInfo.deliveryTime, sql`NOW()`), - gt(deliverySlotInfo.freezeTime, sql`NOW()`) - ) - ) - .orderBy(deliverySlotInfo.deliveryTime); - - // Fetch special deals for this product - const specialDealsData = await db - .select({ - quantity: specialDeals.quantity, - price: specialDeals.price, - validTill: specialDeals.validTill, - }) - .from(specialDeals) - .where( - and( - eq(specialDeals.productId, productId), - gt(specialDeals.validTill, sql`NOW()`) - ) - ) - .orderBy(specialDeals.quantity); - - // Generate signed URLs for images - const signedImages = scaffoldAssetUrl((product.images as string[]) || []); - - const response: Product = { - id: product.id, - name: product.name, - shortDescription: product.shortDescription, - longDescription: product.longDescription, - price: product.price.toString(), - marketPrice: product.marketPrice?.toString() || null, - unitNotation: product.unitShortNotation, - images: signedImages, - isOutOfStock: product.isOutOfStock, - store: storeData ? { - id: storeData.id, - name: storeData.name, - description: storeData.description, - } : null, - incrementStep: product.incrementStep, - productQuantity: product.productQuantity, - isFlashAvailable: product.isFlashAvailable, - flashPrice: product.flashPrice?.toString() || null, - deliverySlots: deliverySlotsData, - specialDeals: specialDealsData.map(d => ({ quantity: d.quantity.toString(), price: d.price.toString(), validTill: d.validTill })), - }; - - return response; - }), - - getProductReviews: publicProcedure - .input(z.object({ - productId: z.number().int().positive(), - limit: z.number().int().min(1).max(50).optional().default(10), - offset: z.number().int().min(0).optional().default(0), - })) - .query(async ({ input }) => { - const { productId, limit, offset } = input; - - const reviews = await db - .select({ - id: productReviews.id, - reviewBody: productReviews.reviewBody, - ratings: productReviews.ratings, - imageUrls: productReviews.imageUrls, - reviewTime: productReviews.reviewTime, - userName: users.name, - }) - .from(productReviews) - .innerJoin(users, eq(productReviews.userId, users.id)) - .where(eq(productReviews.productId, productId)) - .orderBy(desc(productReviews.reviewTime)) - .limit(limit) - .offset(offset); - - // Generate signed URLs for images - const reviewsWithSignedUrls = await Promise.all( - reviews.map(async (review) => ({ - ...review, - signedImageUrls: scaffoldAssetUrl((review.imageUrls as string[]) || []), - })) - ); - - // Check if more reviews exist - const totalCountResult = await db - .select({ count: sql`count(*)` }) - .from(productReviews) - .where(eq(productReviews.productId, productId)); - - const totalCount = Number(totalCountResult[0].count); - const hasMore = offset + limit < totalCount; - - return { reviews: reviewsWithSignedUrls, hasMore }; - }), - - createReview: protectedProcedure - .input(z.object({ - productId: z.number().int().positive(), - reviewBody: z.string().min(1, 'Review body is required'), - ratings: z.number().int().min(1).max(5), - imageUrls: z.array(z.string()).optional().default([]), - uploadUrls: z.array(z.string()).optional().default([]), - })) - .mutation(async ({ input, ctx }) => { - const { productId, reviewBody, ratings, imageUrls, uploadUrls } = input; - const userId = ctx.user.userId; - - // Optional: Check if product exists - const product = await db.query.productInfo.findFirst({ - where: eq(productInfo.id, productId), - }); - if (!product) { - throw new ApiError('Product not found', 404); - } - - // Insert review - const [newReview] = await db.insert(productReviews).values({ - userId, - productId, - reviewBody, - ratings, - imageUrls: uploadUrls.map(item => extractKeyFromPresignedUrl(item)), - }).returning(); - - // Claim upload URLs - if (uploadUrls && uploadUrls.length > 0) { - try { - await Promise.all(uploadUrls.map(url => claimUploadUrl(url))); - } catch (error) { - console.error('Error claiming upload URLs:', error); - // Don't fail the review creation - } - } - - return { success: true, review: newReview }; - }), - - getAllProductsSummary: publicProcedure - .query(async (): Promise => { - // Get all products from cache - const allCachedProducts = await getAllProductsFromCache(); - - // Transform the cached products to match the expected summary format - // (with empty deliverySlots and specialDeals arrays for summary view) - const transformedProducts = allCachedProducts.map(product => ({ - ...product, - deliverySlots: [], // Empty for summary view - specialDeals: [], // Empty for summary view - })); - - return transformedProducts; - }), - -}); diff --git a/apps/backend/src/trpc/user-apis/slots.ts b/apps/backend/src/trpc/user-apis/slots.ts deleted file mode 100644 index 5aa857a..0000000 --- a/apps/backend/src/trpc/user-apis/slots.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { router, publicProcedure } from "../trpc-index"; -import { z } from "zod"; -import { db } from "../../db/db_index"; -import { - deliverySlotInfo, - productSlots, - productInfo, - units, -} from "../../db/schema"; -import { eq, and, gt, asc } from "drizzle-orm"; -import { getAllSlots as getAllSlotsFromCache, getSlotById as getSlotByIdFromCache } from "../../stores/slot-store"; -import dayjs from 'dayjs'; - -// Helper method to get formatted slot data by ID -async function getSlotData(slotId: number) { - const slot = await getSlotByIdFromCache(slotId); - - if (!slot) { - return null; - } - - const currentTime = new Date(); - if (dayjs(slot.freezeTime).isBefore(currentTime)) { - return null; - } - - return { - deliveryTime: slot.deliveryTime, - freezeTime: slot.freezeTime, - slotId: slot.id, - products: slot.products.filter((product) => !product.isOutOfStock), - }; -} - -export const slotsRouter = router({ - getSlots: publicProcedure.query(async () => { - const slots = await db.query.deliverySlotInfo.findMany({ - where: eq(deliverySlotInfo.isActive, true), - }); - return { - slots, - count: slots.length, - }; - }), - - getSlotsWithProducts: publicProcedure.query(async () => { - const allSlots = await getAllSlotsFromCache(); - const currentTime = new Date(); - const validSlots = allSlots - .filter((slot) => { - return dayjs(slot.freezeTime).isAfter(currentTime) && - dayjs(slot.deliveryTime).isAfter(currentTime) && - !slot.isCapacityFull; - }) - .sort((a, b) => dayjs(a.deliveryTime).valueOf() - dayjs(b.deliveryTime).valueOf()); - - return { - slots: validSlots, - count: validSlots.length, - }; - }), - - nextMajorDelivery: publicProcedure.query(async () => { - const now = new Date(); - - // Find the next upcoming active delivery slot ID - const nextSlot = await db.query.deliverySlotInfo.findFirst({ - where: and( - eq(deliverySlotInfo.isActive, true), - gt(deliverySlotInfo.deliveryTime, now), - ), - orderBy: asc(deliverySlotInfo.deliveryTime), - }); - - if (!nextSlot) { - return null; // No upcoming delivery slots - } - - // Get formatted data using helper method - return await getSlotData(nextSlot.id); - }), - - getSlotById: publicProcedure - .input(z.object({ slotId: z.number() })) - .query(async ({ input }) => { - return await getSlotData(input.slotId); - }), -}); diff --git a/apps/backend/src/trpc/user-apis/stores.ts b/apps/backend/src/trpc/user-apis/stores.ts deleted file mode 100644 index 3f51787..0000000 --- a/apps/backend/src/trpc/user-apis/stores.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { router, publicProcedure } from '../trpc-index'; -import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { storeInfo, productInfo, units } from '../../db/schema'; -import { eq, and, sql } from 'drizzle-orm'; -import { scaffoldAssetUrl } from '../../lib/s3-client'; -import { ApiError } from '../../lib/api-error'; - -export const storesRouter = router({ - getStores: publicProcedure - .query(async () => { - const storesData = await db - .select({ - id: storeInfo.id, - name: storeInfo.name, - description: storeInfo.description, - imageUrl: storeInfo.imageUrl, - productCount: sql`count(${productInfo.id})`.as('productCount'), - }) - .from(storeInfo) - .leftJoin( - productInfo, - and(eq(productInfo.storeId, storeInfo.id), eq(productInfo.isSuspended, false)) - ) - .groupBy(storeInfo.id); - - // Generate signed URLs for store images and fetch sample products - const storesWithDetails = await Promise.all( - storesData.map(async (store) => { - const signedImageUrl = store.imageUrl ? scaffoldAssetUrl(store.imageUrl) : null; - - // Fetch up to 3 products for this store - const sampleProducts = await db - .select({ - id: productInfo.id, - name: productInfo.name, - images: productInfo.images, - }) - .from(productInfo) - .where(and(eq(productInfo.storeId, store.id), eq(productInfo.isSuspended, false))) - .limit(3); - - // Generate signed URLs for product images - const productsWithSignedUrls = await Promise.all( - sampleProducts.map(async (product) => { - const images = product.images as string[]; - return { - id: product.id, - name: product.name, - signedImageUrl: (images && images.length > 0) ? scaffoldAssetUrl(images[0]) : null, - }; - }) - ); - - return { - id: store.id, - name: store.name, - description: store.description, - signedImageUrl, - productCount: store.productCount, - sampleProducts: productsWithSignedUrls, - }; - }) - ); - - return { - stores: storesWithDetails, - }; - }), - - getStoreWithProducts: publicProcedure - .input(z.object({ - storeId: z.number(), - })) - .query(async ({ input }) => { - const { storeId } = input; - - // Fetch store info - const storeData = await db.query.storeInfo.findFirst({ - where: eq(storeInfo.id, storeId), - columns: { - id: true, - name: true, - description: true, - imageUrl: true, - }, - }); - - if (!storeData) { - throw new ApiError('Store not found', 404); - } - - // Generate signed URL for store image - const signedImageUrl = storeData.imageUrl ? scaffoldAssetUrl(storeData.imageUrl) : null; - - // Fetch products for this store - const productsData = await db - .select({ - id: productInfo.id, - name: productInfo.name, - shortDescription: productInfo.shortDescription, - price: productInfo.price, - marketPrice: productInfo.marketPrice, - images: productInfo.images, - isOutOfStock: productInfo.isOutOfStock, - incrementStep: productInfo.incrementStep, - unitShortNotation: units.shortNotation, - unitNotation: units.shortNotation, - productQuantity: productInfo.productQuantity, - }) - .from(productInfo) - .innerJoin(units, eq(productInfo.unitId, units.id)) - .where(and(eq(productInfo.storeId, storeId), eq(productInfo.isSuspended, false))); - - - // Generate signed URLs for product images - const productsWithSignedUrls = await Promise.all( - productsData.map(async (product) => ({ - id: product.id, - name: product.name, - shortDescription: product.shortDescription, - price: product.price, - marketPrice: product.marketPrice, - incrementStep: product.incrementStep, - unit: product.unitShortNotation, - unitNotation: product.unitNotation, - images: scaffoldAssetUrl((product.images as string[]) || []), - isOutOfStock: product.isOutOfStock, - productQuantity: product.productQuantity - })) - ); - - return { - store: { - id: storeData.id, - name: storeData.name, - description: storeData.description, - signedImageUrl, - }, - products: productsWithSignedUrls, - }; - }), -}); diff --git a/apps/backend/src/trpc/user-apis/tags.ts b/apps/backend/src/trpc/user-apis/tags.ts deleted file mode 100644 index 5bbbd46..0000000 --- a/apps/backend/src/trpc/user-apis/tags.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { router, publicProcedure } from '../trpc-index'; -import { z } from 'zod'; -import { getTagsByStoreId } from '../../stores/product-tag-store'; -import { ApiError } from '../../lib/api-error'; - -export const tagsRouter = router({ - getTagsByStore: publicProcedure - .input(z.object({ - storeId: z.number(), - })) - .query(async ({ input }) => { - const { storeId } = input; - - // Get tags from cache that are related to this store - const tags = await getTagsByStoreId(storeId); - - - return { - tags: tags.map(tag => ({ - id: tag.id, - tagName: tag.tagName, - tagDescription: tag.tagDescription, - imageUrl: tag.imageUrl, - productIds: tag.productIds, - })), - }; - }), -}); diff --git a/apps/backend/src/trpc/user-apis/user-trpc-index.ts b/apps/backend/src/trpc/user-apis/user-trpc-index.ts deleted file mode 100644 index 2d9d41d..0000000 --- a/apps/backend/src/trpc/user-apis/user-trpc-index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { router } from '../trpc-index'; -import { addressRouter } from './address'; -import { authRouter } from './auth'; -import { bannerRouter } from './banners'; -import { cartRouter } from './cart'; -import { complaintRouter } from './complaint'; -import { orderRouter } from './order'; -import { productRouter } from './product'; -import { slotsRouter } from './slots'; -import { userRouter as userDataRouter } from './user'; -import { userCouponRouter } from './coupon'; -import { paymentRouter } from './payments'; -import { storesRouter } from './stores'; -import { fileUploadRouter } from './file-upload'; -import { tagsRouter } from './tags'; - -export const userRouter = router({ - address: addressRouter, - auth: authRouter, - banner: bannerRouter, - cart: cartRouter, - complaint: complaintRouter, - order: orderRouter, - product: productRouter, - slots: slotsRouter, - user: userDataRouter, - coupon: userCouponRouter, - payment: paymentRouter, - stores: storesRouter, - fileUpload: fileUploadRouter, - tags: tagsRouter, -}); - -export type UserRouter = typeof userRouter; \ No newline at end of file diff --git a/apps/backend/src/trpc/user-apis/user.ts b/apps/backend/src/trpc/user-apis/user.ts deleted file mode 100644 index c53a0e2..0000000 --- a/apps/backend/src/trpc/user-apis/user.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { router, protectedProcedure, publicProcedure } from '../trpc-index'; -import jwt from 'jsonwebtoken'; -import { eq, and } from 'drizzle-orm'; -import { z } from 'zod'; -import { db } from '../../db/db_index'; -import { users, userDetails, userCreds, notifCreds, unloggedUserTokens } from '../../db/schema'; -import { ApiError } from '../../lib/api-error'; -import { jwtSecret } from 'src/lib/env-exporter'; -import { generateSignedUrlFromS3Url } from '../../lib/s3-client'; - -interface AuthResponse { - token: string; - user: { - id: number; - name: string | null; - email: string | null; - mobile: string | null; - profileImage?: string | null; - bio?: string | null; - dateOfBirth?: string | null; - gender?: string | null; - occupation?: string | null; - }; -} - -const generateToken = (userId: number): string => { - const secret = jwtSecret; - if (!secret) { - throw new ApiError('JWT secret not configured', 500); - } - - return jwt.sign({ userId }, secret, { expiresIn: '7d' }); -}; - -export const userRouter = router({ - getSelfData: protectedProcedure - .query(async ({ ctx }) => { - const userId = ctx.user.userId; - - if (!userId) { - throw new ApiError('User not authenticated', 401); - } - - const [user] = await db - .select() - .from(users) - .where(eq(users.id, userId)) - .limit(1); - - if (!user) { - throw new ApiError('User not found', 404); - } - - // Get user details for profile image - const [userDetail] = await db - .select() - .from(userDetails) - .where(eq(userDetails.userId, userId)) - .limit(1); - - // Generate signed URL for profile image if it exists - const profileImageSignedUrl = userDetail?.profileImage - ? await generateSignedUrlFromS3Url(userDetail.profileImage) - : null; - - const response: Omit = { - user: { - id: user.id, - name: user.name, - email: user.email, - mobile: user.mobile, - profileImage: profileImageSignedUrl, - bio: userDetail?.bio || null, - dateOfBirth: userDetail?.dateOfBirth || null, - gender: userDetail?.gender || null, - occupation: userDetail?.occupation || null, - }, - }; - - return { - success: true, - data: response, - }; - }), - - checkProfileComplete: protectedProcedure - .query(async ({ ctx }) => { - const userId = ctx.user.userId; - - if (!userId) { - throw new ApiError('User not authenticated', 401); - } - - const result = await db - .select() - .from(users) - .leftJoin(userCreds, eq(users.id, userCreds.userId)) - .where(eq(users.id, userId)) - .limit(1); - - if (result.length === 0) { - throw new ApiError('User not found', 404); - } - - const { users: user, user_creds: creds } = result[0]; - - return { - isComplete: !!(user.name && user.email && creds), - }; - }), - - savePushToken: publicProcedure - .input(z.object({ token: z.string() })) - .mutation(async ({ input, ctx }) => { - const { token } = input; - const userId = ctx.user?.userId; - - if (userId) { - // AUTHENTICATED USER - // Check if token exists in notif_creds for this user - const existing = await db.query.notifCreds.findFirst({ - where: and( - eq(notifCreds.userId, userId), - eq(notifCreds.token, token) - ), - }); - - if (existing) { - // Update lastVerified timestamp - await db - .update(notifCreds) - .set({ lastVerified: new Date() }) - .where(eq(notifCreds.id, existing.id)); - } else { - // Insert new token into notif_creds - await db.insert(notifCreds).values({ - userId, - token, - lastVerified: new Date(), - }); - } - - // Remove from unlogged_user_tokens if it exists - await db - .delete(unloggedUserTokens) - .where(eq(unloggedUserTokens.token, token)); - - } else { - // UNAUTHENTICATED USER - // Save/update in unlogged_user_tokens - const existing = await db.query.unloggedUserTokens.findFirst({ - where: eq(unloggedUserTokens.token, token), - }); - - if (existing) { - await db - .update(unloggedUserTokens) - .set({ lastVerified: new Date() }) - .where(eq(unloggedUserTokens.id, existing.id)); - } else { - await db.insert(unloggedUserTokens).values({ - token, - lastVerified: new Date(), - }); - } - } - - return { success: true }; - }), -}); \ No newline at end of file diff --git a/apps/backend/src/uv-apis/auth.controller.ts b/apps/backend/src/uv-apis/auth.controller.ts index 6bb54f9..8cdc9c0 100644 --- a/apps/backend/src/uv-apis/auth.controller.ts +++ b/apps/backend/src/uv-apis/auth.controller.ts @@ -2,13 +2,13 @@ import { Request, Response, NextFunction } from 'express'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; import { eq } from 'drizzle-orm'; -import { db } from '../db/db_index'; -import { users, userCreds, userDetails } from '../db/schema'; -import { ApiError } from '../lib/api-error'; -import catchAsync from '../lib/catch-async'; -import { jwtSecret } from 'src/lib/env-exporter'; -import uploadHandler from '../lib/upload-handler'; -import { imageUploadS3, generateSignedUrlFromS3Url } from '../lib/s3-client'; +import { db } from '@/src/db/db_index' +import { users, userCreds, userDetails } from '@/src/db/schema' +import { ApiError } from '@/src/lib/api-error' +import catchAsync from '@/src/lib/catch-async' +import { jwtSecret } from '@/src/lib/env-exporter'; +import uploadHandler from '@/src/lib/upload-handler' +import { imageUploadS3, generateSignedUrlFromS3Url } from '@/src/lib/s3-client' interface RegisterRequest { name: string; @@ -318,4 +318,4 @@ export const updateProfile = catchAsync(async (req: Request, res: Response, next success: true, data: response, }); -}); \ No newline at end of file +}); diff --git a/apps/backend/src/uv-apis/auth.router.ts b/apps/backend/src/uv-apis/auth.router.ts index 829e5cd..63ceff5 100644 --- a/apps/backend/src/uv-apis/auth.router.ts +++ b/apps/backend/src/uv-apis/auth.router.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; -import { register, updateProfile } from './auth.controller'; -import { verifyToken } from '../middleware/auth'; -import uploadHandler from '../lib/upload-handler'; +import { register, updateProfile } from '@/src/uv-apis/auth.controller' +import { verifyToken } from '@/src/middleware/auth' +import uploadHandler from '@/src/lib/upload-handler' const router = Router(); diff --git a/apps/backend/src/uv-apis/user-rest.controller.ts b/apps/backend/src/uv-apis/user-rest.controller.ts index c4e08c2..8fd02a1 100644 --- a/apps/backend/src/uv-apis/user-rest.controller.ts +++ b/apps/backend/src/uv-apis/user-rest.controller.ts @@ -1,9 +1,9 @@ import { Request, Response, NextFunction } from 'express'; -import { db } from '../db/db_index'; -import { complaints } from '../db/schema'; -import { ApiError } from '../lib/api-error'; -import catchAsync from '../lib/catch-async'; -import { imageUploadS3 } from '../lib/s3-client'; +import { db } from '@/src/db/db_index' +import { complaints } from '@/src/db/schema' +import { ApiError } from '@/src/lib/api-error' +import catchAsync from '@/src/lib/catch-async' +import { imageUploadS3 } from '@/src/lib/s3-client' interface RaiseComplaintRequest { orderId?: string; diff --git a/apps/backend/src/uv-apis/uv-router.ts b/apps/backend/src/uv-apis/uv-router.ts index 28a71fe..e205b76 100644 --- a/apps/backend/src/uv-apis/uv-router.ts +++ b/apps/backend/src/uv-apis/uv-router.ts @@ -1,7 +1,7 @@ import { Router } from "express"; -import authRouter from "./auth.router"; -import { raiseComplaint } from "./user-rest.controller"; -import uploadHandler from "src/lib/upload-handler"; +import authRouter from "@/src/uv-apis/auth.router" +import { raiseComplaint } from "@/src/uv-apis/user-rest.controller" +import uploadHandler from "@/src/lib/upload-handler"; const router = Router(); @@ -9,4 +9,4 @@ router.use("/auth", authRouter); router.use("/complaints/raise", uploadHandler.array('images'),raiseComplaint) const uvRouter = router; -export default uvRouter; \ No newline at end of file +export default uvRouter; diff --git a/apps/backend/src/v1-router.ts b/apps/backend/src/v1-router.ts index 8af0e58..91a2e71 100644 --- a/apps/backend/src/v1-router.ts +++ b/apps/backend/src/v1-router.ts @@ -1,7 +1,7 @@ import { Router } from "express"; - import avRouter from "./admin-apis/av-router"; - import commonRouter from "./common-apis/common.router"; - import uvRouter from "./uv-apis/uv-router"; + import avRouter from "@/src/apis/admin-apis/apis/av-router" + import commonRouter from "@/src/apis/common-apis/apis/common.router" + import uvRouter from "@/src/uv-apis/uv-router" const router = Router(); diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json index d0a05d6..b6a2088 100755 --- a/apps/backend/tsconfig.json +++ b/apps/backend/tsconfig.json @@ -1,120 +1,13 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "libReplacement": true, /* Enable lib replacement. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "commonjs", + "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./*"], "shared-types": ["../shared-types"], "@commonTypes": ["../../packages/ui/shared-types"], "@commonTypes/*": ["../../packages/ui/shared-types/*"], - }, - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [""], /* Specify multiple folders that act like './node_modules/@types'. */ - "typeRoots": ["./node_modules/@types", "../shared-types"], - - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ - "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ - // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - }, - "include": ["src", "types", "index.ts", "../shared-types"] + "@backend": ["@apps/backend/*"], + "@backend/*": ["@apps/backend/*"] + } + } } diff --git a/progress.md b/progress.md index 45370f9..e516679 100644 --- a/progress.md +++ b/progress.md @@ -172,12 +172,3 @@ - **Compacted order displays** for better space utilization - **Enhanced delivery sequences** layout -## Important Notes -- **Do not run build, compile, or migration commands** - These should be handled manually by developers -- Avoid running `npm run build`, `tsc`, `drizzle-kit generate`, or similar compilation/migration commands -- Schema changes should be committed and migrations generated manually -- **Signed URLs** are used for secure image access with 3-day expiration -- **React Query** handles all API state management with proper loading/error states -- **Vendor Snippets**: Relations definitions are critical for Drizzle ORM queries - always define relations for foreign key relationships -- **tRPC Usage**: Use `trpc` for React hooks and `trpcClient` for direct API calls outside components -- **Focus Callbacks**: Implemented for automatic data refresh when screens regain focus \ No newline at end of file