Add comprehensive project documentation for ClinicPro in CLAUDE.md and README.md
- Introduced CLAUDE.md for internal guidance on project structure, commands, and architecture. - Created README.md with detailed project overview, technology stack, directory structure, setup instructions, API endpoints, authentication flow, and external services.
This commit is contained in:
@@ -10,12 +10,14 @@ use App\Doctor\Repository\DoctorRepository;
|
||||
use App\Shared\Constant\ErrorCodes;
|
||||
use App\Shared\Controller\BaseController;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
#[OA\Tag(name: 'Appointments')]
|
||||
class AppointmentController extends BaseController
|
||||
{
|
||||
public function __construct(
|
||||
@@ -26,6 +28,78 @@ class AppointmentController extends BaseController
|
||||
|
||||
// ── Public: available slots ───────────────────────────────────────────────
|
||||
|
||||
#[OA\Get(
|
||||
path: '/api/v1/appointment-slots',
|
||||
summary: 'Get available appointment slots for a doctor on a given date',
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'doctor_uuid',
|
||||
in: 'query',
|
||||
required: true,
|
||||
schema: new OA\Schema(type: 'string', format: 'uuid')
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'date',
|
||||
in: 'query',
|
||||
required: true,
|
||||
description: 'Date in Y-m-d format',
|
||||
schema: new OA\Schema(type: 'string', format: 'date', example: '2025-06-15')
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Available slots returned',
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(property: 'success', type: 'boolean', example: true),
|
||||
new OA\Property(
|
||||
property: 'data',
|
||||
properties: [
|
||||
new OA\Property(property: 'doctor_uuid', type: 'string', format: 'uuid'),
|
||||
new OA\Property(property: 'date', type: 'string', format: 'date'),
|
||||
new OA\Property(
|
||||
property: 'slots',
|
||||
type: 'array',
|
||||
items: new OA\Items(
|
||||
properties: [
|
||||
new OA\Property(property: 'start', type: 'integer', description: 'Unix timestamp'),
|
||||
new OA\Property(property: 'end', type: 'integer', description: 'Unix timestamp'),
|
||||
new OA\Property(property: 'available', type: 'boolean'),
|
||||
],
|
||||
type: 'object'
|
||||
)
|
||||
),
|
||||
],
|
||||
type: 'object'
|
||||
),
|
||||
new OA\Property(property: 'errors', type: 'array', items: new OA\Items()),
|
||||
]
|
||||
)
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
description: 'Doctor not found',
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(property: 'success', type: 'boolean', example: false),
|
||||
new OA\Property(
|
||||
property: 'errors',
|
||||
type: 'array',
|
||||
items: new OA\Items(
|
||||
properties: [
|
||||
new OA\Property(property: 'code', type: 'string'),
|
||||
new OA\Property(property: 'message', type: 'string'),
|
||||
],
|
||||
type: 'object'
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
new OA\Response(response: 422, description: 'Invalid date format'),
|
||||
]
|
||||
)]
|
||||
#[Route('/api/v1/appointment-slots', methods: ['GET'])]
|
||||
public function slots(Request $request): JsonResponse
|
||||
{
|
||||
@@ -52,6 +126,40 @@ class AppointmentController extends BaseController
|
||||
|
||||
// ── Authenticated: book / manage ─────────────────────────────────────────
|
||||
|
||||
#[OA\Post(
|
||||
path: '/api/v1/appointment',
|
||||
summary: 'Book a new appointment',
|
||||
security: [['bearerAuth' => []]],
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['doctor_uuid', 'slot_start', 'slot_end'],
|
||||
properties: [
|
||||
new OA\Property(property: 'doctor_uuid', type: 'string', format: 'uuid'),
|
||||
new OA\Property(property: 'slot_start', type: 'integer', description: 'Slot start Unix timestamp'),
|
||||
new OA\Property(property: 'slot_end', type: 'integer', description: 'Slot end Unix timestamp'),
|
||||
new OA\Property(property: 'note', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 201,
|
||||
description: 'Appointment booked successfully',
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(property: 'success', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'data', type: 'object', description: 'Appointment object'),
|
||||
new OA\Property(property: 'errors', type: 'array', items: new OA\Items()),
|
||||
]
|
||||
)
|
||||
),
|
||||
new OA\Response(response: 401, description: 'Unauthenticated'),
|
||||
new OA\Response(response: 404, description: 'Doctor not found'),
|
||||
new OA\Response(response: 409, description: 'Slot already taken'),
|
||||
new OA\Response(response: 422, description: 'Validation error'),
|
||||
]
|
||||
)]
|
||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||
#[Route('/api/v1/appointment', methods: ['POST'])]
|
||||
public function book(Request $request, #[CurrentUser] User $user): JsonResponse
|
||||
@@ -82,6 +190,35 @@ class AppointmentController extends BaseController
|
||||
return $this->success(['data' => $appointment->toArray()], 201);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/api/v1/appointment/{uuid}',
|
||||
summary: 'Get a single appointment by UUID',
|
||||
security: [['bearerAuth' => []]],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: new OA\Schema(type: 'string', format: 'uuid')
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Appointment returned',
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(property: 'success', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'data', type: 'object', description: 'Appointment object'),
|
||||
new OA\Property(property: 'errors', type: 'array', items: new OA\Items()),
|
||||
]
|
||||
)
|
||||
),
|
||||
new OA\Response(response: 401, description: 'Unauthenticated'),
|
||||
new OA\Response(response: 403, description: 'Access denied'),
|
||||
new OA\Response(response: 404, description: 'Appointment not found'),
|
||||
]
|
||||
)]
|
||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||
#[Route('/api/v1/appointment/{uuid}', methods: ['GET'])]
|
||||
public function get(string $uuid, #[CurrentUser] User $user): JsonResponse
|
||||
@@ -98,6 +235,46 @@ class AppointmentController extends BaseController
|
||||
return $this->success(['data' => $appointment->toArray()]);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/api/v1/appointments/doctor/{doctorUuid}',
|
||||
summary: 'List appointments for a specific doctor',
|
||||
security: [['bearerAuth' => []]],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'doctorUuid',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: new OA\Schema(type: 'string', format: 'uuid')
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
required: false,
|
||||
description: 'Filter by appointment status',
|
||||
schema: new OA\Schema(type: 'string', example: 'pending')
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Appointment list returned',
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(property: 'success', type: 'boolean', example: true),
|
||||
new OA\Property(
|
||||
property: 'data',
|
||||
type: 'array',
|
||||
items: new OA\Items(type: 'object', description: 'Appointment object')
|
||||
),
|
||||
new OA\Property(property: 'errors', type: 'array', items: new OA\Items()),
|
||||
]
|
||||
)
|
||||
),
|
||||
new OA\Response(response: 401, description: 'Unauthenticated'),
|
||||
new OA\Response(response: 403, description: 'Access denied'),
|
||||
new OA\Response(response: 404, description: 'Doctor not found'),
|
||||
]
|
||||
)]
|
||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||
#[Route('/api/v1/appointments/doctor/{doctorUuid}', methods: ['GET'])]
|
||||
public function listByDoctor(string $doctorUuid, Request $request, #[CurrentUser] User $user): JsonResponse
|
||||
@@ -117,6 +294,38 @@ class AppointmentController extends BaseController
|
||||
return $this->success(['data' => array_map(fn(Appointment $a) => $a->toArray(), $appointments)]);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/api/v1/appointments/user',
|
||||
summary: 'List appointments for the authenticated user',
|
||||
security: [['bearerAuth' => []]],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
required: false,
|
||||
description: 'Filter by appointment status',
|
||||
schema: new OA\Schema(type: 'string', example: 'pending')
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Appointment list returned',
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(property: 'success', type: 'boolean', example: true),
|
||||
new OA\Property(
|
||||
property: 'data',
|
||||
type: 'array',
|
||||
items: new OA\Items(type: 'object', description: 'Appointment object')
|
||||
),
|
||||
new OA\Property(property: 'errors', type: 'array', items: new OA\Items()),
|
||||
]
|
||||
)
|
||||
),
|
||||
new OA\Response(response: 401, description: 'Unauthenticated'),
|
||||
]
|
||||
)]
|
||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||
#[Route('/api/v1/appointments/user', methods: ['GET'])]
|
||||
public function listByUser(Request $request, #[CurrentUser] User $user): JsonResponse
|
||||
@@ -141,6 +350,47 @@ class AppointmentController extends BaseController
|
||||
|| $user->hasRole('ROLE_ADMIN');
|
||||
}
|
||||
|
||||
#[OA\Patch(
|
||||
path: '/api/v1/appointment/{uuid}/status',
|
||||
summary: 'Update the status of an appointment',
|
||||
security: [['bearerAuth' => []]],
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['status'],
|
||||
properties: [
|
||||
new OA\Property(property: 'status', type: 'string', example: 'confirmed'),
|
||||
new OA\Property(property: 'version', type: 'integer', description: 'Optimistic lock version'),
|
||||
]
|
||||
)
|
||||
),
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: new OA\Schema(type: 'string', format: 'uuid')
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Appointment status updated',
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(property: 'success', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'data', type: 'object', description: 'Updated appointment object'),
|
||||
new OA\Property(property: 'errors', type: 'array', items: new OA\Items()),
|
||||
]
|
||||
)
|
||||
),
|
||||
new OA\Response(response: 401, description: 'Unauthenticated'),
|
||||
new OA\Response(response: 403, description: 'Access denied'),
|
||||
new OA\Response(response: 404, description: 'Appointment not found'),
|
||||
new OA\Response(response: 409, description: 'Optimistic lock conflict'),
|
||||
new OA\Response(response: 422, description: 'Invalid status transition'),
|
||||
]
|
||||
)]
|
||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||
#[Route('/api/v1/appointment/{uuid}/status', methods: ['PATCH'])]
|
||||
public function updateStatus(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
|
||||
|
||||
Reference in New Issue
Block a user