Files
flyer-crawler.projectium.com/src/types.ts
Torben Sorensen 2e72ee81dd
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 41s
maybe a few too many fixes
2025-12-28 21:38:31 -08:00

968 lines
24 KiB
TypeScript

// src/types.ts
export interface Store {
store_id: number;
created_at: string;
updated_at: string;
name: string;
logo_url?: string | null;
created_by?: string | null;
}
export type FlyerStatus = 'processed' | 'needs_review' | 'archived';
export interface Flyer {
flyer_id: number;
created_at: string;
updated_at: string;
file_name: string;
image_url: string;
icon_url?: string | null; // URL for the 64x64 icon version of the flyer
checksum?: string;
store_id?: number;
valid_from?: string | null;
valid_to?: string | null;
store_address?: string | null;
status: FlyerStatus;
item_count: number;
uploaded_by?: string | null; // UUID of the user who uploaded it, can be null for anonymous uploads
store?: Store;
}
/**
* Represents the data required to insert a new flyer into the database.
* It's a subset of the full Flyer type, excluding generated fields like `flyer_id`.
*/
export interface FlyerInsert {
file_name: string;
image_url: string;
icon_url: string;
checksum: string;
store_name: string;
valid_from: string | null;
valid_to: string | null;
store_address: string | null;
status: FlyerStatus;
item_count: number;
uploaded_by?: string | null;
}
/**
* Represents the data required to insert a new flyer into the database, with store_id resolved.
* This is an internal type for the database service.
*/
export type FlyerDbInsert = Omit<FlyerInsert, 'store_name'> & { store_id: number };
/**
* Represents the data required to insert a new flyer item into the database.
* It's a subset of the full FlyerItem type.
*/
export type FlyerItemInsert = Omit<
FlyerItem,
'flyer_item_id' | 'flyer_id' | 'created_at' | 'updated_at'
>;
export interface UnitPrice {
value: number;
unit: string; // e.g., 'g', 'kg', 'ml', 'l', 'oz', 'lb', 'each'
}
export interface FlyerItem {
flyer_item_id: number;
flyer_id: number;
created_at: string;
item: string;
price_display: string;
price_in_cents?: number | null;
quantity?: string;
quantity_num?: number | null;
master_item_id?: number;
master_item_name?: string | null;
category_id?: number | null;
category_name?: string | null;
unit_price?: UnitPrice | null;
product_id?: number | null;
view_count: number;
click_count: number;
updated_at: string;
}
export interface MasterGroceryItem {
master_grocery_item_id: number;
created_at: string;
updated_at: string;
name: string;
category_id?: number | null;
category_name?: string | null;
is_allergen?: boolean;
allergy_info?: unknown | null; // JSONB
created_by?: string | null;
}
export interface Category {
category_id: number;
name: string;
}
export interface Brand {
brand_id: number;
created_at: string;
updated_at: string;
name: string;
logo_url?: string | null;
store_id?: number | null;
store_name?: string | null;
}
export interface Product {
product_id: number;
created_at: string;
updated_at: string;
master_item_id: number;
brand_id?: number | null;
name: string;
description?: string | null;
size?: string | null;
upc_code?: string | null;
}
export interface DealItem {
item: string;
price_display: string;
price_in_cents: number | null;
quantity: string;
storeName: string;
master_item_name?: string | null;
unit_price?: UnitPrice | null;
}
// User-specific types
export interface User {
user_id: string; // UUID
email: string;
}
/**
* Represents the user data including the password hash, used for authentication checks.
* This type is internal to the backend and should not be sent to the client.
*/
export interface UserWithPasswordHash extends User {
password_hash: string | null;
failed_login_attempts: number;
last_failed_login: string | null; // TIMESTAMPTZ
last_login_at?: string | null; // TIMESTAMPTZ
last_login_ip?: string | null;
created_at: string;
updated_at: string;
}
export interface Profile {
created_at: string;
updated_at: string;
full_name?: string | null;
avatar_url?: string | null;
address_id?: number | null;
points: number;
role: 'admin' | 'user';
preferences?: {
darkMode?: boolean;
unitSystem?: 'metric' | 'imperial';
} | null;
created_by?: string | null;
updated_by?: string | null;
}
/**
* Represents the combined user and profile data object returned by the backend's /users/profile endpoint.
* It embeds the User object within the Profile object.
* It also includes the full Address object if one is associated with the profile.
*/
export type UserProfile = Profile & {
user: User;
address?: Address | null;
};
export interface SuggestedCorrection {
suggested_correction_id: number;
flyer_item_id: number;
user_id: string;
correction_type: string;
suggested_value: string;
status: 'pending' | 'approved' | 'rejected';
created_at: string;
updated_at: string;
reviewed_at?: string | null;
reviewed_notes?: string | null;
// Joined data
user_email?: string;
flyer_item_name?: string;
flyer_item_price_display?: string;
}
/**
* Represents the complete data package for a user export.
*/
export interface UserDataExport {
profile: Profile;
watchedItems: MasterGroceryItem[];
shoppingLists: ShoppingList[];
// Add other user-specific data models here as they are implemented
// e.g., pantryItems: PantryItem[];
// e.g., recipes: Recipe[];
}
export interface UserAlert {
user_alert_id: number;
user_watched_item_id: number;
alert_type: 'PRICE_BELOW' | 'PERCENT_OFF_AVERAGE';
threshold_value: number;
is_active: boolean;
created_at: string;
}
export interface Notification {
notification_id: number;
user_id: string; // UUID
content: string;
link_url?: string | null;
is_read: boolean;
created_at: string;
updated_at: string;
}
export interface ShoppingList {
shopping_list_id: number;
user_id: string; // UUID
name: string;
created_at: string;
updated_at: string;
items: ShoppingListItem[]; // Nested items
}
export interface ShoppingListItem {
shopping_list_item_id: number;
shopping_list_id: number;
master_item_id?: number | null;
custom_item_name?: string | null;
quantity: number;
is_purchased: boolean;
notes?: string | null;
added_at: string;
updated_at: string;
// Joined data for display
master_item?: {
name: string;
} | null;
}
export interface UserSubmittedPrice {
user_submitted_price_id: number;
user_id: string; // UUID
master_item_id: number;
store_id: number;
price_in_cents: number;
photo_url?: string | null;
upvotes: number;
downvotes: number;
created_at: string;
}
export interface ItemPriceHistory {
item_price_history_id: number;
master_item_id: number;
summary_date: string; // DATE
min_price_in_cents?: number | null;
max_price_in_cents?: number | null;
avg_price_in_cents?: number | null;
data_points_count: number;
}
/**
* Represents a single data point for an item's price on a specific day.
* This is the raw structure returned by the price history API endpoint.
*/
export interface HistoricalPriceDataPoint {
master_item_id: number;
avg_price_in_cents: number | null;
summary_date: string; // DATE
}
export interface MasterItemAlias {
master_item_alias_id: number;
master_item_id: number;
alias: string;
}
export interface Recipe {
recipe_id: number;
user_id?: string | null; // UUID
original_recipe_id?: number | null;
name: string;
description?: string | null;
instructions?: string | null;
prep_time_minutes?: number | null;
cook_time_minutes?: number | null;
servings?: number | null;
photo_url?: string | null;
calories_per_serving?: number | null;
protein_grams?: number | null;
fat_grams?: number | null;
carb_grams?: number | null;
avg_rating: number;
status: 'private' | 'pending_review' | 'public' | 'rejected';
rating_count: number;
fork_count: number;
created_at: string;
updated_at: string;
comments?: RecipeComment[];
ingredients?: RecipeIngredient[];
}
export interface RecipeIngredient {
recipe_ingredient_id: number;
recipe_id: number;
master_item_id: number;
quantity: number;
unit: string;
}
export interface RecipeIngredientSubstitution {
recipe_ingredient_substitution_id: number;
recipe_ingredient_id: number;
substitute_master_item_id: number;
notes?: string | null;
}
export interface Tag {
tag_id: number;
name: string;
}
export interface RecipeTag {
recipe_id: number;
tag_id: number;
}
export interface RecipeRating {
recipe_rating_id: number;
recipe_id: number;
user_id: string; // UUID
rating: number;
comment?: string | null;
created_at: string;
}
export interface RecipeComment {
recipe_comment_id: number;
recipe_id: number;
user_id: string; // UUID
parent_comment_id?: number | null;
content: string;
status: 'visible' | 'hidden' | 'reported';
created_at: string;
updated_at?: string | null;
user_full_name?: string; // Joined data
user_avatar_url?: string; // Joined data
}
export interface MenuPlan {
menu_plan_id: number;
user_id: string; // UUID
name: string;
start_date: string; // DATE
end_date: string; // DATE
created_at: string;
planned_meals?: PlannedMeal[];
}
export interface SharedMenuPlan {
shared_menu_plan_id: number;
menu_plan_id: number;
shared_by_user_id: string; // UUID
shared_with_user_id: string; // UUID
permission_level: 'view' | 'edit';
created_at: string;
}
export interface PlannedMeal {
planned_meal_id: number;
menu_plan_id: number;
recipe_id: number;
plan_date: string; // DATE
meal_type: string;
servings_to_cook?: number | null;
}
export interface PantryItem {
pantry_item_id: number;
user_id: string; // UUID
master_item_id: number;
quantity: number;
unit?: string | null;
best_before_date?: string | null; // DATE
pantry_location_id?: number | null;
notification_sent_at?: string | null; // TIMESTAMPTZ
updated_at: string;
}
export interface UserItemAlias {
user_item_alias_id: number;
user_id: string; // UUID
master_item_id: number;
alias: string;
}
export interface FavoriteRecipe {
user_id: string; // UUID
recipe_id: number;
created_at: string;
}
export interface FavoriteStore {
user_id: string; // UUID
store_id: number;
created_at: string;
}
export interface RecipeCollection {
recipe_collection_id: number;
user_id: string; // UUID
name: string;
description?: string | null;
created_at: string;
}
export interface RecipeCollectionItem {
collection_id: number;
recipe_id: number;
added_at: string;
}
export interface SharedShoppingList {
shared_shopping_list_id: number;
shopping_list_id: number;
shared_by_user_id: string; // UUID
shared_with_user_id: string; // UUID
permission_level: 'view' | 'edit';
created_at: string;
}
export interface SharedRecipeCollection {
shared_collection_id: number;
recipe_collection_id: number;
shared_by_user_id: string; // UUID
shared_with_user_id: string; // UUID
permission_level: 'view' | 'edit';
}
export interface DietaryRestriction {
dietary_restriction_id: number;
name: string;
type: 'diet' | 'allergy';
}
export interface UserDietaryRestriction {
user_id: string; // UUID
restriction_id: number;
}
export interface Appliance {
appliance_id: number;
name: string;
}
export interface UserAppliance {
user_id: string; // UUID
appliance_id: number;
}
export interface RecipeAppliance {
recipe_id: number;
appliance_id: number;
}
export interface UserFollow {
follower_id: string; // UUID
following_id: string; // UUID
created_at: string;
}
/**
* The list of possible actions for an activity log.
* Using a specific type union instead of a generic 'string' allows for better type checking.
*/
export type ActivityLogAction =
| 'flyer_processed'
| 'recipe_created'
| 'user_registered'
| 'recipe_favorited'
| 'list_shared'
| 'login_failed_password'
| 'password_reset';
/**
* Base interface for all log items, containing common properties.
*/
interface ActivityLogItemBase {
activity_log_id: number;
user_id: string | null;
action: string;
display_text: string;
created_at: string;
updated_at: string;
icon?: string | null;
// Joined data for display in feeds
user_full_name?: string;
user_avatar_url?: string;
}
// --- Discriminated Union for Activity Log Details ---
interface FlyerProcessedLog extends ActivityLogItemBase {
action: 'flyer_processed';
details: {
flyer_id: number;
store_name: string;
};
}
interface RecipeCreatedLog extends ActivityLogItemBase {
action: 'recipe_created';
details: {
recipe_id: number;
recipe_name: string;
};
}
interface UserRegisteredLog extends ActivityLogItemBase {
action: 'user_registered';
details: {
full_name: string;
};
}
interface RecipeFavoritedLog extends ActivityLogItemBase {
action: 'recipe_favorited';
details: {
recipe_name: string;
};
}
interface ListSharedLog extends ActivityLogItemBase {
action: 'list_shared';
details: {
list_name: string;
shopping_list_id: number;
shared_with_name: string;
};
}
/**
* The final ActivityLogItem type is a union of all specific log types.
* TypeScript will now correctly infer the shape of 'details' based on the 'action' property.
*/
export type ActivityLogItem =
| FlyerProcessedLog
| RecipeCreatedLog
| UserRegisteredLog
| RecipeFavoritedLog
| ListSharedLog;
export interface PantryLocation {
pantry_location_id: number;
user_id: string; // UUID
name: string;
}
export interface SearchQuery {
search_query_id: number;
user_id?: string | null; // UUID
query_text: string;
result_count?: number | null;
was_successful?: boolean | null;
created_at: string;
}
export interface ShoppingTripItem {
shopping_trip_item_id: number;
shopping_trip_id: number;
master_item_id?: number | null;
custom_item_name?: string | null;
quantity: number;
price_paid_cents?: number | null;
// Joined data for display
master_item_name?: string | null;
}
export interface ShoppingTrip {
shopping_trip_id: number;
user_id: string; // UUID
shopping_list_id?: number | null;
completed_at: string;
total_spent_cents?: number | null;
items: ShoppingTripItem[]; // Nested items
}
export interface Receipt {
receipt_id: number;
user_id: string; // UUID
store_id?: number | null;
receipt_image_url: string;
transaction_date?: string | null;
total_amount_cents?: number | null;
status: 'pending' | 'processing' | 'completed' | 'failed';
raw_text?: string | null;
created_at: string;
processed_at?: string | null;
items?: ReceiptItem[];
}
export interface ReceiptItem {
receipt_item_id: number;
receipt_id: number;
raw_item_description: string;
quantity: number;
price_paid_cents: number;
master_item_id?: number | null;
product_id?: number | null;
status: 'unmatched' | 'matched' | 'needs_review' | 'ignored';
}
export interface ReceiptDeal {
receipt_item_id: number;
master_item_id: number;
item_name: string;
price_paid_cents: number;
current_best_price_in_cents: number;
potential_savings_cents: number;
deal_store_name: string;
flyer_id: number;
}
/**
* Represents a geographic point in GeoJSON format.
* This is a standard way to represent point data from PostGIS.
*/
export interface GeoJSONPoint {
type: 'Point';
coordinates: [number, number]; // [longitude, latitude]
}
export interface StoreLocation {
store_location_id: number;
store_id?: number | null;
address_id: number;
}
export interface Address {
address_id: number;
address_line_1: string;
address_line_2?: string | null;
city: string;
province_state: string;
postal_code: string;
country: string;
latitude?: number | null;
longitude?: number | null;
location?: GeoJSONPoint | null;
created_at: string;
updated_at: string;
}
export interface FlyerLocation {
flyer_id: number;
store_location_id: number;
}
export enum AnalysisType {
QUICK_INSIGHTS = 'QUICK_INSIGHTS',
DEEP_DIVE = 'DEEP_DIVE',
WEB_SEARCH = 'WEB_SEARCH',
PLAN_TRIP = 'PLAN_TRIP',
GENERATE_IMAGE = 'GENERATE_IMAGE',
COMPARE_PRICES = 'COMPARE_PRICES',
}
/**
* Represents a source for a grounded response, normalized for consistent use in the UI.
*/
export interface Source {
uri: string;
title: string;
}
/**
* Represents a response that may include sources, such as from a web search or map plan.
*/
export interface GroundedResponse {
text: string;
sources: Source[];
}
/**
* Defines the shape of the state managed by the useAiAnalysis hook's reducer.
* This centralizes all state related to AI analysis into a single, predictable object.
*/
export interface AiAnalysisState {
// The type of analysis currently being performed, if any.
loadingAnalysis: AnalysisType | null;
// A general error message for any failed analysis.
error: string | null;
// Stores the text result for each analysis type.
results: { [key in AnalysisType]?: string };
// Stores the sources for analyses that provide them.
sources: { [key in AnalysisType]?: Source[] };
// Stores the URL of the last generated image.
generatedImageUrl: string | null;
}
/**
* Defines the actions that can be dispatched to the AiAnalysisReducer.
* This uses a discriminated union for strict type checking.
*/
export type AiAnalysisAction =
// Dispatched when any analysis starts.
| { type: 'FETCH_START'; payload: { analysisType: AnalysisType } }
// Dispatched when an analysis that returns a simple string succeeds.
| { type: 'FETCH_SUCCESS_TEXT'; payload: { analysisType: AnalysisType; data: string } }
// Dispatched when an analysis that returns text and sources succeeds.
| {
type: 'FETCH_SUCCESS_GROUNDED';
payload: { analysisType: AnalysisType; data: GroundedResponse };
}
// Dispatched when the image generation succeeds.
| { type: 'FETCH_SUCCESS_IMAGE'; payload: { data: string } }
// Dispatched when any analysis fails.
| { type: 'FETCH_ERROR'; payload: { error: string } }
// Dispatched to clear errors or reset state if needed.
| { type: 'CLEAR_ERROR' }
// Dispatched to reset the state to its initial values.
| { type: 'RESET_STATE' };
export type StageStatus = 'pending' | 'in-progress' | 'completed' | 'error';
export interface ProcessingStage {
name: string;
status: StageStatus;
detail?: string;
critical?: boolean;
progress?: { current: number; total: number } | null;
}
export const CATEGORIES = [
'Fruits & Vegetables',
'Meat & Seafood',
'Dairy & Eggs',
'Bakery & Bread',
'Pantry & Dry Goods',
'Beverages',
'Frozen Foods',
'Snacks',
'Household & Cleaning',
'Personal Care & Health',
'Baby & Child',
'Pet Supplies',
'Deli & Prepared Foods',
'Canned Goods',
'Condiments & Spices',
'Breakfast & Cereal',
'Organic',
'International Foods',
'Other/Miscellaneous',
];
/**
* Represents the core data extracted from a flyer by the AI service.
* This is the structure returned from the backend to the frontend.
*/
export interface ExtractedCoreData {
store_name: string;
valid_from: string | null;
valid_to: string | null;
store_address: string | null;
items: ExtractedFlyerItem[];
}
/**
* Represents the shape of a single flyer item as returned by the AI service,
* before it is saved to the database. This is a Data Transfer Object (DTO).
* It intentionally omits database-generated fields like `flyer_item_id`, `created_at`, etc.
*/
export interface ExtractedFlyerItem {
item: string;
price_display: string;
price_in_cents: number | null;
quantity: string;
category_name: string;
master_item_id?: number; // Kept optional as AI might not find a match
unit_price?: UnitPrice | null;
}
/**
* Represents the logo data extracted from a flyer by the AI service.
*/
export interface ExtractedLogoData {
store_logo_base_64: string | null;
}
/**
* Represents the data extracted from a receipt image by the AI service.
*/
export interface ExtractedReceiptData {
raw_text: string;
items: {
raw_item_description: string;
quantity: number;
price_paid_cents: number;
}[];
}
/**
* Represents an item that frequently appears on sale.
* Returned by the `get_most_frequent_sale_items` database function.
*/
export interface MostFrequentSaleItem {
master_item_id: number;
item_name: string;
sale_count: number;
}
/**
* Represents a recipe that can be made from items in a user's pantry.
* Returned by the `find_recipes_from_pantry` database function.
*/
export interface PantryRecipe extends Recipe {
missing_ingredients_count: number;
pantry_ingredients_count: number;
}
/**
* Represents a recommended recipe for a user.
* Returned by the `recommend_recipes_for_user` database function.
*/
export interface RecommendedRecipe extends Recipe {
recommendation_score: number;
reason: string;
}
/**
* Represents the best current sale price for a user's watched item.
* Returned by the `get_best_sale_prices_for_user` database function.
*/
export interface WatchedItemDeal {
master_item_id: number;
item_name: string;
best_price_in_cents: number;
store_name: string;
flyer_id: number;
valid_to: string; // Date string
}
/**
* Represents a suggested unit conversion for a pantry item.
* Returned by the `suggest_pantry_item_conversions` database function.
*/
export interface PantryItemConversion {
to_unit: string;
converted_quantity: number;
}
/**
* Represents an item needed for a menu plan, considering pantry stock.
* Returned by `generate_shopping_list_for_menu_plan` and `add_menu_plan_to_shopping_list`.
*/
export interface MenuPlanShoppingListItem {
master_item_id: number;
item_name: string;
quantity_needed: number;
}
/**
* Represents an unmatched flyer item pending manual review.
* Returned by `getUnmatchedFlyerItems`.
*/
export interface UnmatchedFlyerItem {
unmatched_flyer_item_id: number;
status: 'pending' | 'resolved' | 'ignored'; // 'resolved' is used instead of 'reviewed' from the DB for clarity
created_at: string; // Date string
updated_at: string;
reviewed_at?: string | null;
flyer_item_id: number;
flyer_item_name: string;
price_display: string;
flyer_id: number;
store_name: string;
}
/**
* Represents a user-defined budget for tracking grocery spending.
*/
export interface Budget {
budget_id: number;
user_id: string; // UUID
name: string;
amount_cents: number;
period: 'weekly' | 'monthly';
start_date: string; // DATE
created_at: string;
updated_at: string;
}
/**
* Represents the aggregated spending for a single category.
* Returned by the `get_spending_by_category` database function.
*/
export interface SpendingByCategory {
category_id: number;
category_name: string;
total_spent_cents: number;
}
/**
* Represents a single defined achievement in the system.
*/
export interface Achievement {
achievement_id: number;
name: string;
description: string;
icon?: string | null;
points_value: number;
created_at: string;
}
/**
* Represents an achievement that has been awarded to a user.
*/
export interface UserAchievement {
user_id: string; // UUID
achievement_id: number;
achieved_at: string; // TIMESTAMPTZ
}
/**
* Represents a user's entry on the leaderboard.
* Returned by the `getLeaderboard` database function.
*/
export interface LeaderboardUser {
user_id: string;
full_name: string | null;
avatar_url: string | null;
points: number;
rank: string; // RANK() returns a bigint, which the pg driver returns as a string.
}
/**
* Defines the shape of the user data returned for the admin user list.
* This is a public-facing type and does not include sensitive fields.
*/
export interface AdminUserView {
user_id: string;
email: string;
created_at: string;
role: 'admin' | 'user';
full_name: string | null;
avatar_url: string | null;
}
export interface PriceHistoryData {
master_item_id: number;
price_in_cents: number;
date: string; // ISO date string
}