# Laravel + Vue Ads

**Demo · 8 September 2026**

A Laravel API and Vue client implement an ads catalog: list, detail, create, delete, search, sort and pagination. Response and error contracts match across layers; demo CRUD is session-isolated and API plus images use the same origin.

Source version: `54f1be9aa1057163edd9d095d27e8c5451935de7`

Backend and Vue client: April 2024. Public demo update: 7 September 2026.

[Project notes](https://komaroff-dev.ru/projects/ads/) · [Online documentation](https://komaroff-dev.ru/projects/ads/docs/)

[Demo](https://komaroff-dev.ru/catalog/)

[Source repository](https://github.com/komaroffsergei/laravel-sandbox)

## Task

Public deployment required correcting base URLs, frontend/backend contracts and form validation, while preventing one visitor from changing another visitor’s data.

Browsing and posting classified ads.

## Work done

- Laravel API and Vue client.
- Contract fixes for the list, detail page, descriptions and images.
- Upgrade to Laravel 12, validation, signed cookies and restricted demo CRUD.

## What sets the implementation apart

One contract supports desktop and mobile views, server pagination and validation errors. A session owns its created ads, foreign records return 404, and reset restores the initial dataset without an admin step.

## Application-code composition

- Laravel / PHP: 70.7%
- Vue / JavaScript: 16.3%
- Eloquent / MySQL: 13%

Shares use Git application-file bytes at pinned revisions. Each file belongs to one subsystem under scripts/generate-stack-share.mjs rules; tests, assets, dependencies and generated files are excluded. Rounded to 0.1%. This describes code composition, not proficiency.

## Stack

| Technology | Role |
| --- | --- |
| Laravel / Eloquent | Validation, owner scope, CRUD and expiration. |
| Vue | Catalog routes, listing details, forms, sorting and pagination. |
| MySQL | Listings, DECIMAL prices and retention period. |

## Architecture and decisions

- The client and API share one origin with no external backend URL.
- The 24 seed listings are shared; visitor-created records are filtered by owner.
- Images come from a fixed set of local SVGs; arbitrary URLs and mass assignment are excluded.
- Prices are stored as DECIMAL(10,2); the API returns consistent scalar pagination metadata.

### Components of the published demo

![Components of the published demo](https://komaroff-dev.ru/evidence/ads/diagrams/components.svg)

[Mermaid source](https://komaroff-dev.ru/evidence/ads/diagrams/components.mmd)

### Main workflow

![Main workflow](https://komaroff-dev.ru/evidence/ads/diagrams/scenario.svg)

[Mermaid source](https://komaroff-dev.ru/evidence/ads/diagrams/scenario.mmd)

### Deployment of the published version

![Deployment of the published version](https://komaroff-dev.ru/evidence/ads/diagrams/deployment.svg)

[Mermaid source](https://komaroff-dev.ru/evidence/ads/diagrams/deployment.mmd)

### Data structure and storage boundaries

![Data structure and storage boundaries](https://komaroff-dev.ru/evidence/ads/diagrams/data.svg)

[Mermaid source](https://komaroff-dev.ru/evidence/ads/diagrams/data.mmd)

## Code map

| Feature | Screen / API | Module | Data / verification |
| --- | --- | --- | --- |
| Catalog, search and sorting | AdList / GET api/ad | AdController::index | ads, owner scope |
| Details | AdItem / GET api/ad/id | AdController::show | seed or own record |
| Creation | AdNew / POST api/ad | Validator, Eloquent | allowlist + session_id + expires_at |
| Deletion and reset | AdItem / DELETE, POST demo/reset | AdController, DemoSession | own session only |
| Cleanup | CLI demo:cleanup | scheduled command | expired rows |

Before changing a feature, trace its interface, API, state and storage dependencies. Check inputs, errors, retries and related workflows. Update the map and affected diagrams alongside the code, rerun the related checks and attach evidence for the source version.

## Workflows and API

1. Open the catalog, change sorting and go to the next page.
2. View listing details and create a listing using a local image.
3. Check validation of empty or invalid input.
4. Open another visitor's ID in a second session: expect 404. Delete your own listing and reset the example.

## Verification

- Full production QA repeated at /catalog/: create, delete, search, sort, pagination, sessions and mobile view — passed. The page opens in the original Chrome session with the ad blocker enabled.
- Sorting, pagination, search, empty state
- Required form validation
- Signed Secure HttpOnly cookie; cross-session GET/DELETE both 404
- Server rejects negative price, short text and external image URL
- Cancel, delete and restored seed count
- No verified GitHub CI run is available for this SHA; the listed evidence covers local checks and published-version scenarios. Historical CI links, where available, are labeled separately.

[CI: Vue: production build for /catalog/](https://github.com/komaroffsergei/laravel-sandbox-ui/actions/runs/34082531195) — `ba775c53a6b3b33c1524797390e09a9fb0a432a1`, 2026-09-07; historical run of a different version.

## Results and measurements

- 13 meaningful screenshots of the actual interface retained.
- Each screenshot includes its date, scenario and code revision.
- Peak container RAM in the short test: 39.76 MiB. Shared services are counted separately; the shop includes its own database.
- 6 scenario repetitions measured; median 1605.5 ms, maximum 2981 ms. Timing includes the browser and HTTPS; methodology and limitations are included.

These checks apply to the listed version. Business metrics and model quality on an independent dataset were not measured.

![Total project container resources · 2026-09-07T01:08:27 — 01:11:22 UTC](https://komaroff-dev.ru/evidence/ads/charts/resources.svg)

[Total project container resources · 2026-09-07T01:08:27 — 01:11:22 UTC: source data](https://komaroff-dev.ru/evidence/ads/metrics/measurements.json)

![Measured scenario repetitions over HTTPS · 2026-09-07T01:08:36 UTC; methodology limitations in JSON](https://komaroff-dev.ru/evidence/ads/charts/scenario-time.svg)

[Measured scenario repetitions over HTTPS · 2026-09-07T01:08:36 UTC; methodology limitations in JSON: source data](https://komaroff-dev.ru/evidence/ads/metrics/measurements.json)

## Limitations

- All listings and images are for demonstration. Sales and arbitrary uploads are disabled.
- 20 personal listings per session, 1,000 in total; one-hour TTL and a cleanup timer every five minutes.
- The legacy Vue build tooling has known development-dependency issues; a full frontend tooling migration was not performed.

## Running and operating

Update, 7 September 2026: the frontend is published at /catalog/ on the main domain. The old ads subdomain redirects here because a browser ad blocker blocked its JS/CSS. The Laravel backend is unchanged.

This case combines laravel-sandbox and laravel-sandbox-ui. It is one project with two repositories; both SHAs are retained in release metadata.

Builds run outside the VPS using Dockerfile.portfolio and a separate ads-ui build context. composer.lock and package-lock.json pin dependency versions.

Nginx → localhost:18402 → Apache/Laravel. The ads database and user are separate. Check /healthz, GET /api/ad, POST /api/ad and read/delete for your own ID. A system timer runs php artisan demo:cleanup; reset does not delete seed data or other visitors' records.

Builds run outside the VPS; the server receives static files and versioned images. Secrets are kept separately. Logs rotate; changing log options requires recreating the container. No server-wide docker prune.

The code map links a screen or API to its module and data. A change needs those links checked, the map updated and the affected flows rerun: normal path, error, retry and reset. Diagrams can go stale, so their version is listed alongside them.

Rollback checked at 2026-09-07T01:00:33.586236+00:00: 20260907-verified → 20260907b → 20260907-verified. HTTPS, readiness and SHA were confirmed after each switch. This checks configuration and artifacts at one verified SHA; no reverse database migration was performed.

## Screenshot index

1. [Catalog: cards and sorting](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/01-catalog.png) — 2026-09-07T04:28:20.397008+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

2. [Changing listing order](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/02-sorted.png) — 2026-09-07T04:28:20.909030+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

3. [Going to the next page](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/03-page-two.png) — 2026-09-07T04:28:21.390284+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

4. [Search by title; the frame shows the entered parameters, the API response and the resulting catalog state.](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/04-search.png) — 2026-09-07T04:28:22.406226+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

5. [Empty search result; the frame shows the entered parameters, the API response and the resulting catalog state.](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/05-empty.png) — 2026-09-07T04:28:23.071492+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

6. [Create-listing form; the frame shows the entered parameters, the API response and the resulting catalog state.](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/06-new-form.png) — 2026-09-07T04:28:23.520011+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

7. [Completed form; the frame shows the entered parameters, the API response and the resulting catalog state.](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/07-filled.png) — 2026-09-07T04:28:24.038342+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

8. [Created listing in the current session](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/08-created.png) — 2026-09-07T04:28:24.690287+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

9. [Checking listing isolation between sessions](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/09-isolation.png) — 2026-09-07T04:28:25.372559+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

10. [Deletion confirmation; the frame shows the entered parameters, the API response and the resulting catalog state.](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/10-delete-confirm.png) — 2026-09-07T04:28:25.856226+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

11. [Catalog on a mobile screen; the frame shows the entered parameters, the API response and the resulting catalog state.](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/11-mobile-catalog.png) — 2026-09-07T04:28:26.494282+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

12. [Mobile listing details](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/12-mobile-card.png) — 2026-09-07T04:28:26.916150+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

13. [Mobile form; the frame shows the entered parameters, the API response and the resulting catalog state.](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/full/13-mobile-form.png) — 2026-09-07T04:28:27.341269+00:00, source `ba775c53a6b3b33c1524797390e09a9fb0a432a1`.

Screenshots, diagrams and the original evidence package retain their source language. Captions and this document are translated into English.

[Original evidence package](https://komaroff-dev.ru/evidence/ads/revision-ba775c53/materials.zip)
