Skip to content

Commit 44ea96e

Browse files
blued-gearmelroy89BentiGorlich
authored
Implement support for custom usernames (#2036)
Co-authored-by: Melroy van den Berg <melroy@melroy.org> Co-authored-by: BentiGorlich <benjamin_g@posteo.de>
1 parent 48ed027 commit 44ea96e

32 files changed

+495
-170
lines changed
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 Version20260224224633 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return 'Add title to user';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
$this->addSql('ALTER TABLE "user" ADD title VARCHAR(255) DEFAULT NULL');
23+
$this->addSql('ALTER TABLE "user" ADD title_ts tsvector GENERATED ALWAYS AS (to_tsvector(\'english\', title)) STORED');
24+
$this->addSql('CREATE INDEX user_title_ts ON "user" USING GIN (title_ts)');
25+
}
26+
27+
public function down(Schema $schema): void
28+
{
29+
$this->addSql('DROP INDEX user_title_ts');
30+
$this->addSql('ALTER TABLE "user" DROP title_ts');
31+
$this->addSql('ALTER TABLE "user" DROP title');
32+
}
33+
}

src/Controller/Api/User/UserUpdateApi.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public function profile(
7070
$dto = $manager->createDto($this->getUserOrThrow());
7171

7272
$dto->about = $deserialized->about;
73+
$dto->title = $deserialized->title;
7374

7475
$user = $manager->edit($this->getUserOrThrow(), $dto);
7576

src/Controller/User/Profile/UserEditController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ private function handleForm(
154154
User $user,
155155
): FormInterface|Response|null {
156156
try {
157-
// Could thrown an error on event handlers (eg. onPostSubmit if a user upload an incorrect image)
157+
// Could throw an error on event handlers (eg. onPostSubmit if a user upload an incorrect image)
158158
$form->handleRequest($request);
159159

160160
if ($form->isSubmitted() && $form->has('currentPassword')) {

src/DTO/UserDto.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\DTO\Contracts\UserDtoInterface;
88
use App\Entity\User;
99
use App\Utils\RegPatterns;
10+
use App\Validator\NoSurroundingWhitespace;
1011
use App\Validator\Unique;
1112
use Symfony\Component\HttpFoundation\Request;
1213
use Symfony\Component\Validator\Constraints as Assert;
@@ -23,6 +24,9 @@ class UserDto implements UserDtoInterface
2324
#[Assert\Length(min: 2, max: self::MAX_USERNAME_LENGTH)]
2425
#[Assert\Regex(pattern: RegPatterns::USERNAME, match: true)]
2526
public ?string $username = null;
27+
#[Assert\Length(min: 2, max: self::MAX_USERNAME_LENGTH)]
28+
#[NoSurroundingWhitespace]
29+
public ?string $title = null;
2630
#[Assert\NotBlank]
2731
#[Assert\Email]
2832
public ?string $email = null;
@@ -99,6 +103,7 @@ public static function create(
99103
?int $reputationPoints = null,
100104
?bool $discoverable = null,
101105
?bool $indexable = null,
106+
?string $title = null,
102107
): self {
103108
$dto = new UserDto();
104109
$dto->id = $id;
@@ -119,6 +124,7 @@ public static function create(
119124
$dto->reputationPoints = $reputationPoints;
120125
$dto->discoverable = $discoverable;
121126
$dto->indexable = $indexable;
127+
$dto->title = $title;
122128

123129
return $dto;
124130
}

src/DTO/UserProfileRequestDto.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@
44

55
namespace App\DTO;
66

7+
use App\Validator\NoSurroundingWhitespace;
78
use OpenApi\Attributes as OA;
9+
use Symfony\Component\Validator\Constraints as Assert;
810

911
#[OA\Schema()]
1012
class UserProfileRequestDto
1113
{
14+
#[Assert\Length(min: 2, max: UserDto::MAX_ABOUT_LENGTH, countUnit: Assert\Length::COUNT_GRAPHEMES)]
1215
public ?string $about = null;
16+
17+
#[Assert\Length(min: 2, max: UserDto::MAX_USERNAME_LENGTH)]
18+
#[NoSurroundingWhitespace]
19+
public ?string $title = null;
1320
}

src/DTO/UserResponseDto.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class UserResponseDto implements \JsonSerializable
1313
public ?ImageDto $avatar = null;
1414
public ?ImageDto $cover = null;
1515
public string $username;
16+
public ?string $title;
1617
public int $followersCount = 0;
1718
public ?string $about = null;
1819
public ?\DateTimeImmutable $createdAt = null;
@@ -41,6 +42,7 @@ public function __construct(UserDto $dto)
4142
{
4243
$this->userId = $dto->getId();
4344
$this->username = $dto->username;
45+
$this->title = $dto->title;
4446
$this->about = $dto->about;
4547
$this->avatar = $dto->avatar;
4648
$this->cover = $dto->cover;
@@ -66,6 +68,7 @@ public function jsonSerialize(): mixed
6668
return [
6769
'userId' => $this->userId,
6870
'username' => $this->username,
71+
'title' => $this->title,
6972
'about' => $this->about,
7073
'avatar' => $this->avatar?->jsonSerialize(),
7174
'cover' => $this->cover?->jsonSerialize(),

src/DTO/UserSmallResponseDto.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class UserSmallResponseDto implements \JsonSerializable
1111
{
1212
public ?int $userId = null;
1313
public ?string $username = null;
14+
public ?string $title = null;
1415
public ?bool $isBot = null;
1516
public ?bool $isFollowedByUser = null;
1617
public ?bool $isFollowerOfUser = null;
@@ -28,6 +29,7 @@ public function __construct(UserDto $dto)
2829
{
2930
$this->userId = $dto->getId();
3031
$this->username = $dto->username;
32+
$this->title = $dto->title;
3133
$this->isBot = $dto->isBot;
3234
$this->isFollowedByUser = $dto->isFollowedByUser;
3335
$this->isFollowerOfUser = $dto->isFollowerOfUser;
@@ -47,6 +49,7 @@ public function jsonSerialize(): mixed
4749
return [
4850
'userId' => $this->userId,
4951
'username' => $this->username,
52+
'title' => $this->title,
5053
'isBot' => $this->isBot,
5154
'isFollowedByUser' => $this->isFollowedByUser,
5255
'isFollowerOfUser' => $this->isFollowerOfUser,

src/Entity/Magazine.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ class Magazine implements VisibilityInterface, ActivityPubActorInterface, ApiRes
5656
public ?Image $banner = null;
5757
#[Column(type: 'string', nullable: false)]
5858
public string $name;
59-
#[Column(type: 'string')]
60-
public ?string $title;
6159
#[Column(type: 'text', length: self::MAX_DESCRIPTION_LENGTH, nullable: true)]
6260
public ?string $description = null;
6361
#[Column(type: 'text', length: self::MAX_RULES_LENGTH, nullable: true)]

src/Entity/Traits/ActivityPubActorTrait.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ trait ActivityPubActorTrait
3939
#[Column(type: 'string', nullable: true)]
4040
public ?string $apPreferredUsername = null;
4141

42+
#[Column(type: 'string')]
43+
public ?string $title = null;
44+
4245
#[Column(type: 'boolean', nullable: true)]
4346
public ?bool $apDiscoverable = null;
4447

src/Entity/User.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#[Table(name: '`user`')]
4646
#[Index(columns: ['visibility'], name: 'user_visibility_idx')]
4747
#[Index(columns: ['username_ts'], name: 'user_username_ts')]
48+
#[Index(columns: ['title_ts'], name: 'user_title_ts')]
4849
#[Index(columns: ['about_ts'], name: 'user_about_ts')]
4950
#[UniqueConstraint(name: 'user_email_idx', columns: ['email'])]
5051
#[UniqueConstraint(name: 'user_username_idx', columns: ['username'])]
@@ -268,6 +269,8 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Visibil
268269
#[Column(type: 'text', nullable: true, insertable: false, updatable: false, options: ['default' => null])]
269270
private ?string $usernameTs;
270271
#[Column(type: 'text', nullable: true, insertable: false, updatable: false, options: ['default' => null])]
272+
private ?string $titleTs;
273+
#[Column(type: 'text', nullable: true, insertable: false, updatable: false, options: ['default' => null])]
271274
private ?string $aboutTs;
272275

273276
#[Column(type: 'enumApplicationStatus', nullable: false, options: ['default' => EApplicationStatus::Approved->value])]

0 commit comments

Comments
 (0)