feat: Implement deals repository and routes for fetching best watched item prices
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
- Added a new DealsRepository class to interact with the database for fetching the best sale prices of watched items. - Created a new route `/api/users/deals/best-watched-prices` to handle requests for the best prices of items the authenticated user is watching. - Enhanced logging in the FlyerDataTransformer and FlyerProcessingService for better traceability. - Updated tests to ensure proper logging and functionality in the FlyerProcessingService. - Refactored logger client to support structured logging for better consistency across the application.
This commit is contained in:
@@ -9,6 +9,11 @@ import { logger } from './logger.client';
|
||||
// which is then handled by the Nginx reverse proxy.
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';
|
||||
|
||||
interface ApiOptions {
|
||||
tokenOverride?: string;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
// --- API Fetch Wrapper with Token Refresh Logic ---
|
||||
|
||||
/**
|
||||
@@ -74,7 +79,7 @@ const refreshToken = async (): Promise<string> => {
|
||||
* @param options The fetch options.
|
||||
* @returns A promise that resolves to the fetch Response.
|
||||
*/
|
||||
export const apiFetch = async (url: string, options: RequestInit = {}, tokenOverride?: string): Promise<Response> => {
|
||||
export const apiFetch = async (url: string, options: RequestInit = {}, apiOptions: ApiOptions = {}): Promise<Response> => {
|
||||
// Always construct the full URL from the base and the provided path,
|
||||
// unless the path is already a full URL. This works for both browser and Node.js.
|
||||
const fullUrl = url.startsWith('http') ? url : joinUrl(API_BASE_URL, url);
|
||||
@@ -85,7 +90,7 @@ export const apiFetch = async (url: string, options: RequestInit = {}, tokenOver
|
||||
const headers = new Headers(options.headers || {});
|
||||
// Use the token override if provided (for testing), otherwise get it from localStorage.
|
||||
// The `typeof window` check prevents errors in the Node.js test environment.
|
||||
const token = tokenOverride ?? (typeof window !== 'undefined' ? localStorage.getItem('authToken') : null);
|
||||
const token = apiOptions.tokenOverride ?? (typeof window !== 'undefined' ? localStorage.getItem('authToken') : null);
|
||||
if (token) {
|
||||
headers.set('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
@@ -96,7 +101,7 @@ export const apiFetch = async (url: string, options: RequestInit = {}, tokenOver
|
||||
headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
const newOptions = { ...options, headers, signal: options.signal };
|
||||
const newOptions = { ...options, headers, signal: apiOptions.signal || options.signal };
|
||||
|
||||
let response = await fetch(fullUrl, newOptions);
|
||||
|
||||
@@ -240,7 +245,7 @@ export const uploadAndProcessFlyer = async (file: File, checksum: string, tokenO
|
||||
return apiFetch('/ai/upload-and-process', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
// --- Flyer Item API Functions ---
|
||||
|
||||
@@ -287,7 +292,7 @@ export const uploadLogoAndUpdateStore = async (storeId: number, logoImage: File,
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
// Do not set Content-Type for FormData, browser handles it.
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -305,7 +310,7 @@ export const uploadBrandLogo = async (brandId: number, logoImage: File, tokenOve
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
// Do not set Content-Type for FormData, browser handles it.
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
|
||||
@@ -323,14 +328,14 @@ export const fetchHistoricalPriceData = async (masterItemIds: number[], tokenOve
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ masterItemIds }),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
|
||||
// --- Watched Items API Functions ---
|
||||
|
||||
export const fetchWatchedItems = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/watched-items`, {}, tokenOverride);
|
||||
return apiFetch(`/users/watched-items`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const addWatchedItem = async (itemName: string, category: string, tokenOverride?: string): Promise<Response> => {
|
||||
@@ -338,23 +343,33 @@ export const addWatchedItem = async (itemName: string, category: string, tokenOv
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ itemName, category }),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const removeWatchedItem = async (masterItemId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/watched-items/${masterItemId}`, {
|
||||
method: 'DELETE',
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the best current sale prices for all of the user's watched items.
|
||||
* @param tokenOverride Optional token for testing.
|
||||
* @returns A promise that resolves to an array of WatchedItemDeal objects.
|
||||
*/
|
||||
export const fetchBestSalePrices = async (tokenOverride?: string): Promise<Response> => {
|
||||
// This endpoint assumes an authenticated user session.
|
||||
return apiFetch(`/users/deals/best-watched-prices`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
// --- Shopping List API Functions ---
|
||||
|
||||
export const fetchShoppingLists = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/shopping-lists`, {}, tokenOverride);
|
||||
return apiFetch(`/users/shopping-lists`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const fetchShoppingListById = async (listId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/shopping-lists/${listId}`, {}, tokenOverride);
|
||||
return apiFetch(`/users/shopping-lists/${listId}`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const createShoppingList = async (name: string, tokenOverride?: string): Promise<Response> => {
|
||||
@@ -362,13 +377,13 @@ export const createShoppingList = async (name: string, tokenOverride?: string):
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name }),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const deleteShoppingList = async (listId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/shopping-lists/${listId}`, {
|
||||
method: 'DELETE',
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const addShoppingListItem = async (listId: number, item: { masterItemId?: number, customItemName?: string }, tokenOverride?: string): Promise<Response> => {
|
||||
@@ -376,7 +391,7 @@ export const addShoppingListItem = async (listId: number, item: { masterItemId?:
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(item),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const updateShoppingListItem = async (itemId: number, updates: Partial<ShoppingListItem>, tokenOverride?: string): Promise<Response> => {
|
||||
@@ -384,13 +399,13 @@ export const updateShoppingListItem = async (itemId: number, updates: Partial<Sh
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(updates),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const removeShoppingListItem = async (itemId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/shopping-lists/items/${itemId}`, {
|
||||
method: 'DELETE',
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
|
||||
@@ -400,12 +415,12 @@ export const removeShoppingListItem = async (itemId: number, tokenOverride?: str
|
||||
* @returns A promise that resolves to the user's combined UserProfile object.
|
||||
* @throws An error if the request fails or if the user is not authenticated.
|
||||
*/
|
||||
export const getAuthenticatedUserProfile = async (tokenOverride?: string): Promise<Response> => {
|
||||
export const getAuthenticatedUserProfile = async (options: ApiOptions = {}): Promise<Response> => {
|
||||
// The token is now passed to apiFetch, which handles the Authorization header.
|
||||
// If no token is provided (in browser context), apiFetch will get it from localStorage.
|
||||
return apiFetch(`/users/profile`, {
|
||||
method: 'GET',
|
||||
}, tokenOverride);
|
||||
}, options);
|
||||
};
|
||||
|
||||
export async function loginUser(email: string, password: string, rememberMe: boolean): Promise<Response> {
|
||||
@@ -435,7 +450,7 @@ export const uploadReceipt = async (receiptImage: File, tokenOverride?: string):
|
||||
return apiFetch(`/receipts/upload`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -444,7 +459,7 @@ export const uploadReceipt = async (receiptImage: File, tokenOverride?: string):
|
||||
* @returns A promise that resolves to an array of ReceiptDeal objects.
|
||||
*/
|
||||
export const getDealsForReceipt = async (receiptId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/receipts/${receiptId}/deals`, {}, tokenOverride);
|
||||
return apiFetch(`/receipts/${receiptId}/deals`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
// --- Analytics & Shopping Enhancement API Functions ---
|
||||
@@ -455,7 +470,7 @@ export const trackFlyerItemInteraction = async (itemId: number, type: 'view' | '
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type }),
|
||||
keepalive: true // Helps ensure the request is sent even if the page is closing
|
||||
keepalive: true, // Helps ensure the request is sent even if the page is closing
|
||||
}).catch(error => logger.warn('Failed to track flyer item interaction', { error }));
|
||||
};
|
||||
|
||||
@@ -463,12 +478,12 @@ export const logSearchQuery = async (query: Omit<SearchQuery, 'search_query_id'
|
||||
apiFetch(`/search/log`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(query),
|
||||
keepalive: true
|
||||
}, tokenOverride).catch(error => logger.warn('Failed to log search query', { error }));
|
||||
keepalive: true,
|
||||
}, { tokenOverride }).catch(error => logger.warn('Failed to log search query', { error }));
|
||||
};
|
||||
|
||||
export const getPantryLocations = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/pantry/locations`, {}, tokenOverride);
|
||||
return apiFetch(`/pantry/locations`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const createPantryLocation = async (name: string, tokenOverride?: string): Promise<Response> => {
|
||||
@@ -476,7 +491,7 @@ export const createPantryLocation = async (name: string, tokenOverride?: string)
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name }),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const completeShoppingList = async (shoppingListId: number, totalSpentCents?: number, tokenOverride?: string): Promise<Response> => {
|
||||
@@ -484,11 +499,11 @@ export const completeShoppingList = async (shoppingListId: number, totalSpentCen
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ totalSpentCents }),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const getShoppingTripHistory = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/shopping-history`, {}, tokenOverride);
|
||||
return apiFetch(`/users/shopping-history`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
// --- Personalization & Social API Functions ---
|
||||
@@ -502,7 +517,7 @@ export const getAppliances = async (): Promise<Response> => {
|
||||
};
|
||||
|
||||
export const getUserDietaryRestrictions = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/me/dietary-restrictions`, {}, tokenOverride);
|
||||
return apiFetch(`/users/me/dietary-restrictions`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const setUserDietaryRestrictions = async (restrictionIds: number[], tokenOverride?: string): Promise<Response> => {
|
||||
@@ -510,33 +525,33 @@ export const setUserDietaryRestrictions = async (restrictionIds: number[], token
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ restrictionIds }),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const getCompatibleRecipes = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/me/compatible-recipes`, {}, tokenOverride);
|
||||
return apiFetch(`/users/me/compatible-recipes`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const getUserFeed = async (limit: number = 20, offset: number = 0, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/feed?limit=${limit}&offset=${offset}`, {}, tokenOverride);
|
||||
return apiFetch(`/users/feed?limit=${limit}&offset=${offset}`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const forkRecipe = async (originalRecipeId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/recipes/${originalRecipeId}/fork`, {
|
||||
method: 'POST',
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const followUser = async (userIdToFollow: string, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/${userIdToFollow}/follow`, {
|
||||
method: 'POST',
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const unfollowUser = async (userIdToUnfollow: string, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/${userIdToUnfollow}/follow`, {
|
||||
method: 'DELETE',
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
// --- Activity Log API Function ---
|
||||
@@ -548,7 +563,7 @@ export const unfollowUser = async (userIdToUnfollow: string, tokenOverride?: str
|
||||
* @returns A promise that resolves to an array of ActivityLogItem objects.
|
||||
*/
|
||||
export const fetchActivityLog = async (limit: number = 20, offset: number = 0, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/admin/activity-log?limit=${limit}&offset=${offset}`, {}, tokenOverride);
|
||||
return apiFetch(`/admin/activity-log?limit=${limit}&offset=${offset}`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
// --- Favorite Recipes API Functions ---
|
||||
@@ -559,7 +574,7 @@ export const fetchActivityLog = async (limit: number = 20, offset: number = 0, t
|
||||
* @returns {Promise<Response>} A promise that resolves to the API response.
|
||||
*/
|
||||
export const getUserFavoriteRecipes = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/me/favorite-recipes`, {}, tokenOverride);
|
||||
return apiFetch(`/users/me/favorite-recipes`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const addFavoriteRecipe = async (recipeId: number, tokenOverride?: string): Promise<Response> => {
|
||||
@@ -567,13 +582,13 @@ export const addFavoriteRecipe = async (recipeId: number, tokenOverride?: string
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ recipeId }),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const removeFavoriteRecipe = async (recipeId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/me/favorite-recipes/${recipeId}`, {
|
||||
method: 'DELETE',
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
// --- Recipe Comments API Functions ---
|
||||
@@ -597,7 +612,7 @@ export const addRecipeComment = async (recipeId: number, content: string, parent
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ content, parentCommentId }),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -607,12 +622,12 @@ export const addRecipeComment = async (recipeId: number, content: string, parent
|
||||
* @returns {Promise<Response>} A promise that resolves to the API response.
|
||||
*/
|
||||
export const deleteRecipe = async (recipeId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/recipes/${recipeId}`, { method: 'DELETE' }, tokenOverride);
|
||||
return apiFetch(`/recipes/${recipeId}`, { method: 'DELETE' }, { tokenOverride });
|
||||
};
|
||||
// --- Admin API Functions for New Features ---
|
||||
|
||||
export const getUnmatchedFlyerItems = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`${API_BASE_URL}/admin/unmatched-items`, {}, tokenOverride);
|
||||
return apiFetch(`${API_BASE_URL}/admin/unmatched-items`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const updateRecipeStatus = async (recipeId: number, status: 'private' | 'pending_review' | 'public' | 'rejected', tokenOverride?: string): Promise<Response> => {
|
||||
@@ -620,7 +635,7 @@ export const updateRecipeStatus = async (recipeId: number, status: 'private' | '
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status }),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const updateRecipeCommentStatus = async (commentId: number, status: 'visible' | 'hidden' | 'reported', tokenOverride?: string): Promise<Response> => {
|
||||
@@ -628,7 +643,7 @@ export const updateRecipeCommentStatus = async (commentId: number, status: 'visi
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status }),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -636,7 +651,7 @@ export const updateRecipeCommentStatus = async (commentId: number, status: 'visi
|
||||
* @returns A promise that resolves to an array of Brand objects.
|
||||
*/
|
||||
export const fetchAllBrands = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/admin/brands`, {}, tokenOverride);
|
||||
return apiFetch(`/admin/brands`, {}, { tokenOverride });
|
||||
};
|
||||
export interface AppStats { // This interface should ideally be in types.ts
|
||||
flyerCount: number; userCount: number; flyerItemCount: number; storeCount: number; pendingCorrectionCount: number;
|
||||
@@ -653,32 +668,32 @@ export interface DailyStat {
|
||||
* @returns A promise that resolves to an array of daily stat objects.
|
||||
*/
|
||||
export const getDailyStats = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`${API_BASE_URL}/admin/stats/daily`, {}, tokenOverride);
|
||||
return apiFetch(`${API_BASE_URL}/admin/stats/daily`, {}, { tokenOverride });
|
||||
};
|
||||
/**
|
||||
* Fetches application-wide statistics. Requires admin privileges.
|
||||
* @returns A promise that resolves to an object containing app stats.
|
||||
*/
|
||||
export const getApplicationStats = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`${API_BASE_URL}/admin/stats`, {}, tokenOverride);
|
||||
return apiFetch(`${API_BASE_URL}/admin/stats`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
// --- Admin Correction API Functions ---
|
||||
|
||||
export const getSuggestedCorrections = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`${API_BASE_URL}/admin/corrections`, {}, tokenOverride);
|
||||
return apiFetch(`${API_BASE_URL}/admin/corrections`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const approveCorrection = async (correctionId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/admin/corrections/${correctionId}/approve`, {
|
||||
method: 'POST',
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const rejectCorrection = async (correctionId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/admin/corrections/${correctionId}/reject`, {
|
||||
method: 'POST',
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
export const updateSuggestedCorrection = async (correctionId: number, newSuggestedValue: string, tokenOverride?: string): Promise<Response> => {
|
||||
@@ -686,7 +701,7 @@ export const updateSuggestedCorrection = async (correctionId: number, newSuggest
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ suggested_value: newSuggestedValue }),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -696,7 +711,7 @@ export const updateSuggestedCorrection = async (correctionId: number, newSuggest
|
||||
* @param tokenOverride Optional token for testing.
|
||||
*/
|
||||
export const cleanupFlyerFiles = async (flyerId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/admin/flyers/${flyerId}/cleanup`, { method: 'POST' }, tokenOverride);
|
||||
return apiFetch(`/admin/flyers/${flyerId}/cleanup`, { method: 'POST' }, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -706,7 +721,7 @@ export const cleanupFlyerFiles = async (flyerId: number, tokenOverride?: string)
|
||||
*/
|
||||
export const triggerFailingJob = async (tokenOverride?: string): Promise<Response> => {
|
||||
// This is an admin-only endpoint, so we use apiFetch to include the auth token.
|
||||
return apiFetch(`/admin/trigger/failing-job`, { method: 'POST' }, tokenOverride);
|
||||
return apiFetch(`/admin/trigger/failing-job`, { method: 'POST' }, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -716,7 +731,7 @@ export const triggerFailingJob = async (tokenOverride?: string): Promise<Respons
|
||||
* @returns A promise that resolves to the API response with the job's status.
|
||||
*/
|
||||
export const getJobStatus = async (jobId: string, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/ai/jobs/${jobId}/status`, {}, tokenOverride);
|
||||
return apiFetch(`/ai/jobs/${jobId}/status`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -725,7 +740,7 @@ export const getJobStatus = async (jobId: string, tokenOverride?: string): Promi
|
||||
* @param tokenOverride Optional token for testing.
|
||||
*/
|
||||
export const clearGeocodeCache = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/admin/system/clear-geocode-cache`, { method: 'POST' }, tokenOverride);
|
||||
return apiFetch(`/admin/system/clear-geocode-cache`, { method: 'POST' }, { tokenOverride });
|
||||
};
|
||||
|
||||
export async function registerUser(
|
||||
@@ -780,12 +795,12 @@ export async function resetPassword(token: string, newPassword: string): Promise
|
||||
* @param preferences A partial object of the user's preferences to update.
|
||||
* @returns A promise that resolves to the user's full, updated profile object.
|
||||
*/
|
||||
export async function updateUserPreferences(preferences: Partial<Profile['preferences']>, tokenOverride?: string): Promise<Response> {
|
||||
export async function updateUserPreferences(preferences: Partial<Profile['preferences']>, apiOptions: ApiOptions = {}): Promise<Response> {
|
||||
return apiFetch(`${API_BASE_URL}/users/profile/preferences`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(preferences),
|
||||
}, tokenOverride);
|
||||
}, apiOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -793,26 +808,26 @@ export async function updateUserPreferences(preferences: Partial<Profile['prefer
|
||||
* @param profileData An object containing the full_name and/or avatar_url to update.
|
||||
* @returns A promise that resolves to the user's full, updated profile object.
|
||||
*/
|
||||
export async function updateUserProfile(profileData: Partial<Profile>, tokenOverride?: string): Promise<Response> {
|
||||
export async function updateUserProfile(profileData: Partial<Profile>, apiOptions: ApiOptions = {}): Promise<Response> {
|
||||
return apiFetch(`${API_BASE_URL}/users/profile`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(profileData),
|
||||
}, tokenOverride);
|
||||
}, apiOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a complete export of the user's data from the backend.
|
||||
* @returns A promise that resolves to a JSON object of the user's data.
|
||||
*/
|
||||
export async function exportUserData(tokenOverride?: string): Promise<Response> {
|
||||
export async function exportUserData(apiOptions: ApiOptions = {}): Promise<Response> {
|
||||
return apiFetch(`${API_BASE_URL}/users/data-export`, {
|
||||
method: 'GET',
|
||||
}, tokenOverride);
|
||||
}, apiOptions);
|
||||
}
|
||||
|
||||
export const getUserAppliances = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/appliances`, {}, tokenOverride);
|
||||
return apiFetch(`/users/appliances`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -820,12 +835,12 @@ export const getUserAppliances = async (tokenOverride?: string): Promise<Respons
|
||||
* This will replace all existing appliances with the new set.
|
||||
* @param applianceIds An array of numbers representing the IDs of the selected appliances.
|
||||
*/
|
||||
export const setUserAppliances = async (applianceIds: number[], tokenOverride?: string): Promise<Response> => {
|
||||
export const setUserAppliances = async (applianceIds: number[], apiOptions: ApiOptions = {}): Promise<Response> => {
|
||||
return apiFetch(`/users/appliances`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ applianceIds }),
|
||||
}, tokenOverride);
|
||||
}, apiOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -833,12 +848,12 @@ export const setUserAppliances = async (applianceIds: number[], tokenOverride?:
|
||||
* @param address The full address string.
|
||||
* @param tokenOverride Optional token for testing.
|
||||
*/
|
||||
export const geocodeAddress = async (address: string, tokenOverride?: string): Promise<Response> => {
|
||||
export const geocodeAddress = async (address: string, apiOptions: ApiOptions = {}): Promise<Response> => {
|
||||
return apiFetch(`/system/geocode`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ address }),
|
||||
}, tokenOverride);
|
||||
}, apiOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -846,8 +861,8 @@ export const geocodeAddress = async (address: string, tokenOverride?: string): P
|
||||
* @param addressId The ID of the address to fetch.
|
||||
* @param tokenOverride Optional token for testing.
|
||||
*/
|
||||
export const getUserAddress = async (addressId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/addresses/${addressId}`, {}, tokenOverride);
|
||||
export const getUserAddress = async (addressId: number, apiOptions: ApiOptions = {}): Promise<Response> => {
|
||||
return apiFetch(`/users/addresses/${addressId}`, {}, apiOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -855,12 +870,12 @@ export const getUserAddress = async (addressId: number, tokenOverride?: string):
|
||||
* @param addressData The full address object.
|
||||
* @param tokenOverride Optional token for testing.
|
||||
*/
|
||||
export const updateUserAddress = async (addressData: Partial<Address>, tokenOverride?: string): Promise<Response> => {
|
||||
export const updateUserAddress = async (addressData: Partial<Address>, apiOptions: ApiOptions = {}): Promise<Response> => {
|
||||
return apiFetch(`/users/profile/address`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(addressData),
|
||||
}, tokenOverride);
|
||||
}, apiOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -868,12 +883,12 @@ export const updateUserAddress = async (addressData: Partial<Address>, tokenOver
|
||||
* @param newPassword The user's new password.
|
||||
* @returns A promise that resolves on success.
|
||||
*/
|
||||
export async function updateUserPassword(newPassword: string, tokenOverride?: string): Promise<Response> {
|
||||
export async function updateUserPassword(newPassword: string, apiOptions: ApiOptions = {}): Promise<Response> {
|
||||
return apiFetch(`/users/profile/password`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ newPassword }),
|
||||
}, tokenOverride);
|
||||
}, apiOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -882,12 +897,12 @@ export async function updateUserPassword(newPassword: string, tokenOverride?: st
|
||||
* @param role The new role to assign ('user' or 'admin').
|
||||
* @returns A promise that resolves to the updated Profile object.
|
||||
*/
|
||||
export async function updateUserRole(userId: string, role: 'user' | 'admin', tokenOverride?: string): Promise<Response> {
|
||||
export async function updateUserRole(userId: string, role: 'user' | 'admin', apiOptions: ApiOptions = {}): Promise<Response> {
|
||||
return apiFetch(`/admin/users/${userId}/role`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ role }),
|
||||
}, tokenOverride);
|
||||
}, apiOptions);
|
||||
}
|
||||
|
||||
|
||||
@@ -898,12 +913,12 @@ export async function updateUserRole(userId: string, role: 'user' | 'admin', tok
|
||||
* @param password The user's current password for verification.
|
||||
* @returns A promise that resolves on success.
|
||||
*/
|
||||
export async function deleteUserAccount(password: string, tokenOverride?: string): Promise<Response> {
|
||||
export async function deleteUserAccount(password: string, apiOptions: ApiOptions = {}): Promise<Response> {
|
||||
return apiFetch(`/users/account`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ password }),
|
||||
}, tokenOverride);
|
||||
}, apiOptions);
|
||||
}
|
||||
|
||||
// --- Notification API Functions ---
|
||||
@@ -915,7 +930,7 @@ export async function deleteUserAccount(password: string, tokenOverride?: string
|
||||
* @returns A promise that resolves to the API response.
|
||||
*/
|
||||
export const getNotifications = async (limit: number = 20, offset: number = 0, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/notifications?limit=${limit}&offset=${offset}`, {}, tokenOverride);
|
||||
return apiFetch(`/users/notifications?limit=${limit}&offset=${offset}`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -923,7 +938,7 @@ export const getNotifications = async (limit: number = 20, offset: number = 0, t
|
||||
* @returns A promise that resolves to the API response.
|
||||
*/
|
||||
export const markAllNotificationsAsRead = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/notifications/mark-all-read`, { method: 'POST' }, tokenOverride);
|
||||
return apiFetch(`/users/notifications/mark-all-read`, { method: 'POST' }, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -932,7 +947,7 @@ export const markAllNotificationsAsRead = async (tokenOverride?: string): Promis
|
||||
* @returns A promise that resolves to the API response.
|
||||
*/
|
||||
export const markNotificationAsRead = async (notificationId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/users/notifications/${notificationId}/mark-read`, { method: 'POST' }, tokenOverride);
|
||||
return apiFetch(`/users/notifications/${notificationId}/mark-read`, { method: 'POST' }, { tokenOverride });
|
||||
};
|
||||
|
||||
// --- Budgeting and Spending Analysis API Functions ---
|
||||
@@ -942,7 +957,7 @@ export const markNotificationAsRead = async (notificationId: number, tokenOverri
|
||||
* @returns A promise that resolves to the API response.
|
||||
*/
|
||||
export const getBudgets = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/budgets`, {}, tokenOverride);
|
||||
return apiFetch(`/budgets`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -955,7 +970,7 @@ export const createBudget = async (budgetData: Omit<Budget, 'budget_id' | 'user_
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(budgetData),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -969,7 +984,7 @@ export const updateBudget = async (budgetId: number, budgetData: Partial<Omit<Bu
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(budgetData),
|
||||
}, tokenOverride);
|
||||
}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -978,7 +993,7 @@ export const updateBudget = async (budgetId: number, budgetData: Partial<Omit<Bu
|
||||
* @returns A promise that resolves to the API response.
|
||||
*/
|
||||
export const deleteBudget = async (budgetId: number, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/budgets/${budgetId}`, { method: 'DELETE' }, tokenOverride);
|
||||
return apiFetch(`/budgets/${budgetId}`, { method: 'DELETE' }, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -988,7 +1003,7 @@ export const deleteBudget = async (budgetId: number, tokenOverride?: string): Pr
|
||||
* @returns A promise that resolves to the API response.
|
||||
*/
|
||||
export const getSpendingAnalysis = async (startDate: string, endDate: string, tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/budgets/spending-analysis?startDate=${startDate}&endDate=${endDate}`, {}, tokenOverride);
|
||||
return apiFetch(`/budgets/spending-analysis?startDate=${startDate}&endDate=${endDate}`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
// --- Gamification API Functions ---
|
||||
@@ -1009,7 +1024,7 @@ export const getAchievements = async (): Promise<Response> => {
|
||||
* @returns A promise that resolves to the API response.
|
||||
*/
|
||||
export const getUserAchievements = async (tokenOverride?: string): Promise<Response> => {
|
||||
return apiFetch(`/achievements/me`, {}, tokenOverride);
|
||||
return apiFetch(`/achievements/me`, {}, { tokenOverride });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1033,5 +1048,5 @@ export const uploadAvatar = async (avatarFile: File, tokenOverride?: string): Pr
|
||||
formData.append('avatar', avatarFile);
|
||||
|
||||
// Use apiFetch, which now correctly handles FormData.
|
||||
return apiFetch('/users/profile/avatar', { method: 'POST', body: formData }, tokenOverride);
|
||||
return apiFetch('/users/profile/avatar', { method: 'POST', body: formData }, { tokenOverride });
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user