Feature: Product Management
Relevant source files
The following files were used as context for generating this wiki page:The Product Management feature is a core component of the PriceBuddy application, enabling users to track and compare prices of products from various online retailers. It encompasses the creation, administration, and visualization of products and their associated price histories. Users can add products by providing a URL, which the system then scrapes to extract product details and prices.
This feature is managed through a Filament-based administrative interface and a RESTful API. It handles data persistence through Eloquent models, tracks price changes over time, and provides functionalities like tagging, notifications, and status management for each product.
Data Models
The foundation of the product management system is built upon several related Eloquent models. The primary models are Product, Url, Price, Store, and Tag.
Sources: app/Models/Product.php, app/Models/Url.php
Entity Relationship Diagram
The following diagram illustrates the relationships between the main database tables involved in product management.
Product Model
The Product model is the central entity. It stores the main details of a product, including its title, image, and user-defined settings.
| Field | Type | Description |
|---|---|---|
id | int | Primary key. |
user_id | int | Foreign key linking to the users table. |
title | string | The name of the product. |
image | string | URL of the product's primary image. |
status | Statuses (enum) | The status of the product (e.g., Published, Archived). |
favourite | boolean | Whether the product is marked as a favourite. |
current_price | float | The latest known lowest price for the product. |
price_cache | array (json) | A cached list of prices from different URLs, sorted by price. |
notify_price | float | Price threshold for notifications. |
notify_percent | float | Percentage drop threshold for notifications. |
ignored_urls | array (json) | A list of URLs to ignore during searches. |
created_at | datetime | Timestamp of creation. |
Sources: app/Models/Product.php:25-50
Key relationships defined in the Product model include:
user(): ABelongsTorelationship with theUsermodel.urls(): AHasManyrelationship with theUrlmodel.prices(): AHasManyThroughrelationship with thePricemodel viaUrl.stores(): AHasManyThroughrelationship with theStoremodel viaUrl.tags(): AMorphToManyrelationship with theTagmodel.
Sources: app/Models/Product.php:73-131
Url Model
The Url model represents a specific product page from an online store. A single Product can have multiple Url records, allowing for price comparison across different retailers.
| Field | Type | Description |
|---|---|---|
id | int | Primary key. |
product_id | int | Foreign key linking to the products table. |
store_id | int | Foreign key linking to the stores table. |
url | string | The full URL of the product page. |
created_at | datetime | Timestamp of creation. |
Sources: app/Models/Url.php, tests/Feature/Models/UrlTest.php:47-56
Product Creation
Products can be created either through the Filament admin panel or via the API. In both cases, the creation process is initiated by providing a product URL.
Creation Flow
The system scrapes the provided URL to automatically populate product information like title, price, and image. If the URL belongs to a new domain, the system can attempt to create a new Store record.
The following sequence diagram shows the creation of a new product from a URL.
Filament UI Creation
In the Filament admin panel, product creation is handled by ProductResource. The creation form allows a user to either create a brand new product from a URL or add a URL to an existing product.
The form contains the following fields:
- Existing product (
product_id): A searchable select dropdown to associate the new URL with an existing product. - Product URL (
url): A required text input for the product page URL. It is validated by a customStoreUrlrule. - Create store (
create_store): A toggle to automatically create aStoreif the URL's domain is not yet registered.
Sources: app/Filament/Resources/ProductResource.php:57-93
API Creation
A new product can be created by sending a POST request to the /api/products endpoint. The request body must contain a url. The title is optional, as it can be scraped from the URL.
Endpoint: POST /api/products
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The URL of the product to scrape and add. |
title | string | No | The title of the product. If not provided, it will be scraped. |
A successful request returns a 201 Created status with the newly created product data.
Sources: tests/Feature/Api/ProductApiTest.php:108-141
Product Administration (Filament UI)
The ProductResource class defines the administrative interface for managing products within the Filament panel.
Sources: [app/Filament/Resources/ProductResource.php]
Product List View
The index page (/admin/products) displays a list of all products belonging to the current user. The layout is a split view, showing product details and a summary card.
Key columns include:
- Image (
primary_image): A 60x60 thumbnail of the product. - Title (
title): The product title, searchable and sortable. - Badges: A view component displaying badges for status, favourite, etc.
- Tags: A comma-separated list of tags.
- Product Card (
product_card): A custom column that displays detailed price information.
Sources: app/Filament/Resources/ProductResource.php:178-228
The list can be filtered by:
- Status: (e.g.,
Published,Archived) - Lowest Price Period: Show products where the current price is the lowest in the last week, month, etc.
- Tags: Filter by one or more tags.
Sources: app/Filament/Resources/ProductResource.php:229-251
Product Edit Form
The edit form allows modification of a product's core attributes.
- Basics Section:
title: Product title.image: Image URL.status: Product status (Published,Archived, etc.).tags: A multi-select relationship field for assigning tags.weight: Homepage sort order.favourite: A toggle to mark the product as a favourite.
- Notifications Section:
notify_price: Price threshold for notifications.notify_percent: Percentage drop threshold for notifications.
Sources: app/Filament/Resources/ProductResource.php:95-176
API Endpoints
The system exposes a RESTful API for programmatic management of products. All endpoints require authentication and are scoped to the authenticated user's products.
Sources: [tests/Feature/Api/ProductApiTest.php]
| Method | Endpoint | Description |
|---|---|---|
GET | /api/products | Lists all products for the authenticated user. Supports filtering, sorting, and pagination. |
POST | /api/products | Creates a new product from a URL. |
GET | /api/products/{id} | Retrieves a single product. |
PUT | /api/products/{id} | Updates a product's details. |
DELETE | /api/products/{id} | Deletes a product and its associated URLs and prices. |
Sources: tests/Feature/Api/ProductApiTest.php:33-228
API responses for product listings are paginated and follow a standard JSON:API-like structure with data, links, and meta keys. The API also supports including related resources like tags and user via an include query parameter.
Sources: tests/Feature/Api/ProductApiTest.php:261-274, tests/Feature/Api/ProductApiTest.php:298-323
Price Tracking and Caching
A key function of the product management system is to track price history and maintain a cache of the most recent prices for quick access.
Price Update Flow
The Product::updatePrices() method orchestrates the price update process for all URLs associated with a product. It iterates through each Url and calls its updatePrice() method, which triggers the scraping process. After all URLs have been checked, the product's price_cache is rebuilt.
Price Cache
The price_cache attribute on the Product model is a JSON column that stores an array of price information, with one entry per URL. This cache is crucial for performance, as it avoids expensive database queries on the prices table when displaying product information.
Each entry in the cache is a PriceCacheDto object, containing:
store_idandstore_nameurl_idandurltrend(up, down, lowest, none)price(the latest price)history(a map of dates to prices)last_scrapetimestamp
The Product::buildPriceCache() method is responsible for generating this data structure by querying the price history and formatting it correctly. The cache is sorted by price, so the first element always represents the store with the lowest current price.
Sources: app/Models/Product.php:322-380, tests/Feature/Models/ProductTest.php:180-216