enh
This commit is contained in:
parent
5df040de9a
commit
2d37726c62
17 changed files with 223 additions and 203 deletions
|
|
@ -1,6 +1,6 @@
|
|||
ENV_MODE=PROD
|
||||
# DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
|
||||
DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
||||
DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
|
||||
# DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
||||
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
|
||||
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
||||
PHONE_PE_CLIENT_VERSION=1
|
||||
|
|
@ -22,7 +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_API_TOKEN=I8Vp4E9TX58E8qEDeH0nTFDS2d2zXNYiXvbs4Ckj
|
||||
CLOUDFLARE_API_TOKEN=N7jAg5X-RUj_fVfMW6zbfJ8qIYc81TSIKKlbZ6oh
|
||||
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
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -6,7 +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
|
||||
|
|
@ -66,11 +66,6 @@ export const createTag = async (req: Request, res: Response) => {
|
|||
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)
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -191,11 +186,6 @@ export const updateTag = async (req: Request, res: Response) => {
|
|||
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)
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -233,9 +223,4 @@ export const deleteTag = async (req: Request, res: Response) => {
|
|||
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)
|
||||
})
|
||||
};
|
||||
|
|
@ -7,7 +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;
|
||||
|
|
@ -117,11 +117,6 @@ export const createProduct = async (req: Request, res: Response) => {
|
|||
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)
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -308,9 +303,4 @@ export const updateProduct = async (req: Request, res: Response) => {
|
|||
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)
|
||||
})
|
||||
};
|
||||
|
|
@ -193,6 +193,117 @@ export async function createAllStoresFiles(): Promise<string[]> {
|
|||
return results
|
||||
}
|
||||
|
||||
export interface CreateAllCacheFilesResult {
|
||||
products: string
|
||||
essentialConsts: string
|
||||
stores: string
|
||||
slots: string
|
||||
banners: string
|
||||
individualStores: string[]
|
||||
}
|
||||
|
||||
export async function createAllCacheFiles(): Promise<CreateAllCacheFilesResult> {
|
||||
console.log('Starting creation of all cache files...')
|
||||
|
||||
// Create all global cache files in parallel
|
||||
const [
|
||||
productsKey,
|
||||
essentialConstsKey,
|
||||
storesKey,
|
||||
slotsKey,
|
||||
bannersKey,
|
||||
individualStoreKeys,
|
||||
] = await Promise.all([
|
||||
createProductsFileInternal(),
|
||||
createEssentialConstsFileInternal(),
|
||||
createStoresFileInternal(),
|
||||
createSlotsFileInternal(),
|
||||
createBannersFileInternal(),
|
||||
createAllStoresFilesInternal(),
|
||||
])
|
||||
|
||||
// Collect all URLs for batch cache purge
|
||||
const urls = [
|
||||
constructCacheUrl(CACHE_FILENAMES.products),
|
||||
constructCacheUrl(CACHE_FILENAMES.essentialConsts),
|
||||
constructCacheUrl(CACHE_FILENAMES.stores),
|
||||
constructCacheUrl(CACHE_FILENAMES.slots),
|
||||
constructCacheUrl(CACHE_FILENAMES.banners),
|
||||
...individualStoreKeys.map((_, index) => constructCacheUrl(`stores/${index + 1}.json`)),
|
||||
]
|
||||
|
||||
// Purge all caches in one batch with retry
|
||||
try {
|
||||
await retryWithExponentialBackoff(() => clearUrlCache(urls))
|
||||
console.log(`Cache purged for all ${urls.length} files`)
|
||||
} catch (error) {
|
||||
console.error(`Failed to purge cache for all files after 3 retries`, error)
|
||||
}
|
||||
|
||||
console.log('All cache files created successfully')
|
||||
|
||||
return {
|
||||
products: productsKey,
|
||||
essentialConsts: essentialConstsKey,
|
||||
stores: storesKey,
|
||||
slots: slotsKey,
|
||||
banners: bannersKey,
|
||||
individualStores: individualStoreKeys,
|
||||
}
|
||||
}
|
||||
|
||||
// Internal versions that skip cache purging (for batch operations)
|
||||
async function createProductsFileInternal(): Promise<string> {
|
||||
const productsData = await scaffoldProducts()
|
||||
const jsonContent = JSON.stringify(productsData, null, 2)
|
||||
const buffer = Buffer.from(jsonContent, 'utf-8')
|
||||
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.products}`)
|
||||
}
|
||||
|
||||
async function createEssentialConstsFileInternal(): Promise<string> {
|
||||
const essentialConstsData = await scaffoldEssentialConsts()
|
||||
const jsonContent = JSON.stringify(essentialConstsData, null, 2)
|
||||
const buffer = Buffer.from(jsonContent, 'utf-8')
|
||||
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.essentialConsts}`)
|
||||
}
|
||||
|
||||
async function createStoresFileInternal(): Promise<string> {
|
||||
const storesData = await scaffoldStores()
|
||||
const jsonContent = JSON.stringify(storesData, null, 2)
|
||||
const buffer = Buffer.from(jsonContent, 'utf-8')
|
||||
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.stores}`)
|
||||
}
|
||||
|
||||
async function createSlotsFileInternal(): Promise<string> {
|
||||
const slotsData = await scaffoldSlotsWithProducts()
|
||||
const jsonContent = JSON.stringify(slotsData, null, 2)
|
||||
const buffer = Buffer.from(jsonContent, 'utf-8')
|
||||
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.slots}`)
|
||||
}
|
||||
|
||||
async function createBannersFileInternal(): Promise<string> {
|
||||
const bannersData = await scaffoldBanners()
|
||||
const jsonContent = JSON.stringify(bannersData, null, 2)
|
||||
const buffer = Buffer.from(jsonContent, 'utf-8')
|
||||
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.banners}`)
|
||||
}
|
||||
|
||||
async function createAllStoresFilesInternal(): Promise<string[]> {
|
||||
const stores = await db.select({ id: storeInfo.id }).from(storeInfo)
|
||||
const results: string[] = []
|
||||
|
||||
for (const store of stores) {
|
||||
const storeData = await scaffoldStoreWithProducts(store.id)
|
||||
const jsonContent = JSON.stringify(storeData, null, 2)
|
||||
const buffer = Buffer.from(jsonContent, 'utf-8')
|
||||
const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/stores/${store.id}.json`)
|
||||
results.push(s3Key)
|
||||
}
|
||||
|
||||
console.log(`Created ${results.length} store cache files`)
|
||||
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')
|
||||
|
|
@ -222,6 +333,7 @@ export async function clearUrlCache(urls: string[]): Promise<{ success: boolean;
|
|||
console.log(`Successfully purged ${urls.length} URLs from Cloudflare cache: ${urls.join(', ')}`)
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.log(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] }
|
||||
|
|
|
|||
|
|
@ -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, createEssentialConstsFile, createStoresFile, createSlotsFile, createBannersFile, createAllStoresFiles } from '@/src/lib/cloud_cache'
|
||||
import { createAllCacheFiles } from '@/src/lib/cloud_cache'
|
||||
|
||||
/**
|
||||
* Initialize all application services
|
||||
|
|
@ -26,15 +26,8 @@ export const initFunc = async (): Promise<void> => {
|
|||
startCancellationHandler(),
|
||||
]);
|
||||
|
||||
// Create cache files in parallel after stores are initialized
|
||||
await Promise.all([
|
||||
createProductsFile(),
|
||||
createEssentialConstsFile(),
|
||||
createStoresFile(),
|
||||
createSlotsFile(),
|
||||
createBannersFile(),
|
||||
createAllStoresFiles(),
|
||||
]);
|
||||
// Create all cache files after stores are initialized
|
||||
await createAllCacheFiles();
|
||||
console.log('Cache files created successfully');
|
||||
|
||||
console.log('Application initialization completed successfully');
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { initializeProducts } from '@/src/stores/product-store'
|
|||
import { initializeProductTagStore } from '@/src/stores/product-tag-store'
|
||||
import { initializeSlotStore } from '@/src/stores/slot-store'
|
||||
import { initializeBannerStore } from '@/src/stores/banner-store'
|
||||
import { createAllCacheFiles } from '@/src/lib/cloud_cache'
|
||||
|
||||
/**
|
||||
* Initialize all application stores
|
||||
|
|
@ -29,6 +30,11 @@ export const initializeAllStores = async (): Promise<void> => {
|
|||
]);
|
||||
|
||||
console.log('All application stores initialized successfully');
|
||||
|
||||
// Regenerate all cache files (fire-and-forget)
|
||||
createAllCacheFiles().catch(error => {
|
||||
console.error('Failed to regenerate cache files during store initialization:', error)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Application stores initialization failed:', error);
|
||||
throw error;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { extractKeyFromPresignedUrl, generateSignedUrlFromS3Url } from '@/src/li
|
|||
import { ApiError } from '@/src/lib/api-error';
|
||||
import { initializeAllStores } from '@/src/stores/store-initializer'
|
||||
|
||||
|
||||
export const bannerRouter = router({
|
||||
// Get all banners
|
||||
getBanners: protectedProcedure
|
||||
|
|
|
|||
|
|
@ -8,7 +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;
|
||||
|
|
@ -105,11 +105,6 @@ 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",
|
||||
};
|
||||
|
|
@ -197,11 +192,6 @@ 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,
|
||||
|
|
@ -404,11 +394,6 @@ 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',
|
||||
|
|
@ -457,11 +442,6 @@ 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',
|
||||
|
|
@ -491,12 +471,6 @@ export const productRouter = router({
|
|||
// Reinitialize stores to reflect changes
|
||||
await initializeAllStores();
|
||||
|
||||
// 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)
|
||||
})
|
||||
|
||||
return {
|
||||
message: 'Group deleted successfully',
|
||||
};
|
||||
|
|
@ -550,17 +524,12 @@ export const productRouter = router({
|
|||
|
||||
await Promise.all(updatePromises);
|
||||
|
||||
// Reinitialize stores to reflect changes
|
||||
await initializeAllStores();
|
||||
// Reinitialize stores to reflect changes
|
||||
await initializeAllStores();
|
||||
|
||||
// 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,
|
||||
};
|
||||
}),
|
||||
});
|
||||
return {
|
||||
message: `Updated prices for ${updates.length} product(s)`,
|
||||
updatedCount: updates.length,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +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[];
|
||||
|
|
@ -218,11 +218,6 @@ 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,
|
||||
|
|
@ -306,11 +301,6 @@ 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;
|
||||
}),
|
||||
|
||||
|
|
@ -470,11 +460,6 @@ 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) {
|
||||
|
|
@ -505,11 +490,6 @@ 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",
|
||||
};
|
||||
|
|
@ -590,11 +570,6 @@ 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",
|
||||
|
|
@ -626,11 +601,6 @@ 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,
|
||||
|
|
|
|||
|
|
@ -7,7 +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
|
||||
|
|
@ -88,11 +88,6 @@ 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",
|
||||
|
|
@ -169,19 +164,14 @@ export const storeRouter = router({
|
|||
}
|
||||
}
|
||||
|
||||
// Reinitialize stores to reflect changes
|
||||
await initializeAllStores();
|
||||
// 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 update:', error)
|
||||
})
|
||||
|
||||
return {
|
||||
store: updatedStore,
|
||||
message: "Store updated successfully",
|
||||
};
|
||||
}),
|
||||
return {
|
||||
store: updatedStore,
|
||||
message: "Store updated successfully",
|
||||
};
|
||||
}),
|
||||
|
||||
deleteStore: protectedProcedure
|
||||
.input(z.object({
|
||||
|
|
@ -212,14 +202,9 @@ export const storeRouter = router({
|
|||
};
|
||||
});
|
||||
|
||||
// Reinitialize stores to reflect changes (outside transaction)
|
||||
await initializeAllStores();
|
||||
// Reinitialize stores to reflect changes (outside transaction)
|
||||
await initializeAllStores();
|
||||
|
||||
// Regenerate stores cache (fire-and-forget)
|
||||
createStoresFile().catch(error => {
|
||||
console.error('Failed to regenerate stores cache after store deletion:', error)
|
||||
})
|
||||
|
||||
return result;
|
||||
}),
|
||||
});
|
||||
return result;
|
||||
}),
|
||||
});
|
||||
|
|
@ -7,7 +7,7 @@ import {
|
|||
productInfo,
|
||||
units,
|
||||
} from "@/src/db/schema";
|
||||
import { eq, and, gt, asc } from "drizzle-orm";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { getAllSlots as getAllSlotsFromCache, getSlotById as getSlotByIdFromCache } from "@/src/stores/slot-store";
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
|
|
@ -84,26 +84,6 @@ export const slotsRouter = router({
|
|||
return response;
|
||||
}),
|
||||
|
||||
nextMajorDelivery: publicProcedure.query(async () => {
|
||||
const now = new Date();
|
||||
|
||||
// Find the next upcoming active delivery slot ID
|
||||
const nextSlot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: and(
|
||||
eq(deliverySlotInfo.isActive, true),
|
||||
gt(deliverySlotInfo.deliveryTime, now),
|
||||
),
|
||||
orderBy: asc(deliverySlotInfo.deliveryTime),
|
||||
});
|
||||
|
||||
if (!nextSlot) {
|
||||
return null; // No upcoming delivery slots
|
||||
}
|
||||
|
||||
// Get formatted data using helper method
|
||||
return await getSlotData(nextSlot.id);
|
||||
}),
|
||||
|
||||
getSlotById: publicProcedure
|
||||
.input(z.object({ slotId: z.number() }))
|
||||
.query(async ({ input }) => {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ 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 { useCentralProductStore } from "@/src/store/centralProductStore";
|
||||
import FloatingCartBar from "@/components/floating-cart-bar";
|
||||
import BannerCarousel from "@/components/BannerCarousel";
|
||||
import { useUserDetails } from "@/src/contexts/AuthContext";
|
||||
|
|
@ -369,19 +370,20 @@ export default function Dashboard() {
|
|||
const { backgroundColor } = useStatusBarStore();
|
||||
const { getQuickestSlot } = useProductSlotIdentifier();
|
||||
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap);
|
||||
const refetchProducts = useCentralProductStore((state) => state.refetchProducts);
|
||||
const refetchSlotsFromStore = useCentralSlotStore((state) => state.refetchSlots);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
const {
|
||||
data: productsData,
|
||||
isLoading,
|
||||
error,
|
||||
refetch,
|
||||
} = useAllProducts();
|
||||
|
||||
const { data: essentialConsts, isLoading: isLoadingConsts, error: constsError, refetch: refetchConsts } = useGetEssentialConsts();
|
||||
|
||||
const { data: storesData, refetch: refetchStores } = useStores();
|
||||
const { data: slotsData, refetch: refetchSlots } = useSlots();
|
||||
const { data: slotsData } = useSlots();
|
||||
|
||||
const products = productsData?.products || [];
|
||||
|
||||
|
|
@ -440,11 +442,22 @@ export default function Dashboard() {
|
|||
const handleRefresh = useCallback(async () => {
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
await Promise.all([refetch(), refetchStores(), refetchSlots(), refetchConsts()]);
|
||||
const promises = [];
|
||||
|
||||
if (refetchProducts) {
|
||||
promises.push(refetchProducts());
|
||||
}
|
||||
if (refetchSlotsFromStore) {
|
||||
promises.push(refetchSlotsFromStore());
|
||||
}
|
||||
promises.push(refetchStores());
|
||||
promises.push(refetchConsts());
|
||||
|
||||
await Promise.all(promises);
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
}, [refetch, refetchStores, refetchSlots, refetchConsts]);
|
||||
}, [refetchProducts, refetchSlotsFromStore, refetchStores, refetchConsts]);
|
||||
|
||||
useManualRefresh(() => {
|
||||
handleRefresh();
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ 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 { useAllProducts, useStores, useSlots } from '@/src/hooks/prominent-api-hooks';
|
||||
import { AllProductsApiType } from '@backend/trpc/router';
|
||||
import { useQuickDeliveryStore } from '@/src/store/quickDeliveryStore';
|
||||
import { useCentralSlotStore } from '@/src/store/centralSlotStore';
|
||||
import { useCentralProductStore } from '@/src/store/centralProductStore';
|
||||
import { useAddToCart, useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks';
|
||||
import { useHideTabNav } from '@/src/hooks/useHideTabNav';
|
||||
import CartIcon from '@/components/icons/CartIcon';
|
||||
|
|
@ -34,7 +36,7 @@ interface SlotLayoutProps {
|
|||
function CustomDrawerContent(baseUrl: string, drawerProps: DrawerContentComponentProps, slotIdParent?: number, storeIdParent?: number) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { data: storesData } = trpc.user.stores.getStores.useQuery();
|
||||
const { data: storesData } = useStores();
|
||||
const setStoreId = useSlotStore(state => state.setStoreId);
|
||||
|
||||
const { slotId, storeId } = useSlotStore();
|
||||
|
|
@ -181,17 +183,10 @@ export function SlotLayout({ slotId, storeId, baseUrl, isForFlashDelivery }: Slo
|
|||
router.replace(`${baseUrl}?slotId=${newSlotId}` as any);
|
||||
};
|
||||
|
||||
const slotQuery = slotId
|
||||
? trpc.user.slots.getSlotById.useQuery({ slotId: Number(slotId) })
|
||||
: trpc.user.slots.nextMajorDelivery.useQuery();
|
||||
const deliveryTime = dayjs(slotQuery.data?.deliveryTime).format('DD MMM hh:mm A');
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={tw` w-full flex-row bg-white px-4 py-2 mb-1`}>
|
||||
<QuickDeliveryAddressSelector
|
||||
deliveryTime={deliveryTime}
|
||||
slotId={Number(slotId)}
|
||||
onSlotChange={handleSlotChange}
|
||||
isForFlashDelivery={isForFlashDelivery}
|
||||
|
|
@ -344,22 +339,20 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
|
|||
const slotId = slotIdParent;
|
||||
const storeId = storeIdParent;
|
||||
const storeIdNum = storeId;
|
||||
// const { storeId, slotId: slotIdRaw } = useLocalSearchParams();
|
||||
// const slotId = Number(slotIdRaw);
|
||||
|
||||
const { data: slotsData, isLoading: slotsLoading, error: slotsError } = useSlots();
|
||||
const { productsById } = useCentralProductStore();
|
||||
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap);
|
||||
|
||||
// const storeIdNum = storeId ? Number(storeId) : undefined;
|
||||
|
||||
const slotQuery = trpc.user.slots.getSlotById.useQuery({ slotId: slotId! }, { enabled: !!slotId });
|
||||
|
||||
const productsQuery = useAllProducts();
|
||||
// Find the specific slot from cached data
|
||||
const slot = slotsData?.slots?.find(s => s.id === slotId);
|
||||
|
||||
const { addToCart = () => { } } = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }, "regular") || {};
|
||||
|
||||
const handleAddToCart = (productId: number) => {
|
||||
setIsLoadingDialogOpen(true);
|
||||
const item = filteredProducts.find((p: any) => p.id === productId);
|
||||
const deliveryTime = slotQuery.data?.deliveryTime ? dayjs(slotQuery.data.deliveryTime).format('ddd, DD MMM • h:mm A') : '';
|
||||
const item = filteredProducts.find((p) => p.id === productId);
|
||||
const deliveryTime = slot?.deliveryTime ? dayjs(slot.deliveryTime).format('ddd, DD MMM • h:mm A') : '';
|
||||
addToCart(productId, 1, slotId || 0, () => {
|
||||
setIsLoadingDialogOpen(false);
|
||||
if (item) {
|
||||
|
|
@ -368,7 +361,7 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
|
|||
});
|
||||
};
|
||||
|
||||
if (slotQuery.isLoading || (storeIdNum && productsQuery?.isLoading)) {
|
||||
if (slotsLoading) {
|
||||
return (
|
||||
<AppContainer>
|
||||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
||||
|
|
@ -378,7 +371,7 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
|
|||
);
|
||||
}
|
||||
|
||||
if (slotQuery.error || (storeIdNum && productsQuery?.error)) {
|
||||
if (slotsError) {
|
||||
return (
|
||||
<AppContainer>
|
||||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
||||
|
|
@ -390,7 +383,7 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
|
|||
);
|
||||
}
|
||||
|
||||
if (!slotQuery.data) {
|
||||
if (!slot) {
|
||||
return (
|
||||
<AppContainer>
|
||||
<View style={tw`flex-1 justify-center items-center`}>
|
||||
|
|
@ -401,14 +394,16 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
|
|||
);
|
||||
}
|
||||
|
||||
// Create a Set of product IDs from slot data for O(1) lookup
|
||||
const slotProductIds = new Set(slotQuery.data.products?.map((p: any) => p.id) || []);
|
||||
// Get product details from central store using slot product IDs
|
||||
// Filter: 1) Must exist in productsById, 2) Must not be out of stock (from slots data)
|
||||
const slotProducts = slot.products
|
||||
?.map(p => productsById[p.id])
|
||||
?.filter((product): product is NonNullable<typeof product> => product !== null && product !== undefined)
|
||||
?.filter(product => !productSlotsMap[product.id]?.isOutOfStock) || [];
|
||||
|
||||
const filteredProducts: any[] = storeIdNum
|
||||
? productsQuery?.data?.products?.filter(p =>
|
||||
p.storeId === storeIdNum && slotProductIds.has(p.id)
|
||||
) || []
|
||||
: slotQuery.data.products;
|
||||
const filteredProducts = storeIdNum
|
||||
? slotProducts.filter(p => p.storeId === storeIdNum)
|
||||
: slotProducts;
|
||||
|
||||
return (
|
||||
<View testID="slot-detail-page" style={tw`flex-1`}>
|
||||
|
|
@ -426,7 +421,7 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
|
|||
keyExtractor={(item, index) => index.toString()}
|
||||
columnWrapperStyle={{ gap: 16, justifyContent: 'flex-start' }}
|
||||
contentContainerStyle={[tw`pb-24 px-4`, { gap: 16 }]}
|
||||
onRefresh={() => slotQuery.refetch()}
|
||||
onRefresh={() => {}}
|
||||
ListEmptyComponent={
|
||||
storeIdNum ? (
|
||||
<View style={tw`items-center justify-center py-10`}>
|
||||
|
|
@ -491,7 +486,7 @@ export function FlashDeliveryProducts({ storeId:storeIdParent, baseUrl, onProduc
|
|||
}
|
||||
|
||||
// Filter products to only include those eligible for flash delivery
|
||||
let flashProducts: any[] = [];
|
||||
let flashProducts: AllProductsApiType['products'][number][] = [];
|
||||
if (storeIdNum) {
|
||||
// Filter by store, flash availability, and stock status
|
||||
flashProducts = productsQuery?.data?.products?.filter(p => {
|
||||
|
|
|
|||
|
|
@ -3,20 +3,21 @@ import { useEffect } from 'react'
|
|||
import { useAllProducts } from '@/src/hooks/prominent-api-hooks'
|
||||
import { AllProductsApiType } from '@backend/trpc/router'
|
||||
|
||||
type Product = AllProductsApiType['products'][number] & {
|
||||
flashPrice?: number | null
|
||||
}
|
||||
type Product = AllProductsApiType['products'][number]
|
||||
|
||||
interface CentralProductState {
|
||||
products: Product[]
|
||||
productsById: Record<number, Product>
|
||||
refetchProducts: (() => Promise<void>) | null
|
||||
setProducts: (products: Product[]) => void
|
||||
clearProducts: () => void
|
||||
setRefetchProducts: (refetch: () => Promise<void>) => void
|
||||
}
|
||||
|
||||
export const useCentralProductStore = create<CentralProductState>((set) => ({
|
||||
products: [],
|
||||
productsById: {},
|
||||
refetchProducts: null,
|
||||
setProducts: (products) => {
|
||||
const productsById: Record<number, Product> = {}
|
||||
|
||||
|
|
@ -27,15 +28,23 @@ export const useCentralProductStore = create<CentralProductState>((set) => ({
|
|||
set({ products, productsById })
|
||||
},
|
||||
clearProducts: () => set({ products: [], productsById: {} }),
|
||||
setRefetchProducts: (refetchProducts) => set({ refetchProducts }),
|
||||
}))
|
||||
|
||||
export function useInitializeCentralProductStore() {
|
||||
const { data: productsData } = useAllProducts()
|
||||
const { data: productsData, refetch } = useAllProducts()
|
||||
const setProducts = useCentralProductStore((state) => state.setProducts)
|
||||
const setRefetchProducts = useCentralProductStore((state) => state.setRefetchProducts)
|
||||
|
||||
useEffect(() => {
|
||||
if (productsData?.products) {
|
||||
setProducts(productsData.products)
|
||||
}
|
||||
}, [productsData, setProducts])
|
||||
|
||||
useEffect(() => {
|
||||
setRefetchProducts(async () => {
|
||||
await refetch()
|
||||
})
|
||||
}, [refetch, setRefetchProducts])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,13 +15,16 @@ interface ProductSlotInfo {
|
|||
interface CentralSlotState {
|
||||
slots: Slot[];
|
||||
productSlotsMap: Record<number, ProductSlotInfo>;
|
||||
refetchSlots: (() => Promise<void>) | null;
|
||||
setSlotsData: (slots: Slot[], productAvailability: ProductAvailability[]) => void;
|
||||
clearSlotsData: () => void;
|
||||
setRefetchSlots: (refetch: () => Promise<void>) => void;
|
||||
}
|
||||
|
||||
export const useCentralSlotStore = create<CentralSlotState>((set) => ({
|
||||
slots: [],
|
||||
productSlotsMap: {},
|
||||
refetchSlots: null,
|
||||
setSlotsData: (slots, productAvailability) => {
|
||||
const productSlotsMap: Record<number, ProductSlotInfo> = {};
|
||||
|
||||
|
|
@ -46,15 +49,23 @@ export const useCentralSlotStore = create<CentralSlotState>((set) => ({
|
|||
set({ slots, productSlotsMap });
|
||||
},
|
||||
clearSlotsData: () => set({ slots: [], productSlotsMap: {} }),
|
||||
setRefetchSlots: (refetchSlots) => set({ refetchSlots }),
|
||||
}));
|
||||
|
||||
export function useInitializeCentralSlotStore() {
|
||||
const { data: slotsData } = useSlots();
|
||||
const { data: slotsData, refetch } = useSlots();
|
||||
const setSlotsData = useCentralSlotStore((state) => state.setSlotsData);
|
||||
const setRefetchSlots = useCentralSlotStore((state) => state.setRefetchSlots);
|
||||
|
||||
useEffect(() => {
|
||||
if (slotsData?.slots) {
|
||||
setSlotsData(slotsData.slots, slotsData.productAvailability || []);
|
||||
}
|
||||
}, [slotsData, setSlotsData]);
|
||||
|
||||
useEffect(() => {
|
||||
setRefetchSlots(async () => {
|
||||
await refetch();
|
||||
});
|
||||
}, [refetch, setRefetchSlots]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue