Release 2.8.0 (#4508)

This commit is contained in:
Kevin Papst
2024-01-03 00:28:35 +01:00
committed by GitHub
parent d8f89d9f3d
commit 8eeb9c120a
27 changed files with 409 additions and 407 deletions

View File

@@ -15,14 +15,13 @@ use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\Timesheet;
use App\Event\PageActionsEvent;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security as ApiSecurity;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -73,7 +72,7 @@ final class ActionsController extends BaseApiController
#[OA\Parameter(name: 'id', in: 'path', description: 'Timesheet ID to fetch', required: true)]
#[OA\Parameter(name: 'view', in: 'path', description: 'View to display the actions at (e.g. index, custom)', required: true)]
#[OA\Parameter(name: 'locale', in: 'path', description: 'Language to translate the action title to (e.g. de, en)', required: true)]
#[Rest\Get(path: '/timesheet/{id}/{view}/{locale}', name: 'get_timesheet_actions', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/timesheet/{id}/{view}/{locale}', name: 'get_timesheet_actions', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getTimesheetActions(Timesheet $timesheet, string $view, string $locale): Response
@@ -93,7 +92,7 @@ final class ActionsController extends BaseApiController
#[OA\Parameter(name: 'id', in: 'path', description: 'Activity ID to fetch', required: true)]
#[OA\Parameter(name: 'view', in: 'path', description: 'View to display the actions at (e.g. index, custom)', required: true)]
#[OA\Parameter(name: 'locale', in: 'path', description: 'Language to translate the action title to (e.g. de, en)', required: true)]
#[Rest\Get(path: '/activity/{id}/{view}/{locale}', name: 'get_activity_actions', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/activity/{id}/{view}/{locale}', name: 'get_activity_actions', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getActivityActions(Activity $activity, string $view, string $locale): Response
@@ -113,7 +112,7 @@ final class ActionsController extends BaseApiController
#[OA\Parameter(name: 'id', in: 'path', description: 'Project ID to fetch', required: true)]
#[OA\Parameter(name: 'view', in: 'path', description: 'View to display the actions at (e.g. index, custom)', required: true)]
#[OA\Parameter(name: 'locale', in: 'path', description: 'Language to translate the action title to (e.g. de, en)', required: true)]
#[Rest\Get(path: '/project/{id}/{view}/{locale}', name: 'get_project_actions', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/project/{id}/{view}/{locale}', name: 'get_project_actions', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getProjectActions(Project $project, string $view, string $locale): Response
@@ -133,7 +132,7 @@ final class ActionsController extends BaseApiController
#[OA\Parameter(name: 'id', in: 'path', description: 'Customer ID to fetch', required: true)]
#[OA\Parameter(name: 'view', in: 'path', description: 'View to display the actions at (e.g. index, custom)', required: true)]
#[OA\Parameter(name: 'locale', in: 'path', description: 'Language to translate the action title to (e.g. de, en)', required: true)]
#[Rest\Get(path: '/customer/{id}/{view}/{locale}', name: 'get_customer_actions', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/customer/{id}/{view}/{locale}', name: 'get_customer_actions', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getCustomerActions(Customer $customer, string $view, string $locale): Response

View File

@@ -30,7 +30,7 @@ use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route(path: '/activities')]
@@ -55,7 +55,7 @@ final class ActivityController extends BaseApiController
* Returns a collection of activities (which are visible to the user)
*/
#[OA\Response(response: 200, description: 'Returns a collection of activities', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/ActivityCollection')))]
#[Rest\Get(path: '', name: 'get_activities')]
#[Route(methods: ['GET'], path: '', name: 'get_activities')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\QueryParam(name: 'project', requirements: '\d+', strict: true, nullable: true, description: 'Project ID to filter activities')]
@@ -125,7 +125,7 @@ final class ActivityController extends BaseApiController
*/
#[OA\Response(response: 200, description: 'Returns one activity entity', content: new OA\JsonContent(ref: '#/components/schemas/ActivityEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Activity ID to fetch', required: true)]
#[Rest\Get(path: '/{id}', name: 'get_activity', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/{id}', name: 'get_activity', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getAction(Activity $activity): Response
@@ -141,7 +141,7 @@ final class ActivityController extends BaseApiController
*/
#[OA\Post(description: 'Creates a new activity and returns it afterwards', responses: [new OA\Response(response: 200, description: 'Returns the new created activity', content: new OA\JsonContent(ref: '#/components/schemas/ActivityEntity'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/ActivityEditForm'))]
#[Rest\Post(path: '', name: 'post_activity')]
#[Route(methods: ['POST'], path: '', name: 'post_activity')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postAction(Request $request): Response
@@ -184,7 +184,7 @@ final class ActivityController extends BaseApiController
#[OA\Patch(description: 'Update an existing activity, you can pass all or just a subset of all attributes', responses: [new OA\Response(response: 200, description: 'Returns the updated activity', content: new OA\JsonContent(ref: '#/components/schemas/ActivityEntity'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/ActivityEditForm'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Activity ID to update', required: true)]
#[Rest\Patch(path: '/{id}', name: 'patch_activity', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}', name: 'patch_activity', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function patchAction(Request $request, Activity $activity): Response
@@ -221,7 +221,7 @@ final class ActivityController extends BaseApiController
#[IsGranted('edit', 'activity')]
#[OA\Response(response: 200, description: 'Sets the value of an existing/configured meta-field. You cannot create unknown meta-fields, if the given name is not a configured meta-field, this will return an exception.', content: new OA\JsonContent(ref: '#/components/schemas/ActivityEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Activity record ID to set the meta-field value for', required: true)]
#[Rest\Patch(path: '/{id}/meta', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}/meta', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\RequestParam(name: 'name', strict: true, nullable: false, description: 'The meta-field name')]
@@ -254,7 +254,7 @@ final class ActivityController extends BaseApiController
#[IsGranted('edit', 'activity')]
#[OA\Response(response: 200, description: 'Returns a collection of activity rate entities', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/ActivityRate')))]
#[OA\Parameter(name: 'id', in: 'path', description: 'The activity whose rates will be returned', required: true)]
#[Rest\Get(path: '/{id}/rates', name: 'get_activity_rates', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/{id}/rates', name: 'get_activity_rates', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getRatesAction(Activity $activity): Response
@@ -276,7 +276,7 @@ final class ActivityController extends BaseApiController
#[OA\Parameter(name: 'rateId', in: 'path', description: 'The rate to remove', required: true)]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\Delete(path: '/{id}/rates/{rateId}', name: 'delete_activity_rate', requirements: ['id' => '\d+', 'rateId' => '\d+'])]
#[Route(methods: ['DELETE'], path: '/{id}/rates/{rateId}', name: 'delete_activity_rate', requirements: ['id' => '\d+', 'rateId' => '\d+'])]
public function deleteRateAction(Activity $activity, #[MapEntity(mapping: ['rateId' => 'id'])] ActivityRate $rate): Response
{
if ($rate->getActivity() !== $activity) {
@@ -297,7 +297,7 @@ final class ActivityController extends BaseApiController
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Returns the new created rate', content: new OA\JsonContent(ref: '#/components/schemas/ActivityRate'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The activity to add the rate for', required: true)]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/ActivityRateForm'))]
#[Rest\Post(path: '/{id}/rates', name: 'post_activity_rate', requirements: ['id' => '\d+'])]
#[Route(methods: ['POST'], path: '/{id}/rates', name: 'post_activity_rate', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postRateAction(Activity $activity, Request $request): Response

View File

@@ -11,13 +11,13 @@ namespace App\API;
use App\API\Model\TimesheetConfig;
use App\Configuration\SystemConfiguration;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security as ApiSecurity;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('IS_AUTHENTICATED_REMEMBERED')]
@@ -32,7 +32,7 @@ final class ConfigurationController extends BaseApiController
* Returns the timesheet configuration
*/
#[OA\Response(response: 200, description: 'Returns the instance specific timesheet configuration', content: new OA\JsonContent(ref: new Model(type: TimesheetConfig::class)))]
#[Rest\Get(path: '/config/timesheet')]
#[Route(methods: ['GET'], path: '/config/timesheet')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function timesheetConfigAction(SystemConfiguration $configuration): Response

View File

@@ -30,7 +30,7 @@ use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route(path: '/customers')]
@@ -55,7 +55,7 @@ final class CustomerController extends BaseApiController
* Returns a collection of customers (which are visible to the user)
*/
#[OA\Response(response: 200, description: 'Returns a collection of customers', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/CustomerCollection')))]
#[Rest\Get(path: '', name: 'get_customers')]
#[Route(methods: ['GET'], path: '', name: 'get_customers')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\QueryParam(name: 'visible', requirements: '1|2|3', default: 1, strict: true, nullable: true, description: 'Visibility status to filter customers: 1=visible, 2=hidden, 3=both')]
@@ -102,7 +102,7 @@ final class CustomerController extends BaseApiController
* Returns one customer
*/
#[OA\Response(response: 200, description: 'Returns one customer entity', content: new OA\JsonContent(ref: '#/components/schemas/CustomerEntity'))]
#[Rest\Get(path: '/{id}', name: 'get_customer', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/{id}', name: 'get_customer', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getAction(Customer $customer): Response
@@ -118,7 +118,7 @@ final class CustomerController extends BaseApiController
*/
#[OA\Post(description: 'Creates a new customer and returns it afterwards', responses: [new OA\Response(response: 200, description: 'Returns the new created customer', content: new OA\JsonContent(ref: '#/components/schemas/CustomerEntity'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/CustomerEditForm'))]
#[Rest\Post(path: '', name: 'post_customer')]
#[Route(methods: ['POST'], path: '', name: 'post_customer')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postAction(Request $request, CustomerService $customerService): Response
@@ -161,7 +161,7 @@ final class CustomerController extends BaseApiController
#[OA\Patch(description: 'Update an existing customer, you can pass all or just a subset of all attributes', responses: [new OA\Response(response: 200, description: 'Returns the updated customer', content: new OA\JsonContent(ref: '#/components/schemas/CustomerEntity'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/CustomerEditForm'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Customer ID to update', required: true)]
#[Rest\Patch(path: '/{id}', name: 'patch_customer', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}', name: 'patch_customer', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function patchAction(Request $request, Customer $customer): Response
@@ -198,7 +198,7 @@ final class CustomerController extends BaseApiController
#[IsGranted('edit', 'customer')]
#[OA\Response(response: 200, description: 'Sets the value of an existing/configured meta-field. You cannot create unknown meta-fields, if the given name is not a configured meta-field, this will return an exception.', content: new OA\JsonContent(ref: '#/components/schemas/CustomerEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Customer record ID to set the meta-field value for', required: true)]
#[Rest\Patch(path: '/{id}/meta', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}/meta', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\RequestParam(name: 'name', strict: true, nullable: false, description: 'The meta-field name')]
@@ -231,7 +231,7 @@ final class CustomerController extends BaseApiController
#[IsGranted('edit', 'customer')]
#[OA\Response(response: 200, description: 'Returns a collection of customer rate entities', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/CustomerRate')))]
#[OA\Parameter(name: 'id', in: 'path', description: 'The customer whose rates will be returned', required: true)]
#[Rest\Get(path: '/{id}/rates', name: 'get_customer_rates', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/{id}/rates', name: 'get_customer_rates', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getRatesAction(Customer $customer): Response
@@ -253,7 +253,7 @@ final class CustomerController extends BaseApiController
#[OA\Parameter(name: 'rateId', in: 'path', description: 'The rate to remove', required: true)]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\Delete(path: '/{id}/rates/{rateId}', name: 'delete_customer_rate', requirements: ['id' => '\d+', 'rateId' => '\d+'])]
#[Route(methods: ['DELETE'], path: '/{id}/rates/{rateId}', name: 'delete_customer_rate', requirements: ['id' => '\d+', 'rateId' => '\d+'])]
public function deleteRateAction(Customer $customer, #[MapEntity(mapping: ['rateId' => 'id'])] CustomerRate $rate): Response
{
if ($rate->getCustomer() !== $customer) {
@@ -274,7 +274,7 @@ final class CustomerController extends BaseApiController
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Returns the new created rate', content: new OA\JsonContent(ref: '#/components/schemas/CustomerRate'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The customer to add the rate for', required: true)]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/CustomerRateForm'))]
#[Rest\Post(path: '/{id}/rates', name: 'post_customer_rate', requirements: ['id' => '\d+'])]
#[Route(methods: ['POST'], path: '/{id}/rates', name: 'post_customer_rate', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postRateAction(Customer $customer, Request $request): Response

View File

@@ -48,15 +48,15 @@ final class CalendarEvent
*/
#[Serializer\Expose]
#[Serializer\Groups(['Default'])]
#[Serializer\Type(name: 'DateTime')]
private \DateTimeInterface $start; // @phpstan-ignore-line
#[Serializer\Type(name: 'DateTimeImmutable')]
private \DateTimeImmutable $start; // @phpstan-ignore-line
/**
* Calendar entry end date
*/
#[Serializer\Expose]
#[Serializer\Groups(['Default'])]
#[Serializer\Type(name: 'DateTime')]
private \DateTimeInterface $end; // @phpstan-ignore-line
#[Serializer\Type(name: 'DateTimeImmutable')]
private \DateTimeImmutable $end; // @phpstan-ignore-line
public function setTitle(string $title): void
{
@@ -65,12 +65,12 @@ final class CalendarEvent
public function setStart(\DateTimeInterface $start): void
{
$this->start = $start;
$this->start = \DateTimeImmutable::createFromInterface($start);
}
public function setEnd(\DateTimeInterface $end): void
{
$this->end = $end;
$this->end = \DateTimeImmutable::createFromInterface($end);
}
public function setColor(?string $color): void

View File

@@ -31,7 +31,7 @@ use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Constraints;
@@ -58,7 +58,7 @@ final class ProjectController extends BaseApiController
* Returns a collection of projects (which are visible to the user)
*/
#[OA\Response(response: 200, description: 'Returns a collection of projects', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/ProjectCollection')))]
#[Rest\Get(path: '', name: 'get_projects')]
#[Route(methods: ['GET'], path: '', name: 'get_projects')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\QueryParam(name: 'customer', requirements: '\d+', strict: true, nullable: true, description: 'Customer ID to filter projects')]
@@ -156,7 +156,7 @@ final class ProjectController extends BaseApiController
* Returns one project
*/
#[OA\Response(response: 200, description: 'Returns one project entity', content: new OA\JsonContent(ref: '#/components/schemas/ProjectEntity'))]
#[Rest\Get(path: '/{id}', name: 'get_project', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/{id}', name: 'get_project', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getAction(Project $project): Response
@@ -172,7 +172,7 @@ final class ProjectController extends BaseApiController
*/
#[OA\Post(description: 'Creates a new project and returns it afterwards', responses: [new OA\Response(response: 200, description: 'Returns the new created project', content: new OA\JsonContent(ref: '#/components/schemas/ProjectEntity'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/ProjectEditForm'))]
#[Rest\Post(path: '', name: 'post_project')]
#[Route(methods: ['POST'], path: '', name: 'post_project')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postAction(Request $request): Response
@@ -214,7 +214,7 @@ final class ProjectController extends BaseApiController
#[OA\Patch(description: 'Update an existing project, you can pass all or just a subset of all attributes', responses: [new OA\Response(response: 200, description: 'Returns the updated project', content: new OA\JsonContent(ref: '#/components/schemas/ProjectEntity'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/ProjectEditForm'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Project ID to update', required: true)]
#[Rest\Patch(path: '/{id}', name: 'patch_project', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}', name: 'patch_project', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function patchAction(Request $request, Project $project): Response
@@ -253,7 +253,7 @@ final class ProjectController extends BaseApiController
#[IsGranted('edit', 'project')]
#[OA\Response(response: 200, description: 'Sets the value of an existing/configured meta-field. You cannot create unknown meta-fields, if the given name is not a configured meta-field, this will return an exception.', content: new OA\JsonContent(ref: '#/components/schemas/ProjectEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Project record ID to set the meta-field value for', required: true)]
#[Rest\Patch(path: '/{id}/meta', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}/meta', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\RequestParam(name: 'name', strict: true, nullable: false, description: 'The meta-field name')]
@@ -286,7 +286,7 @@ final class ProjectController extends BaseApiController
#[IsGranted('edit', 'project')]
#[OA\Response(response: 200, description: 'Returns a collection of project rate entities', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/ProjectRate')))]
#[OA\Parameter(name: 'id', in: 'path', description: 'The project whose rates will be returned', required: true)]
#[Rest\Get(path: '/{id}/rates', name: 'get_project_rates', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/{id}/rates', name: 'get_project_rates', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getRatesAction(Project $project): Response
@@ -308,7 +308,7 @@ final class ProjectController extends BaseApiController
#[OA\Parameter(name: 'rateId', in: 'path', description: 'The rate to remove', required: true)]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\Delete(path: '/{id}/rates/{rateId}', name: 'delete_project_rate', requirements: ['id' => '\d+', 'rateId' => '\d+'])]
#[Route(methods: ['DELETE'], path: '/{id}/rates/{rateId}', name: 'delete_project_rate', requirements: ['id' => '\d+', 'rateId' => '\d+'])]
public function deleteRateAction(Project $project, #[MapEntity(mapping: ['rateId' => 'id'])] ProjectRate $rate): Response
{
if ($rate->getProject() !== $project) {
@@ -329,7 +329,7 @@ final class ProjectController extends BaseApiController
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Returns the new created rate', content: new OA\JsonContent(ref: '#/components/schemas/ProjectRate'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The project to add the rate for', required: true)]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/ProjectRateForm'))]
#[Rest\Post(path: '/{id}/rates', name: 'post_project_rate', requirements: ['id' => '\d+'])]
#[Route(methods: ['POST'], path: '/{id}/rates', name: 'post_project_rate', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postRateAction(Project $project, Request $request): Response

View File

@@ -12,13 +12,13 @@ namespace App\API;
use App\API\Model\Plugin;
use App\API\Model\Version;
use App\Plugin\PluginManager;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security as ApiSecurity;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('IS_AUTHENTICATED_REMEMBERED')]
@@ -33,7 +33,7 @@ final class StatusController extends BaseApiController
* A testing route for the API
*/
#[OA\Response(response: 200, description: "A simple route that returns a 'pong', which you can use for testing the API", content: new OA\JsonContent(example: "{'message': 'pong'}"))]
#[Rest\Get(path: '/ping')]
#[Route(methods: ['GET'], path: '/ping')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function pingAction(): Response
@@ -47,7 +47,7 @@ final class StatusController extends BaseApiController
* Returns information about the Kimai release
*/
#[OA\Response(response: 200, description: 'Returns version information about the current release', content: new OA\JsonContent(ref: new Model(type: Version::class)))]
#[Rest\Get(path: '/version')]
#[Route(methods: ['GET'], path: '/version')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function versionAction(): Response
@@ -59,7 +59,7 @@ final class StatusController extends BaseApiController
* Returns information about installed Plugins
*/
#[OA\Response(response: 200, description: 'Returns a list of plugin names and versions', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: new Model(type: Plugin::class))))]
#[Rest\Get(path: '/plugins')]
#[Route(methods: ['GET'], path: '/plugins')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function pluginAction(PluginManager $pluginManager): Response

View File

@@ -20,7 +20,7 @@ use Nelmio\ApiDocBundle\Annotation\Security as ApiSecurity;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route(path: '/tags')]
@@ -40,7 +40,7 @@ final class TagController extends BaseApiController
* Fetch all existing tags
*/
#[OA\Response(response: 200, description: 'Returns the collection of all existing tags as string array', content: new OA\JsonContent(type: 'array', items: new OA\Items(type: 'string')))]
#[Rest\Get(name: 'get_tags')]
#[Route(methods: ['GET'], name: 'get_tags')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\QueryParam(name: 'name', strict: true, nullable: true, description: 'Search term to filter tag list')]
@@ -61,7 +61,7 @@ final class TagController extends BaseApiController
*/
#[OA\Post(description: 'Creates a new tag and returns it afterwards', responses: [new OA\Response(response: 200, description: 'Returns the new created tag', content: new OA\JsonContent(ref: '#/components/schemas/TagEntity'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/TagEditForm'))]
#[Rest\Post(name: 'post_tag')]
#[Route(methods: ['POST'], name: 'post_tag')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postAction(Request $request): Response
@@ -99,7 +99,7 @@ final class TagController extends BaseApiController
#[OA\Parameter(name: 'id', in: 'path', description: 'Tag ID to delete', required: true)]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\Delete(path: '/{id}', name: 'delete_tag')]
#[Route(methods: ['DELETE'], path: '/{id}', name: 'delete_tag')]
public function deleteAction(Tag $tag): Response
{
$this->repository->deleteTag($tag);

View File

@@ -19,7 +19,6 @@ use App\Repository\ActivityRepository;
use App\Repository\CustomerRepository;
use App\Repository\ProjectRepository;
use App\Repository\TeamRepository;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Nelmio\ApiDocBundle\Annotation\Security as ApiSecurity;
@@ -28,7 +27,7 @@ use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route(path: '/teams')]
@@ -49,7 +48,7 @@ final class TeamController extends BaseApiController
*/
#[IsGranted('view_team')]
#[OA\Response(response: 200, description: 'Returns the collection of teams', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/TeamCollection')))]
#[Rest\Get(path: '', name: 'get_teams')]
#[Route(methods: ['GET'], path: '', name: 'get_teams')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function cgetAction(): Response
@@ -67,7 +66,7 @@ final class TeamController extends BaseApiController
*/
#[IsGranted('view_team')]
#[OA\Response(response: 200, description: 'Returns one team entity', content: new OA\JsonContent(ref: '#/components/schemas/Team'))]
#[Rest\Get(path: '/{id}', name: 'get_team', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/{id}', name: 'get_team', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getAction(Team $team): Response
@@ -86,7 +85,7 @@ final class TeamController extends BaseApiController
#[OA\Parameter(name: 'id', in: 'path', description: 'Team ID to delete', required: true)]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\Delete(path: '/{id}', name: 'delete_team', requirements: ['id' => '\d+'])]
#[Route(methods: ['DELETE'], path: '/{id}', name: 'delete_team', requirements: ['id' => '\d+'])]
public function deleteAction(Team $team): Response
{
$this->repository->deleteTeam($team);
@@ -102,7 +101,7 @@ final class TeamController extends BaseApiController
#[IsGranted('create_team')]
#[OA\Post(description: 'Creates a new team and returns it afterwards', responses: [new OA\Response(response: 200, description: 'Returns the new created team', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/TeamEditForm'))]
#[Rest\Post(path: '', name: 'post_team')]
#[Route(methods: ['POST'], path: '', name: 'post_team')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postAction(Request $request): Response
@@ -134,7 +133,7 @@ final class TeamController extends BaseApiController
#[OA\Patch(description: 'Update an existing team, you can pass all or just a subset of all attributes (passing members will replace all existing ones)', responses: [new OA\Response(response: 200, description: 'Returns the updated team', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/TeamEditForm'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Team ID to update', required: true)]
#[Rest\Patch(path: '/{id}', name: 'patch_team', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}', name: 'patch_team', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function patchAction(Request $request, Team $team): Response
@@ -174,7 +173,7 @@ final class TeamController extends BaseApiController
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Adds a new user to a team.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team which will receive the new member', required: true)]
#[OA\Parameter(name: 'userId', in: 'path', description: 'The team member to add (User ID)', required: true)]
#[Rest\Post(path: '/{id}/members/{userId}', name: 'post_team_member', requirements: ['id' => '\d+', 'userId' => '\d+'])]
#[Route(methods: ['POST'], path: '/{id}/members/{userId}', name: 'post_team_member', requirements: ['id' => '\d+', 'userId' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postMemberAction(Team $team, #[MapEntity(mapping: ['userId' => 'id'])] User $member): Response
@@ -202,7 +201,7 @@ final class TeamController extends BaseApiController
#[OA\Parameter(name: 'userId', in: 'path', description: 'The team member to remove (User ID)', required: true)]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\Delete(path: '/{id}/members/{userId}', name: 'delete_team_member', requirements: ['id' => '\d+', 'userId' => '\d+'])]
#[Route(methods: ['DELETE'], path: '/{id}/members/{userId}', name: 'delete_team_member', requirements: ['id' => '\d+', 'userId' => '\d+'])]
public function deleteMemberAction(Team $team, #[MapEntity(mapping: ['userId' => 'id'])] User $member): Response
{
if (!$member->isInTeam($team)) {
@@ -230,7 +229,7 @@ final class TeamController extends BaseApiController
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Adds a new customer to a team.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team that is granted access', required: true)]
#[OA\Parameter(name: 'customerId', in: 'path', description: 'The customer to grant acecess to (Customer ID)', required: true)]
#[Rest\Post(path: '/{id}/customers/{customerId}', name: 'post_team_customer', requirements: ['id' => '\d+', 'customerId' => '\d+'])]
#[Route(methods: ['POST'], path: '/{id}/customers/{customerId}', name: 'post_team_customer', requirements: ['id' => '\d+', 'customerId' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postCustomerAction(Team $team, #[MapEntity(mapping: ['customerId' => 'id'])] Customer $customer, CustomerRepository $customerRepository): Response
@@ -257,7 +256,7 @@ final class TeamController extends BaseApiController
#[OA\Parameter(name: 'customerId', in: 'path', description: 'The customer to remove (Customer ID)', required: true)]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\Delete(path: '/{id}/customers/{customerId}', name: 'delete_team_customer', requirements: ['id' => '\d+', 'customerId' => '\d+'])]
#[Route(methods: ['DELETE'], path: '/{id}/customers/{customerId}', name: 'delete_team_customer', requirements: ['id' => '\d+', 'customerId' => '\d+'])]
public function deleteCustomerAction(Team $team, #[MapEntity(mapping: ['customerId' => 'id'])] Customer $customer, CustomerRepository $customerRepository): Response
{
if (!$team->hasCustomer($customer)) {
@@ -280,7 +279,7 @@ final class TeamController extends BaseApiController
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Adds a new project to a team.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team that is granted access', required: true)]
#[OA\Parameter(name: 'projectId', in: 'path', description: 'The project to grant acecess to (Project ID)', required: true)]
#[Rest\Post(path: '/{id}/projects/{projectId}', name: 'post_team_project', requirements: ['id' => '\d+', 'projectId' => '\d+'])]
#[Route(methods: ['POST'], path: '/{id}/projects/{projectId}', name: 'post_team_project', requirements: ['id' => '\d+', 'projectId' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postProjectAction(Team $team, #[MapEntity(mapping: ['projectId' => 'id'])] Project $project, ProjectRepository $projectRepository): Response
@@ -307,7 +306,7 @@ final class TeamController extends BaseApiController
#[OA\Parameter(name: 'projectId', in: 'path', description: 'The project to remove (Project ID)', required: true)]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\Delete(path: '/{id}/projects/{projectId}', name: 'delete_team_project', requirements: ['id' => '\d+', 'projectId' => '\d+'])]
#[Route(methods: ['DELETE'], path: '/{id}/projects/{projectId}', name: 'delete_team_project', requirements: ['id' => '\d+', 'projectId' => '\d+'])]
public function deleteProjectAction(Team $team, #[MapEntity(mapping: ['projectId' => 'id'])] Project $project, ProjectRepository $projectRepository): Response
{
if (!$team->hasProject($project)) {
@@ -330,7 +329,7 @@ final class TeamController extends BaseApiController
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Adds a new activity to a team.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team that is granted access', required: true)]
#[OA\Parameter(name: 'activityId', in: 'path', description: 'The activity to grant acecess to (Activity ID)', required: true)]
#[Rest\Post(path: '/{id}/activities/{activityId}', name: 'post_team_activity', requirements: ['id' => '\d+', 'activityId' => '\d+'])]
#[Route(methods: ['POST'], path: '/{id}/activities/{activityId}', name: 'post_team_activity', requirements: ['id' => '\d+', 'activityId' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postActivityAction(Team $team, #[MapEntity(mapping: ['activityId' => 'id'])] Activity $activity, ActivityRepository $activityRepository): Response
@@ -357,7 +356,7 @@ final class TeamController extends BaseApiController
#[OA\Parameter(name: 'activityId', in: 'path', description: 'The activity to remove (Activity ID)', required: true)]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\Delete(path: '/{id}/activities/{activityId}', name: 'delete_team_activity', requirements: ['id' => '\d+', 'activityId' => '\d+'])]
#[Route(methods: ['DELETE'], path: '/{id}/activities/{activityId}', name: 'delete_team_activity', requirements: ['id' => '\d+', 'activityId' => '\d+'])]
public function deleteActivityAction(Team $team, #[MapEntity(mapping: ['activityId' => 'id'])] Activity $activity, ActivityRepository $activityRepository): Response
{
if (!$team->hasActivity($activity)) {

View File

@@ -38,7 +38,7 @@ use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Constraints;
@@ -72,7 +72,7 @@ final class TimesheetController extends BaseApiController
*/
#[IsGranted(new Expression("is_granted('view_own_timesheet') or is_granted('view_other_timesheet')"))]
#[OA\Response(response: 200, description: 'Returns a collection of timesheet records. The datetime fields are given in the users local time including the timezone offset (ISO-8601).', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/TimesheetCollection')))]
#[Rest\Get(path: '', name: 'get_timesheets')]
#[Route(methods: ['GET'], path: '', name: 'get_timesheets')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\QueryParam(name: 'user', requirements: '\d+|all', strict: true, nullable: true, description: "User ID to filter timesheets. Needs permission 'view_other_timesheet', pass 'all' to fetch data for all user (default: current user)")]
@@ -273,7 +273,7 @@ final class TimesheetController extends BaseApiController
#[IsGranted('view', 'timesheet')]
#[OA\Response(response: 200, description: 'Returns one timesheet record. Be aware that the datetime fields are given in the users local time including the timezone offset via ISO 8601.', content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Timesheet record ID to fetch', required: true)]
#[Rest\Get(path: '/{id}', name: 'get_timesheet', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/{id}', name: 'get_timesheet', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getAction(Timesheet $timesheet): Response
@@ -290,7 +290,7 @@ final class TimesheetController extends BaseApiController
#[IsGranted('create_own_timesheet')]
#[OA\Post(description: 'Creates a new timesheet record for the current user and returns it afterwards.', responses: [new OA\Response(response: 200, description: 'Returns the new created timesheet', content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEntity'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEditForm'))]
#[Rest\Post(path: '', name: 'post_timesheet')]
#[Route(methods: ['POST'], path: '', name: 'post_timesheet')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\QueryParam(name: 'full', strict: true, nullable: true, description: 'Allows to fetch fully serialized objects including subresources (TimesheetExpanded). Allowed values: true (default: false)')]
@@ -346,7 +346,7 @@ final class TimesheetController extends BaseApiController
#[OA\Patch(description: 'Update an existing timesheet record, you can pass all or just a subset of the attributes.', responses: [new OA\Response(response: 200, description: 'Returns the updated timesheet', content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEntity'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'Timesheet record ID to update', required: true)]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEditForm'))]
#[Rest\Patch(path: '/{id}', name: 'patch_timesheet', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}', name: 'patch_timesheet', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function patchAction(Request $request, Timesheet $timesheet): Response
@@ -392,7 +392,7 @@ final class TimesheetController extends BaseApiController
#[OA\Parameter(name: 'id', in: 'path', description: 'Timesheet record ID to delete', required: true)]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\Delete(path: '/{id}', name: 'delete_timesheet', requirements: ['id' => '\d+'])]
#[Route(methods: ['DELETE'], path: '/{id}', name: 'delete_timesheet', requirements: ['id' => '\d+'])]
public function deleteAction(Timesheet $timesheet): Response
{
$this->service->deleteTimesheet($timesheet);
@@ -407,7 +407,7 @@ final class TimesheetController extends BaseApiController
*/
#[IsGranted('view_own_timesheet')]
#[OA\Response(response: 200, description: 'Returns the collection of recent user activities (always the latest entry of a unique working set grouped by customer, project and activity)', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/TimesheetCollectionExpanded')))]
#[Rest\Get(path: '/recent', name: 'recent_timesheet')]
#[Route(methods: ['GET'], path: '/recent', name: 'recent_timesheet')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\QueryParam(name: 'begin', requirements: [new Constraints\DateTime(format: 'Y-m-d\TH:i:s')], strict: true, nullable: true, description: 'Only records after this date will be included. Default: today - 1 year (format: HTML5)')]
@@ -443,7 +443,7 @@ final class TimesheetController extends BaseApiController
*/
#[IsGranted('view_own_timesheet')]
#[OA\Response(response: 200, description: 'Returns the collection of active timesheet records for the current user', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/TimesheetCollectionExpanded')))]
#[Rest\Get(path: '/active', name: 'active_timesheet')]
#[Route(methods: ['GET'], path: '/active', name: 'active_timesheet')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function activeAction(): Response
@@ -468,8 +468,8 @@ final class TimesheetController extends BaseApiController
#[IsGranted('stop', 'timesheet')]
#[OA\Response(response: 200, description: 'Stops an active timesheet record and returns it afterwards.', content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Timesheet record ID to stop', required: true)]
#[Rest\Get(path: '/{id}/stop', name: 'stop_timesheet_get', requirements: ['id' => '\d+'])]
#[Rest\Patch(path: '/{id}/stop', name: 'stop_timesheet', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/{id}/stop', name: 'stop_timesheet_get', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}/stop', name: 'stop_timesheet', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function stopAction(Timesheet $timesheet): Response
@@ -488,8 +488,8 @@ final class TimesheetController extends BaseApiController
#[IsGranted('start', 'timesheet')]
#[OA\Response(response: 200, description: 'Restarts a timesheet record for the same customer, project, activity combination. The current user will be the owner of the new record. Kimai tries to stop running records, which is expected to fail depending on the configured rules. Data will be copied from the original record if requested.', content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Timesheet record ID to restart', required: true)]
#[Rest\Get(path: '/{id}/restart', name: 'restart_timesheet_get', requirements: ['id' => '\d+'])]
#[Rest\Patch(path: '/{id}/restart', name: 'restart_timesheet', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/{id}/restart', name: 'restart_timesheet_get', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}/restart', name: 'restart_timesheet', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\RequestParam(name: 'copy', requirements: 'all', strict: true, nullable: true, description: 'Whether data should be copied to the new entry. Allowed values: all (default: nothing is copied)')]
@@ -551,7 +551,7 @@ final class TimesheetController extends BaseApiController
#[IsGranted('duplicate', 'timesheet')]
#[OA\Response(response: 200, description: 'Duplicates a timesheet record, resetting the export state only.', content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Timesheet record ID to duplicate', required: true)]
#[Rest\Patch(path: '/{id}/duplicate', name: 'duplicate_timesheet', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}/duplicate', name: 'duplicate_timesheet', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function duplicateAction(Timesheet $timesheet): Response
@@ -574,7 +574,7 @@ final class TimesheetController extends BaseApiController
#[IsGranted('edit_export', 'timesheet')]
#[OA\Response(response: 200, description: 'Switches the exported state on the record and therefor locks / unlocks it for further updates. Needs edit_export_*_timesheet permission.', content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Timesheet record ID to switch export state', required: true)]
#[Rest\Patch(path: '/{id}/export', name: 'export_timesheet', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}/export', name: 'export_timesheet', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function exportAction(Timesheet $timesheet): Response
@@ -599,7 +599,7 @@ final class TimesheetController extends BaseApiController
#[IsGranted('edit', 'timesheet')]
#[OA\Response(response: 200, description: 'Sets the value of an existing/configured meta-field. You cannot create unknown meta-fields, if the given name is not a configured meta-field, this will return an exception.', content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Timesheet record ID to set the meta-field value for', required: true)]
#[Rest\Patch(path: '/{id}/meta', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}/meta', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\RequestParam(name: 'name', strict: true, nullable: false, description: 'The meta-field name')]

View File

@@ -28,7 +28,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route(path: '/users')]
@@ -54,7 +54,7 @@ final class UserController extends BaseApiController
*/
#[IsGranted('view_user')]
#[OA\Response(response: 200, description: 'Returns the collection of users. Required permission: view_user', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/UserCollection')))]
#[Rest\Get(path: '', name: 'get_users')]
#[Route(methods: ['GET'], path: '', name: 'get_users')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\QueryParam(name: 'visible', requirements: '1|2|3', default: 1, strict: true, nullable: true, description: 'Visibility status to filter users: 1=visible, 2=hidden, 3=all')]
@@ -107,7 +107,7 @@ final class UserController extends BaseApiController
#[IsGranted('view', 'profile')]
#[OA\Response(response: 200, description: 'Return one user entity.', content: new OA\JsonContent(ref: '#/components/schemas/UserEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'User ID to fetch', required: true)]
#[Rest\Get(path: '/{id}', name: 'get_user', requirements: ['id' => '\d+'])]
#[Route(methods: ['GET'], path: '/{id}', name: 'get_user', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getAction(User $profile, EventDispatcherInterface $dispatcher): Response
@@ -126,7 +126,7 @@ final class UserController extends BaseApiController
* Return the current user entity
*/
#[OA\Response(response: 200, description: 'Return the current user entity.', content: new OA\JsonContent(ref: '#/components/schemas/UserEntity'))]
#[Rest\Get(path: '/me', name: 'me_user')]
#[Route(methods: ['GET'], path: '/me', name: 'me_user')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function meAction(): Response
@@ -143,7 +143,7 @@ final class UserController extends BaseApiController
#[IsGranted('create_user')]
#[OA\Post(description: 'Creates a new user and returns it afterwards')]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/UserCreateForm'))]
#[Rest\Post(path: '', name: 'post_user')]
#[Route(methods: ['POST'], path: '', name: 'post_user')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postAction(Request $request): Response
@@ -197,7 +197,7 @@ final class UserController extends BaseApiController
#[OA\Patch(description: 'Update an existing user, you can pass all or just a subset of all attributes (passing roles will replace all existing ones)', responses: [new OA\Response(response: 200, description: 'Returns the updated user', content: new OA\JsonContent(ref: '#/components/schemas/UserEntity'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/UserEditForm'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'User ID to update', required: true)]
#[Rest\Patch(path: '/{id}', name: 'patch_user', requirements: ['id' => '\d+'])]
#[Route(methods: ['PATCH'], path: '/{id}', name: 'patch_user', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function patchAction(Request $request, User $profile): Response