some more re-org + fixes
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 3m55s

This commit is contained in:
2025-11-24 15:02:13 -08:00
parent a4d5e95937
commit 2f55a303b0
5 changed files with 40 additions and 24 deletions

11
package-lock.json generated
View File

@@ -51,6 +51,7 @@
"@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38",
"@types/pg": "^8.15.6",
"@types/react": "^19.2.7",
"@types/zxcvbn": "^4.4.5",
"@typescript-eslint/eslint-plugin": "^8.47.0",
"@typescript-eslint/parser": "^8.47.0",
@@ -4426,6 +4427,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/@types/send": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",

View File

@@ -63,6 +63,7 @@
"@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38",
"@types/pg": "^8.15.6",
"@types/react": "^19.2.7",
"@types/zxcvbn": "^4.4.5",
"@typescript-eslint/eslint-plugin": "^8.47.0",
"@typescript-eslint/parser": "^8.47.0",

View File

@@ -352,14 +352,14 @@ function App() {
const today = new Date();
today.setHours(0, 0, 0, 0);
const validFlyers = flyers.filter(flyer => {
const validFlyers = flyers.filter((flyer: Flyer) => {
if (!flyer.valid_from || !flyer.valid_to) return false;
try {
const from = new Date(`${flyer.valid_from}T00:00:00`);
const to = new Date(`${flyer.valid_to}T00:00:00`);
return today >= from && today <= to;
} catch (e) {
logger.error("Error parsing flyer date", e);
logger.error("Error parsing flyer date", { error: e });
return false;
}
});
@@ -372,12 +372,12 @@ function App() {
const validFlyerIds = validFlyers.map(f => f.flyer_id);
const allItems = await apiFetchFlyerItemsForFlyers(validFlyerIds);
const watchedItemIds = new Set(watchedItems.map(item => item.item_id));
const watchedItemIds = new Set(watchedItems.map((item: MasterGroceryItem) => item.master_grocery_item_id));
const dealItemsRaw = allItems.filter(item =>
item.master_item_id && watchedItemIds.has(item.master_item_id)
); // This seems correct as it's comparing with master_item_id
const flyerIdToStoreName = new Map(validFlyers.map(f => [f.flyer_id, f.store?.name || 'Unknown Store']));
const flyerIdToStoreName = new Map(validFlyers.map((f: Flyer) => [f.flyer_id, f.store?.name || 'Unknown Store']));
const deals: DealItem[] = dealItemsRaw.map(item => ({
item: item.item,
@@ -412,14 +412,14 @@ function App() {
const today = new Date();
today.setHours(0, 0, 0, 0);
const validFlyers = flyers.filter(flyer => {
const validFlyers = flyers.filter((flyer: Flyer) => {
if (!flyer.valid_from || !flyer.valid_to) return false;
try {
const from = new Date(`${flyer.valid_from}T00:00:00`);
const to = new Date(`${flyer.valid_to}T00:00:00`);
return today >= from && today <= to;
} catch (e) {
logger.error("Error parsing flyer date", e);
logger.error("Error parsing flyer date", { error: e });
return false;
}
});
@@ -494,6 +494,11 @@ function App() {
}
}
// If extractedData is null or undefined at this point, it means the AI call failed.
if (!extractedData) {
throw new Error("Core data extraction failed. The AI did not return valid data.");
}
const { store_name, valid_from, valid_to, items: extractedItems } = extractedData;
stageIndex += 2; // Increment by 2 for the stages we just completed. stageIndex is now 3
@@ -657,9 +662,8 @@ function App() {
try {
const updatedOrNewItem = await apiAddWatchedItem(itemName, category);
setWatchedItems(prevItems => {
// The API returns an object with `id`, but our state uses `item_id`.
// We compare the existing item's `item_id` with the new item's `id`.
const itemExists = prevItems.some(item => item.item_id === (updatedOrNewItem as any).id);
// Check if the item already exists in the state by its correct ID property.
const itemExists = prevItems.some(item => item.master_grocery_item_id === updatedOrNewItem.master_grocery_item_id);
if (!itemExists) {
const newItems = [...prevItems, updatedOrNewItem]; // This was correct, but the check above was wrong.
return newItems.sort((a,b) => a.name.localeCompare(b.name));
@@ -676,8 +680,8 @@ function App() {
const handleRemoveWatchedItem = useCallback(async (masterItemId: number) => {
if (!user) return;
try {
await apiRemoveWatchedItem(masterItemId);
setWatchedItems(prevItems => prevItems.filter(item => item.item_id !== masterItemId));
await apiRemoveWatchedItem(masterItemId); // API call is correct
setWatchedItems(prevItems => prevItems.filter(item => item.master_grocery_item_id !== masterItemId)); // State update must use correct property
} catch (e) {
const errorMessage = e instanceof Error ? e.message : String(e);
setError(`Could not remove watched item: ${errorMessage}`);
@@ -718,9 +722,8 @@ function App() {
setShoppingLists(prevLists => prevLists.map(list => {
if (list.shopping_list_id === listId) {
// Avoid adding duplicates to the state if it's already there
// The API returns an object with `id`, but our state uses `item_id`.
// We compare the existing item's `item_id` with the new item's `id`.
const itemExists = list.items.some(i => i.item_id === (newItem as any).id);
// Check if the item already exists in the list by its correct ID property.
const itemExists = list.items.some(i => i.shopping_list_item_id === newItem.shopping_list_item_id);
if (itemExists) return list;
return { ...list, items: [...list.items, newItem] };
}
@@ -738,7 +741,7 @@ function App() {
const updatedItem = await apiUpdateShoppingListItem(itemId, updates);
setShoppingLists(prevLists => prevLists.map(list => {
if (list.shopping_list_id === activeListId) {
return { ...list, items: list.items.map(i => i.item_id === itemId ? updatedItem : i) };
return { ...list, items: list.items.map(i => i.shopping_list_item_id === itemId ? updatedItem : i) };
}
return list;
}));
@@ -754,7 +757,7 @@ function App() {
await apiRemoveShoppingListItem(itemId);
setShoppingLists(prevLists => prevLists.map(list => {
if (list.shopping_list_id === activeListId) {
return { ...list, items: list.items.filter(i => i.item_id !== itemId) };
return { ...list, items: list.items.filter(i => i.shopping_list_item_id !== itemId) };
}
return list;
}));
@@ -772,10 +775,10 @@ function App() {
};
const handleActivityLogClick: ActivityLogClickHandler = (log) => {
if (log.activity_type === 'share_shopping_list' && log.entity_id) {
const listId = parseInt(log.entity_id, 10);
if (log.action === 'list_shared' && log.details?.shopping_list_id) {
const listId = parseInt(String(log.details.shopping_list_id), 10);
// Check if the list exists before setting it as active. This was correct.
if (shoppingLists.some(list => list.shopping_list_id === listId)) {
if (shoppingLists.some(list => list.shopping_list_id === listId) && typeof listId === 'number') {
setActiveListId(listId);
}
}
@@ -880,7 +883,7 @@ function App() {
</div>
<div className="lg:col-span-2 flex flex-col space-y-6">
<ErrorDisplay message={error} />
{error && <ErrorDisplay message={error} />}
{isProcessing ? (
<ProcessingStatus

View File

@@ -302,7 +302,7 @@ export const processFlyerFile = async (
flyerImage: File,
checksum: string,
originalFileName: string,
extractedData: { store_name: string; valid_from: string; valid_to: string; items: Omit<FlyerItem, 'id' | 'flyer_id' | 'created_at'>[]; store_address: string | null }
extractedData: { store_name: string; valid_from: string | null; valid_to: string | null; items: Omit<FlyerItem, 'flyer_item_id' | 'flyer_id' | 'created_at'>[]; store_address: string | null }
): Promise<{ message: string; flyer: Flyer }> => {
const formData = new FormData();
formData.append('flyerImage', flyerImage);
@@ -1293,10 +1293,10 @@ export async function deleteUserAccount(password: string, tokenOverride?: string
try {
const data = await response.json();
errorMessage = data.message || errorMessage;
} catch (e) {
} catch (jsonError) {
// Response was not JSON (likely 404 HTML or 500 text)
const text = await response.clone().text(); // Use clone() to avoid "Body already read" error
logger.error(`deleteUserAccount failed with status ${response.status}. Body: ${text}`);
logger.error(`deleteUserAccount failed with status ${response.status}. Could not parse JSON response. Body: ${text}`, { jsonError });
errorMessage += `: ${response.status} ${response.statusText}`;
}
throw new Error(errorMessage);

View File

@@ -14,7 +14,8 @@ const { test: _unusedTest, ...baseViteConfig } = viteConfig as any;
console.error('\n[DEBUG] --- INTEGRATION CONFIG SETUP ---');
// Use _unusedTest to satisfy linter
console.error(`[DEBUG] Stripped "test" config. Original test config existed: ${!!_unusedTest}`);
console.error('[DEBUG] Does baseViteConfig have "test"?', !!(baseViteConfig as any).test);
// Use the 'in' operator for a type-safe property check instead of casting to 'any'.
console.error('[DEBUG] Does baseViteConfig have "test"?', 'test' in baseViteConfig);
/**
* This configuration is specifically for integration tests.