Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
790008ae0d | ||
|
|
b5b91eb968 | ||
| 38eb810e7a |
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.0",
|
||||
"dependencies": {
|
||||
"@bull-board/api": "^6.14.2",
|
||||
"@bull-board/express": "^6.14.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"private": true,
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm:start:dev\" \"vite\"",
|
||||
|
||||
@@ -15,7 +15,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
// FIX: Stabilize the apiFunction passed to useApi.
|
||||
// By wrapping this in useCallback, we ensure the same function instance is passed to
|
||||
// useApi on every render. This prevents the `execute` function returned by `useApi`
|
||||
// from being recreated, which in turn breaks the infinite re-render loop in the useEffect below.
|
||||
// from being recreated, which in turn breaks the infinite re-render loop in the useEffect.
|
||||
const getProfileCallback = useCallback(() => apiClient.getAuthenticatedUserProfile(), []);
|
||||
|
||||
const { execute: checkTokenApi } = useApi<UserProfile, []>(getProfileCallback);
|
||||
|
||||
@@ -4,17 +4,21 @@ import { FlyersContext, FlyersContextType } from '../contexts/FlyersContext';
|
||||
import type { Flyer } from '../types';
|
||||
import * as apiClient from '../services/apiClient';
|
||||
import { useInfiniteQuery } from '../hooks/useInfiniteQuery';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const FlyersProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
// Memoize the fetch function to ensure stability for the useInfiniteQuery hook.
|
||||
const fetchFlyersFn = useCallback(apiClient.fetchFlyers, []);
|
||||
|
||||
const {
|
||||
data: flyers,
|
||||
isLoading: isLoadingFlyers,
|
||||
isLoading: isLoadingFlyers,
|
||||
error: flyersError,
|
||||
fetchNextPage: fetchNextFlyersPage,
|
||||
hasNextPage: hasNextFlyersPage,
|
||||
refetch: refetchFlyers,
|
||||
isRefetching: isRefetchingFlyers,
|
||||
} = useInfiniteQuery<Flyer>(apiClient.fetchFlyers);
|
||||
} = useInfiniteQuery<Flyer>(fetchFlyersFn);
|
||||
|
||||
const value: FlyersContextType = {
|
||||
flyers: flyers || [],
|
||||
@@ -26,5 +30,5 @@ export const FlyersProvider: React.FC<{ children: ReactNode }> = ({ children })
|
||||
refetchFlyers,
|
||||
};
|
||||
|
||||
return <FlyersContext.Provider value={value}>{children}</FlyersContext.Provider>;
|
||||
return <FlyersContext.Provider value={value}>{children}</FlyersContext.Provider>;
|
||||
};
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
// src/providers/MasterItemsProvider.tsx
|
||||
import React, { ReactNode, useMemo } from 'react';
|
||||
import React, { ReactNode, useMemo, useEffect, useCallback } from 'react';
|
||||
import { MasterItemsContext } from '../contexts/MasterItemsContext';
|
||||
import type { MasterGroceryItem } from '../types';
|
||||
import * as apiClient from '../services/apiClient';
|
||||
import { useApiOnMount } from '../hooks/useApiOnMount';
|
||||
import { logger } from '../services/logger.client';
|
||||
|
||||
export const MasterItemsProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const { data, loading, error } = useApiOnMount<MasterGroceryItem[], []>(() =>
|
||||
apiClient.fetchMasterItems(),
|
||||
);
|
||||
// LOGGING: Check if the provider is unmounting/remounting repeatedly
|
||||
useEffect(() => {
|
||||
logger.debug('MasterItemsProvider: MOUNTED');
|
||||
return () => logger.debug('MasterItemsProvider: UNMOUNTED');
|
||||
}, []);
|
||||
|
||||
// Memoize the fetch function to ensure stability for the useApiOnMount hook.
|
||||
const fetchFn = useCallback(() => apiClient.fetchMasterItems(), []);
|
||||
|
||||
const { data, loading, error } = useApiOnMount<MasterGroceryItem[], []>(fetchFn);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// src/providers/UserDataProvider.tsx
|
||||
import React, { useState, useEffect, useMemo, ReactNode } from 'react';
|
||||
import { logger } from '../services/logger.client';
|
||||
import React, { useState, useEffect, useMemo, ReactNode, useCallback } from 'react';
|
||||
import { UserDataContext } from '../contexts/UserDataContext';
|
||||
import type { MasterGroceryItem, ShoppingList } from '../types';
|
||||
import * as apiClient from '../services/apiClient';
|
||||
@@ -9,18 +10,25 @@ import { useAuth } from '../hooks/useAuth';
|
||||
export const UserDataProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const { userProfile } = useAuth();
|
||||
|
||||
// Wrap the API calls in useCallback to prevent unnecessary re-renders.
|
||||
const fetchWatchedItemsFn = useCallback(
|
||||
() => apiClient.fetchWatchedItems(),
|
||||
[],
|
||||
);
|
||||
const fetchShoppingListsFn = useCallback(() => apiClient.fetchShoppingLists(), []);
|
||||
|
||||
const {
|
||||
data: watchedItemsData,
|
||||
loading: isLoadingWatched,
|
||||
error: watchedItemsError,
|
||||
} = useApiOnMount<MasterGroceryItem[], []>(() => apiClient.fetchWatchedItems(), [userProfile], {
|
||||
} = useApiOnMount<MasterGroceryItem[], []>(fetchWatchedItemsFn, [userProfile], {
|
||||
enabled: !!userProfile,
|
||||
});
|
||||
const {
|
||||
data: shoppingListsData,
|
||||
loading: isLoadingShoppingLists,
|
||||
loading: isLoadingShoppingLists,
|
||||
error: shoppingListsError,
|
||||
} = useApiOnMount<ShoppingList[], []>(() => apiClient.fetchShoppingLists(), [userProfile], {
|
||||
} = useApiOnMount<ShoppingList[], []>(fetchShoppingListsFn, [userProfile], {
|
||||
enabled: !!userProfile,
|
||||
});
|
||||
|
||||
@@ -32,7 +40,7 @@ export const UserDataProvider: React.FC<{ children: ReactNode }> = ({ children }
|
||||
useEffect(() => {
|
||||
// When the user logs out (user becomes null), immediately clear all user-specific data.
|
||||
// This also serves to clear out old data when a new user logs in, before their new data arrives.
|
||||
if (!userProfile) {
|
||||
if (!userProfile) {
|
||||
setWatchedItems([]);
|
||||
setShoppingLists([]);
|
||||
return;
|
||||
@@ -60,7 +68,7 @@ export const UserDataProvider: React.FC<{ children: ReactNode }> = ({ children }
|
||||
watchedItemsError,
|
||||
shoppingListsError,
|
||||
],
|
||||
);
|
||||
);
|
||||
|
||||
return <UserDataContext.Provider value={value}>{children}</UserDataContext.Provider>;
|
||||
};
|
||||
|
||||
@@ -19,6 +19,12 @@ router.get(
|
||||
validateRequest(emptySchema),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// LOGGING: Track how often this heavy DB call is actually made vs served from cache
|
||||
req.log.info('Fetching master items list from database...');
|
||||
|
||||
// Optimization: This list changes rarely. Instruct clients to cache it for 1 hour (3600s).
|
||||
res.set('Cache-Control', 'public, max-age=3600');
|
||||
|
||||
const masterItems = await db.personalizationRepo.getAllMasterItems(req.log);
|
||||
res.json(masterItems);
|
||||
} catch (error) {
|
||||
|
||||
@@ -283,7 +283,10 @@ export const fetchFlyerById = (flyerId: number): Promise<Response> =>
|
||||
* Fetches all master grocery items from the backend.
|
||||
* @returns A promise that resolves to an array of MasterGroceryItem objects.
|
||||
*/
|
||||
export const fetchMasterItems = (): Promise<Response> => publicGet('/personalization/master-items');
|
||||
export const fetchMasterItems = (): Promise<Response> => {
|
||||
logger.debug('apiClient: fetchMasterItems called');
|
||||
return publicGet('/personalization/master-items');
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches all categories from the backend.
|
||||
|
||||
@@ -133,6 +133,12 @@ export class FlyerProcessingService {
|
||||
return { flyerId: flyer.flyer_id };
|
||||
} catch (error) {
|
||||
logger.warn('Job failed. Temporary files will NOT be cleaned up to allow for manual inspection.');
|
||||
// Add detailed logging of the raw error object
|
||||
if (error instanceof Error) {
|
||||
logger.error({ err: error, stack: error.stack }, 'Raw error object in processJob catch block');
|
||||
} else {
|
||||
logger.error({ error }, 'Raw non-Error object in processJob catch block');
|
||||
}
|
||||
// This private method handles error reporting and re-throwing.
|
||||
await this._reportErrorAndThrow(error, job, logger, stages);
|
||||
// This line is technically unreachable because the above method always throws,
|
||||
|
||||
@@ -38,6 +38,7 @@ describe('Public API Routes Integration Tests', () => {
|
||||
email: userEmail,
|
||||
password: 'a-Very-Strong-Password-123!',
|
||||
fullName: 'Public Routes Test User',
|
||||
request,
|
||||
});
|
||||
testUser = createdUser;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user