padosoft / product-image-discovery-admin
Laravel admin demo application for padosoft/product-image-discovery.
Package info
github.com/padosoft/product_image_discovery_admin
Language:JavaScript
Type:project
pkg:composer/padosoft/product-image-discovery-admin
Requires
- php: ^8.3
- laravel/framework: ^13.0
- laravel/sanctum: ^4.2
- padosoft/product-image-discovery: ^0.1.3
Requires (Dev)
- mockery/mockery: ^1.6
- phpunit/phpunit: ^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v1.1.0
- v1.0.1
- v1.0.0
- dev-m-ad-update-pid-1.2.1
- dev-m-ad-update-pid-1.2.0
- dev-m-ad-fix-query
- dev-m-ad-fix-queue-config
- dev-m-ad-new-command-key-generate
- dev-task/admin-auth-login
- dev-task/final-admin-docs
- dev-task/dashboard-polish-ci
- dev-task/diagnostics-debug-health
- dev-task/configuration-management
- dev-task/request-review-workflows
- dev-task/foundation-laravel-admin-app
This package is auto-updated.
Last update: 2026-10-08 16:14:21 UTC
README
Professional Laravel admin console for padosoft/product-image-discovery: inspect discovery requests, approve or reject candidates, tune provider configuration, run diagnostics, and ship safer product-image workflows without building a back office from scratch.
Table of Contents
- Why Teams Use It
- What You Get
- Screenshots
- Architecture
- Quickstart
- Local Setup
- Login
- Demo Data
- Testing
- CI
- Queues
- Production On Laravel Cloud
- Security Model
- Admin Routes
- Price Intelligence Panel
- Relationship To The Package
- Process Docs
- License
Why Teams Use It
Product images are operational data. A wrong product-color image can leak into ERP, PIM, marketplaces, catalogs, feeds, and customer-facing storefronts. This admin app gives catalog and operations teams a dense, production-shaped cockpit for the conservative discovery pipeline in padosoft/product-image-discovery.
- Review uncertain matches before they publish.
- Compare candidates with source, score, AI, quality, and audit evidence.
- Manage thresholds, providers, trusted sources, and client overrides.
- Run debug flows against real or fake providers.
- Inspect health, queues, storage, AI configuration, and redacted reports.
- Export filtered request data and keep demo operator filters locally.
What You Get
- Operations dashboard with KPI cards, score distribution, provider readiness, queue phase health, latest attention items, and shortcuts.
- Requests and Manual Review pages with dense filters, pagination, CSV export, detail drawer, compare mode, protected image previews, approve/reject/retry flows, and audit timeline.
- Configuration tools for settings, search providers, provider credential status, trusted sources, trust scores, policies, scopes, and URL patterns.
- Diagnostics suite with guided debug-flow runner, report viewer, JSON search, redacted report download, health checks, and API workbench.
- Offline demo path with deterministic seeded data for Herno, Nike, and New Balance scenarios.
- Release gate through PHPUnit, Vitest, Vite, Playwright, Composer validation, and GitHub Actions.
Screenshots
Overview Dashboard
Request Search
Request Detail And Candidate Review
API Test Workbench
Debug Flow
Debug Flow Results
Scoring Configuration
Trusted Sources Configuration
Architecture
This repository is a Laravel 13 admin/demo application, not a reusable UI package. It consumes the sibling headless package through a Composer path repository:
..\product_image_discovery
..\product_image_discovery_admin
The stack is intentionally boring and inspectable:
- Laravel 13 host app.
- SQLite by default for local development and tests.
- Blade shell at the admin route.
- Vite + React for the operator UI.
- Session/CSRF-protected admin JSON wrappers.
- Package API left available under its own prefix.
- Playwright browser smoke coverage for desktop and tablet layouts.
Quickstart
End-to-end walkthrough from a fresh clone to a working Brave-backed debug run on macOS/Linux. Windows/Herd users have an equivalent block below in Local Setup.
Prerequisites: PHP 8.3+, Composer, Node 20+ (npm 10+), and a checkout of the sibling package next to this repo:
parent/
├─ product_image_discovery/ # headless package
└─ product_image_discovery_admin/ # this repo
1. Install dependencies
composer install npm ci
2. Configure the environment
cp .env.example .env php artisan key:generate touch database/database.sqlite
Open .env and confirm at minimum:
APP_URL=http://127.0.0.1:8000 DB_CONNECTION=sqlite DB_DATABASE=/absolute/path/to/database/database.sqlite # or leave default SESSION_DRIVER=file PID_ADMIN_ROUTE_MIDDLEWARE=web,auth PID_ADMIN_DEBUG_RUN_MIDDLEWARE=auth
BRAVE_SEARCH_API_KEY is intentionally not read from .env. Search-provider credentials live in the database (write-only via the admin UI). See step 6.
3. Migrate and seed demo data
php artisan migrate php artisan pid-admin:seed-demo
This creates:
- the deterministic Herno / Nike / New Balance demo requests,
- a demo
fake-demosearch provider (seededis_active=true,priority=1), - a demo admin user — email
admin@demo.test, passwordpassword, only whenAPP_ENVislocalortesting. In any other environment the seeder skips the user step so a known-credentials operator never lands in a non-dev DB. Usepid-admin:create-userfor staging/production.
The artisan command is namespaced. Use
pid-admin:seed-demo, notseed-demo.
If you want a real user instead of the demo one:
php artisan pid-admin:create-user you@example.com --name="You" --password=secret # omit --password to receive a generated one (printed once)
API credentials for an external client (e.g. Gescat) are created with three commands; each secret is printed once:
# 1. technical user that only owns API tokens (skipped if it already exists) php artisan pid-admin:create-api-user gescat-api@surfacesrl.com --name="Gescat API" # 2. non-expiring Sanctum token with product-image-discovery:read + :write php artisan pid-admin:create-api-token gescat-api@surfacesrl.com --name=gescat # 3. Price Intelligence tenant (created or reused) + pi_ API key without extra scopes php artisan pid-admin:create-price-key gescat --tenant-name=Gescat
4. Build the frontend and start the server
npm run build php artisan serve
Leave php artisan serve running. For frontend hot reload during development run npm run dev in a second terminal instead of npm run build.
5. Sign in
Open http://127.0.0.1:8000/login, log in with admin@demo.test / password, and you'll land on http://127.0.0.1:8000/admin/product-image-discovery.
The whole admin shell (Blade + React) and its JSON endpoints sit behind web,auth by default (PID_ADMIN_ROUTE_MIDDLEWARE), so guests are redirected to /login. The Logout button lives in the topbar.
6. Configure a real search provider (Brave)
The demo fake-demo provider is intentionally first in priority so out-of-the-box runs work offline. To exercise a real Brave search you need to either disable the fake provider or give Brave a higher priority (lower number).
- Sign in, then open
http://127.0.0.1:8000/admin/product-image-discovery/providers. - Edit
fake-demoand setis_active = false, or keep it active and bump itspriorityto a high number (e.g.100). - Create or update a Brave provider:
code:bravedriver:bravename:Bravebase_url:https://api.search.brave.comapi_key: your Brave API key (write-only — saved encrypted, never echoed back)is_active:truepriority:1(or any value lower than the rest)
Provider selection:
SearchProviderManagerorders active providers bypriorityASC and uses the first that returns non-empty results. The rest are treated as fallbacks.
7. Run a debug flow against Brave
-
Open
http://127.0.0.1:8000/admin/product-image-discovery/debug. -
Leave the default Herno payload (or paste your own product JSON). The fields the package actually reads are:
Required Optional client_id,erp_model_color_id,brandsupplier,supplier_sku,model_code,color_code,color_name,category,description,ean,season,material,metadata(any keys)Anything outside this set is preserved on the request but not used by the search/scoring/AI pipeline.
description(or its fallbacksname/title/metadata.description/metadata.title) is used as a search term whenmodel_codeis empty, so for products that don't have a clean SKU it's important to fill it in. -
Recommended options for a first real test:
no_env_brave: true(default) — keep it; we are using the DB provider, not auto-creating one from env.no_download: true(default) — skip downloading the candidate images. You will still see them via the View Image column in the report.max_candidates: 2(default) or higher.
-
Click Run debug. The right panel polls until status becomes
succeededorfailed.
8. Inspect the report
Open http://127.0.0.1:8000/admin/product-image-discovery/reports, click Open on the latest run.
The Request tab shows two sections:
package_request_summary— the slim summary the package emits in the report (id, status, brand, model_code, color_name, scores, candidate pointers).original_request_payload— the full JSON you submitted to the debug flow, redacted. This is the place to verify thatdescription,ean,metadata, and any other optional field actually arrived at the package.
In the Candidates tab the table is sorted by final_score desc — the top row is the best match. Each row exposes:
View URL— openssource_page_urlin a new tab (the page Brave found)View Image— opensimage_urlin a new tab (works even withno_download: true)
Empty/invalid URLs render as a disabled chip.
Common pitfalls
401 Unauthorizedon/debug-runs— you are not signed in. Visit/login(step 5).Command "seed-demo" is not defined— use the prefix:php artisan pid-admin:seed-demo.BRAVE_SEARCH_API_KEY missingon the Health screen — the slot label is nowBRAVE_SEARCH_PROVIDER. It checks the DB, not env. Configure a Brave provider (step 6).- Brave never gets called — the
fake-demoprovider haspriority: 1and short-circuits the chain. Disable it or raise its priority (step 6). - All candidates land in
manual_revieweven with high score — that's expected without trusted sources. Add the candidate's host on/admin/product-image-discovery/trustedif you wantverified_match/ready_to_publish. - You changed React/CSS and don't see the change —
npm run buildwrites topublic/build/; the browser may cache the old bundle. Hard refresh (Cmd+Shift+Ron macOS,Ctrl+F5on Linux). For iterative work prefernpm run dev.
Local Setup
Keep the headless package checked out beside this app:
..\product_image_discovery
..\product_image_discovery_admin
Install and bootstrap the app:
composer install Copy-Item .env.example .env -ErrorAction SilentlyContinue php artisan key:generate --ansi New-Item -ItemType File database\database.sqlite -Force php artisan migrate php artisan pid-admin:seed-demo --fresh npm ci npm run build php artisan serve
Open:
http://127.0.0.1:8000/admin/product-image-discovery
If php is not on PATH, this Windows/Herd workstation uses:
%USERPROFILE%\.config\herd\bin\php84\php.exe
Login
Debug-run endpoints (POST /admin/product-image-discovery/debug-runs, POST /admin/product-image-discovery/requests) sit behind the auth middleware controlled by pid-admin.debug_run_middleware. The rest of the admin shell is also behind auth through PID_ADMIN_ROUTE_MIDDLEWARE=web,auth.
The app ships with three minimal endpoints, registered outside the admin prefix:
GET /login
POST /login
POST /logout
The pid-admin:seed-demo command seeds a demo operator suitable for local/offline testing only when APP_ENV is local or testing:
- email:
admin@demo.test - password:
password
Outside those environments the seeder skips the demo user, so a well-known credential never lands in a staging or production database. Use pid-admin:create-user instead.
Create or update a real user with the artisan helper:
php artisan pid-admin:create-user me@example.com --name="Op" # omit --password to receive a generated one (printed once) php artisan pid-admin:create-user me@example.com --password=secret
The admin topbar exposes a Logout button that submits a CSRF-protected POST /logout.
To bypass auth in CI or short-lived local smoke tests, set PID_ADMIN_ROUTE_MIDDLEWARE=web and leave PID_ADMIN_DEBUG_RUN_MIDDLEWARE empty (Playwright does this). PHPUnit keeps web,auth and acts as a signed-in user by default via Tests\TestCase::$authenticateByDefault.
Demo Data
The local seeder gives you deterministic, offline-friendly scenarios:
- Herno manual-review fashion request.
- Nike candidate review and retry/no-candidate workflows.
- New Balance ready/selected-image example.
- Fake demo provider and trusted demo sources.
- Inline generated candidate images so previews work without live downloads.
Reset the demo state with:
php artisan pid-admin:seed-demo --fresh
Testing
Run the full local release gate:
composer validate --strict
npm run phpunit
npm run test
npm run build
npm run e2e
npm run phpunit is the preferred PHP test entry point on this machine because it resolves Herd PHP 8.4 before falling back to php on PATH.
CI
GitHub Actions runs the same release gate on pushes and pull requests:
- checkout this admin app and the sibling
padosoft/product_image_discoverypackage composer validate --strictcomposer installnpm cinpm run phpunitnpm run testnpm run buildnpm run e2e:ci- upload Playwright traces/screenshots on failure
Queues
Pipeline jobs from padosoft/product-image-discovery and padosoft/laravel-ai-price-intelligence are dispatched on dedicated queues, not on default. A worker that only listens to default never runs them: requests stay queued.
Queue names come from config and can be changed from the environment without touching code:
| Env var | Effect |
|---|---|
PRODUCT_IMAGE_DISCOVERY_QUEUE |
routes every image-discovery stage to one queue |
PRODUCT_IMAGE_DISCOVERY_QUEUE_<STAGE> |
overrides one stage (INGEST, SEARCH, EXTRACT, VERIFY, DOWNLOAD, QUALITY) |
PRICE_INTELLIGENCE_QUEUE |
routes every price-intelligence lane to one queue |
PRICE_INTELLIGENCE_QUEUE_<LANE> |
overrides one lane (DISCOVERY, SCRAPE, ENRICH, AI, NOTIFICATIONS) |
Without env vars the defaults are the historical names (image-discovery-<stage>, pi-<lane>). FETCH, ENHANCE and DESCRIPTION exist in the map but the package has no job for them yet.
What each image-discovery stage actually does (package v1.1):
| Stage | Work | Profile |
|---|---|---|
| ingest | DB upsert, dispatches search | light |
| search | external search provider, up to 8 sequential queries | external API, rate-limited |
| extract | turns search results into candidates (DB only, no HTML fetch) | light |
| verify | one job per candidate; calls the AI vision model when PRODUCT_IMAGE_DISCOVERY_AI_ENABLED=true (timeout 45s), otherwise pure scoring |
external API when AI is on, light otherwise |
| download | Http::get()->body() of the image, stored on PRODUCT_IMAGE_DISCOVERY_STORAGE_DISK |
light (a few MB in memory) |
| quality | getimagesize() header read, no pixel decoding |
light |
Recommended setup on Laravel Cloud managed queues. Each managed queue serves exactly one queue name, so route the stages to the names you actually created:
# one managed queue (cheapest): everything on it. Use the exact name shown in Cloud. PRODUCT_IMAGE_DISCOVERY_QUEUE=default PRICE_INTELLIGENCE_QUEUE=default # optional second managed queue, only when search/AI hit rate limits PRODUCT_IMAGE_DISCOVERY_QUEUE_SEARCH=image-discovery-api PRODUCT_IMAGE_DISCOVERY_QUEUE_VERIFY=image-discovery-api
image-discovery-api(if created): small workers with a low max worker count; the cap keeps search/AI calls under provider rate limits. With AI disabled, verify belongs on the main queue.- Managed queues take no
--timeoutflag. Flex workers cap a job at 90 seconds: keepPRODUCT_IMAGE_DISCOVERY_AI_TIMEOUTwell below it (default 45). - No large-memory workers are needed: no stage decodes image pixels.
- Downloaded files are optional. Nothing reads them back (quality uses search metadata, the API exposes the remote
image_url, the admin preview falls back to it), soPRODUCT_IMAGE_DISCOVERY_STORAGE_DISK=localworks even on ephemeral worker disks. Use object storage only if the files must be kept.
Production On Laravel Cloud
Checklist for a working pipeline (image discovery + price intelligence):
- Database: MySQL or Postgres attached to the environment (SQLite is not supported in production).
- Deploy command:
php artisan migrate --force(the packages auto-load their migrations). - Queues: at least one managed queue plus the
*_QUEUEenv vars above.aws/aws-sdk-phpis required incomposer.jsonfor managed queues. - Scheduler: enable it on the App cluster. Price intelligence runs
piprice:run-dueevery minute andpiprice:aggregates:dailyat 02:30. - Cache: a shared store (
CACHE_STORE=database, or an attached Valkey/Redis). The scheduler uses cache locks (withoutOverlapping), which do not work across instances withfile. APP_KEY: set once and never rotated casually: search-provider API keys are stored encrypted with it.- Search providers: run
php artisan db:seed --forceonce to create the default provider rows (all inactive). Never put it in the deploy commands: re-running it resets every default provider tois_active=falseand silently disables search. Activate at least one image-capable driver (brave, tavily, exa, firecrawl, searchapi) with its API key from the admin Settings page. With no active provider every request ends inno_candidates_found. Never runpid-admin:seed-demoin production. - AI (optional):
PRODUCT_IMAGE_DISCOVERY_AI_ENABLED=true,PRODUCT_IMAGE_DISCOVERY_AI_PROVIDER(regolo,anthropic,openai,openrouter), the matching key (REGOLO_API_KEY,ANTHROPIC_API_KEY, ...) andPRODUCT_IMAGE_DISCOVERY_AI_VISION_MODEL. Price intelligence uses the LLM only withPI_LLM_DRIVER=laravel-aiplusPI_LLM_PROVIDER/PI_LLM_MODEL. - Client credentials:
pid-admin:create-api-user,pid-admin:create-api-token,pid-admin:create-price-key(see Login). Image discovery usesAuthorization: Bearer <token>; price intelligence usesX-Api-Key: pi_...under/api/v1. - Outbound HTTPS from workers to search APIs, image hosts, AI providers and competitor sites.
- Price intelligence panel:
APP_URLmust be the public URL (e.g.https://image-discovery.surfacesrl.com) so the panel's browser calls are recognized as same-domain, and thegescattenant must exist.
Security Model
- Provider credentials are write-only.
- JSON responses expose credential booleans such as
has_api_key, never raw secrets or partial previews. - Debug reports and stored artifacts are redacted before display/download.
- Admin wrappers use same-origin fetch and CSRF/session protection.
- High-impact debug/request creation endpoints support stricter configurable middleware and throttling.
- Candidate source URLs are normalized before browser navigation.
- CSV exports neutralize spreadsheet formula prefixes in untrusted text cells.
Admin Routes
The admin console lives at:
/admin/product-image-discovery
Admin JSON wrappers live under:
/admin/product-image-discovery/...
The package API remains available at:
/api/product-image-discovery/...
Price Intelligence Panel
The web panel of padosoft/laravel-ai-price-intelligence-admin is mounted at /admin/price-intelligence and linked from the console sidebar (Modules → Price Intelligence). The link appears only when the package is installed. It is a React SPA that only calls the price-intelligence API under /api/v1: it adds one route and no tables.
- Install: the package is not on Packagist;
composer.jsonpulls it from GitHub through avcsrepository. - Assets: GitHub archives ship without the built SPA, so the build is committed in
public/vendor/price-intelligence-admin. After everycomposer update padosoft/laravel-ai-price-intelligence-adminrunnpm run build:price-intelligence-adminand commit the result. - Login: same session as the console (
web,auth,auth.session, forced password change), then theprice-intelligence:admingate, granted to every console user. - API access:
/api/v1adds Sanctum'sEnsureFrontendRequestsAreStateful, so same-domain browser requests carry the session and the CSRF token.APP_URL's host is always a stateful domain; add others withSANCTUM_STATEFUL_DOMAINS. Machine clients (Gescat) keep usingX-Api-Keyand stay stateless. - Tenant: single tenant. Session users work on the tenant with code
PRICE_INTELLIGENCE_ADMIN_TENANT(defaultgescat, created bypid-admin:create-price-key gescat). If that tenant does not exist the panel API answers 401. - Realtime:
PRICE_INTELLIGENCE_ADMIN_REALTIME=pollingby default (every 15s);ssekeeps a PHP worker busy per open page.
Relationship To The Package
Use this app when you want the ready-made professional admin experience. Use padosoft/product-image-discovery directly when you only need the headless API, models, jobs, configuration, and package-level integration surface.
The admin app intentionally stays close to the package contracts and avoids exposing implementation-only secrets or internal raw payloads.
Process Docs
Read AGENTS.md first when resuming this project. The durable implementation plan is saved at:
%USERPROFILE%\Downloads\productimagesearch-admin\plan.md
The project history and non-obvious setup lessons are tracked in:
License
Apache-2.0. See LICENSE.
Auth and forced password change
- The whole admin (
/admin/product-image-discovery/..., plus/adminwhich redirects to it) requires login viaPID_ADMIN_ROUTE_MIDDLEWARE=web,auth. Guests navigating pages are redirected to/login; JSON requests (Accept: application/json) get401, and the React shell redirects to/loginon401. php artisan pid-admin:create-user {email}always setsusers.must_change_password = true, both on create and when re-run with--passwordon an existing user. There is no opt-out. At the next login the user is redirected to/password/change(min. 8 characters, different from the current one); admin JSON endpoints answer409 password_change_requireduntil the password is changed.- The admin group also runs
auth.session: when a user's password changes (from/password/changeor acreate-user --passwordreset), every other session and remember-me cookie of that user is logged out; the session that changed it stays signed in. /admin/product-image-discovery/healthis an authenticated diagnostics page (environment, debug flag, configured keys, providers). For external uptime monitoring use Laravel's public/upendpoint.- The demo user seeded locally (
admin@demo.test) is not forced to change password. - Run
php artisan migrateto add themust_change_passwordcolumn. Package API routes (/api/product-image-discovery) are not affected.







