Files
clinicpro/docs/api/category-import.md
T
hamed ccbb1d0b1f feat: Add City entity methods and migration for title field
- Created a new JSON file for the City entity's AST representation, detailing its methods and properties.
- Added a migration to alter the cities table by adding a nullable title column.
2026-07-08 12:19:06 +03:30

5.6 KiB

Category Import / Export API

Prefix: /api/v1/admin/categories/{bundle}

Bulk JSON export and import for the admin «دسته‌بندی‌ها» page (CategoriesPage), backed by App\Category\Controller\CategoryImportController.

{bundle} is one of: provinces, cities, specialties, doctor_services, insurances, tags.

Permission: ROLE_ADMIN (whole controller).

Each bundle maps to one table:

bundle table has slug has weight references
provinces provinces
cities cities province_id → provinces, representation_id → representations
specialties specialties parent_id → specialties (same file)
doctor_services doctor_services specialty_id → specialties
insurances insurances type ∈ {basic,supplementary}
tags tags

GET /api/v1/admin/categories/{bundle}/export

Complete export of a bundle — every row, no pagination cap. Returns the raw table columns (the same keys an import expects), so export → import is lossless.

⚠️ Do not use the paginated admin list endpoints (e.g. /api/v1/admin/specialties?limit=9999) as an export source for import: those cap at 100 rows, and feeding a truncated file into the wipe+replace import below deletes every row beyond the first 100.

Response 200

{
  "success": true,
  "data": [
    { "id": 1, "uuid": "…", "name": "قلب", "slug": "ghalb", "status": 1, "weight": 0, "parent_id": null }
  ]
}

For cities the export rows carry every column plus province_name, and social_media is returned as a JSON object — the exact shape nobat724_front/data/city.json uses, so the export file can be dropped into the frontend (and re-imported) without any transformation.

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_AUTH_006 403 Not admin
ERR_BUNDLE_UNKNOWN 404 Unknown bundle

POST /api/v1/admin/categories/{bundle}/import

Wipe + full replace of the bundle's table from the uploaded JSON, inside a single transaction (FK checks disabled during the swap). Raw id values from the file are preserved so relations stay intact.

Validation is strict and two-phase: the whole file is validated first; if any row is invalid, nothing is written and every error is returned. Unknown extra keys (e.g. province_name produced by export) are ignored.

Request Body (application/json)

A non-empty array of records, or { "items": [ … ] }. Max 5000 rows.

[
  { "id": 1, "name": "قلب",  "slug": "ghalb", "status": 1, "weight": 0, "parent_id": null },
  { "id": 2, "name": "آریتمی", "slug": "arr", "status": 1, "weight": 1, "parent_id": 1 }
]

Per-row validation rules

Field Rule
id required, positive integer, unique within the file
name required, non-empty string ≤ 255
status 0 or 1 (default 1)
weight integer ≥ 0 — bundles with a weight column only
slug required, ≤ 255, unique within file — specialties/doctor_services/tags
type basic or supplementaryinsurances only
uuid optional; generated if missing
parent_id specialties: null, or an id present in the same file
specialty_id doctor_services: null, or an existing specialties.id
province_id / representation_id cities: null, or an existing id in the referenced table
title, site_name, slogan, domain, keywords, description, footer_description, contact_phone, email, logo_url cities: optional SEO/branding strings (title = meta title of the city website)
social_media cities: optional; JSON object (e.g. {"instagram": "…"}) or its string form — stored as JSON

Response 200

{ "success": true, "data": { "imported": 128 } }

Response 422 (validation failed — nothing written)

{
  "success": false,
  "data": null,
  "errors": [
    { "row": 2, "field": "parent_id", "message": "ردیف 2: والد با شناسه 999 در همین فایل وجود ندارد" }
  ]
}

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_AUTH_006 403 Not admin
ERR_BUNDLE_UNKNOWN 404 Unknown bundle
ERR_IMPORT_FORMAT 422 Body is not a non-empty array of records
ERR_IMPORT_TOO_LARGE 422 More than 5000 rows
(field errors) 422 One entry per failed row/field; no rows written
ERR_IMPORT_FAILED 500 DB error during replace (transaction rolled back)

Seeding a from-scratch database

Canonical category data ships in the repo at data/seed/*.json (provinces.json, cities.json, specialties.json, doctor_services.json). After creating an empty schema, populate it with:

ddev exec php bin/console app:seed-categories

The command (App\Category\Command\SeedCategoriesCommand) reads those files and runs the same strict validation + wipe-and-replace as the HTTP import, in dependency order (provinces → cities, specialties → doctor_services). The shared logic lives in App\Category\Service\CategoryImporter.

Full rebuild:

ddev exec php bin/console doctrine:database:drop --force
ddev exec php bin/console doctrine:database:create
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
ddev exec php bin/console app:seed-categories
ddev exec php bin/console app:create-admin 09100000001 'Test@1234'