diff --git a/app/Http/Controllers/Apis/Protected/Main/OAuth2ElectionsApiController.php b/app/Http/Controllers/Apis/Protected/Main/OAuth2ElectionsApiController.php index e42e97351..ed79dcd04 100644 --- a/app/Http/Controllers/Apis/Protected/Main/OAuth2ElectionsApiController.php +++ b/app/Http/Controllers/Apis/Protected/Main/OAuth2ElectionsApiController.php @@ -15,12 +15,15 @@ use App\Models\Foundation\Elections\Election; use App\Models\Foundation\Elections\IElectionsRepository; use App\ModelSerializers\SerializerUtils; +use App\Security\ElectionScopes; use App\Services\Model\IElectionService; use libs\utils\HTMLCleaner; use models\oauth2\IResourceServerContext; use ModelSerializers\SerializerRegistry; use utils\Filter; use utils\PagingInfo; +use OpenApi\Attributes as OA; +use Symfony\Component\HttpFoundation\Response; /** * Class OAuth2ElectionsApiController @@ -50,9 +53,60 @@ public function __construct $this->service = $service; } - /** - * @return mixed - */ + #[OA\Get( + path: "/api/v1/elections", + operationId: "getAllElections", + description: "Get all elections with pagination and filtering", + tags: ["Elections"], + security: [['election_oauth2' => [ElectionScopes::ReadAllElections]]], + parameters: [ + new OA\Parameter( + name: "page", + in: "query", + required: false, + schema: new OA\Schema(type: "integer", default: 1), + description: "Page number" + ), + new OA\Parameter( + name: "per_page", + in: "query", + required: false, + schema: new OA\Schema(type: "integer", default: 20), + description: "Items per page" + ), + new OA\Parameter( + name: "filter", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Filter by name, opens, or closes (epoch format). Example: name[]=Test&opens[from]=1634567890" + ), + new OA\Parameter( + name: "order", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Order by: name, id, opens, or closes. Example: name,-opens" + ), + new OA\Parameter( + name: "expand", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Expand relationships: " + ), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: "Elections list retrieved successfully", + content: new OA\JsonContent(ref: "#/components/schemas/PaginatedElectionsResponse") + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: "Bad Request"), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: "Validation Error"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] public function getAll() { return $this->_getAll( @@ -97,9 +151,30 @@ function ($page, $per_page, $filter, $order, $applyExtraFilters) { ); } - /** - * @return \Illuminate\Http\JsonResponse|mixed - */ + #[OA\Get( + path: "/api/public/v1/elections/current", + operationId: "getCurrentElection", + description: "Get the current active election", + tags: ["Elections (Public)"], + parameters: [ + new OA\Parameter( + name: "expand", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Expand relationships" + ), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: "Current election retrieved successfully", + content: new OA\JsonContent(ref: "#/components/schemas/Election") + ), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: "No current election found"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] public function getCurrent() { return $this->processRequest(function () { @@ -122,10 +197,38 @@ public function getCurrent() }); } - /** - * @param $election_id - * @return mixed - */ + #[OA\Get( + path: "/api/v1/elections/{election_id}", + operationId: "getElectionById", + description: "Get election by ID", + tags: ["Elections"], + security: [['election_oauth2' => [ElectionScopes::ReadAllElections]]], + parameters: [ + new OA\Parameter( + name: "election_id", + in: "path", + required: true, + schema: new OA\Schema(type: "integer", format: "int64"), + description: "Election ID" + ), + new OA\Parameter( + name: "expand", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Expand relationships" + ), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: "Election retrieved successfully", + content: new OA\JsonContent(ref: "#/components/schemas/Election") + ), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: "Election not found"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] public function getById($election_id) { return $this->processRequest(function () use ($election_id) { @@ -148,9 +251,58 @@ public function getById($election_id) }); } - /** - * @return \Illuminate\Http\JsonResponse|mixed - */ + #[OA\Get( + path: "/api/public/v1/elections/current/candidates", + operationId: "getCurrentElectionCandidates", + description: "Get all accepted candidates for the current election. Supports expand parameter to include member and/or election objects", + tags: ["Elections (Public)"], + parameters: [ + new OA\Parameter( + name: "page", + in: "query", + required: false, + schema: new OA\Schema(type: "integer", default: 1), + description: "Page number" + ), + new OA\Parameter( + name: "per_page", + in: "query", + required: false, + schema: new OA\Schema(type: "integer", default: 20), + description: "Items per page" + ), + new OA\Parameter( + name: "filter", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Filter by first_name, last_name, or full_name" + ), + new OA\Parameter( + name: "order", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Order by first_name or last_name" + ), + new OA\Parameter( + name: "expand", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Expand relationships: member, election (comma-separated)" + ), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: "Current election candidates retrieved successfully", + content: new OA\JsonContent(ref: "#/components/schemas/PaginatedCandidatesResponse") + ), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: "No current election found"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] public function getCurrentCandidates() { @@ -199,10 +351,67 @@ function ($page, $per_page, $filter, $order, $applyExtraFilters) use ($election) ); } - /** - * @return \Illuminate\Http\JsonResponse|mixed - */ - public function getElectionCandidates($election_id) + #[OA\Get( + path: "/api/v1/elections/{election_id}/candidates", + operationId: "getElectionCandidates", + description: "Get all accepted candidates for a specific election. Supports expand parameter to include member and/or election objects", + tags: ["Elections"], + security: [['election_oauth2' => [ElectionScopes::ReadAllElections]]], + parameters: [ + new OA\Parameter( + name: "election_id", + in: "path", + required: true, + schema: new OA\Schema(type: "integer", format: "int64"), + description: "Election ID" + ), + new OA\Parameter( + name: "page", + in: "query", + required: false, + schema: new OA\Schema(type: "integer", default: 1), + description: "Page number" + ), + new OA\Parameter( + name: "per_page", + in: "query", + required: false, + schema: new OA\Schema(type: "integer", default: 20), + description: "Items per page" + ), + new OA\Parameter( + name: "filter", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Filter by first_name, last_name, or full_name" + ), + new OA\Parameter( + name: "order", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Order by first_name or last_name" + ), + new OA\Parameter( + name: "expand", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Expand relationships: member, election (comma-separated)" + ), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: "Election candidates retrieved successfully", + content: new OA\JsonContent(ref: "#/components/schemas/PaginatedCandidatesResponse") + ), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: "Election not found"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] + public function getElectionCandidates(int $election_id) { $election = $this->repository->getById(intval($election_id)); @@ -250,9 +459,58 @@ function ($page, $per_page, $filter, $order, $applyExtraFilters) use ($election) ); } - /** - * @return \Illuminate\Http\JsonResponse|mixed - */ + #[OA\Get( + path: "/api/public/v1/elections/current/candidates/gold", + operationId: "getCurrentGoldCandidates", + description: "Get all gold (featured) candidates for the current election. Supports expand parameter to include member and/or election objects", + tags: ["Elections (Public)"], + parameters: [ + new OA\Parameter( + name: "page", + in: "query", + required: false, + schema: new OA\Schema(type: "integer", default: 1), + description: "Page number" + ), + new OA\Parameter( + name: "per_page", + in: "query", + required: false, + schema: new OA\Schema(type: "integer", default: 20), + description: "Items per page" + ), + new OA\Parameter( + name: "filter", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Filter by first_name, last_name, or full_name" + ), + new OA\Parameter( + name: "order", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Order by first_name or last_name" + ), + new OA\Parameter( + name: "expand", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Expand relationships: member, election (comma-separated)" + ), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: "Gold candidates retrieved successfully", + content: new OA\JsonContent(ref: "#/components/schemas/PaginatedCandidatesResponse") + ), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: "No current election found"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] public function getCurrentGoldCandidates() { @@ -301,6 +559,66 @@ function ($page, $per_page, $filter, $order, $applyExtraFilters) use ($election) ); } + #[OA\Get( + path: "/api/v1/elections/{election_id}/candidates/gold", + operationId: "getElectionGoldCandidates", + description: "Get all gold (featured) candidates for a specific election. Supports expand parameter to include member and/or election objects", + tags: ["Elections"], + security: [['election_oauth2' => [ElectionScopes::ReadAllElections]]], + parameters: [ + new OA\Parameter( + name: "election_id", + in: "path", + required: true, + schema: new OA\Schema(type: "integer", format: "int64"), + description: "Election ID" + ), + new OA\Parameter( + name: "page", + in: "query", + required: false, + schema: new OA\Schema(type: "integer", default: 1), + description: "Page number" + ), + new OA\Parameter( + name: "per_page", + in: "query", + required: false, + schema: new OA\Schema(type: "integer", default: 20), + description: "Items per page" + ), + new OA\Parameter( + name: "filter", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Filter by first_name, last_name, or full_name" + ), + new OA\Parameter( + name: "order", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Order by first_name or last_name" + ), + new OA\Parameter( + name: "expand", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Expand relationships: member, election (comma-separated)" + ), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: "Gold candidates retrieved successfully", + content: new OA\JsonContent(ref: "#/components/schemas/PaginatedCandidatesResponse") + ), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: "Election not found"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] public function getElectionGoldCandidates($election_id) { @@ -349,11 +667,29 @@ function ($page, $per_page, $filter, $order, $applyExtraFilters) use ($election) ); } - use GetAndValidateJsonPayload; - - /** - * @return \Illuminate\Http\JsonResponse|mixed - */ + #[OA\Put( + path: "/api/v1/elections/current/candidates/me", + operationId: "updateMyCandidateProfile", + description: "Update current user's candidate profile for the current election", + tags: ["Elections"], + security: [['election_oauth2' => [ElectionScopes::WriteMyCandidateProfile]]], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent(ref: "#/components/schemas/CandidateProfileUpdateRequest") + ), + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: "Candidate profile updated successfully", + content: new OA\JsonContent(ref: "#/components/schemas/Member") + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: "Bad Request"), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: "Forbidden - User not authenticated"), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: "No current election found"), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: "Validation Error"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] public function updateMyCandidateProfile() { return $this->processRequest(function () { @@ -393,11 +729,46 @@ public function updateMyCandidateProfile() }); } - /** - * @param $candidate_id - * @return \Illuminate\Http\JsonResponse|mixed - */ - public function nominateCandidate($candidate_id) + #[OA\Post( + path: "/api/v1/elections/current/candidates/{candidate_id}", + operationId: "nominateCandidate", + description: "Nominate a candidate for the current election. Supports expand parameter to include related objects (election, candidate, nominator)", + tags: ["Elections"], + security: [['election_oauth2' => [ElectionScopes::NominatesCandidates]]], + parameters: [ + new OA\Parameter( + name: "candidate_id", + in: "path", + required: true, + schema: new OA\Schema(type: "integer", format: "int64"), + description: "Candidate ID to nominate" + ), + new OA\Parameter( + name: "expand", + in: "query", + required: false, + schema: new OA\Schema(type: "string"), + description: "Expand relationships: election, candidate, nominator (comma-separated)" + ), + ], + requestBody: new OA\RequestBody( + required: false, + content: new OA\JsonContent(ref: "#/components/schemas/NominationRequest") + ), + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: "Candidate nominated successfully", + content: new OA\JsonContent(ref: "#/components/schemas/Nomination") + ), + new OA\Response(response: Response::HTTP_BAD_REQUEST, description: "Bad Request"), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: "Forbidden - User not authenticated"), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: "Candidate or current election not found"), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: "Validation Error"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] + public function nominateCandidate(int $candidate_id) { return $this->processRequest(function () use ($candidate_id) { $current_member = $this->resource_server_context->getCurrentUser(); @@ -420,4 +791,4 @@ public function nominateCandidate($candidate_id) }); } -} \ No newline at end of file +} diff --git a/app/Swagger/ElectionSchemas.php b/app/Swagger/ElectionSchemas.php new file mode 100644 index 000000000..242774a75 --- /dev/null +++ b/app/Swagger/ElectionSchemas.php @@ -0,0 +1,69 @@ + 'Read All Elections', + ElectionScopes::WriteMyCandidateProfile => 'Write My Candidate Profile', + ElectionScopes::NominatesCandidates => 'Nominate Candidates', + ], + ), + ], + ) +] +class ElectionOAuth2Schema {} diff --git a/app/Swagger/security/SummitBookableRoomsAttributeTypeSecurity.php b/app/Swagger/Security/SummitBookableRoomsAttributeTypeSecurity.php similarity index 95% rename from app/Swagger/security/SummitBookableRoomsAttributeTypeSecurity.php rename to app/Swagger/Security/SummitBookableRoomsAttributeTypeSecurity.php index 8b3ad5e8c..18238d0da 100644 --- a/app/Swagger/security/SummitBookableRoomsAttributeTypeSecurity.php +++ b/app/Swagger/Security/SummitBookableRoomsAttributeTypeSecurity.php @@ -1,11 +1,10 @@ -