Skip to content

Commit d7683ef

Browse files
committed
Step 2 - Implement public and private file uploads, starabe, and File entity
1 parent 090b37a commit d7683ef

40 files changed

+988
-38
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ cs-check: sync-env ## cs-check
129129
phpstan: sync-env ## phpstan
130130
docker compose exec back composer -- run phpstan
131131

132+
.PHONY: backlint
133+
backlint: sync-env ## phpstan
134+
docker compose exec back composer -- run lint-all
135+
132136
.PHONY: frontlint
133137
frontlint: sync-env ## lint front (fix)
134138
docker compose exec front yarn lint --fix

apps/back/config/packages/security.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ security:
4545
- ROLE_RIGHT_USER_CREATE
4646
- ROLE_RIGHT_USER_DELETE
4747
- ROLE_RIGHT_USER_UPDATE
48+
- ROLE_RIGHT_COMPANY_READ
49+
- ROLE_RIGHT_COMPANY_CREATE
50+
- ROLE_RIGHT_COMPANY_DELETE
51+
- ROLE_RIGHT_COMPANY_UPDATE
4852
ROLE_USER:
4953
- ROLE_RIGHT_ACCESS
5054

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20231005121328 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return '';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
// this up() migration is auto-generated, please modify it to your needs
23+
$this->addSql('CREATE TABLE company (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
24+
$this->addSql('ALTER TABLE user ADD company_id INT DEFAULT NULL');
25+
$this->addSql('ALTER TABLE user ADD CONSTRAINT FK_8D93D649979B1AD6 FOREIGN KEY (company_id) REFERENCES company (id)');
26+
$this->addSql('CREATE INDEX IDX_8D93D649979B1AD6 ON user (company_id)');
27+
}
28+
29+
public function down(Schema $schema): void
30+
{
31+
// this down() migration is auto-generated, please modify it to your needs
32+
$this->addSql('ALTER TABLE user DROP FOREIGN KEY FK_8D93D649979B1AD6');
33+
$this->addSql('DROP TABLE company');
34+
$this->addSql('DROP INDEX IDX_8D93D649979B1AD6 ON user');
35+
$this->addSql('ALTER TABLE user DROP company_id');
36+
}
37+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20231006041542 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return '';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
// this up() migration is auto-generated, please modify it to your needs
23+
$this->addSql('ALTER TABLE company ADD identity_file VARCHAR(255) DEFAULT NULL');
24+
$this->addSql('ALTER TABLE user ADD profile_picture VARCHAR(255) DEFAULT NULL');
25+
}
26+
27+
public function down(Schema $schema): void
28+
{
29+
// this down() migration is auto-generated, please modify it to your needs
30+
$this->addSql('ALTER TABLE company DROP identity_file');
31+
$this->addSql('ALTER TABLE user DROP profile_picture');
32+
}
33+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20231015065234 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return '';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
// this up() migration is auto-generated, please modify it to your needs
23+
$this->addSql('CREATE TABLE file (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, private TINYINT(1) NOT NULL, storage_path VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
24+
$this->addSql('ALTER TABLE company ADD indentity_file_id INT NOT NULL');
25+
$this->addSql('ALTER TABLE company ADD CONSTRAINT FK_4FBF094FDB804B4A FOREIGN KEY (indentity_file_id) REFERENCES file (id)');
26+
$this->addSql('CREATE UNIQUE INDEX UNIQ_4FBF094FDB804B4A ON company (indentity_file_id)');
27+
$this->addSql('ALTER TABLE user ADD profile_picture_id INT DEFAULT NULL, DROP profile_picture, CHANGE roles roles JSON NOT NULL COMMENT \'(DC2Type:json)\'');
28+
$this->addSql('ALTER TABLE user ADD CONSTRAINT FK_8D93D649292E8AE2 FOREIGN KEY (profile_picture_id) REFERENCES file (id)');
29+
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649292E8AE2 ON user (profile_picture_id)');
30+
}
31+
32+
public function down(Schema $schema): void
33+
{
34+
// this down() migration is auto-generated, please modify it to your needs
35+
$this->addSql('ALTER TABLE company DROP FOREIGN KEY FK_4FBF094FDB804B4A');
36+
$this->addSql('ALTER TABLE user DROP FOREIGN KEY FK_8D93D649292E8AE2');
37+
$this->addSql('DROP TABLE file');
38+
$this->addSql('DROP INDEX UNIQ_4FBF094FDB804B4A ON company');
39+
$this->addSql('ALTER TABLE company ADD identity_file VARCHAR(255) DEFAULT NULL, DROP indentity_file_id');
40+
$this->addSql('DROP INDEX UNIQ_8D93D649292E8AE2 ON user');
41+
$this->addSql('ALTER TABLE user ADD profile_picture VARCHAR(255) DEFAULT NULL, DROP profile_picture_id, CHANGE roles roles JSON NOT NULL COMMENT \'(DC2Type:json)\'');
42+
}
43+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20231022121013 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return '';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
// this up() migration is auto-generated, please modify it to your needs
23+
$this->addSql('ALTER TABLE company CHANGE indentity_file_id indentity_file_id INT DEFAULT NULL');
24+
}
25+
26+
public function down(Schema $schema): void
27+
{
28+
// this down() migration is auto-generated, please modify it to your needs
29+
$this->addSql('ALTER TABLE company CHANGE indentity_file_id indentity_file_id INT NOT NULL');
30+
}
31+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Controller;
6+
7+
use App\Dto\Request\Company\CompanyRequestDto;
8+
use App\Entity\Company;
9+
use App\Repository\CompanyRepository;
10+
use App\UseCase\Company\CreateCompany;
11+
use App\UseCase\Company\UpdateCompany;
12+
use Doctrine\ORM\EntityManagerInterface;
13+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
14+
use Symfony\Component\HttpFoundation\File\UploadedFile;
15+
use Symfony\Component\HttpFoundation\JsonResponse;
16+
use Symfony\Component\HttpFoundation\Request;
17+
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
18+
use Symfony\Component\Routing\Annotation\Route;
19+
use Symfony\Component\Security\Http\Attribute\IsGranted;
20+
21+
class CompanyController extends AbstractController
22+
{
23+
public function __construct(
24+
private readonly EntityManagerInterface $entityManager,
25+
private readonly CompanyRepository $companyRepository,
26+
private readonly CreateCompany $createCompany,
27+
private readonly UpdateCompany $updateCompany,
28+
) {
29+
}
30+
31+
#[Route('/companies', name: 'list_companies', methods: ['GET'])]
32+
public function list(): JsonResponse
33+
{
34+
$companies = $this->companyRepository->findAll();
35+
36+
return $this->json($companies, 200, [], ['groups' => Company::GROUP_LIST]);
37+
}
38+
39+
#[Route('/companies/{id}', name: 'get_company', methods: ['GET'])]
40+
#[IsGranted('ROLE_RIGHT_COMPANY_READ')]
41+
public function get(Company $company): JsonResponse
42+
{
43+
return $this->json($company, 200, [], ['groups' => Company::GROUP_DETAILS]);
44+
}
45+
46+
#[Route('/companies', name: 'create_company', methods: ['POST'])]
47+
#[IsGranted('ROLE_RIGHT_COMPANY_CREATE')]
48+
public function create(#[MapRequestPayload] CompanyRequestDto $companyRequestDto, Request $request): JsonResponse
49+
{
50+
$identityFile = $request->files->get('identityFile');
51+
if ($identityFile !== null) {
52+
\assert($identityFile instanceof UploadedFile);
53+
$companyRequestDto->setIndentityFile($identityFile);
54+
}
55+
$company = $this->createCompany->create($companyRequestDto);
56+
$this->entityManager->flush();
57+
58+
return $this->json($company, 200, [], ['groups' => Company::GROUP_DETAILS]);
59+
}
60+
61+
#[Route('/companies/{id}', name: 'update_company', methods: ['POST'])]
62+
#[IsGranted('ROLE_RIGHT_COMPANY_UPDATE')]
63+
public function update(Company $company, #[MapRequestPayload] CompanyRequestDto $companyRequestDto, Request $request): JsonResponse
64+
{
65+
$identityFile = $request->files->get('identityFile');
66+
if ($identityFile !== null) {
67+
\assert($identityFile instanceof UploadedFile);
68+
$companyRequestDto->setIndentityFile($identityFile);
69+
}
70+
$company = $this->updateCompany->update($company, $companyRequestDto);
71+
$this->entityManager->flush();
72+
73+
return $this->json($company, 200, [], ['groups' => Company::GROUP_DETAILS]);
74+
}
75+
}

apps/back/src/Controller/UserController.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44

55
namespace App\Controller;
66

7-
use App\Dto\Request\CreateUserDto;
8-
use App\Dto\Request\UpdateUserDto;
7+
use App\Dto\Request\User\CreateUserDto;
8+
use App\Dto\Request\User\UpdateUserDto;
99
use App\Entity\User;
1010
use App\Repository\UserRepository;
1111
use App\UseCase\User\CreateUser;
1212
use App\UseCase\User\UpdateUser;
1313
use Doctrine\ORM\EntityManagerInterface;
14+
use Symfony\Component\HttpFoundation\File\UploadedFile;
1415
use Symfony\Component\HttpFoundation\JsonResponse;
16+
use Symfony\Component\HttpFoundation\Request;
1517
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
1618
use Symfony\Component\Routing\Annotation\Route;
1719
use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -27,8 +29,13 @@ public function __construct(
2729
}
2830

2931
#[Route('/users', name: 'create_user', methods: ['POST'])]
30-
public function createUser(#[MapRequestPayload] CreateUserDto $userDto): JsonResponse
32+
public function createUser(#[MapRequestPayload] CreateUserDto $userDto, Request $request): JsonResponse
3133
{
34+
$profilePicture = $request->files->get('profilePictureFile');
35+
if ($profilePicture !== null) {
36+
\assert($profilePicture instanceof UploadedFile);
37+
$userDto->setProfilePicture($profilePicture);
38+
}
3239
$user = $this->createUser->createUser($userDto);
3340

3441
$this->entityManager->flush();
@@ -54,10 +61,17 @@ public function getUser(User $user): JsonResponse
5461
]);
5562
}
5663

57-
#[Route('/users/{id}', name: 'update_user', methods: ['PUT'])]
64+
#[Route('/users/{id}', name: 'update_user', methods: ['POST'])]
5865
#[IsGranted('ROLE_RIGHT_USER_UPDATE')]
59-
public function updateUser(User $user, #[MapRequestPayload] UpdateUserDto $userDto): JsonResponse
66+
public function updateUser(User $user, #[MapRequestPayload] UpdateUserDto $userDto, Request $request): JsonResponse
6067
{
68+
// phpcs:disable Generic.Commenting.Fixme.TaskFound
69+
// FIXME: Using POST instead of PUT here because PUT does not handle multipart form data correctly
70+
$profilePicture = $request->files->get('profilePictureFile');
71+
if ($profilePicture !== null) {
72+
\assert($profilePicture instanceof UploadedFile);
73+
$userDto->setProfilePicture($profilePicture);
74+
}
6175
$user = $this->updateUser->updateUser($user, $userDto);
6276

6377
$this->entityManager->flush();
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Dto\Request\Company;
6+
7+
use Symfony\Component\HttpFoundation\File\UploadedFile;
8+
9+
class CompanyRequestDto
10+
{
11+
private UploadedFile|null $indentityFile = null;
12+
13+
public function __construct(
14+
private readonly string $name,
15+
) {
16+
}
17+
18+
public function getName(): string
19+
{
20+
return $this->name;
21+
}
22+
23+
public function getIndentityFile(): UploadedFile|null
24+
{
25+
return $this->indentityFile;
26+
}
27+
28+
public function setIndentityFile(UploadedFile|null $indentityFile): void
29+
{
30+
$this->indentityFile = $indentityFile;
31+
}
32+
}

apps/back/src/Dto/Request/CreateUserDto.php renamed to apps/back/src/Dto/Request/User/CreateUserDto.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
declare(strict_types=1);
44

5-
namespace App\Dto\Request;
5+
namespace App\Dto\Request\User;
66

77
use Symfony\Component\Validator\Constraints as Assert;
88

99
class CreateUserDto
1010
{
11+
use ProfilePicture;
12+
1113
public function __construct(
1214
#[Assert\Email]
1315
#[Assert\NotBlank]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Dto\Request\User;
6+
7+
use Symfony\Component\HttpFoundation\File\UploadedFile;
8+
9+
trait ProfilePicture
10+
{
11+
private UploadedFile|null $profilePicture = null;
12+
13+
public function getProfilePicture(): UploadedFile|null
14+
{
15+
return $this->profilePicture;
16+
}
17+
18+
public function setProfilePicture(UploadedFile|null $profilePicture): void
19+
{
20+
$this->profilePicture = $profilePicture;
21+
}
22+
}

apps/back/src/Dto/Request/UpdateUserDto.php renamed to apps/back/src/Dto/Request/User/UpdateUserDto.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
declare(strict_types=1);
44

5-
namespace App\Dto\Request;
5+
namespace App\Dto\Request\User;
66

77
use Symfony\Component\Validator\Constraints as Assert;
88

99
class UpdateUserDto
1010
{
11+
use ProfilePicture;
12+
1113
public function __construct(
1214
#[Assert\Email]
1315
private string $email,

0 commit comments

Comments
 (0)