diff --git a/apps/backend/.env b/apps/backend/.env index a935514..61b05b2 100755 --- a/apps/backend/.env +++ b/apps/backend/.env @@ -22,6 +22,8 @@ EXPO_ACCESS_TOKEN=Asvpy8cByRh6T4ksnWScO6PLcio2n35-BwES5zK- JWT_SECRET=my_meatfarmer_jwt_secret_key ASSETS_DOMAIN=https://assets.freshyo.in/ API_CACHE_KEY=api-cache-dev +CLOUDFLARE_API_TOKEN=I8Vp4E9TX58E8qEDeH0nTFDS2d2zXNYiXvbs4Ckj +CLOUDFLARE_ZONE_ID=edefbf750bfc3ff26ccd11e8e28dc8d7 # REDIS_URL=redis://default:redis_shafi_password@5.223.55.14:6379 REDIS_URL=redis://default:redis_shafi_password@57.128.212.174:6379 APP_URL=http://localhost:4000 diff --git a/apps/backend/assets/signed-url-cache.json b/apps/backend/assets/signed-url-cache.json index 8aff057..516e5f8 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%2F20260309%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260309T193417Z&X-Amz-Expires=259200&X-Amz-Signature=271c0272ba9048bfd6785da9284c96ed15cd8f139aac094acc8883513c9adcb1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773343997623},"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%2F20260309%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260309T193417Z&X-Amz-Expires=259200&X-Amz-Signature=e28f8c17df312e020caad9b63a4af47d1c299be263bf6a2289e1755ce37a5f46&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773343997623},"tags/1773132996598-1773119837289-8c93f343-2885-415e-b545-dcaa1dc88857.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260310%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260310T051718Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3D22358087a6f102caf7eb7a4b3cfd455df9aca13685fff8bb751d3c3d813b9d72%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1773132996598-1773119837289-8c93f343-2885-415e-b545-dcaa1dc88857.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260310%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260310T051718Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253D22358087a6f102caf7eb7a4b3cfd455df9aca13685fff8bb751d3c3d813b9d72%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%2F20260310%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260310T091443Z&X-Amz-Expires=259200&X-Amz-Signature=b107b388b2c1507b70d611c1c91dcdda5450083e7f8888ad99572b770b7efcf1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773393223027},"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%2F20260310%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260310T091448Z&X-Amz-Expires=259200&X-Amz-Signature=e2595e57f41d7b66b08c23ab6a5a89631eb9c69c323dd558743938e7673cceda&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773393228306}},"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%2F20260309%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260309T193417Z&X-Amz-Expires=259200&X-Amz-Signature=271c0272ba9048bfd6785da9284c96ed15cd8f139aac094acc8883513c9adcb1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1770281046297.jpg","expiresAt":1773343997623},"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%2F20260309%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260309T193417Z&X-Amz-Expires=259200&X-Amz-Signature=e28f8c17df312e020caad9b63a4af47d1c299be263bf6a2289e1755ce37a5f46&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1770429593455.jpg","expiresAt":1773343997623},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1773132996598-1773119837289-8c93f343-2885-415e-b545-dcaa1dc88857.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260310%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260310T051718Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253D22358087a6f102caf7eb7a4b3cfd455df9aca13685fff8bb751d3c3d813b9d72%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%2F20260310%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260310T091443Z&X-Amz-Expires=259200&X-Amz-Signature=b107b388b2c1507b70d611c1c91dcdda5450083e7f8888ad99572b770b7efcf1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1773132996598-1773119837289-8c93f343-2885-415e-b545-dcaa1dc88857.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260310%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260310T051718Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3D22358087a6f102caf7eb7a4b3cfd455df9aca13685fff8bb751d3c3d813b9d72%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject","expiresAt":1773393223027},"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%2F20260310%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260310T091448Z&X-Amz-Expires=259200&X-Amz-Signature=e2595e57f41d7b66b08c23ab6a5a89631eb9c69c323dd558743938e7673cceda&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"profile-images/1766160314135-1000000018.jpg","expiresAt":1773393228306}}} \ No newline at end of file +{"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%2F20260309%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260309T193417Z&X-Amz-Expires=259200&X-Amz-Signature=271c0272ba9048bfd6785da9284c96ed15cd8f139aac094acc8883513c9adcb1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773343997623},"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%2F20260309%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260309T193417Z&X-Amz-Expires=259200&X-Amz-Signature=e28f8c17df312e020caad9b63a4af47d1c299be263bf6a2289e1755ce37a5f46&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773343997623},"tags/1773132996598-1773119837289-8c93f343-2885-415e-b545-dcaa1dc88857.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260310%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260310T051718Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3D22358087a6f102caf7eb7a4b3cfd455df9aca13685fff8bb751d3c3d813b9d72%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject":{"value":"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1773132996598-1773119837289-8c93f343-2885-415e-b545-dcaa1dc88857.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260310%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260310T051718Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253D22358087a6f102caf7eb7a4b3cfd455df9aca13685fff8bb751d3c3d813b9d72%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%2F20260310%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260310T091443Z&X-Amz-Expires=259200&X-Amz-Signature=b107b388b2c1507b70d611c1c91dcdda5450083e7f8888ad99572b770b7efcf1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773393223027},"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%2F20260310%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260310T091448Z&X-Amz-Expires=259200&X-Amz-Signature=e2595e57f41d7b66b08c23ab6a5a89631eb9c69c323dd558743938e7673cceda&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773393228306},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110957Z&X-Amz-Expires=259200&X-Amz-Signature=367a176f8a688457d85db22252a80caabb39a1bdd63ea3cdc72f66bf9af29a01&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773486537225},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110957Z&X-Amz-Expires=259200&X-Amz-Signature=437a86c8b956eda8929f72ce76d19bc510463774d5a7c20b09ff84c228ae90c1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773486537444},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110957Z&X-Amz-Expires=259200&X-Amz-Signature=12c0135edfd884a1df1f0e6baa411804b2ede5824c34148d59eba203a546662f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773486537653},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110957Z&X-Amz-Expires=259200&X-Amz-Signature=0ffa59d8343863e574d71cb6cbf244436ef634e5a543cc925e0425e8a1b513a5&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773486537862},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110958Z&X-Amz-Expires=259200&X-Amz-Signature=989b84cfdf4b05ea954e9f56d1751e33d406c71f0ae1fb877c0884ba188a5b7c&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773486538258},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110958Z&X-Amz-Expires=259200&X-Amz-Signature=b004857f0563e5bb4694a28f5ec7827f8cf710d31f40ea62c155e9339d79f144&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","expiresAt":1773486538972}},"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%2F20260309%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260309T193417Z&X-Amz-Expires=259200&X-Amz-Signature=271c0272ba9048bfd6785da9284c96ed15cd8f139aac094acc8883513c9adcb1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1770281046297.jpg","expiresAt":1773343997623},"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%2F20260309%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260309T193417Z&X-Amz-Expires=259200&X-Amz-Signature=e28f8c17df312e020caad9b63a4af47d1c299be263bf6a2289e1755ce37a5f46&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"store-images/1770429593455.jpg","expiresAt":1773343997623},"https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com/meatfarmer/tags/1773132996598-1773119837289-8c93f343-2885-415e-b545-dcaa1dc88857.jpeg%253FX-Amz-Algorithm%253DAWS4-HMAC-SHA256%2526X-Amz-Content-Sha256%253DUNSIGNED-PAYLOAD%2526X-Amz-Credential%253D8fab47503efb9547b50e4fb317e35cc7%25252F20260310%25252Fapac%25252Fs3%25252Faws4_request%2526X-Amz-Date%253D20260310T051718Z%2526X-Amz-Expires%253D259200%2526X-Amz-Signature%253D22358087a6f102caf7eb7a4b3cfd455df9aca13685fff8bb751d3c3d813b9d72%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%2F20260310%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260310T091443Z&X-Amz-Expires=259200&X-Amz-Signature=b107b388b2c1507b70d611c1c91dcdda5450083e7f8888ad99572b770b7efcf1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1773132996598-1773119837289-8c93f343-2885-415e-b545-dcaa1dc88857.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3D8fab47503efb9547b50e4fb317e35cc7%252F20260310%252Fapac%252Fs3%252Faws4_request%26X-Amz-Date%3D20260310T051718Z%26X-Amz-Expires%3D259200%26X-Amz-Signature%3D22358087a6f102caf7eb7a4b3cfd455df9aca13685fff8bb751d3c3d813b9d72%26X-Amz-SignedHeaders%3Dhost%26x-amz-checksum-mode%3DENABLED%26x-id%3DGetObject","expiresAt":1773393223027},"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%2F20260310%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260310T091448Z&X-Amz-Expires=259200&X-Amz-Signature=e2595e57f41d7b66b08c23ab6a5a89631eb9c69c323dd558743938e7673cceda&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"profile-images/1766160314135-1000000018.jpg","expiresAt":1773393228306},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110957Z&X-Amz-Expires=259200&X-Amz-Signature=367a176f8a688457d85db22252a80caabb39a1bdd63ea3cdc72f66bf9af29a01&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1763835253683-c9c3e293-0bef-4c58-a976-dd49c050cd36.jpeg","expiresAt":1773486537225},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110957Z&X-Amz-Expires=259200&X-Amz-Signature=437a86c8b956eda8929f72ce76d19bc510463774d5a7c20b09ff84c228ae90c1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1763835293899-43b3fbe1-9b5b-441c-b4d4-d1691c3f02f3.webp","expiresAt":1773486537444},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110957Z&X-Amz-Expires=259200&X-Amz-Signature=12c0135edfd884a1df1f0e6baa411804b2ede5824c34148d59eba203a546662f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1768709725124-ebf421c5-ad52-49a9-b65c-1de008110b8a.png","expiresAt":1773486537653},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110957Z&X-Amz-Expires=259200&X-Amz-Signature=0ffa59d8343863e574d71cb6cbf244436ef634e5a543cc925e0425e8a1b513a5&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":1773486537862},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110958Z&X-Amz-Expires=259200&X-Amz-Signature=989b84cfdf4b05ea954e9f56d1751e33d406c71f0ae1fb877c0884ba188a5b7c&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":1773486538258},"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%2F20260311%2Fapac%2Fs3%2Faws4_request&X-Amz-Date=20260311T110958Z&X-Amz-Expires=259200&X-Amz-Signature=b004857f0563e5bb4694a28f5ec7827f8cf710d31f40ea62c155e9339d79f144&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject":{"value":"tags/1770323560823-fd0ec463-bed0-474e-aa14-dc6480ce36af.jpeg","expiresAt":1773486538972}}} \ No newline at end of file diff --git a/apps/backend/src/apis/admin-apis/apis/product-tags.controller.ts b/apps/backend/src/apis/admin-apis/apis/product-tags.controller.ts index 7b6a4fe..dd3a665 100644 --- a/apps/backend/src/apis/admin-apis/apis/product-tags.controller.ts +++ b/apps/backend/src/apis/admin-apis/apis/product-tags.controller.ts @@ -6,6 +6,7 @@ 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'; +import { createStoresFile } from '@/src/lib/cloud_cache'; /** * Create a new product tag @@ -60,10 +61,16 @@ export const createTag = async (req: Request, res: Response) => { // Reinitialize stores to reflect changes in cache await initializeAllStores(); - return res.status(201).json({ + // Send response first + res.status(201).json({ tag: newTag, message: "Tag created successfully", }); + + // Then regenerate stores cache (fire-and-forget) + createStoresFile().catch(error => { + console.error('Failed to regenerate stores cache after tag creation:', error) + }) }; /** @@ -179,10 +186,16 @@ export const updateTag = async (req: Request, res: Response) => { // Reinitialize stores to reflect changes in cache await initializeAllStores(); - return res.status(200).json({ + // Send response first + res.status(200).json({ tag: updatedTag, message: "Tag updated successfully", }); + + // Then regenerate stores cache (fire-and-forget) + createStoresFile().catch(error => { + console.error('Failed to regenerate stores cache after tag update:', error) + }) }; /** @@ -216,7 +229,13 @@ export const deleteTag = async (req: Request, res: Response) => { // Reinitialize stores to reflect changes in cache await initializeAllStores(); - return res.status(200).json({ + // Send response first + res.status(200).json({ message: "Tag deleted successfully", }); + + // Then regenerate stores cache (fire-and-forget) + createStoresFile().catch(error => { + console.error('Failed to regenerate stores cache after tag deletion:', error) + }) }; \ No newline at end of file diff --git a/apps/backend/src/apis/admin-apis/apis/product.controller.ts b/apps/backend/src/apis/admin-apis/apis/product.controller.ts index dcd3471..b9db94a 100644 --- a/apps/backend/src/apis/admin-apis/apis/product.controller.ts +++ b/apps/backend/src/apis/admin-apis/apis/product.controller.ts @@ -7,6 +7,7 @@ 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'; +import { createProductsFile } from '@/src/lib/cloud_cache'; type CreateDeal = { quantity: number; @@ -110,11 +111,17 @@ export const createProduct = async (req: Request, res: Response) => { // Reinitialize stores to reflect changes await initializeAllStores(); - return res.status(201).json({ + // Send response first + res.status(201).json({ product: newProduct, deals: createdDeals, message: "Product created successfully", }); + + // Then regenerate products cache (fire-and-forget) + createProductsFile().catch(error => { + console.error('Failed to regenerate products cache after create:', error) + }) }; /** @@ -296,8 +303,14 @@ export const updateProduct = async (req: Request, res: Response) => { // Reinitialize stores to reflect changes await initializeAllStores(); - return res.status(200).json({ + // Send response first + res.status(200).json({ product: updatedProduct, message: "Product updated successfully", }); + + // Then regenerate products cache (fire-and-forget) + createProductsFile().catch(error => { + console.error('Failed to regenerate products cache after update:', error) + }) }; \ No newline at end of file diff --git a/apps/backend/src/lib/cloud_cache.ts b/apps/backend/src/lib/cloud_cache.ts index 6ea9a24..dbf80b1 100644 --- a/apps/backend/src/lib/cloud_cache.ts +++ b/apps/backend/src/lib/cloud_cache.ts @@ -1,3 +1,4 @@ +import axios from 'axios' import { scaffoldProducts } from '@/src/trpc/apis/common-apis/common' import { scaffoldEssentialConsts } from '@/src/trpc/apis/common-apis/common-trpc-index' import { scaffoldStores } from '@/src/trpc/apis/user-apis/apis/stores' @@ -7,8 +8,13 @@ import { scaffoldStoreWithProducts } from '@/src/trpc/apis/user-apis/apis/stores import { storeInfo } from '@/src/db/schema' import { db } from '@/src/db/db_index' import { imageUploadS3 } from '@/src/lib/s3-client' -import { apiCacheKey } from '@/src/lib/env-exporter' +import { apiCacheKey, cloudflareApiToken, cloudflareZoneId, assetsDomain } from '@/src/lib/env-exporter' import { CACHE_FILENAMES } from '@packages/shared' +import { retryWithExponentialBackoff } from '@/src/lib/retry' + +function constructCacheUrl(path: string): string { + return `${assetsDomain}${apiCacheKey}/${path}` +} export async function createProductsFile(): Promise { // Get products data from the API method @@ -23,6 +29,15 @@ export async function createProductsFile(): Promise { // Upload to S3 at the specified path using apiCacheKey const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.products}`) + // Purge cache with retry + const url = constructCacheUrl(CACHE_FILENAMES.products) + try { + await retryWithExponentialBackoff(() => clearUrlCache([url])) + console.log(`Cache purged for ${url}`) + } catch (error) { + console.error(`Failed to purge cache for ${url} after 3 retries:`, error) + } + return s3Key } @@ -39,6 +54,15 @@ export async function createEssentialConstsFile(): Promise { // Upload to S3 at the specified path using apiCacheKey const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.essentialConsts}`) + // Purge cache with retry + const url = constructCacheUrl(CACHE_FILENAMES.essentialConsts) + try { + await retryWithExponentialBackoff(() => clearUrlCache([url])) + console.log(`Cache purged for ${url}`) + } catch (error) { + console.error(`Failed to purge cache for ${url} after 3 retries:`, error) + } + return s3Key } @@ -55,6 +79,15 @@ export async function createStoresFile(): Promise { // Upload to S3 at the specified path using apiCacheKey const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.stores}`) + // Purge cache with retry + const url = constructCacheUrl(CACHE_FILENAMES.stores) + try { + await retryWithExponentialBackoff(() => clearUrlCache([url])) + console.log(`Cache purged for ${url}`) + } catch (error) { + console.error(`Failed to purge cache for ${url} after 3 retries:`, error) + } + return s3Key } @@ -71,6 +104,15 @@ export async function createSlotsFile(): Promise { // Upload to S3 at the specified path using apiCacheKey const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.slots}`) + // Purge cache with retry + const url = constructCacheUrl(CACHE_FILENAMES.slots) + try { + await retryWithExponentialBackoff(() => clearUrlCache([url])) + console.log(`Cache purged for ${url}`) + } catch (error) { + console.error(`Failed to purge cache for ${url} after 3 retries:`, error) + } + return s3Key } @@ -87,6 +129,15 @@ export async function createBannersFile(): Promise { // Upload to S3 at the specified path using apiCacheKey const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.banners}`) + // Purge cache with retry + const url = constructCacheUrl(CACHE_FILENAMES.banners) + try { + await retryWithExponentialBackoff(() => clearUrlCache([url])) + console.log(`Cache purged for ${url}`) + } catch (error) { + console.error(`Failed to purge cache for ${url} after 3 retries:`, error) + } + return s3Key } @@ -103,6 +154,15 @@ export async function createStoreFile(storeId: number): Promise { // Upload to S3 at the specified path using apiCacheKey const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/stores/${storeId}.json`) + // Purge cache with retry + const url = constructCacheUrl(`stores/${storeId}.json`) + try { + await retryWithExponentialBackoff(() => clearUrlCache([url])) + console.log(`Cache purged for ${url}`) + } catch (error) { + console.error(`Failed to purge cache for ${url} after 3 retries:`, error) + } + return s3Key } @@ -110,11 +170,95 @@ export async function createAllStoresFiles(): Promise { // Fetch all store IDs from database const stores = await db.select({ id: storeInfo.id }).from(storeInfo) - // Create cache files for all stores in parallel - const results = await Promise.all( - stores.map(store => createStoreFile(store.id)) - ) + // Create cache files for all stores and collect URLs + const results: string[] = [] + const urls: string[] = [] + + for (const store of stores) { + const s3Key = await createStoreFile(store.id) + results.push(s3Key) + urls.push(constructCacheUrl(`stores/${store.id}.json`)) + } console.log(`Created ${results.length} store cache files`) + + // Purge all store caches in one batch with retry + try { + await retryWithExponentialBackoff(() => clearUrlCache(urls)) + console.log(`Cache purged for ${urls.length} store files`) + } catch (error) { + console.error(`Failed to purge cache for store files after 3 retries. URLs: ${urls.join(', ')}`, error) + } + return results } + +export async function clearUrlCache(urls: string[]): Promise<{ success: boolean; errors?: string[] }> { + if (!cloudflareApiToken || !cloudflareZoneId) { + console.warn('Cloudflare credentials not configured, skipping cache clear') + return { success: false, errors: ['Cloudflare credentials not configured'] } + } + + try { + const response = await axios.post( + `https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/purge_cache`, + { files: urls }, + { + headers: { + 'Authorization': `Bearer ${cloudflareApiToken}`, + 'Content-Type': 'application/json', + }, + } + ) + + const result = response.data as { success: boolean; errors?: { message: string }[] } + + if (!result.success) { + const errorMessages = result.errors?.map(e => e.message) || ['Unknown error'] + console.error(`Cloudflare cache purge failed for URLs: ${urls.join(', ')}`, errorMessages) + return { success: false, errors: errorMessages } + } + + console.log(`Successfully purged ${urls.length} URLs from Cloudflare cache: ${urls.join(', ')}`) + return { success: true } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error' + console.error(`Error clearing Cloudflare cache for URLs: ${urls.join(', ')}`, errorMessage) + return { success: false, errors: [errorMessage] } + } +} + +export async function clearAllCache(): Promise<{ success: boolean; errors?: string[] }> { + if (!cloudflareApiToken || !cloudflareZoneId) { + console.warn('Cloudflare credentials not configured, skipping cache clear') + return { success: false, errors: ['Cloudflare credentials not configured'] } + } + + try { + const response = await axios.post( + `https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/purge_cache`, + { purge_everything: true }, + { + headers: { + 'Authorization': `Bearer ${cloudflareApiToken}`, + 'Content-Type': 'application/json', + }, + } + ) + + const result = response.data as { success: boolean; errors?: { message: string }[] } + + if (!result.success) { + const errorMessages = result.errors?.map(e => e.message) || ['Unknown error'] + console.error('Cloudflare cache purge failed:', errorMessages) + return { success: false, errors: errorMessages } + } + + console.log('Successfully purged all cache from Cloudflare') + return { success: true } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error' + console.error('Error clearing Cloudflare cache:', errorMessage) + return { success: false, errors: [errorMessage] } + } +} \ No newline at end of file diff --git a/apps/backend/src/lib/env-exporter.ts b/apps/backend/src/lib/env-exporter.ts index dde4f77..19255d8 100755 --- a/apps/backend/src/lib/env-exporter.ts +++ b/apps/backend/src/lib/env-exporter.ts @@ -19,6 +19,10 @@ export const assetsDomain = process.env.ASSETS_DOMAIN as string; export const apiCacheKey = process.env.API_CACHE_KEY as string; +export const cloudflareApiToken = process.env.CLOUDFLARE_API_TOKEN as string; + +export const cloudflareZoneId = process.env.CLOUDFLARE_ZONE_ID as string; + export const s3Url = process.env.S3_URL as string export const redisUrl = process.env.REDIS_URL as string diff --git a/apps/backend/src/lib/retry.ts b/apps/backend/src/lib/retry.ts new file mode 100644 index 0000000..ed95940 --- /dev/null +++ b/apps/backend/src/lib/retry.ts @@ -0,0 +1,23 @@ +export async function retryWithExponentialBackoff( + fn: () => Promise, + maxRetries: number = 3, + delayMs: number = 1000 +): Promise { + let lastError: Error | undefined + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await fn() + } catch (error) { + lastError = error instanceof Error ? error : new Error(String(error)) + + if (attempt < maxRetries) { + console.log(`Attempt ${attempt} failed, retrying in ${delayMs}ms...`) + await new Promise(resolve => setTimeout(resolve, delayMs)) + delayMs *= 2 + } + } + } + + throw lastError +} \ No newline at end of file diff --git a/apps/backend/src/trpc/apis/admin-apis/apis/product.ts b/apps/backend/src/trpc/apis/admin-apis/apis/product.ts index df77159..1fef358 100644 --- a/apps/backend/src/trpc/apis/admin-apis/apis/product.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/product.ts @@ -8,6 +8,7 @@ import { imageUploadS3, generateSignedUrlsFromS3Urls, getOriginalUrlFromSignedUr import { deleteS3Image } from '@/src/lib/delete-image' import type { SpecialDeal } from '@/src/db/types' import { initializeAllStores } from '@/src/stores/store-initializer' +import { createProductsFile } from '@/src/lib/cloud_cache' type CreateDeal = { quantity: number; @@ -104,6 +105,11 @@ export const productRouter = router({ // Reinitialize stores to reflect changes await initializeAllStores(); + // Regenerate products cache (fire-and-forget) + createProductsFile().catch(error => { + console.error('Failed to regenerate products cache after delete:', error) + }) + return { message: "Product deleted successfully", }; @@ -191,6 +197,11 @@ export const productRouter = router({ // Reinitialize stores to reflect changes await initializeAllStores(); + // Regenerate products cache (fire-and-forget) + createProductsFile().catch(error => { + console.error('Failed to regenerate products cache after slot products update:', error) + }) + return { message: "Slot products updated successfully", added: productsToAdd.length, @@ -393,6 +404,11 @@ export const productRouter = router({ // Reinitialize stores to reflect changes await initializeAllStores(); + // Regenerate products cache (fire-and-forget) + createProductsFile().catch(error => { + console.error('Failed to regenerate products cache after group creation:', error) + }) + return { group: newGroup, message: 'Group created successfully', @@ -441,6 +457,11 @@ export const productRouter = router({ // Reinitialize stores to reflect changes await initializeAllStores(); + // Regenerate products cache (fire-and-forget) + createProductsFile().catch(error => { + console.error('Failed to regenerate products cache after group update:', error) + }) + return { group: updatedGroup, message: 'Group updated successfully', @@ -467,15 +488,21 @@ export const productRouter = router({ throw new ApiError('Group not found', 404); } - // Reinitialize stores to reflect changes - await initializeAllStores(); + // Reinitialize stores to reflect changes + await initializeAllStores(); - return { - message: 'Group deleted successfully', - }; - }), + // Regenerate products cache (non-blocking) + // Regenerate products cache (fire-and-forget) + createProductsFile().catch(error => { + console.error('Failed to regenerate products cache after group deletion:', error) + }) - updateProductPrices: protectedProcedure + return { + message: 'Group deleted successfully', + }; + }), + + updateProductPrices: protectedProcedure .input(z.object({ updates: z.array(z.object({ productId: z.number(), @@ -523,12 +550,17 @@ export const productRouter = router({ await Promise.all(updatePromises); - // Reinitialize stores to reflect changes - await initializeAllStores(); + // Reinitialize stores to reflect changes + await initializeAllStores(); - return { - message: `Updated prices for ${updates.length} product(s)`, - updatedCount: updates.length, - }; - }), - }); + // Regenerate products cache (fire-and-forget) + createProductsFile().catch(error => { + console.error('Failed to regenerate products cache after price update:', error) + }) + + return { + message: `Updated prices for ${updates.length} product(s)`, + updatedCount: updates.length, + }; + }), + }); diff --git a/apps/backend/src/trpc/apis/admin-apis/apis/slots.ts b/apps/backend/src/trpc/apis/admin-apis/apis/slots.ts index 4e9ef18..d958ccf 100644 --- a/apps/backend/src/trpc/apis/admin-apis/apis/slots.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/slots.ts @@ -9,6 +9,7 @@ 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' +import { createSlotsFile } from '@/src/lib/cloud_cache' interface CachedDeliverySequence { [userId: string]: number[]; @@ -217,6 +218,11 @@ export const slotsRouter = router({ // Reinitialize stores to reflect changes await initializeAllStores(); + // Regenerate slots cache (fire-and-forget) + createSlotsFile().catch(error => { + console.error('Failed to regenerate slots cache after slot products update:', error) + }) + return { message: "Slot products updated successfully", added: productsToAdd.length, @@ -300,6 +306,11 @@ export const slotsRouter = router({ // Reinitialize stores to reflect changes (outside transaction) await initializeAllStores(); + // Regenerate slots cache (fire-and-forget) + createSlotsFile().catch(error => { + console.error('Failed to regenerate slots cache after slot creation:', error) + }) + return result; }), @@ -459,6 +470,11 @@ export const slotsRouter = router({ // Reinitialize stores to reflect changes (outside transaction) await initializeAllStores(); + // Regenerate slots cache (fire-and-forget) + createSlotsFile().catch(error => { + console.error('Failed to regenerate slots cache after slot update:', error) + }) + return result; } catch(e) { @@ -489,6 +505,11 @@ export const slotsRouter = router({ // Reinitialize stores to reflect changes await initializeAllStores(); + // Regenerate slots cache (fire-and-forget) + createSlotsFile().catch(error => { + console.error('Failed to regenerate slots cache after slot deletion:', error) + }) + return { message: "Slot deleted successfully", }; @@ -569,6 +590,11 @@ export const slotsRouter = router({ console.warn('Redis cache write failed:', cacheError); } + // Regenerate slots cache (fire-and-forget) + createSlotsFile().catch(error => { + console.error('Failed to regenerate slots cache after delivery sequence update:', error) + }) + return { slot: updatedSlot, message: "Delivery sequence updated successfully", @@ -600,6 +626,11 @@ export const slotsRouter = router({ // Reinitialize stores to reflect changes await initializeAllStores(); + // Regenerate slots cache (fire-and-forget) + createSlotsFile().catch(error => { + console.error('Failed to regenerate slots cache after slot capacity update:', error) + }) + return { success: true, slot: updatedSlot, diff --git a/apps/backend/src/trpc/apis/admin-apis/apis/store.ts b/apps/backend/src/trpc/apis/admin-apis/apis/store.ts index 6d1cee2..46f6ab8 100644 --- a/apps/backend/src/trpc/apis/admin-apis/apis/store.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/store.ts @@ -7,6 +7,7 @@ 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 '@/src/stores/store-initializer' +import { createStoresFile } from '@/src/lib/cloud_cache' export const storeRouter = router({ getStores: protectedProcedure @@ -87,6 +88,11 @@ export const storeRouter = router({ // Reinitialize stores to reflect changes await initializeAllStores(); + // Regenerate stores cache (fire-and-forget) + createStoresFile().catch(error => { + console.error('Failed to regenerate stores cache after store creation:', error) + }) + return { store: newStore, message: "Store created successfully", @@ -163,16 +169,21 @@ export const storeRouter = router({ } } - // Reinitialize stores to reflect changes - await initializeAllStores(); + // Reinitialize stores to reflect changes + await initializeAllStores(); - return { - store: updatedStore, - message: "Store updated successfully", - }; - }), + // Regenerate stores cache (fire-and-forget) + createStoresFile().catch(error => { + console.error('Failed to regenerate stores cache after store update:', error) + }) - deleteStore: protectedProcedure + return { + store: updatedStore, + message: "Store updated successfully", + }; + }), + + deleteStore: protectedProcedure .input(z.object({ storeId: z.number(), })) @@ -201,9 +212,14 @@ export const storeRouter = router({ }; }); - // Reinitialize stores to reflect changes (outside transaction) - await initializeAllStores(); + // Reinitialize stores to reflect changes (outside transaction) + await initializeAllStores(); - return result; - }), + // Regenerate stores cache (fire-and-forget) + createStoresFile().catch(error => { + console.error('Failed to regenerate stores cache after store deletion:', error) + }) + + return result; + }), }); \ No newline at end of file diff --git a/apps/backend/src/trpc/apis/common-apis/common.ts b/apps/backend/src/trpc/apis/common-apis/common.ts index e34a63d..2896035 100644 --- a/apps/backend/src/trpc/apis/common-apis/common.ts +++ b/apps/backend/src/trpc/apis/common-apis/common.ts @@ -61,6 +61,7 @@ export async function scaffoldProducts() { isFlashAvailable: product.isFlashAvailable, nextDeliveryDate: nextDeliveryDate ? nextDeliveryDate.toISOString() : null, images: product.images, + flashPrice: product.flashPrice }; }) ); diff --git a/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx b/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx index 57fe1bd..8dc08d2 100755 --- a/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx +++ b/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx @@ -23,6 +23,7 @@ import MyFlatList from "common-ui/src/components/flat-list"; import { trpc } from "@/src/trpc-client"; import { useAllProducts, useStores, useSlots, useGetEssentialConsts } from "@/src/hooks/prominent-api-hooks"; import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier"; +import { useCentralSlotStore } from "@/src/store/centralSlotStore"; import FloatingCartBar from "@/components/floating-cart-bar"; import BannerCarousel from "@/components/BannerCarousel"; import { useUserDetails } from "@/src/contexts/AuthContext"; @@ -367,6 +368,7 @@ export default function Dashboard() { const [isLoadingMore, setIsLoadingMore] = useState(false); const { backgroundColor } = useStatusBarStore(); const { getQuickestSlot } = useProductSlotIdentifier(); + const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); const [isRefreshing, setIsRefreshing] = useState(false); const { @@ -392,15 +394,18 @@ export default function Dashboard() { const slotB = getQuickestSlot(b.id); if (slotA && !slotB) return -1; if (!slotA && slotB) return 1; - if (a.isOutOfStock && !b.isOutOfStock) return 1; - if (!a.isOutOfStock && b.isOutOfStock) return -1; + const aOutOfStock = productSlotsMap[a.id]?.isOutOfStock; + const bOutOfStock = productSlotsMap[b.id]?.isOutOfStock; + if (aOutOfStock && !bOutOfStock) return 1; + if (!aOutOfStock && bOutOfStock) return -1; return 0; }); - + + console.log('setting the displayed products') setDisplayedProducts(initialBatch); setHasMore(products.length > 10); } - }, [productsData]); + }, [productsData, productSlotsMap]); const popularItemIds = useMemo(() => { const popularItems = essentialConsts?.popularItems; @@ -507,7 +512,9 @@ export default function Dashboard() { ); } - + let str = '' + displayedProducts.forEach(product => str += `${product.id}-`) + // console.log(str) return ( diff --git a/apps/user-ui/components/PaymentAndOrderComponent.tsx b/apps/user-ui/components/PaymentAndOrderComponent.tsx index 864bcec..e05b0fa 100644 --- a/apps/user-ui/components/PaymentAndOrderComponent.tsx +++ b/apps/user-ui/components/PaymentAndOrderComponent.tsx @@ -7,6 +7,7 @@ import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import { trpc } from '@/src/trpc-client'; import { useCentralProductStore } from '@/src/store/centralProductStore'; +import { useCentralSlotStore } from '@/src/store/centralSlotStore'; import { clearLocalCart } from '@/hooks/cart-query-hooks'; import { useQueryClient } from '@tanstack/react-query'; import { FontAwesome5, FontAwesome6 } from '@expo/vector-icons'; @@ -57,16 +58,17 @@ const PaymentAndOrderComponent: React.FC = ({ const products = useCentralProductStore((state) => state.products); const productsById = useCentralProductStore((state) => state.productsById); + const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); // Memoized flash-eligible product IDs const flashEligibleProductIds = useMemo(() => { if (!products.length) return new Set(); return new Set( products - .filter((product) => product.isFlashAvailable) + .filter((product) => productSlotsMap[product.id]?.isFlashAvailable) .map((product) => product.id) ); - }, [products]); + }, [products, productSlotsMap]); const placeOrderMutation = trpc.user.order.placeOrder.useMutation({ onSuccess: (data) => { @@ -128,7 +130,7 @@ const PaymentAndOrderComponent: React.FC = ({ const availableItems = cartItems .filter(item => { - if (productsById[item.productId]?.isOutOfStock) return false; + if (productSlotsMap[item.productId]?.isOutOfStock) return false; // For flash delivery, check if product supports flash delivery if (isFlashDelivery) { return flashEligibleProductIds.has(item.productId); diff --git a/apps/user-ui/components/ProductCard.tsx b/apps/user-ui/components/ProductCard.tsx index 055b8ee..0c1e7a2 100644 --- a/apps/user-ui/components/ProductCard.tsx +++ b/apps/user-ui/components/ProductCard.tsx @@ -123,6 +123,7 @@ const ProductCard: React.FC = ({ } }; + // console.log('rendering the product cart for id', item.id) return ( = ({ + /> {displayIsOutOfStock && ( diff --git a/apps/user-ui/components/ProductDetail.tsx b/apps/user-ui/components/ProductDetail.tsx index ab3f948..d4ce7a5 100644 --- a/apps/user-ui/components/ProductDetail.tsx +++ b/apps/user-ui/components/ProductDetail.tsx @@ -16,6 +16,7 @@ import { useSlots } from '@/src/hooks/prominent-api-hooks'; import FloatingCartBar from './floating-cart-bar'; import { useStoreHeaderStore } from '@/src/store/storeHeaderStore'; import { useCartStore } from '@/src/store/cartStore'; +import { useCentralSlotStore } from '@/src/store/centralSlotStore'; const { width: screenWidth } = Dimensions.get("window"); const carouselWidth = screenWidth; @@ -59,6 +60,12 @@ const ProductDetail: React.FC = ({ productId, isFlashDeliver const { setShouldNavigateToCart } = useFlashNavigationStore(); const { setAddedToCartProduct } = useCartStore(); const { data: slotsData } = useSlots(); + const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); + + const productAvailability = useMemo(() => { + if (!productDetail) return null; + return productSlotsMap[productDetail.id]; + }, [productDetail, productSlotsMap]); const sortedDeliverySlots = useMemo(() => { if (!slotsData?.slots || !productDetail) return [] @@ -102,7 +109,7 @@ const ProductDetail: React.FC = ({ productId, isFlashDeliver const handleAddToCart = (productId: number) => { if (isFlashDelivery) { - if (!productDetail?.isFlashAvailable) { + if (!productAvailability?.isFlashAvailable) { Alert.alert("Error", "This product is not available for flash delivery"); return; } @@ -121,7 +128,7 @@ const ProductDetail: React.FC = ({ productId, isFlashDeliver const handleBuyNow = (productId: number) => { if (isFlashDelivery) { - if (!productDetail?.isFlashAvailable) { + if (!productAvailability?.isFlashAvailable) { Alert.alert("Error", "This product is not available for flash delivery"); return; } @@ -249,13 +256,13 @@ const ProductDetail: React.FC = ({ productId, isFlashDeliver {productDetail.name} - {productDetail.isFlashAvailable && ( + {productAvailability?.isFlashAvailable && ( 1 Hr Delivery )} - {productDetail.isOutOfStock && ( + {productAvailability?.isOutOfStock && ( Out of Stock @@ -285,7 +292,7 @@ const ProductDetail: React.FC = ({ productId, isFlashDeliver {/* Flash price on separate line - smaller and less prominent */} - {productDetail.isFlashAvailable && productDetail.flashPrice && productDetail.flashPrice !== productDetail.price && ( + {productAvailability?.isFlashAvailable && productDetail.flashPrice && productDetail.flashPrice !== productDetail.price && ( 1 Hr Delivery: ₹{productDetail.flashPrice} / {formatQuantity(productDetail.productQuantity || 1, productDetail.unitNotation).display} @@ -312,11 +319,11 @@ const ProductDetail: React.FC = ({ productId, isFlashDeliver // Show "Add to Cart" button when not in cart { - if (productDetail.isOutOfStock || (isFlashDelivery && !productDetail.isFlashAvailable)) { + if (productAvailability?.isOutOfStock || (isFlashDelivery && !productAvailability?.isFlashAvailable)) { return; } if (isFlashDelivery) { @@ -327,10 +334,10 @@ const ProductDetail: React.FC = ({ productId, isFlashDeliver setAddedToCartProduct({ productId: productDetail.id, product: productDetail }); } }} - disabled={productDetail.isOutOfStock || (isFlashDelivery && !productDetail.isFlashAvailable)} + disabled={productAvailability?.isOutOfStock || (isFlashDelivery && !productAvailability?.isFlashAvailable)} > - - {(productDetail.isOutOfStock || (isFlashDelivery && !productDetail.isFlashAvailable)) ? 'Unavailable' : 'Add to Cart'} + + {(productAvailability?.isOutOfStock || (isFlashDelivery && !productAvailability?.isFlashAvailable)) ? 'Unavailable' : 'Add to Cart'} )} @@ -338,17 +345,17 @@ const ProductDetail: React.FC = ({ productId, isFlashDeliver {isFlashDelivery ? ( !(productDetail.isOutOfStock || !productDetail.isFlashAvailable) && handleBuyNow(productDetail.id)} - disabled={productDetail.isOutOfStock || !productDetail.isFlashAvailable} + onPress={() => !(productAvailability?.isOutOfStock || !productAvailability?.isFlashAvailable) && handleBuyNow(productDetail.id)} + disabled={productAvailability?.isOutOfStock || !productAvailability?.isFlashAvailable} > - - {productDetail.isOutOfStock ? 'Out of Stock' : - (!productDetail.isFlashAvailable ? 'Not Flash Eligible' : 'Get in 1 Hour')} + + {productAvailability?.isOutOfStock ? 'Out of Stock' : + (!productAvailability?.isFlashAvailable ? 'Not Flash Eligible' : 'Get in 1 Hour')} - ) : productDetail.isFlashAvailable ? ( + ) : productAvailability?.isFlashAvailable ? ( = ({ productId, isFlashDeliver key={index} style={tw`flex-row items-start mb-4 bg-gray-50 p-3 rounded-xl border border-gray-100`} onPress={() => handleSlotAddToCart(productDetail.id, slot.id)} - disabled={productDetail.isOutOfStock} + disabled={productAvailability?.isOutOfStock} activeOpacity={0.7} > @@ -598,7 +605,7 @@ const ProductDetail: React.FC = ({ productId, isFlashDeliver key={index} style={tw`flex-row items-start mb-4 bg-gray-50 p-4 rounded-xl border border-gray-100`} onPress={() => handleSlotAddToCart(productDetail.id, slot.id)} - disabled={productDetail.isOutOfStock} + disabled={productAvailability?.isOutOfStock} activeOpacity={0.7} > diff --git a/apps/user-ui/components/SlotSpecificView.tsx b/apps/user-ui/components/SlotSpecificView.tsx index cac6b97..871e09c 100644 --- a/apps/user-ui/components/SlotSpecificView.tsx +++ b/apps/user-ui/components/SlotSpecificView.tsx @@ -9,6 +9,7 @@ import { tw, theme, MyText, MyTouchableOpacity, MyFlatList, AppContainer, MiniQu import { trpc } from '@/src/trpc-client'; import { useAllProducts } from '@/src/hooks/prominent-api-hooks'; import { useQuickDeliveryStore } from '@/src/store/quickDeliveryStore'; +import { useCentralSlotStore } from '@/src/store/centralSlotStore'; import { useAddToCart, useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks'; import { useHideTabNav } from '@/src/hooks/useHideTabNav'; import CartIcon from '@/components/icons/CartIcon'; @@ -244,6 +245,7 @@ const CompactProductCard = ({ // Cart management for miniView const { data: cartData } = useGetCart({}, cartType); + const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); const updateCartItem = useUpdateCartItem({ showSuccessAlert: false, showErrorAlert: false, @@ -257,6 +259,7 @@ const CompactProductCard = ({ const cartItem = cartData?.items?.find((cartItem: any) => cartItem.productId === item.id); const quantity = cartItem?.quantity || 0; + const isOutOfStock = productSlotsMap[item.id]?.isOutOfStock; const handleQuantityChange = (newQuantity: number) => { if (newQuantity === 0 && cartItem) { @@ -282,7 +285,7 @@ const CompactProductCard = ({ source={{ uri: item.images?.[0] }} style={{ width: "100%", height: itemWidth, resizeMode: "cover" }} /> - {item.isOutOfStock && ( + {isOutOfStock && ( Out of Stock @@ -450,6 +453,7 @@ export function FlashDeliveryProducts({ storeId:storeIdParent, baseUrl, onProduc const storeIdNum = storeId; const productsQuery = useAllProducts(); + const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); const { addToCart = () => { } } = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }, "flash") || {}; @@ -490,17 +494,19 @@ export function FlashDeliveryProducts({ storeId:storeIdParent, baseUrl, onProduc let flashProducts: any[] = []; if (storeIdNum) { // Filter by store, flash availability, and stock status - flashProducts = productsQuery?.data?.products?.filter(p => - p.storeId === storeIdNum && - p.isFlashAvailable && - !p.isOutOfStock - ) || []; + flashProducts = productsQuery?.data?.products?.filter(p => { + const productInfo = productSlotsMap[p.id]; + return p.storeId === storeIdNum && + productInfo?.isFlashAvailable && + !productInfo?.isOutOfStock; + }) || []; } else { // Show all flash-available products that are in stock - flashProducts = productsQuery?.data?.products?.filter(p => - p.isFlashAvailable && - !p.isOutOfStock - ) || []; + flashProducts = productsQuery?.data?.products?.filter(p => { + const productInfo = productSlotsMap[p.id]; + return productInfo?.isFlashAvailable && + !productInfo?.isOutOfStock; + }) || []; } return ( diff --git a/apps/user-ui/components/cart-page.tsx b/apps/user-ui/components/cart-page.tsx index adec182..8f6dfa6 100644 --- a/apps/user-ui/components/cart-page.tsx +++ b/apps/user-ui/components/cart-page.tsx @@ -25,6 +25,7 @@ import TestingPhaseNote from "@/components/TestingPhaseNote"; import dayjs from "dayjs"; import { trpc } from "@/src/trpc-client"; import { useCentralProductStore } from '@/src/store/centralProductStore'; +import { useCentralSlotStore } from '@/src/store/centralSlotStore'; import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks'; import { useGetEssentialConsts } from '@/src/hooks/prominent-api-hooks'; @@ -83,6 +84,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) { const { data: constsData } = useGetEssentialConsts(); const products = useCentralProductStore((state) => state.products); const productsById = useCentralProductStore((state) => state.productsById); + const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); const cartItems = cartData?.items || []; @@ -92,20 +94,20 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) { if (!products.length) return new Set(); return new Set( products - .filter((product) => product.isFlashAvailable) + .filter((product) => productSlotsMap[product.id]?.isFlashAvailable) .map((product) => product.id) ); - }, [products]); + }, [products, productSlotsMap]); // Base total price without discounts for coupon eligibility check - const baseTotalPrice = useMemo( - () => - cartItems - .filter((item) => !productsById[item.productId]?.isOutOfStock) - .reduce((sum, item) => { - const product = productsById[item.productId]; - const price = product?.price || 0; - return sum + price * (quantities[item.id] || item.quantity); + const baseTotalPrice = useMemo( + () => + cartItems + .filter((item) => !productSlotsMap[item.productId]?.isOutOfStock) + .reduce((sum, item) => { + const product = productsById[item.productId]; + const price = product?.price || 0; + return sum + price * (quantities[item.id] || item.quantity); }, 0), [cartItems, quantities, productsById] ); @@ -201,7 +203,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) { ); const totalPrice = cartItems - .filter((item) => !productsById[item.productId]?.isOutOfStock) + .filter((item) => !productSlotsMap[item.productId]?.isOutOfStock) .reduce((sum, item) => { const product = productsById[item.productId]; const quantity = quantities[item.id] || item.quantity; @@ -275,7 +277,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) { const finalTotalWithDelivery = finalTotal + deliveryCharge; - const hasAvailableItems = cartItems.some(item => !productsById[item.productId]?.isOutOfStock); + const hasAvailableItems = cartItems.some(item => !productSlotsMap[item.productId]?.isOutOfStock); useEffect(() => { const initial: Record = {}; @@ -413,10 +415,11 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) { const selectedSlotForItem = selectedSlots[item.id]; const isFlashEligible = isFlashDelivery ? flashEligibleProductIds.has(item.productId) : true; const product = productsById[item.productId]; + const productSlotInfo = productSlotsMap[item.productId]; // const isAvailable = (productSlots.length > 0 || isFlashDelivery) && !item.product?.isOutOfStock && isFlashEligible; let isAvailable = true; - if (product?.isOutOfStock) { + if (productSlotInfo?.isOutOfStock) { isAvailable = false; } else if(isFlashDelivery) { if(!isFlashEligible) { @@ -673,12 +676,12 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) { )} {!isAvailable && ( - - - {product?.isOutOfStock - ? "Out of Stock" + + + {productSlotInfo?.isOutOfStock + ? "Out of Stock" : isFlashDelivery && !flashEligibleProductIds.has(item.productId) ? "Not available for flash delivery. Please remove" : "No delivery slots available"} @@ -911,7 +914,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) { onPress={() => { const availableItems = cartItems .filter(item => { - if (productsById[item.productId]?.isOutOfStock) return false; + if (productSlotsMap[item.productId]?.isOutOfStock) return false; if (isFlashDelivery) { // Check if product supports flash delivery return flashEligibleProductIds.has(item.productId); @@ -920,12 +923,10 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) { }) .map(item => item.id); - - - if (availableItems.length === 0) { - // Determine why no items are available - const outOfStockItems = cartItems.filter(item => productsById[item.productId]?.isOutOfStock); - const inStockItems = cartItems.filter(item => !productsById[item.productId]?.isOutOfStock); + if (availableItems.length === 0) { + // Determine why no items are available + const outOfStockItems = cartItems.filter(item => productSlotsMap[item.productId]?.isOutOfStock); + const inStockItems = cartItems.filter(item => !productSlotsMap[item.productId]?.isOutOfStock); let errorTitle = "Cannot Proceed"; let errorMessage = ""; @@ -962,9 +963,9 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) { return; } - // Check if there are items without slots (for regular delivery) - if (!isFlashDelivery && availableItems.length < cartItems.length) { - const itemsWithoutSlots = cartItems.filter(item => !selectedSlots[item.id] && !productsById[item.productId]?.isOutOfStock); + // Check if there are items without slots (for regular delivery) + if (!isFlashDelivery && availableItems.length < cartItems.length) { + const itemsWithoutSlots = cartItems.filter(item => !selectedSlots[item.id] && !productSlotsMap[item.productId]?.isOutOfStock); if (itemsWithoutSlots.length > 0) { Alert.alert( "Delivery Slot Required", diff --git a/apps/user-ui/components/checkout-page.tsx b/apps/user-ui/components/checkout-page.tsx index 6be1b95..84f1d50 100644 --- a/apps/user-ui/components/checkout-page.tsx +++ b/apps/user-ui/components/checkout-page.tsx @@ -9,6 +9,7 @@ import { useAuthenticatedRoute } from '@/hooks/useAuthenticatedRoute'; import { trpc } from '@/src/trpc-client'; import { useCentralProductStore } from '@/src/store/centralProductStore'; +import { useCentralSlotStore } from '@/src/store/centralSlotStore'; import { useGetCart } from '@/hooks/cart-query-hooks'; import { useGetEssentialConsts } from '@/src/hooks/prominent-api-hooks'; import PaymentAndOrderComponent from '@/components/PaymentAndOrderComponent'; @@ -38,6 +39,7 @@ const CheckoutPage: React.FC = ({ isFlashDelivery = false }) const { data: constsData } = useGetEssentialConsts(); const products = useCentralProductStore((state) => state.products); const productsById = useCentralProductStore((state) => state.productsById); + const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); useMarkDataFetchers(() => { refetchCart(); @@ -53,15 +55,15 @@ const CheckoutPage: React.FC = ({ isFlashDelivery = false }) const cartItems = cartData?.items || []; - // Memoized flash-eligible product IDs - const flashEligibleProductIds = useMemo(() => { - if (!products.length) return new Set(); - return new Set( - products - .filter((product) => product.isFlashAvailable) - .map((product) => product.id) - ); - }, [products]); + // Memoized flash-eligible product IDs + const flashEligibleProductIds = useMemo(() => { + if (!products.length) return new Set(); + return new Set( + products + .filter((product) => productSlotsMap[product.id]?.isFlashAvailable) + .map((product) => product.id) + ); + }, [products, productSlotsMap]); // Parse slots parameter from URL (format: "1:1,2,3;2:4,5") const selectedSlots = useMemo(() => { @@ -125,7 +127,7 @@ const CheckoutPage: React.FC = ({ isFlashDelivery = false }) const totalPrice = selectedItems - .filter((item) => !productsById[item.productId]?.isOutOfStock) + .filter((item) => !productSlotsMap[item.productId]?.isOutOfStock) .reduce( (sum, item) => { const product = productsById[item.productId]; diff --git a/apps/user-ui/hooks/cart-query-hooks.tsx b/apps/user-ui/hooks/cart-query-hooks.tsx index ceee1df..b63bfda 100644 --- a/apps/user-ui/hooks/cart-query-hooks.tsx +++ b/apps/user-ui/hooks/cart-query-hooks.tsx @@ -38,7 +38,6 @@ export interface CartItem { productId: number; quantity: number; addedAt: string; - product: ProductSummary; subtotal: number; slotId: number; } @@ -119,20 +118,6 @@ interface UseRemoveFromCartReturn { removeFromCartAsync: (itemId: number) => Promise; } -interface AllProductsResponse { - products: Array<{ - id: number; - price: number; - incrementStep: number; - marketPrice?: number | null; - name?: string; - flashPrice?: string | null; - images?: string[]; - productQuantity?: number; - unitNotation?: string; - }>; -} - const getLocalCart = async (cartType: CartType = "regular"): Promise => { const key = getCartStorageKey(cartType); const data = await StorageServiceCasual.getItem(key); @@ -199,7 +184,7 @@ const clearLocalCart = async (cartType: CartType = "regular"): Promise => }; export function useGetCart(options: UseGetCartOptions = {}, cartType: CartType = "regular"): UseGetCartReturn { - const { data: products } = useAllProducts() as { data: AllProductsResponse | undefined }; + const { data: products } = useAllProducts(); const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); const query: UseQueryResult = useQuery({ @@ -235,13 +220,7 @@ export function useGetCart(options: UseGetCartOptions = {}, cartType: CartType = id: cartItem.id, productId: cartItem.productId, quantity: cartItem.quantity, - addedAt: cartItem.addedAt, - product: { - ...productBasic, - isOutOfStock: productAvailability.isOutOfStock, - isFlashAvailable: productAvailability.isFlashAvailable, - }, - incrementStep: productBasic.incrementStep, + addedAt: cartItem.addedAt, subtotal: Number(productBasic.price) * cartItem.quantity, slotId: cartItem.slotId, }; diff --git a/apps/user-ui/src/components/AddToCartDialog.tsx b/apps/user-ui/src/components/AddToCartDialog.tsx index aaa8f79..8a6e124 100644 --- a/apps/user-ui/src/components/AddToCartDialog.tsx +++ b/apps/user-ui/src/components/AddToCartDialog.tsx @@ -5,6 +5,7 @@ import { tw, BottomDialog, MyText, MyTouchableOpacity, Quantifier } from 'common import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import { useCartStore } from '@/src/store/cartStore'; import { useFlashCartStore } from '@/src/store/flashCartStore'; +import { useCentralSlotStore } from '@/src/store/centralSlotStore'; import { useAddToCart, useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks'; import { useGetEssentialConsts, useSlots } from '@/src/hooks/prominent-api-hooks'; import dayjs from 'dayjs'; @@ -33,6 +34,7 @@ export default function AddToCartDialog() { const { data: slotsData } = useSlots(); const { data: cartData } = useGetCart(); const { data: constsData } = useGetEssentialConsts(); + const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); // const isFlashDeliveryEnabled = constsData?.isFlashDeliveryEnabled === true; const isFlashDeliveryEnabled = true; @@ -112,7 +114,7 @@ export default function AddToCartDialog() { const isUpdate = (cartItem?.quantity || 0) >= 1; // Check if flash delivery option should be shown - const showFlashOption = product?.isFlashAvailable === true && isFlashDeliveryEnabled; + const showFlashOption = productSlotsMap[product?.id]?.isFlashAvailable === true && isFlashDeliveryEnabled; const handleAddToCart = () => { if (selectedFlashDelivery) { diff --git a/packages/ui/index.ts b/packages/ui/index.ts index ac64e7d..45428fc 100755 --- a/packages/ui/index.ts +++ b/packages/ui/index.ts @@ -63,10 +63,10 @@ const isDevMode = Constants.executionEnvironment !== "standalone"; // const BASE_API_URL = API_URL; // const BASE_API_URL = 'http://10.0.2.2:4000'; // const BASE_API_URL = 'http://192.168.100.101:4000'; -// const BASE_API_URL = 'http://192.168.1.5:4000'; +const BASE_API_URL = 'http://192.168.1.5:4000'; // let BASE_API_URL = "https://mf.freshyo.in"; // let BASE_API_URL = "https://freshyo.technocracy.ovh"; -let BASE_API_URL = 'http://192.168.100.107:4000'; +// let BASE_API_URL = 'http://192.168.100.107:4000'; // let BASE_API_URL = 'http://192.168.29.176:4000'; // if(isDevMode) {