This commit is contained in:
shafi54 2026-03-10 10:03:49 +05:30
parent e5f80c9237
commit a4218ee1ad
20 changed files with 155 additions and 108 deletions

View file

@ -21,6 +21,7 @@ S3_BUCKET_NAME=meatfarmer
EXPO_ACCESS_TOKEN=Asvpy8cByRh6T4ksnWScO6PLcio2n35-BwES5zK-
JWT_SECRET=my_meatfarmer_jwt_secret_key
ASSETS_DOMAIN=https://assets.freshyo.in/
API_CACHE_KEY='api-cache'
# 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

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,19 @@
import { scaffoldProducts } from '@/src/trpc/apis/common-apis/common'
import { imageUploadS3 } from '@/src/lib/s3-client'
import { apiCacheKey } from '@/src/lib/env-exporter'
export async function createProductsFile(): Promise<string> {
// Get products data from the API method
const productsData = await scaffoldProducts()
// Convert to JSON string with pretty formatting
const jsonContent = JSON.stringify(productsData, null, 2)
// Convert to Buffer for S3 upload
const buffer = Buffer.from(jsonContent, 'utf-8')
// Upload to S3 at the specified path using apiCacheKey
const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/products.json`)
return s3Key
}

View file

@ -17,6 +17,8 @@ export const s3Region = process.env.S3_REGION as string
export const assetsDomain = process.env.ASSETS_DOMAIN as string;
export const apiCacheKey = process.env.API_CACHE_KEY as string;
export const s3Url = process.env.S3_URL as string
export const redisUrl = process.env.REDIS_URL as string

View file

@ -3,6 +3,7 @@ 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'
import { createProductsFile } from '@/src/lib/cloud_cache'
/**
* Initialize all application services
@ -25,6 +26,10 @@ export const initFunc = async (): Promise<void> => {
startCancellationHandler(),
]);
// Create products cache file after stores are initialized
await createProductsFile();
console.log('Products cache file created successfully');
console.log('Application initialization completed successfully');
} catch (error) {
console.error('Application initialization failed:', error);

View file

@ -9,6 +9,7 @@ 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'
import { assetsDomain, apiCacheKey } from '@/src/lib/env-exporter'
const polygon = turf.polygon(mbnrGeoJson.features[0].geometry.coordinates);
@ -115,6 +116,8 @@ export const commonApiRouter = router({
isFlashDeliveryEnabled: consts[CONST_KEYS.isFlashDeliveryEnabled] ?? true,
supportMobile: consts[CONST_KEYS.supportMobile] ?? '',
supportEmail: consts[CONST_KEYS.supportEmail] ?? '',
assetsDomain,
apiCacheKey,
};
}),
});

View file

@ -1,12 +1,10 @@
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 { productInfo, units, productSlots, deliverySlotInfo, storeInfo } from '@/src/db/schema'
import { eq, gt, and, sql, inArray } from 'drizzle-orm';
import { generateSignedUrlsFromS3Urls, generateSignedUrlFromS3Url } from '@/src/lib/s3-client'
import { z } from 'zod';
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<Date | null> => {
const result = await db
@ -28,66 +26,11 @@ export const getNextDeliveryDate = async (productId: number): Promise<Date | nul
return result[0]?.deliveryTime || null;
};
export const commonRouter = router({
getDashboardTags: publicProcedure
.query(async () => {
// Get dashboard tags from cache
const tags = await getDashboardTagsFromCache();
return {
tags: tags,
};
}),
getAllProductsSummary: publicProcedure
.input(z.object({
searchQuery: z.string().optional(),
tagId: z.number().optional()
}))
.query(async ({ input }) => {
const { searchQuery, tagId } = input;
export async function scaffoldProducts() {
// Get all products from cache
let products = await getAllProductsFromCache();
products = products.filter(item => Boolean(item.id))
// Apply tag filtering if tagId is provided
if (tagId) {
// Get products that have this tag from the database
const taggedProducts = await db
.select({ productId: productTags.productId })
.from(productTags)
.where(eq(productTags.tagId, tagId));
const taggedProductIds = new Set(taggedProducts.map(tp => tp.productId));
// Filter products based on tag
products = products.filter(product => taggedProductIds.has(product.id));
}
// Apply search filtering if searchQuery is provided using Fuse.js
if (searchQuery) {
const fuse = new Fuse(products, {
keys: [
'name',
'shortDescription',
'longDescription',
'store.name', // Search in store name too
'productTags', // Search in product tags too
],
threshold: 0.3, // Adjust fuzziness (0.0 = exact match, 1.0 = match anything)
includeScore: true,
shouldSort: true,
});
const fuseResults = fuse.search(searchQuery);
products = fuseResults.map(result => result.item);
}
// Get suspended product IDs to filter them out
const suspendedProducts = await db
.select({ id: productInfo.id })
@ -117,16 +60,31 @@ export const commonRouter = router({
isOutOfStock: product.isOutOfStock,
isFlashAvailable: product.isFlashAvailable,
nextDeliveryDate: nextDeliveryDate ? nextDeliveryDate.toISOString() : null,
images: product.images, // Already signed URLs from cache
images: product.images,
};
})
);
return {
products: formattedProducts,
count: formattedProducts.length,
};
}
export const commonRouter = router({
getDashboardTags: publicProcedure
.query(async () => {
// Get dashboard tags from cache
const tags = await getDashboardTagsFromCache();
return {
tags: tags,
};
}),
getAllProductsSummary: publicProcedure
.query(async () => {
return scaffoldProducts();
}),
getStoresSummary: publicProcedure

View file

@ -3,6 +3,7 @@ import { z } from 'zod';
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'
import { scaffoldProducts } from './apis/common-apis/common';
// Create the main app router
export const appRouter = router({
@ -16,5 +17,8 @@ export const appRouter = router({
common: commonApiRouter,
});
// Export type definition of API
export type AppRouter = typeof appRouter;
export type AllProductsApiType = Awaited<ReturnType<typeof scaffoldProducts>>;

View file

@ -21,6 +21,7 @@ import AddToCartDialog from "@/src/components/AddToCartDialog";
import MyFlatList from "common-ui/src/components/flat-list";
import { trpc } from "@/src/trpc-client";
import { useAllProducts } from "@/src/hooks/prominent-api-hooks";
import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier";
import FloatingCartBar from "@/components/floating-cart-bar";
import BannerCarousel from "@/components/BannerCarousel";
@ -360,8 +361,6 @@ export default function Dashboard() {
const router = useRouter();
const userDetails = useUserDetails();
const [inputQuery, setInputQuery] = useState("");
const [searchQuery, setSearchQuery] = useState("");
const [selectedTagId, setSelectedTagId] = useState<number | null>(null);
const [isLoadingDialogOpen, setIsLoadingDialogOpen] = useState(false);
const [gradientHeight, setGradientHeight] = useState(0);
const [displayedProducts, setDisplayedProducts] = useState<any[]>([]);
@ -376,10 +375,7 @@ export default function Dashboard() {
isLoading,
error,
refetch,
} = trpc.common.product.getAllProductsSummary.useQuery({
searchQuery: searchQuery || undefined,
tagId: selectedTagId || undefined,
});
} = useAllProducts();
const { data: essentialConsts, isLoading: isLoadingConsts, error: constsError, refetch: refetchConsts } = useGetEssentialConsts();

View file

@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect, useCallback } from "react";
import React, { useState, useRef, useEffect, useCallback, useMemo } from "react";
import { View, Dimensions } from "react-native";
import { useRouter, useLocalSearchParams } from "expo-router";
import {
@ -10,7 +10,8 @@ import {
SearchBar,
} from "common-ui";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { trpc } from "@/src/trpc-client";
import Fuse from "fuse.js";
import { useAllProducts } from "@/src/hooks/prominent-api-hooks";
import ProductCard from "@/components/ProductCard";
import FloatingCartBar from "@/components/floating-cart-bar";
@ -51,12 +52,27 @@ export default function SearchResults() {
});
}, []);
const { data: productsData, isLoading, error, refetch } =
trpc.common.product.getAllProductsSummary.useQuery({
searchQuery: debouncedQuery || undefined,
const { data: productsData, isLoading, error, refetch } = useAllProducts();
const allProducts = productsData?.products || [];
// Client-side search filtering using Fuse.js
const products = useMemo(() => {
if (!debouncedQuery.trim()) return allProducts;
const fuse = new Fuse(allProducts, {
keys: [
'name',
'shortDescription',
],
threshold: 0.3,
includeScore: true,
shouldSort: true,
});
const products = productsData?.products || [];
const fuseResults = fuse.search(debouncedQuery);
return fuseResults.map(result => result.item);
}, [allProducts, debouncedQuery]);
useManualRefresh(() => {
refetch();

View file

@ -6,6 +6,7 @@ import MaterialIcons from '@expo/vector-icons/MaterialIcons';
// import RazorpayCheckout from 'react-native-razorpay';
import { trpc } from '@/src/trpc-client';
import { useAllProducts } from '@/src/hooks/prominent-api-hooks';
import { clearLocalCart } from '@/hooks/cart-query-hooks';
import { useQueryClient } from '@tanstack/react-query';
import { FontAwesome5, FontAwesome6 } from '@expo/vector-icons';
@ -54,7 +55,7 @@ const PaymentAndOrderComponent: React.FC<PaymentAndOrderProps> = ({
queryClient.invalidateQueries({ queryKey: [`local-cart-${cartType}`] });
};
const { data: productsData } = trpc.common.product.getAllProductsSummary.useQuery({});
const { data: productsData } = useAllProducts();
// Memoized flash-eligible product IDs
const flashEligibleProductIds = useMemo(() => {

View file

@ -7,6 +7,7 @@ import { useRouter, usePathname } from 'expo-router';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { tw, theme, MyText, MyTouchableOpacity, MyFlatList, AppContainer, MiniQuantifier } from 'common-ui';
import { trpc } from '@/src/trpc-client';
import { useAllProducts } from '@/src/hooks/prominent-api-hooks';
import { useQuickDeliveryStore } from '@/src/store/quickDeliveryStore';
import { useAddToCart, useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks';
import { useHideTabNav } from '@/src/hooks/useHideTabNav';
@ -348,7 +349,7 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
const slotQuery = trpc.user.slots.getSlotById.useQuery({ slotId: slotId! }, { enabled: !!slotId });
const productsQuery = trpc.common.product.getAllProductsSummary.useQuery({});
const productsQuery = useAllProducts();
const { addToCart = () => { } } = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }, "regular") || {};
@ -448,7 +449,7 @@ export function FlashDeliveryProducts({ storeId:storeIdParent, baseUrl, onProduc
const storeId = storeIdParent;
const storeIdNum = storeId;
const productsQuery = trpc.common.product.getAllProductsSummary.useQuery({});
const productsQuery = useAllProducts();
const { addToCart = () => { } } = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }, "flash") || {};

View file

@ -24,6 +24,7 @@ import TestingPhaseNote from "@/components/TestingPhaseNote";
import dayjs from "dayjs";
import { trpc } from "@/src/trpc-client";
import { useAllProducts } from "@/src/hooks/prominent-api-hooks";
import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks';
import { useGetEssentialConsts } from '@/src/api-hooks/essential-consts.api';
@ -80,7 +81,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
const { data: couponsRaw, error: couponsError } = trpc.user.coupon.getEligible.useQuery();
const { data: constsData } = useGetEssentialConsts();
const { data: productsData } = trpc.common.product.getAllProductsSummary.useQuery({});
const { data: productsData } = useAllProducts();
const cartItems = cartData?.items || [];

View file

@ -8,6 +8,7 @@ import AddressForm from '@/src/components/AddressForm';
import { useAuthenticatedRoute } from '@/hooks/useAuthenticatedRoute';
import { trpc } from '@/src/trpc-client';
import { useAllProducts } from '@/src/hooks/prominent-api-hooks';
import { useGetCart } from '@/hooks/cart-query-hooks';
import { useGetEssentialConsts } from '@/src/api-hooks/essential-consts.api';
import PaymentAndOrderComponent from '@/components/PaymentAndOrderComponent';
@ -35,7 +36,7 @@ const CheckoutPage: React.FC<CheckoutPageProps> = ({ isFlashDelivery = false })
const { data: addresses, refetch: refetchAddresses } = trpc.user.address.getUserAddresses.useQuery();
const { data: slotsData, refetch: refetchSlots } = trpc.user.slots.getSlots.useQuery();
const { data: constsData } = useGetEssentialConsts();
const { data: productsData } = trpc.common.product.getAllProductsSummary.useQuery({});
const { data: productsData } = useAllProducts();
useMarkDataFetchers(() => {
refetchCart();

View file

@ -1,4 +1,5 @@
import { trpc } from '@/src/trpc-client';
import { useAllProducts } from '@/src/hooks/prominent-api-hooks';
import { Alert } from 'react-native';
import { useState, useEffect } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
@ -133,7 +134,7 @@ export function useGetCart(options?: {
};
} else {
const { data: products } = trpc.common.product.getAllProductsSummary.useQuery({});
const { data: products } = useAllProducts();
const query = useQuery({
queryKey: [`local-cart-${cartType}`],
queryFn: async () => {

View file

@ -48,6 +48,7 @@
"expo-updates": "~0.28.17",
"expo-web-browser": "~14.2.0",
"formik": "^2.4.6",
"fuse.js": "^7.1.0",
"jwt-decode": "^4.0.0",
"react": "19.0.0",
"react-dom": "19.0.0",

View file

@ -0,0 +1,34 @@
import { useQuery } from '@tanstack/react-query'
import axios from 'axios'
import { useGetEssentialConsts } from '@/src/api-hooks/essential-consts.api'
// import { AllProductsApiType } from '@backend/trpc/router'
import { AllProductsApiType } from "@backend/trpc/router";
type ProductsResponse = AllProductsApiType;
export function useAllProducts() {
const { data: essentialConsts } = useGetEssentialConsts()
const assetsDomain = essentialConsts?.assetsDomain
const apiCacheKey = essentialConsts?.apiCacheKey
const cacheUrl = assetsDomain && apiCacheKey
? `${assetsDomain}${apiCacheKey}/products.json`
: null
return useQuery<ProductsResponse>({
queryKey: ['all-products', cacheUrl],
queryFn: async () => {
if (!cacheUrl) {
throw new Error('Cache URL not available')
}
console.log(cacheUrl)
const response = await axios.get<ProductsResponse>(cacheUrl)
return response.data
},
staleTime: 60000, // 1 minute
enabled: !!cacheUrl,
})
}

2
package-lock.json generated
View file

@ -25,6 +25,7 @@
"expo-crypto": "~14.1.5",
"expo-server-sdk": "^5.0.0",
"expo-web-browser": "~14.2.0",
"fuse.js": "^7.1.0",
"node-cron": "^4.2.1",
"pg": "^8.20.0",
"react": "19.0.0",
@ -507,6 +508,7 @@
"expo-updates": "~0.28.17",
"expo-web-browser": "~14.2.0",
"formik": "^2.4.6",
"fuse.js": "^7.1.0",
"jwt-decode": "^4.0.0",
"react": "19.0.0",
"react-dom": "19.0.0",

View file

@ -42,6 +42,7 @@
"expo-crypto": "~14.1.5",
"expo-server-sdk": "^5.0.0",
"expo-web-browser": "~14.2.0",
"fuse.js": "^7.1.0",
"node-cron": "^4.2.1",
"pg": "^8.20.0",
"react": "19.0.0",

View file

@ -64,9 +64,9 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
// 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';
let BASE_API_URL = "https://mf.freshyo.in";
// 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) {