--- name: add-admin-endpoint description: Add a new paginated admin API endpoint to AdminApiController. Use when the user wants to add a backend admin list, stats, or action endpoint — things like "add an endpoint for X", "create an admin API for Y", "I need a route that lists Z". --- ## Target file `src/Admin/Controller/AdminApiController.php` All admin endpoints live here. The class already has `#[IsGranted('ROLE_ADMIN')]` and injects `EntityManagerInterface $em`. ## Checklist 1. **Stats endpoint** (optional but standard): a separate `#[Route('/api/v1/admin/{entity}/stats')]` method that returns counts via raw SQL (`$this->em->getConnection()->fetchOne()`). Return with `$this->success([...])`. 2. **List endpoint**: use QueryBuilder with `->getArrayResult()` — never load full entities for list queries (entity getters may not exist for all fields). Pattern: ```php #[Route('/api/v1/admin/{entities}', methods: ['GET'])] public function list{Entity}(Request $request): JsonResponse { $page = max(1, (int) $request->query->get('page', 1)); $limit = min(100, max(1, (int) $request->query->get('limit', 15))); $search = trim((string) $request->query->get('search', '')); $qb = $this->em->createQueryBuilder() ->select('e.id, e.uuid, e.someField, e.createdAt') ->from(SomeEntity::class, 'e'); if ($search !== '') { $qb->andWhere('e.name LIKE :s')->setParameter('s', "%$search%"); } $total = (clone $qb)->select('COUNT(e.id)')->getQuery()->getSingleScalarResult(); $items = $qb ->orderBy('e.id', 'DESC') ->setFirstResult(($page - 1) * $limit) ->setMaxResults($limit) ->getQuery() ->getArrayResult(); return $this->paginated($items, (int) $total, $page, $limit); } ``` 3. **Action endpoints** (toggle status, etc.) follow this shape: ```php #[Route('/api/v1/admin/{entities}/{uuid}/status', methods: ['POST'])] public function toggle{Entity}Status(string $uuid): JsonResponse { $entity = $this->em->getRepository(SomeEntity::class)->findOneBy(['uuid' => $uuid]); if (!$entity) return $this->error('NOT_FOUND', 'Entity not found', 404); $entity->setIsActive(!$entity->getIsActive()); $this->em->flush(); return $this->success(['is_active' => $entity->getIsActive()]); } ``` ## Critical rules - **Always use `getArrayResult()`** for list queries. Never call entity getters inside admin list methods. - `createdAt` and `updatedAt` are Unix integer timestamps — do not format them in PHP, let the frontend handle it. - For JOINs to categories (city, state, specialty), use LEFT JOIN in DQL and select the name field directly into the array result. - Response shape for lists: `$this->paginated($items, $total, $page, $limit)` — frontend reads `data?.data` for items and `data?.meta?.totalRecords` for count. - Add `use` imports for any new entity class at the top of the file. - Run `/sync-db` only if a new entity or column was added as part of this change.