fix(seo): keep former slugs reserved to their entity, date sitemap products from Hiboutik

This commit is contained in:
MastersGame
2026-11-14 12:23:47 +01:00
parent 657e8a6b30
commit 290d4baf26
7 changed files with 111 additions and 29 deletions

View File

@@ -65,7 +65,7 @@ Domain notes:
- Customers: an email or phone removed in Hiboutik is erased in database (`null`, data minimization).
- Dates: Hiboutik sends French local time without offset; `parseHiboutikDate` reads datetimes as `Europe/Paris` whatever the server time zone, and date-only values (date of birth) as midnight UTC.
- Orders come from the customer's `sales`: sales already stored as `COMPLETED` and linked to this customer are skipped (an order that lost its customer is fetched again to be linked back), the others are fetched and upserted on `hiboutikSaleId` (`source: STORE` on creation only). `status` is `COMPLETED` or `PENDING`, `orderedAt` is the completion date else the creation date. Lines are replaced on each upsert and snapshot name, version, barcode, price and Hiboutik ids; the `ProductVersion` link is optional (`SetNull`). `unitPrice` is the paid price, taxes and discount included. Quotations are skipped, anonymous sales are never synced (only customers' sales are read), and orders are never deleted.
- Slugs (SEO): `Brand`, `Category` and `Product` have a unique `slug`, generated once by `SlugService.assignMissingSlugs` at the end of their sync step and never overwritten by the sync (an admin may replace it). Generation handles the oldest `hiboutikId` first: the slugified name, else `name-<hiboutikId>` on a collision (logged as a warning), else `brand|category|product-<hiboutikId>` for a name without latin letters. `SlugService.changeSlug` keeps the former slug in `SlugRedirect` (for a 301); slugs of redirects stay reserved. `GET /sitemap` lists the public slugs (brands `enabled`, categories `webEnabled`, products `enabled` and not `archived`) for the storefront's sitemap.xml.
- Slugs (SEO): `Brand`, `Category` and `Product` have a unique `slug`, generated once by `SlugService.assignMissingSlugs` at the end of their sync step and never overwritten by the sync (an admin may replace it). Generation handles the oldest `hiboutikId` first: the slugified name, else `name-<hiboutikId>` on a collision (logged as a warning), else `brand|category|product-<hiboutikId>` for a name without latin letters. `SlugService.changeSlug` keeps the former slug in `SlugRedirect` (for a 301); slugs of redirects stay reserved (an entity may only take back its own former slug). `GET /sitemap` lists the public slugs (brands `enabled`, categories `webEnabled`, products `enabled` and not `archived`) for the storefront's sitemap.xml; only products carry a date, `hiboutikUpdatedAt` (the row's `updatedAt` changes on every sync).
- Shared Prisma payload types (e.g. `ProductWithVersions`) live in `prisma/customObjects/`.
## Input validation