This commit is contained in:
shafi54 2026-03-10 14:20:14 +05:30
parent c14e32522a
commit f7c55ea492
11 changed files with 4366 additions and 124 deletions

View file

@ -1,4 +1,7 @@
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'
import { scaffoldSlotsWithProducts } from '@/src/trpc/apis/user-apis/apis/slots'
import { imageUploadS3 } from '@/src/lib/s3-client'
import { apiCacheKey } from '@/src/lib/env-exporter'
@ -17,3 +20,51 @@ export async function createProductsFile(): Promise<string> {
return s3Key
}
export async function createEssentialConstsFile(): Promise<string> {
// Get essential consts data from the API method
const essentialConstsData = await scaffoldEssentialConsts()
// Convert to JSON string with pretty formatting
const jsonContent = JSON.stringify(essentialConstsData, 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}/essential-consts.json`)
return s3Key
}
export async function createStoresFile(): Promise<string> {
// Get stores data from the API method
const storesData = await scaffoldStores()
// Convert to JSON string with pretty formatting
const jsonContent = JSON.stringify(storesData, 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}/stores.json`)
return s3Key
}
export async function createSlotsFile(): Promise<string> {
// Get slots data from the API method
const slotsData = await scaffoldSlotsWithProducts()
// Convert to JSON string with pretty formatting
const jsonContent = JSON.stringify(slotsData, 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}/slots.json`)
return s3Key
}

View file

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

View file

@ -13,6 +13,28 @@ import { assetsDomain, apiCacheKey } from '@/src/lib/env-exporter'
const polygon = turf.polygon(mbnrGeoJson.features[0].geometry.coordinates);
export async function scaffoldEssentialConsts() {
const consts = await getAllConstValues();
return {
freeDeliveryThreshold: consts[CONST_KEYS.freeDeliveryThreshold] ?? 200,
deliveryCharge: consts[CONST_KEYS.deliveryCharge] ?? 0,
flashFreeDeliveryThreshold: consts[CONST_KEYS.flashFreeDeliveryThreshold] ?? 500,
flashDeliveryCharge: consts[CONST_KEYS.flashDeliveryCharge] ?? 69,
popularItems: consts[CONST_KEYS.popularItems] ?? '5,3,2,4,1',
versionNum: consts[CONST_KEYS.versionNum] ?? '1.1.0',
playStoreUrl: consts[CONST_KEYS.playStoreUrl] ?? 'https://play.google.com/store/apps/details?id=in.freshyo.app',
appStoreUrl: consts[CONST_KEYS.appStoreUrl] ?? 'https://apps.apple.com/in/app/freshyo/id6756889077',
webViewHtml: null,
isWebviewClosable: true,
isFlashDeliveryEnabled: consts[CONST_KEYS.isFlashDeliveryEnabled] ?? true,
supportMobile: consts[CONST_KEYS.supportMobile] ?? '',
supportEmail: consts[CONST_KEYS.supportEmail] ?? '',
assetsDomain,
apiCacheKey,
};
}
export const commonApiRouter = router({
product: commonRouter,
getStoresSummary: publicProcedure
@ -100,25 +122,8 @@ export const commonApiRouter = router({
}),
essentialConsts: publicProcedure
.query(async () => {
const consts = await getAllConstValues();
return {
freeDeliveryThreshold: consts[CONST_KEYS.freeDeliveryThreshold] ?? 200,
deliveryCharge: consts[CONST_KEYS.deliveryCharge] ?? 0,
flashFreeDeliveryThreshold: consts[CONST_KEYS.flashFreeDeliveryThreshold] ?? 500,
flashDeliveryCharge: consts[CONST_KEYS.flashDeliveryCharge] ?? 69,
popularItems: consts[CONST_KEYS.popularItems] ?? '5,3,2,4,1',
versionNum: consts[CONST_KEYS.versionNum] ?? '1.1.0',
playStoreUrl: consts[CONST_KEYS.playStoreUrl] ?? 'https://play.google.com/store/apps/details?id=in.freshyo.app',
appStoreUrl: consts[CONST_KEYS.appStoreUrl] ?? 'https://apps.apple.com/in/app/freshyo/id6756889077',
webViewHtml: null,
isWebviewClosable: true,
isFlashDeliveryEnabled: consts[CONST_KEYS.isFlashDeliveryEnabled] ?? true,
supportMobile: consts[CONST_KEYS.supportMobile] ?? '',
supportEmail: consts[CONST_KEYS.supportEmail] ?? '',
assetsDomain,
apiCacheKey,
};
const response = await scaffoldEssentialConsts();
return response;
}),
});

View file

@ -84,7 +84,8 @@ export const commonRouter = router({
getAllProductsSummary: publicProcedure
.query(async () => {
return scaffoldProducts();
const response = await scaffoldProducts();
return response;
}),
getStoresSummary: publicProcedure
@ -111,4 +112,4 @@ export const commonRouter = router({
status: "ok",
};
}),
});
});

View file

@ -32,6 +32,23 @@ async function getSlotData(slotId: number) {
};
}
export async function scaffoldSlotsWithProducts() {
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,
};
}
export const slotsRouter = router({
getSlots: publicProcedure.query(async () => {
const slots = await db.query.deliverySlotInfo.findMany({
@ -44,20 +61,8 @@ export const slotsRouter = router({
}),
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,
};
const response = await scaffoldSlotsWithProducts();
return response;
}),
nextMajorDelivery: publicProcedure.query(async () => {

View file

@ -6,66 +6,71 @@ import { eq, and, sql } from 'drizzle-orm';
import { scaffoldAssetUrl } from '@/src/lib/s3-client';
import { ApiError } from '@/src/lib/api-error';
export const storesRouter = router({
getStores: publicProcedure
.query(async () => {
const storesData = await db
export async function scaffoldStores() {
const storesData = await db
.select({
id: storeInfo.id,
name: storeInfo.name,
description: storeInfo.description,
imageUrl: storeInfo.imageUrl,
productCount: sql<number>`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: storeInfo.id,
name: storeInfo.name,
description: storeInfo.description,
imageUrl: storeInfo.imageUrl,
productCount: sql<number>`count(${productInfo.id})`.as('productCount'),
id: productInfo.id,
name: productInfo.name,
images: productInfo.images,
})
.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,
};
})
);
.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: store.id,
name: store.name,
description: store.description,
signedImageUrl,
productCount: store.productCount,
sampleProducts: productsWithSignedUrls,
id: product.id,
name: product.name,
signedImageUrl: (images && images.length > 0) ? scaffoldAssetUrl(images[0]) : null,
};
})
);
return {
stores: storesWithDetails,
id: store.id,
name: store.name,
description: store.description,
signedImageUrl,
productCount: store.productCount,
sampleProducts: productsWithSignedUrls,
};
})
);
return {
stores: storesWithDetails,
};
}
export const storesRouter = router({
getStores: publicProcedure
.query(async () => {
const response = await scaffoldStores();
return response;
}),
getStoreWithProducts: publicProcedure

View file

@ -4,6 +4,9 @@ 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';
import { scaffoldStores } from './apis/user-apis/apis/stores';
import { scaffoldSlotsWithProducts } from './apis/user-apis/apis/slots';
import { scaffoldEssentialConsts } from './apis/common-apis/common-trpc-index';
// Create the main app router
export const appRouter = router({
@ -22,3 +25,6 @@ export const appRouter = router({
export type AppRouter = typeof appRouter;
export type AllProductsApiType = Awaited<ReturnType<typeof scaffoldProducts>>;
export type StoresApiType = Awaited<ReturnType<typeof scaffoldStores>>;
export type SlotsApiType = Awaited<ReturnType<typeof scaffoldSlotsWithProducts>>;
export type EssentialConstsApiType = Awaited<ReturnType<typeof scaffoldEssentialConsts>>;

View file

@ -21,14 +21,13 @@ 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 { useAllProducts, useStores, useSlots, useGetEssentialConsts } from "@/src/hooks/prominent-api-hooks";
import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier";
import FloatingCartBar from "@/components/floating-cart-bar";
import BannerCarousel from "@/components/BannerCarousel";
import { useUserDetails } from "@/src/contexts/AuthContext";
import TabLayoutWrapper from "@/components/TabLayoutWrapper";
import { useNavigationStore } from "@/src/store/navigationStore";
import { useGetEssentialConsts } from "@/src/hooks/prominent-api-hooks";
import NextOrderGlimpse from "@/components/NextOrderGlimpse";
dayjs.extend(relativeTime);
@ -379,8 +378,8 @@ export default function Dashboard() {
const { data: essentialConsts, isLoading: isLoadingConsts, error: constsError, refetch: refetchConsts } = useGetEssentialConsts();
const { data: storesData, refetch: refetchStores } = trpc.user.stores.getStores.useQuery();
const { data: slotsData, refetch: refetchSlots } = trpc.user.slots.getSlotsWithProducts.useQuery();
const { data: storesData, refetch: refetchStores } = useStores();
const { data: slotsData, refetch: refetchSlots } = useSlots();
const products = productsData?.products || [];

View file

@ -16,7 +16,7 @@ import {
} from "common-ui";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { Ionicons } from "@expo/vector-icons";
import { trpc } from "@/src/trpc-client";
import { useStores } from "@/src/hooks/prominent-api-hooks";
import { LinearGradient } from "expo-linear-gradient";
import TabLayoutWrapper from "@/components/TabLayoutWrapper";
import FloatingCartBar from "@/components/floating-cart-bar";
@ -157,7 +157,7 @@ export default function Stores() {
isLoading,
error,
refetch,
} = trpc.user.stores.getStores.useQuery();
} = useStores();
const stores = storesData?.stores || [];

View file

@ -1,7 +1,8 @@
import { useQuery } from '@tanstack/react-query'
import axios from 'axios'
import { trpc } from '@/src/trpc-client'
import { AllProductsApiType } from "@backend/trpc/router";
import { AllProductsApiType, StoresApiType, SlotsApiType, EssentialConstsApiType } from "@backend/trpc/router";
// Local useGetEssentialConsts hook
export const useGetEssentialConsts = () => {
const query = trpc.common.essentialConsts.useQuery(undefined, {
@ -10,40 +11,24 @@ export const useGetEssentialConsts = () => {
return { ...query, refetch: query.refetch }
}
// // Type definitions matching the backend response
// interface ProductSummary {
// id: number
// name: string
// shortDescription: string | null
// price: number
// marketPrice: number | null
// unit: string
// unitNotation: string
// incrementStep: number
// productQuantity: number
// storeId: number | null
// isOutOfStock: boolean
// isFlashAvailable: boolean
// nextDeliveryDate: string | null
// images: string[]
// }
//
// interface ProductsResponse {
// products: ProductSummary[]
// count: number
// }
//
type ProductsResponse = AllProductsApiType;
type StoresResponse = StoresApiType;
type SlotsResponse = SlotsApiType;
type EssentialConstsResponse = EssentialConstsApiType;
export function useAllProducts() {
function useCacheUrl(filename: string): string | null {
const { data: essentialConsts } = useGetEssentialConsts()
const assetsDomain = essentialConsts?.assetsDomain
const apiCacheKey = essentialConsts?.apiCacheKey
const cacheUrl = assetsDomain && apiCacheKey
? `${assetsDomain}${apiCacheKey}/products.json`
return assetsDomain && apiCacheKey
? `${assetsDomain}${apiCacheKey}/${filename}`
: null
}
export function useAllProducts() {
const cacheUrl = useCacheUrl('products.json')
return useQuery<ProductsResponse>({
queryKey: ['all-products', cacheUrl],
@ -58,3 +43,37 @@ export function useAllProducts() {
enabled: !!cacheUrl,
})
}
export function useStores() {
const cacheUrl = useCacheUrl('stores.json')
return useQuery<StoresResponse>({
queryKey: ['stores', cacheUrl],
queryFn: async () => {
if (!cacheUrl) {
throw new Error('Cache URL not available')
}
const response = await axios.get<StoresResponse>(cacheUrl)
return response.data
},
staleTime: 60000, // 1 minute
enabled: !!cacheUrl,
})
}
export function useSlots() {
const cacheUrl = useCacheUrl('slots.json')
return useQuery<SlotsResponse>({
queryKey: ['slots', cacheUrl],
queryFn: async () => {
if (!cacheUrl) {
throw new Error('Cache URL not available')
}
const response = await axios.get<SlotsResponse>(cacheUrl)
return response.data
},
staleTime: 60000, // 1 minute
enabled: !!cacheUrl,
})
}

4146
session-ses_32c7.md Normal file

File diff suppressed because it is too large Load diff