API methods to handle teams and users (#1384)
This commit is contained in:
@@ -21,8 +21,11 @@ nelmio_api_doc:
|
||||
- { alias: TimesheetMeta, type: App\Entity\TimesheetMeta, groups: [Default, Timesheet] }
|
||||
- { alias: TimesheetCollection, type: App\Entity\Timesheet, groups: [Default, Collection, Timesheet] }
|
||||
- { alias: TimesheetSubCollection, type: App\Entity\Timesheet, groups: [Default, Subresource, Timesheet] }
|
||||
- { alias: UserCreateForm, type: App\Form\API\UserApiCreateForm, groups: [Default, Entity, User, User_Entity] }
|
||||
- { alias: UserEditForm, type: App\Form\API\UserApiEditForm, groups: [Default, Entity, User, User_Entity] }
|
||||
- { alias: UserEntity, type: App\Entity\User, groups: [Default, Entity, User, User_Entity] }
|
||||
- { alias: UserCollection, type: App\Entity\User, groups: [Default, Collection, User] }
|
||||
- { alias: TeamEditForm, type: App\Form\API\TeamApiEditForm, groups: [Default, Entity, Team] }
|
||||
- { alias: TeamEntity, type: App\Entity\Team, groups: [Default, Entity, Team, Team_Entity] }
|
||||
- { alias: TeamCollection, type: App\Entity\Team, groups: [Default, Collection, Team] }
|
||||
- { alias: I18nConfig, type: App\API\Model\I18n, groups: [Default] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
App\Entity\Customer:
|
||||
exclusion_policy: All
|
||||
custom_accessor_order: [id, name, number, comment, visible, company, contact, address, country, currency, phone, fax, mobile, email, homepage, timezone, fixedRate, hourlyRate, color, budget, timeBudget, metaFields]
|
||||
custom_accessor_order: [id, name, number, comment, visible, company, contact, address, country, currency, phone, fax, mobile, email, homepage, timezone, fixedRate, hourlyRate, color, budget, timeBudget, metaFields, teams]
|
||||
properties:
|
||||
id:
|
||||
include: true
|
||||
@@ -63,6 +63,9 @@ App\Entity\Customer:
|
||||
include: true
|
||||
metaFields:
|
||||
exclude: true
|
||||
teams:
|
||||
include: true
|
||||
groups: [Customer]
|
||||
virtual_properties:
|
||||
getMetaFields:
|
||||
serialized_name: metaFields
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
App\Entity\Project:
|
||||
exclusion_policy: All
|
||||
custom_accessor_order: [id, name, comment, visible, orderNumber, orderDate, customer, start, end, fixedRate, hourlyRate, color, budget, timeBudget, metaFields, parentTitle]
|
||||
custom_accessor_order: [id, name, comment, visible, orderNumber, orderDate, customer, start, end, fixedRate, hourlyRate, color, budget, timeBudget, metaFields, parentTitle, teams]
|
||||
properties:
|
||||
id:
|
||||
include: true
|
||||
@@ -41,6 +41,9 @@ App\Entity\Project:
|
||||
include: true
|
||||
metaFields:
|
||||
exclude: true
|
||||
teams:
|
||||
include: true
|
||||
groups: [Project]
|
||||
virtual_properties:
|
||||
parentTitle:
|
||||
serialized_name: parentTitle
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
App\Entity\Team:
|
||||
exclusion_policy: All
|
||||
custom_accessor_order: [id, name, teamlead, users]
|
||||
custom_accessor_order: [id, name, teamlead, users, customers, projects]
|
||||
properties:
|
||||
id:
|
||||
include: true
|
||||
@@ -13,6 +13,8 @@ App\Entity\Team:
|
||||
include: true
|
||||
groups: [Team_Entity]
|
||||
customers:
|
||||
exclude: true
|
||||
include: true
|
||||
groups: [Team_Entity]
|
||||
projects:
|
||||
exclude: true
|
||||
include: true
|
||||
groups: [Team_Entity]
|
||||
|
||||
@@ -11,8 +11,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\API;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Team;
|
||||
use App\Entity\User;
|
||||
use App\Form\API\TeamApiEditForm;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\TeamRepository;
|
||||
use App\Repository\UserRepository;
|
||||
use FOS\RestBundle\Controller\Annotations\RouteResource;
|
||||
use FOS\RestBundle\Request\ParamFetcherInterface;
|
||||
use FOS\RestBundle\View\View;
|
||||
@@ -20,23 +27,25 @@ use FOS\RestBundle\View\ViewHandlerInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\Security as ApiSecurity;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Swagger\Annotations as SWG;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
/**
|
||||
* @RouteResource("Team")
|
||||
*
|
||||
* @Security("is_granted('IS_AUTHENTICATED_REMEMBERED')")
|
||||
*/
|
||||
class TeamController extends BaseApiController
|
||||
final class TeamController extends BaseApiController
|
||||
{
|
||||
/**
|
||||
* @var TeamRepository
|
||||
*/
|
||||
protected $repository;
|
||||
private $repository;
|
||||
/**
|
||||
* @var ViewHandlerInterface
|
||||
*/
|
||||
protected $viewHandler;
|
||||
private $viewHandler;
|
||||
|
||||
public function __construct(ViewHandlerInterface $viewHandler, TeamRepository $repository)
|
||||
{
|
||||
@@ -135,4 +144,481 @@ class TeamController extends BaseApiController
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new team
|
||||
*
|
||||
* @SWG\Post(
|
||||
* description="Creates a new team and returns it afterwards",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Returns the new created team",
|
||||
* @SWG\Schema(ref="#/definitions/TeamEntity",),
|
||||
* )
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="body",
|
||||
* in="body",
|
||||
* required=true,
|
||||
* @SWG\Schema(ref="#/definitions/TeamEditForm")
|
||||
* )
|
||||
*
|
||||
* @Security("is_granted('create_team')")
|
||||
*
|
||||
* @ApiSecurity(name="apiUser")
|
||||
* @ApiSecurity(name="apiToken")
|
||||
*/
|
||||
public function postAction(Request $request): Response
|
||||
{
|
||||
$team = new Team();
|
||||
$team->setTeamLead($this->getUser());
|
||||
|
||||
$form = $this->createForm(TeamApiEditForm::class, $team);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
$team->addUser($team->getTeamLead());
|
||||
|
||||
if ($form->isValid()) {
|
||||
$this->repository->saveTeam($team);
|
||||
|
||||
$view = new View($team, 200);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Team_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
$view = new View($form);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Team_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing team
|
||||
*
|
||||
* @SWG\Patch(
|
||||
* description="Update an existing team, you can pass all or just a subset of all attributes (passing users will replace all existing ones)",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Returns the updated team",
|
||||
* @SWG\Schema(ref="#/definitions/TeamEntity")
|
||||
* )
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="body",
|
||||
* in="body",
|
||||
* required=true,
|
||||
* @SWG\Schema(ref="#/definitions/TeamEditForm")
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="Team ID to update",
|
||||
* required=true,
|
||||
* )
|
||||
*
|
||||
* @Security("is_granted('edit_team')")
|
||||
*
|
||||
* @ApiSecurity(name="apiUser")
|
||||
* @ApiSecurity(name="apiToken")
|
||||
*/
|
||||
public function patchAction(Request $request, int $id): Response
|
||||
{
|
||||
$team = $this->repository->find($id);
|
||||
|
||||
if (null === $team) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
$form = $this->createForm(TeamApiEditForm::class, $team);
|
||||
|
||||
$form->setData($team);
|
||||
$form->submit($request->request->all(), false);
|
||||
$team->addUser($team->getTeamLead());
|
||||
|
||||
if (false === $form->isValid()) {
|
||||
$view = new View($form, Response::HTTP_OK);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Team_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
$this->repository->saveTeam($team);
|
||||
|
||||
$view = new View($team, Response::HTTP_OK);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Team_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new member to a team
|
||||
*
|
||||
* @SWG\Post(
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Adds a new user to a team. The user must not be deactivated.",
|
||||
* @SWG\Schema(ref="#/definitions/TeamEntity")
|
||||
* )
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The team which will receive the new member",
|
||||
* required=true,
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="userId",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The team member to add (User ID)",
|
||||
* required=true,
|
||||
* )
|
||||
*
|
||||
* @Security("is_granted('edit_team')")
|
||||
*
|
||||
* @ApiSecurity(name="apiUser")
|
||||
* @ApiSecurity(name="apiToken")
|
||||
*/
|
||||
public function postMemberAction(int $id, int $userId, UserRepository $repository): Response
|
||||
{
|
||||
$team = $this->repository->find($id);
|
||||
|
||||
if (null === $team) {
|
||||
throw new NotFoundException('Team not found');
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
$user = $repository->find($userId);
|
||||
|
||||
if (null === $user) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
|
||||
if (!$user->isEnabled()) {
|
||||
throw new BadRequestHttpException('Cannot add disabled user to team');
|
||||
}
|
||||
|
||||
if ($user->isInTeam($team)) {
|
||||
throw new BadRequestHttpException('User is already member of the team');
|
||||
}
|
||||
|
||||
$team->addUser($user);
|
||||
|
||||
$this->repository->saveTeam($team);
|
||||
|
||||
$view = new View($team, Response::HTTP_OK);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Team_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a member from the team
|
||||
*
|
||||
* @SWG\Delete(
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Removes a user from the team. The teamlead cannot be removed.",
|
||||
* @SWG\Schema(ref="#/definitions/TeamEntity")
|
||||
* )
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The team from which the member will be removed",
|
||||
* required=true,
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="userId",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The team member to remove (User ID)",
|
||||
* required=true,
|
||||
* )
|
||||
*
|
||||
* @Security("is_granted('edit_team')")
|
||||
*
|
||||
* @ApiSecurity(name="apiUser")
|
||||
* @ApiSecurity(name="apiToken")
|
||||
*/
|
||||
public function deleteMemberAction(int $id, int $userId, UserRepository $repository): Response
|
||||
{
|
||||
$team = $this->repository->find($id);
|
||||
|
||||
if (null === $team) {
|
||||
throw new NotFoundException('Team not found');
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
$user = $repository->find($userId);
|
||||
|
||||
if (null === $user) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
|
||||
if (!$user->isInTeam($team)) {
|
||||
throw new BadRequestHttpException('User is not a member of the team');
|
||||
}
|
||||
|
||||
if ($team->isTeamlead($user)) {
|
||||
throw new BadRequestHttpException('Cannot remove teamlead');
|
||||
}
|
||||
|
||||
$team->removeUser($user);
|
||||
|
||||
$this->repository->saveTeam($team);
|
||||
|
||||
$view = new View($team, Response::HTTP_OK);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Team_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant the team access to a customer
|
||||
*
|
||||
* @SWG\Post(
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Adds a new customer to a team. The customer must not be invisible.",
|
||||
* @SWG\Schema(ref="#/definitions/TeamEntity")
|
||||
* )
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The team that is granted access",
|
||||
* required=true,
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="customerId",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The customer to grant acecess to (Customer ID)",
|
||||
* required=true,
|
||||
* )
|
||||
*
|
||||
* @Security("is_granted('edit_team')")
|
||||
*
|
||||
* @ApiSecurity(name="apiUser")
|
||||
* @ApiSecurity(name="apiToken")
|
||||
*/
|
||||
public function postCustomerAction(int $id, int $customerId, CustomerRepository $repository): Response
|
||||
{
|
||||
$team = $this->repository->find($id);
|
||||
|
||||
if (null === $team) {
|
||||
throw new NotFoundException('Team not found');
|
||||
}
|
||||
|
||||
/** @var Customer $customer */
|
||||
$customer = $repository->find($customerId);
|
||||
|
||||
if (null === $customer) {
|
||||
throw new NotFoundException('Customer not found');
|
||||
}
|
||||
|
||||
if (!$customer->isVisible()) {
|
||||
throw new BadRequestHttpException('Cannot grant access to an invisible customer');
|
||||
}
|
||||
|
||||
if ($team->hasCustomer($customer)) {
|
||||
throw new BadRequestHttpException('Team has already access to customer');
|
||||
}
|
||||
|
||||
$team->addCustomer($customer);
|
||||
|
||||
$this->repository->saveTeam($team);
|
||||
|
||||
$view = new View($team, Response::HTTP_OK);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Team_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes access for a customer from a team
|
||||
*
|
||||
* @SWG\Delete(
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Removes a customer from the team.",
|
||||
* @SWG\Schema(ref="#/definitions/TeamEntity")
|
||||
* )
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The team whose permission will be revoked",
|
||||
* required=true,
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="customerId",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The customer to remove (Customer ID)",
|
||||
* required=true,
|
||||
* )
|
||||
*
|
||||
* @Security("is_granted('edit_team')")
|
||||
*
|
||||
* @ApiSecurity(name="apiUser")
|
||||
* @ApiSecurity(name="apiToken")
|
||||
*/
|
||||
public function deleteCustomerAction(int $id, int $customerId, CustomerRepository $repository): Response
|
||||
{
|
||||
$team = $this->repository->find($id);
|
||||
|
||||
if (null === $team) {
|
||||
throw new NotFoundException('Team not found');
|
||||
}
|
||||
|
||||
/** @var Customer $customer */
|
||||
$customer = $repository->find($customerId);
|
||||
|
||||
if (null === $customer) {
|
||||
throw new NotFoundException('Customer not found');
|
||||
}
|
||||
|
||||
if (!$team->hasCustomer($customer)) {
|
||||
throw new BadRequestHttpException('Customer is not assigned to the team');
|
||||
}
|
||||
|
||||
$team->removeCustomer($customer);
|
||||
|
||||
$this->repository->saveTeam($team);
|
||||
|
||||
$view = new View($team, Response::HTTP_OK);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Team_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant the team access to a project
|
||||
*
|
||||
* @SWG\Post(
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Adds a new project to a team. The project must not be invisible.",
|
||||
* @SWG\Schema(ref="#/definitions/TeamEntity")
|
||||
* )
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The team that is granted access",
|
||||
* required=true,
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="projectId",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The project to grant acecess to (Project ID)",
|
||||
* required=true,
|
||||
* )
|
||||
*
|
||||
* @Security("is_granted('edit_team')")
|
||||
*
|
||||
* @ApiSecurity(name="apiUser")
|
||||
* @ApiSecurity(name="apiToken")
|
||||
*/
|
||||
public function postProjectAction(int $id, int $projectId, ProjectRepository $repository): Response
|
||||
{
|
||||
$team = $this->repository->find($id);
|
||||
|
||||
if (null === $team) {
|
||||
throw new NotFoundException('Team not found');
|
||||
}
|
||||
|
||||
/** @var Project $project */
|
||||
$project = $repository->find($projectId);
|
||||
|
||||
if (null === $project) {
|
||||
throw new NotFoundException('Project not found');
|
||||
}
|
||||
|
||||
if (!$project->isVisible()) {
|
||||
throw new BadRequestHttpException('Cannot grant access to an invisible project');
|
||||
}
|
||||
|
||||
if ($team->hasProject($project)) {
|
||||
throw new BadRequestHttpException('Team has already access to project');
|
||||
}
|
||||
|
||||
$team->addProject($project);
|
||||
|
||||
$this->repository->saveTeam($team);
|
||||
|
||||
$view = new View($team, Response::HTTP_OK);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Team_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes access for a project from a team
|
||||
*
|
||||
* @SWG\Delete(
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Removes a project from the team.",
|
||||
* @SWG\Schema(ref="#/definitions/TeamEntity")
|
||||
* )
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The team whose permission will be revoked",
|
||||
* required=true,
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="projectId",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="The project to remove (Project ID)",
|
||||
* required=true,
|
||||
* )
|
||||
*
|
||||
* @Security("is_granted('edit_team')")
|
||||
*
|
||||
* @ApiSecurity(name="apiUser")
|
||||
* @ApiSecurity(name="apiToken")
|
||||
*/
|
||||
public function deleteProjectAction(int $id, int $projectId, ProjectRepository $repository): Response
|
||||
{
|
||||
$team = $this->repository->find($id);
|
||||
|
||||
if (null === $team) {
|
||||
throw new NotFoundException('Team not found');
|
||||
}
|
||||
|
||||
/** @var Project $project */
|
||||
$project = $repository->find($projectId);
|
||||
|
||||
if (null === $project) {
|
||||
throw new NotFoundException('Project not found');
|
||||
}
|
||||
|
||||
if (!$team->hasProject($project)) {
|
||||
throw new BadRequestHttpException('Project is not assigned to the team');
|
||||
}
|
||||
|
||||
$team->removeProject($project);
|
||||
|
||||
$this->repository->saveTeam($team);
|
||||
|
||||
$view = new View($team, Response::HTTP_OK);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Team_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\API;
|
||||
|
||||
use App\Configuration\FormConfiguration;
|
||||
use App\Entity\User;
|
||||
use App\Form\API\UserApiCreateForm;
|
||||
use App\Form\API\UserApiEditForm;
|
||||
use App\Repository\Query\UserQuery;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Utils\SearchTerm;
|
||||
@@ -22,34 +26,45 @@ use FOS\RestBundle\View\ViewHandlerInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\Security as ApiSecurity;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Swagger\Annotations as SWG;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
|
||||
/**
|
||||
* @RouteResource("User")
|
||||
*
|
||||
* @Security("is_granted('IS_AUTHENTICATED_REMEMBERED')")
|
||||
*/
|
||||
class UserController extends BaseApiController
|
||||
final class UserController extends BaseApiController
|
||||
{
|
||||
/**
|
||||
* @var UserRepository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
private $repository;
|
||||
/**
|
||||
* @var ViewHandlerInterface
|
||||
*/
|
||||
protected $viewHandler;
|
||||
private $viewHandler;
|
||||
/**
|
||||
* @var UserPasswordEncoderInterface
|
||||
*/
|
||||
private $encoder;
|
||||
/**
|
||||
* @var FormConfiguration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
/**
|
||||
* @param ViewHandlerInterface $viewHandler
|
||||
* @param UserRepository $repository
|
||||
*/
|
||||
public function __construct(ViewHandlerInterface $viewHandler, UserRepository $repository)
|
||||
public function __construct(ViewHandlerInterface $viewHandler, UserRepository $repository, UserPasswordEncoderInterface $encoder, FormConfiguration $config)
|
||||
{
|
||||
$this->viewHandler = $viewHandler;
|
||||
$this->repository = $repository;
|
||||
$this->encoder = $encoder;
|
||||
$this->configuration = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,4 +174,125 @@ class UserController extends BaseApiController
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user
|
||||
*
|
||||
* @SWG\Post(
|
||||
* description="Creates a new user and returns it afterwards",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Returns the new created user",
|
||||
* @SWG\Schema(ref="#/definitions/UserEntity",),
|
||||
* )
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="body",
|
||||
* in="body",
|
||||
* required=true,
|
||||
* @SWG\Schema(ref="#/definitions/UserCreateForm")
|
||||
* )
|
||||
*
|
||||
* @Security("is_granted('create_user')")
|
||||
*
|
||||
* @ApiSecurity(name="apiUser")
|
||||
* @ApiSecurity(name="apiToken")
|
||||
*/
|
||||
public function postAction(Request $request): Response
|
||||
{
|
||||
$user = new User();
|
||||
$user->setEnabled(true);
|
||||
$user->setRoles([User::DEFAULT_ROLE]);
|
||||
$user->setTimezone($this->configuration->getUserDefaultTimezone());
|
||||
$user->setLanguage($this->configuration->getUserDefaultLanguage());
|
||||
|
||||
$form = $this->createForm(UserApiCreateForm::class, $user, [
|
||||
'include_roles' => $this->isGranted('roles', $user),
|
||||
'include_active_flag' => true,
|
||||
'include_preferences' => $this->isGranted('preferences', $user),
|
||||
]);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
|
||||
if ($form->isValid()) {
|
||||
$password = $this->encoder->encodePassword($user, $user->getPlainPassword());
|
||||
$user->setPassword($password);
|
||||
|
||||
$this->repository->saveUser($user);
|
||||
|
||||
$view = new View($user, 200);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'User', 'User_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
$view = new View($form);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'User', 'User_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing user
|
||||
*
|
||||
* @SWG\Patch(
|
||||
* description="Update an existing user, you can pass all or just a subset of all attributes (passing roles will replace all existing ones)",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Returns the updated user",
|
||||
* @SWG\Schema(ref="#/definitions/UserEntity")
|
||||
* )
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="body",
|
||||
* in="body",
|
||||
* required=true,
|
||||
* @SWG\Schema(ref="#/definitions/UserEditForm")
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="User ID to update",
|
||||
* required=true,
|
||||
* )
|
||||
*
|
||||
* @ApiSecurity(name="apiUser")
|
||||
* @ApiSecurity(name="apiToken")
|
||||
*/
|
||||
public function patchAction(Request $request, int $id): Response
|
||||
{
|
||||
$user = $this->repository->getUserById($id);
|
||||
|
||||
if (null === $user) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!$this->isGranted('edit', $user)) {
|
||||
throw new AccessDeniedHttpException('Not allowed to edit user');
|
||||
}
|
||||
|
||||
$form = $this->createForm(UserApiEditForm::class, $user, [
|
||||
'include_roles' => $this->isGranted('roles', $user),
|
||||
'include_active_flag' => ($user->getId() !== $this->getUser()->getId()),
|
||||
'include_preferences' => $this->isGranted('preferences', $user),
|
||||
]);
|
||||
|
||||
$form->setData($user);
|
||||
$form->submit($request->request->all(), false);
|
||||
|
||||
if (false === $form->isValid()) {
|
||||
$view = new View($form, Response::HTTP_OK);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'User', 'User_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
$this->repository->saveUser($user);
|
||||
|
||||
$view = new View($user, Response::HTTP_OK);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'User', 'User_Entity']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ use App\Form\UserRolesType;
|
||||
use App\Form\UserTeamsType;
|
||||
use App\Repository\TeamRepository;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use App\Voter\UserVoter;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
@@ -281,23 +280,23 @@ class ProfileController extends AbstractController
|
||||
): Response {
|
||||
$forms = [];
|
||||
|
||||
if ($this->isGranted(UserVoter::EDIT, $user)) {
|
||||
if ($this->isGranted('edit', $user)) {
|
||||
$editForm = $editForm ?: $this->createEditForm($user);
|
||||
$forms['settings'] = $editForm->createView();
|
||||
}
|
||||
if ($this->isGranted(UserVoter::PASSWORD, $user)) {
|
||||
if ($this->isGranted('password', $user)) {
|
||||
$pwdForm = $pwdForm ?: $this->createPasswordForm($user);
|
||||
$forms['password'] = $pwdForm->createView();
|
||||
}
|
||||
if ($this->isGranted(UserVoter::API_TOKEN, $user)) {
|
||||
if ($this->isGranted('api-token', $user)) {
|
||||
$apiTokenForm = $apiTokenForm ?: $this->createApiTokenForm($user);
|
||||
$forms['api-token'] = $apiTokenForm->createView();
|
||||
}
|
||||
if ($this->isGranted(UserVoter::TEAMS, $user) && $this->teams->count([]) > 0) {
|
||||
if ($this->isGranted('teams', $user) && $this->teams->count([]) > 0) {
|
||||
$teamsForm = $teamsForm ?: $this->createTeamsForm($user);
|
||||
$forms['teams'] = $teamsForm->createView();
|
||||
}
|
||||
if ($this->isGranted(UserVoter::ROLES, $user)) {
|
||||
if ($this->isGranted('roles', $user)) {
|
||||
$rolesForm = $rolesForm ?: $this->createRolesForm($user);
|
||||
$forms['roles'] = $rolesForm->createView();
|
||||
}
|
||||
@@ -329,7 +328,8 @@ class ProfileController extends AbstractController
|
||||
[
|
||||
'action' => $this->generateUrl('user_profile_edit', ['username' => $user->getUsername()]),
|
||||
'method' => 'POST',
|
||||
'include_active_flag' => ($user->getId() !== $this->getUser()->getId())
|
||||
'include_active_flag' => ($user->getId() !== $this->getUser()->getId()),
|
||||
'include_preferences' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Configuration\FormConfiguration;
|
||||
use App\Entity\User;
|
||||
use App\Event\UserPreferenceDisplayEvent;
|
||||
use App\Form\Toolbar\UserToolbarForm;
|
||||
@@ -31,20 +32,20 @@ use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
* @Route(path="/admin/user")
|
||||
* @Security("is_granted('view_user')")
|
||||
*/
|
||||
class UserController extends AbstractController
|
||||
final class UserController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @var UserPasswordEncoderInterface
|
||||
*/
|
||||
protected $encoder;
|
||||
private $encoder;
|
||||
/**
|
||||
* @var UserRepository
|
||||
*/
|
||||
protected $repository;
|
||||
private $repository;
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
private $dispatcher;
|
||||
|
||||
public function __construct(UserPasswordEncoderInterface $encoder, UserRepository $repository, EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
@@ -93,23 +94,31 @@ class UserController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
private function createNewDefaultUser(FormConfiguration $config): User
|
||||
{
|
||||
$user = new User();
|
||||
$user->setEnabled(true);
|
||||
$user->setRoles([User::DEFAULT_ROLE]);
|
||||
$user->setTimezone($config->getUserDefaultTimezone());
|
||||
$user->setLanguage($config->getUserDefaultLanguage());
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/create", name="admin_user_create", methods={"GET", "POST"})
|
||||
* @Security("is_granted('create_user')")
|
||||
*/
|
||||
public function createAction(Request $request): Response
|
||||
public function createAction(Request $request, FormConfiguration $config): Response
|
||||
{
|
||||
$user = new User();
|
||||
$user->setEnabled(true);
|
||||
$editForm = $this->createEditForm($user);
|
||||
$user = $this->createNewDefaultUser($config);
|
||||
$editForm = $this->getCreateUserForm($user);
|
||||
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||
$password = $this->encoder->encodePassword($user, $user->getPlainPassword());
|
||||
$user->setPassword($password);
|
||||
$user->setEnabled(true);
|
||||
$user->setRoles([User::DEFAULT_ROLE]);
|
||||
|
||||
$entityManager = $this->getDoctrine()->getManager();
|
||||
$entityManager->persist($user);
|
||||
@@ -121,8 +130,12 @@ class UserController extends AbstractController
|
||||
return $this->redirectToRoute('user_profile_edit', ['username' => $user->getUsername()]);
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
$editForm = $this->createEditForm($user);
|
||||
$firstUser = $user;
|
||||
$user = $this->createNewDefaultUser($config);
|
||||
$user->setLanguage($firstUser->getLanguage());
|
||||
$user->setTimezone($firstUser->getTimezone());
|
||||
|
||||
$editForm = $this->getCreateUserForm($user);
|
||||
$editForm->get('create_more')->setData(true);
|
||||
}
|
||||
|
||||
@@ -187,12 +200,14 @@ class UserController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
private function createEditForm(User $user): FormInterface
|
||||
private function getCreateUserForm(User $user): FormInterface
|
||||
{
|
||||
return $this->createForm(UserCreateType::class, $user, [
|
||||
'action' => $this->generateUrl('admin_user_create'),
|
||||
'method' => 'POST',
|
||||
'include_active_flag' => true
|
||||
'include_active_flag' => true,
|
||||
'include_preferences' => $this->isGranted('preferences', $user),
|
||||
'include_add_more' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,11 @@ class Team
|
||||
return $this->users;
|
||||
}
|
||||
|
||||
public function hasCustomer(Customer $customer): bool
|
||||
{
|
||||
return $this->customers->contains($customer);
|
||||
}
|
||||
|
||||
public function addCustomer(Customer $customer)
|
||||
{
|
||||
if ($this->customers->contains($customer)) {
|
||||
@@ -172,6 +177,11 @@ class Team
|
||||
return $this->customers;
|
||||
}
|
||||
|
||||
public function hasProject(Project $project): bool
|
||||
{
|
||||
return $this->projects->contains($project);
|
||||
}
|
||||
|
||||
public function addProject(Project $project)
|
||||
{
|
||||
if ($this->projects->contains($project)) {
|
||||
|
||||
@@ -266,6 +266,27 @@ class User extends BaseUser implements UserInterface
|
||||
return $this->getPreferenceValue(UserPreference::TIMEZONE, date_default_timezone_get());
|
||||
}
|
||||
|
||||
public function getLanguage(): string
|
||||
{
|
||||
return $this->getLocale();
|
||||
}
|
||||
|
||||
public function setLanguage(?string $language)
|
||||
{
|
||||
if ($language === null) {
|
||||
$language = User::DEFAULT_LANGUAGE;
|
||||
}
|
||||
$this->setPreferenceValue(UserPreference::LOCALE, $language);
|
||||
}
|
||||
|
||||
public function setTimezone(?string $timezone)
|
||||
{
|
||||
if ($timezone === null) {
|
||||
$timezone = date_default_timezone_get();
|
||||
}
|
||||
$this->setPreferenceValue(UserPreference::TIMEZONE, $timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $default
|
||||
|
||||
35
src/Form/API/TeamApiEditForm.php
Normal file
35
src/Form/API/TeamApiEditForm.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form\API;
|
||||
|
||||
use App\Form\TeamEditForm;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TeamApiEditForm extends TeamEditForm
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'expand_users' => false,
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
58
src/Form/API/UserApiCreateForm.php
Normal file
58
src/Form/API/UserApiCreateForm.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form\API;
|
||||
|
||||
use App\Form\Type\UserRoleType;
|
||||
use App\Form\UserCreateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class UserApiCreateForm extends UserCreateType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder->remove('plainPassword');
|
||||
|
||||
$builder->add('plainPassword', PasswordType::class, [
|
||||
'required' => true,
|
||||
'label' => 'label.password',
|
||||
'documentation' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Plain text password',
|
||||
],
|
||||
]);
|
||||
|
||||
if ($options['include_roles']) {
|
||||
$builder->add('roles', UserRoleType::class, [
|
||||
'label' => 'label.roles',
|
||||
'required' => false,
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
'include_roles' => true,
|
||||
'include_add_more' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
45
src/Form/API/UserApiEditForm.php
Normal file
45
src/Form/API/UserApiEditForm.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form\API;
|
||||
|
||||
use App\Form\Type\UserRoleType;
|
||||
use App\Form\UserEditType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class UserApiEditForm extends UserEditType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
if ($options['include_roles']) {
|
||||
$builder->add('roles', UserRoleType::class, [
|
||||
'label' => 'label.roles',
|
||||
'required' => false,
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
'include_roles' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -29,15 +29,25 @@ class TeamEditForm extends AbstractType
|
||||
'attr' => [
|
||||
'autofocus' => 'autofocus'
|
||||
],
|
||||
// documentation is for NelmioApiDocBundle
|
||||
'documentation' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Name of the new team',
|
||||
],
|
||||
])
|
||||
->add('teamlead', UserType::class, [
|
||||
'label' => 'label.teamlead',
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
// documentation is for NelmioApiDocBundle
|
||||
'documentation' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'User ID for the teamlead',
|
||||
],
|
||||
])
|
||||
->add('users', UserType::class, [
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'expanded' => $options['expand_users'],
|
||||
'by_reference' => false,
|
||||
])
|
||||
;
|
||||
@@ -52,6 +62,7 @@ class TeamEditForm extends AbstractType
|
||||
'data_class' => Team::class,
|
||||
'csrf_protection' => true,
|
||||
'csrf_field_name' => '_token',
|
||||
'expand_users' => true,
|
||||
'csrf_token_id' => 'admin_team_edit',
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.teamUpdate'
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
@@ -43,20 +42,21 @@ class UserCreateType extends UserEditType
|
||||
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder->add('create_more', CheckboxType::class, [
|
||||
'label' => 'label.create_more',
|
||||
'required' => false,
|
||||
'mapped' => false,
|
||||
]);
|
||||
if ($options['include_add_more'] === true) {
|
||||
$builder->add('create_more', CheckboxType::class, [
|
||||
'label' => 'label.create_more',
|
||||
'required' => false,
|
||||
'mapped' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'class' => User::class,
|
||||
'include_add_more' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,12 @@ namespace App\Form;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\Type\AvatarType;
|
||||
use App\Form\Type\LanguageType;
|
||||
use App\Form\Type\YesNoType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TimezoneType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
@@ -45,6 +47,16 @@ class UserEditType extends AbstractType
|
||||
])
|
||||
;
|
||||
|
||||
if ($options['include_preferences']) {
|
||||
$builder->add('language', LanguageType::class, [
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$builder->add('timezone', TimezoneType::class, [
|
||||
'required' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($options['include_active_flag']) {
|
||||
$builder
|
||||
->add('enabled', YesNoType::class, [
|
||||
@@ -64,7 +76,8 @@ class UserEditType extends AbstractType
|
||||
'csrf_protection' => true,
|
||||
'csrf_field_name' => '_token',
|
||||
'csrf_token_id' => 'edit_user_profile',
|
||||
'include_active_flag' => false,
|
||||
'include_active_flag' => true,
|
||||
'include_preferences' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use App\Repository\Query\BaseQuery;
|
||||
use App\Repository\Query\UserFormTypeQuery;
|
||||
use App\Repository\Query\UserQuery;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Pagerfanta\Adapter\DoctrineORMAdapter;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
@@ -32,7 +33,19 @@ class UserRepository extends EntityRepository implements UserLoaderInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to fetch the currently logged-in user.
|
||||
* @param User $user
|
||||
* @throws ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
public function saveUser(User $user)
|
||||
{
|
||||
$entityManager = $this->getEntityManager();
|
||||
$entityManager->persist($user);
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to fetch a user by its ID.
|
||||
*
|
||||
* @param int $id
|
||||
* @return null|User
|
||||
|
||||
@@ -15,17 +15,13 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
|
||||
class TeamVoter extends AbstractVoter
|
||||
{
|
||||
public const VIEW = 'view';
|
||||
public const EDIT = 'edit';
|
||||
public const DELETE = 'delete';
|
||||
|
||||
/**
|
||||
* support rules based on the given $subject (here: Team)
|
||||
*/
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
self::VIEW,
|
||||
self::EDIT,
|
||||
self::DELETE,
|
||||
'view',
|
||||
'edit',
|
||||
'delete',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,26 +17,16 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
*/
|
||||
class UserVoter extends AbstractVoter
|
||||
{
|
||||
public const VIEW = 'view';
|
||||
public const EDIT = 'edit';
|
||||
public const DELETE = 'delete';
|
||||
public const PASSWORD = 'password';
|
||||
public const ROLES = 'roles';
|
||||
public const TEAMS = 'teams';
|
||||
public const PREFERENCES = 'preferences';
|
||||
public const API_TOKEN = 'api-token';
|
||||
public const HOURLY_RATE = 'hourly-rate';
|
||||
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
self::VIEW,
|
||||
self::EDIT,
|
||||
self::ROLES,
|
||||
self::TEAMS,
|
||||
self::PASSWORD,
|
||||
self::DELETE,
|
||||
self::PREFERENCES,
|
||||
self::API_TOKEN,
|
||||
self::HOURLY_RATE,
|
||||
'view',
|
||||
'edit',
|
||||
'roles',
|
||||
'teams',
|
||||
'password',
|
||||
'delete',
|
||||
'preferences',
|
||||
'api-token',
|
||||
'hourly-rate',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -71,39 +61,21 @@ class UserVoter extends AbstractVoter
|
||||
return false;
|
||||
}
|
||||
|
||||
$permission = '';
|
||||
|
||||
switch ($attribute) {
|
||||
// special case for the UserController
|
||||
case self::DELETE:
|
||||
if ($subject->getId() === $user->getId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasRolePermission($user, 'delete_user');
|
||||
|
||||
case self::VIEW:
|
||||
case self::EDIT:
|
||||
case self::PREFERENCES:
|
||||
case self::PASSWORD:
|
||||
case self::API_TOKEN:
|
||||
case self::ROLES:
|
||||
case self::TEAMS:
|
||||
case self::HOURLY_RATE:
|
||||
$permission .= $attribute;
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($attribute === 'delete') {
|
||||
if ($subject->getId() === $user->getId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasRolePermission($user, 'delete_user');
|
||||
}
|
||||
|
||||
$permission .= '_';
|
||||
$permission = $attribute;
|
||||
|
||||
// extend me for "team" support later on
|
||||
if ($subject->getId() == $user->getId()) {
|
||||
$permission .= 'own';
|
||||
if ($subject->getId() === $user->getId()) {
|
||||
$permission .= '_own';
|
||||
} else {
|
||||
$permission .= 'other';
|
||||
$permission .= '_other';
|
||||
}
|
||||
|
||||
$permission .= '_profile';
|
||||
|
||||
@@ -230,7 +230,7 @@ class CustomerControllerTest extends APIControllerBaseTest
|
||||
protected function assertStructure(array $result, $full = true)
|
||||
{
|
||||
$expectedKeys = [
|
||||
'id', 'name', 'visible', 'hourlyRate', 'fixedRate', 'color', 'metaFields'
|
||||
'id', 'name', 'visible', 'hourlyRate', 'fixedRate', 'color', 'metaFields', 'teams'
|
||||
];
|
||||
|
||||
if ($full) {
|
||||
|
||||
@@ -277,7 +277,7 @@ class ProjectControllerTest extends APIControllerBaseTest
|
||||
protected function assertStructure(array $result, $full = true)
|
||||
{
|
||||
$expectedKeys = [
|
||||
'id', 'name', 'visible', 'customer', 'hourlyRate', 'fixedRate', 'color', 'metaFields', 'parentTitle', 'start', 'end'
|
||||
'id', 'name', 'visible', 'customer', 'hourlyRate', 'fixedRate', 'color', 'metaFields', 'parentTitle', 'start', 'end', 'teams'
|
||||
];
|
||||
|
||||
if ($full) {
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace App\Tests\API;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
use App\Tests\DataFixtures\TeamFixtures;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -42,7 +44,7 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(2, count($result));
|
||||
self::assertEquals(2, count($result));
|
||||
$this->assertStructure($result[0], false);
|
||||
}
|
||||
|
||||
@@ -66,6 +68,64 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->assertEntityNotFoundForDelete(User::ROLE_ADMIN, '/api/teams/255', []);
|
||||
}
|
||||
|
||||
public function testPostAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
$this->assertNotEmpty($result['id']);
|
||||
}
|
||||
|
||||
public function testPostActionWithInvalidUser()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$response = $client->getResponse();
|
||||
$this->assertFalse($response->isSuccessful());
|
||||
self::assertEquals(Response::HTTP_FORBIDDEN, $response->getStatusCode());
|
||||
$json = json_decode($response->getContent(), true);
|
||||
self::assertEquals('Access denied.', $json['message']);
|
||||
}
|
||||
|
||||
public function testPatchAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 2,
|
||||
'users' => [1, 5, 4]
|
||||
];
|
||||
$this->request($client, '/api/teams/' . $result['id'], 'PATCH', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
$this->assertNotEmpty($result['id']);
|
||||
self::assertCount(4, $result['users']);
|
||||
}
|
||||
|
||||
public function testDeleteAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
@@ -79,12 +139,443 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
|
||||
$this->request($client, '/api/teams/' . $id, 'DELETE');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$this->assertEquals(Response::HTTP_NO_CONTENT, $client->getResponse()->getStatusCode());
|
||||
self::assertEquals(Response::HTTP_NO_CONTENT, $client->getResponse()->getStatusCode());
|
||||
$this->assertEmpty($client->getResponse()->getContent());
|
||||
|
||||
$this->assertEntityNotFound(User::ROLE_ADMIN, '/api/teams/' . $id);
|
||||
}
|
||||
|
||||
public function testPostMemberAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertCount(1, $result['users']);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/2', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
self::assertCount(2, $result['users']);
|
||||
}
|
||||
|
||||
public function testPostMemberActionErrors()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
'users' => [2]
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
// team not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/999/members/999', 'POST');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Team not found', $json['message']);
|
||||
|
||||
// user not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/999', 'POST');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('User not found', $json['message']);
|
||||
|
||||
// add user
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/5', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
// cannot add existing member
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/5', 'POST');
|
||||
self::assertEquals(Response::HTTP_BAD_REQUEST, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('User is already member of the team', $json['message']);
|
||||
|
||||
// cannot add disabled user
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/3', 'POST');
|
||||
self::assertEquals(Response::HTTP_BAD_REQUEST, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Cannot add disabled user to team', $json['message']);
|
||||
}
|
||||
|
||||
public function testDeleteMemberAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
'users' => [2, 4, 5]
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertCount(4, $result['users']);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/2', 'DELETE');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
self::assertCount(3, $result['users']);
|
||||
}
|
||||
|
||||
public function testDeleteMemberActionErrors()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
'users' => [2, 4, 5]
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
// team not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/999/members/999', 'DELETE');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Team not found', $json['message']);
|
||||
|
||||
// user not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/999', 'DELETE');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('User not found', $json['message']);
|
||||
|
||||
// remove user
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/2', 'DELETE');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
// cannot remove non-member
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/2', 'DELETE');
|
||||
self::assertEquals(Response::HTTP_BAD_REQUEST, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('User is not a member of the team', $json['message']);
|
||||
|
||||
// cannot remove teamlead
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/1', 'DELETE');
|
||||
self::assertEquals(Response::HTTP_BAD_REQUEST, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Cannot remove teamlead', $json['message']);
|
||||
}
|
||||
|
||||
public function testPostCustomerAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertCount(0, $result['customers']);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
self::assertCount(1, $result['customers']);
|
||||
self::assertEquals(1, $result['customers'][0]['id']);
|
||||
}
|
||||
|
||||
public function testPostCustomerActionErrors()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
'users' => [2]
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
// team not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/999/customers/999', 'POST');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Team not found', $json['message']);
|
||||
|
||||
// customer not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/999', 'POST');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Customer not found', $json['message']);
|
||||
|
||||
// add customer
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertCount(1, $result['customers']);
|
||||
|
||||
// cannot add existing customer
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'POST');
|
||||
self::assertEquals(Response::HTTP_BAD_REQUEST, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Team has already access to customer', $json['message']);
|
||||
|
||||
$customer = new Customer();
|
||||
$customer->setName('foooo');
|
||||
$customer->setVisible(false);
|
||||
$customer->setCountry('DE');
|
||||
$customer->setTimezone('Europe/Berlin');
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$em->persist($customer);
|
||||
$em->flush();
|
||||
|
||||
// cannot add invisible customer
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/' . $customer->getId(), 'POST');
|
||||
self::assertEquals(Response::HTTP_BAD_REQUEST, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Cannot grant access to an invisible customer', $json['message']);
|
||||
}
|
||||
|
||||
public function testDeleteCustomerAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
'users' => [2, 4, 5]
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertCount(0, $result['customers']);
|
||||
|
||||
// add customer
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertCount(1, $result['customers']);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'DELETE');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
self::assertCount(0, $result['customers']);
|
||||
}
|
||||
|
||||
public function testDeleteCustomerActionErrors()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
'users' => [2, 4, 5]
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
// team not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/999/customers/999', 'DELETE');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Team not found', $json['message']);
|
||||
|
||||
// customer not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/999', 'DELETE');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Customer not found', $json['message']);
|
||||
|
||||
// cannot remove customer
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'DELETE');
|
||||
self::assertEquals(Response::HTTP_BAD_REQUEST, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Customer is not assigned to the team', $json['message']);
|
||||
}
|
||||
|
||||
public function testPostProjectAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertCount(0, $result['projects']);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
self::assertCount(1, $result['projects']);
|
||||
self::assertEquals(1, $result['projects'][0]['id']);
|
||||
}
|
||||
|
||||
public function testPostProjectActionErrors()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
'users' => [2]
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
// team not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/999/projects/999', 'POST');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Team not found', $json['message']);
|
||||
|
||||
// project not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/999', 'POST');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Project not found', $json['message']);
|
||||
|
||||
// add project
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertCount(1, $result['projects']);
|
||||
|
||||
// cannot add existing project
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'POST');
|
||||
self::assertEquals(Response::HTTP_BAD_REQUEST, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Team has already access to project', $json['message']);
|
||||
|
||||
$customer = new Customer();
|
||||
$customer->setName('foooo');
|
||||
$customer->setVisible(false);
|
||||
$customer->setCountry('DE');
|
||||
$customer->setTimezone('Europe/Berlin');
|
||||
|
||||
$project = new Project();
|
||||
$project->setName('foooo');
|
||||
$project->setVisible(false);
|
||||
$project->setCustomer($customer);
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$em->persist($customer);
|
||||
$em->persist($project);
|
||||
$em->flush();
|
||||
|
||||
// cannot add invisible project
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/' . $project->getId(), 'POST');
|
||||
self::assertEquals(Response::HTTP_BAD_REQUEST, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Cannot grant access to an invisible project', $json['message']);
|
||||
}
|
||||
|
||||
public function testDeleteProjectAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
'users' => [2, 4, 5]
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertCount(0, $result['projects']);
|
||||
|
||||
// add project
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertCount(1, $result['projects']);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'DELETE');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
self::assertCount(0, $result['projects']);
|
||||
}
|
||||
|
||||
public function testDeleteProjectActionErrors()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
'teamlead' => 1,
|
||||
'users' => [2, 4, 5]
|
||||
];
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
// team not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/999/projects/999', 'DELETE');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Team not found', $json['message']);
|
||||
|
||||
// project not found
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/999', 'DELETE');
|
||||
self::assertEquals(Response::HTTP_NOT_FOUND, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Project not found', $json['message']);
|
||||
|
||||
// cannot remove project
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'DELETE');
|
||||
self::assertEquals(Response::HTTP_BAD_REQUEST, $client->getResponse()->getStatusCode());
|
||||
$json = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertEquals('Project is not assigned to the team', $json['message']);
|
||||
}
|
||||
|
||||
protected function assertStructure(array $result, $full = true)
|
||||
{
|
||||
$expectedKeys = [
|
||||
@@ -93,7 +584,7 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
|
||||
if ($full) {
|
||||
$expectedKeys = array_merge($expectedKeys, [
|
||||
'teamlead', 'users'
|
||||
'teamlead', 'users', 'customers', 'projects'
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -101,6 +592,6 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
sort($actual);
|
||||
sort($expectedKeys);
|
||||
|
||||
$this->assertEquals($expectedKeys, $actual, 'Team structure does not match');
|
||||
self::assertEquals($expectedKeys, $actual, 'Team structure does not match');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Tests\API;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
@@ -113,6 +114,106 @@ class UserControllerTest extends APIControllerBaseTest
|
||||
$this->assertStructure($result);
|
||||
}
|
||||
|
||||
public function testPostAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||
$data = [
|
||||
'username' => 'foo',
|
||||
'email' => 'foo@example.com',
|
||||
'avatar' => 'test123',
|
||||
'title' => 'asdfghjkl',
|
||||
'plainPassword' => 'foo@example.com',
|
||||
'enabled' => true,
|
||||
'language' => 'ru',
|
||||
'timezone' => 'Europe/Paris',
|
||||
'roles' => [
|
||||
'ROLE_TEAMLEAD',
|
||||
'ROLE_ADMIN'
|
||||
],
|
||||
];
|
||||
$this->request($client, '/api/users', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
$this->assertNotEmpty($result['id']);
|
||||
self::assertEquals('foo', $result['username']);
|
||||
self::assertEquals('test123', $result['avatar']);
|
||||
self::assertEquals('asdfghjkl', $result['title']);
|
||||
self::assertTrue($result['enabled']);
|
||||
self::assertEquals('ru', $result['language']);
|
||||
self::assertEquals('Europe/Paris', $result['timezone']);
|
||||
self::assertEquals(['ROLE_TEAMLEAD', 'ROLE_ADMIN'], $result['roles']);
|
||||
}
|
||||
|
||||
public function testPostActionWithInvalidUser()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$data = [
|
||||
'username' => 'foo',
|
||||
'email' => 'foo@example.com',
|
||||
'plainPassword' => 'foo@example.com',
|
||||
'enabled' => true,
|
||||
'language' => 'ru',
|
||||
'timezone' => 'Europe/Paris',
|
||||
];
|
||||
$this->request($client, '/api/users', 'POST', [], json_encode($data));
|
||||
$response = $client->getResponse();
|
||||
$this->assertFalse($response->isSuccessful());
|
||||
$this->assertEquals(Response::HTTP_FORBIDDEN, $response->getStatusCode());
|
||||
$json = json_decode($response->getContent(), true);
|
||||
$this->assertEquals('Access denied.', $json['message']);
|
||||
}
|
||||
|
||||
public function testPatchAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||
$data = [
|
||||
'username' => 'foo',
|
||||
'email' => 'foo@example.com',
|
||||
'avatar' => 'test123',
|
||||
'title' => 'asdfghjkl',
|
||||
'plainPassword' => 'foo@example.com',
|
||||
'enabled' => true,
|
||||
'language' => 'ru',
|
||||
'timezone' => 'Europe/Paris',
|
||||
'roles' => [
|
||||
'ROLE_TEAMLEAD',
|
||||
'ROLE_ADMIN'
|
||||
],
|
||||
];
|
||||
$this->request($client, '/api/users', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||
$data = [
|
||||
'avatar' => 'test321',
|
||||
'title' => 'qwertzui',
|
||||
'enabled' => false,
|
||||
'language' => 'it',
|
||||
'timezone' => 'America/New_York',
|
||||
'roles' => [
|
||||
'ROLE_TEAMLEAD',
|
||||
],
|
||||
];
|
||||
$this->request($client, '/api/users/' . $result['id'], 'PATCH', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
$this->assertNotEmpty($result['id']);
|
||||
self::assertEquals('foo', $result['username']);
|
||||
self::assertEquals('test321', $result['avatar']);
|
||||
self::assertEquals('qwertzui', $result['title']);
|
||||
self::assertFalse($result['enabled']);
|
||||
self::assertEquals('it', $result['language']);
|
||||
self::assertEquals('America/New_York', $result['timezone']);
|
||||
self::assertEquals(['ROLE_TEAMLEAD'], $result['roles']);
|
||||
}
|
||||
|
||||
protected function assertStructure(array $result, $full = true)
|
||||
{
|
||||
$expectedKeys = ['id', 'username', 'enabled', 'alias'];
|
||||
|
||||
@@ -57,8 +57,10 @@ class TeamTest extends TestCase
|
||||
self::assertEmpty($customer->getTeams());
|
||||
|
||||
$sut = new Team();
|
||||
self::assertFalse($sut->hasCustomer($customer));
|
||||
$sut->addCustomer($customer);
|
||||
self::assertEquals(1, $sut->getCustomers()->count());
|
||||
self::assertTrue($sut->hasCustomer($customer));
|
||||
$actual = $sut->getCustomers()[0];
|
||||
self::assertSame($actual, $customer);
|
||||
self::assertSame($sut, $customer->getTeams()[0]);
|
||||
@@ -75,8 +77,10 @@ class TeamTest extends TestCase
|
||||
self::assertEmpty($project->getTeams());
|
||||
|
||||
$sut = new Team();
|
||||
self::assertFalse($sut->hasProject($project));
|
||||
$sut->addProject($project);
|
||||
self::assertEquals(1, $sut->getProjects()->count());
|
||||
self::assertTrue($sut->hasProject($project));
|
||||
$actual = $sut->getProjects()[0];
|
||||
self::assertSame($actual, $project);
|
||||
self::assertSame($sut, $project->getTeams()[0]);
|
||||
|
||||
Reference in New Issue
Block a user