feat: public profiles, a handle on the account domain with name, bio, links and picture

Stage 1 of the public tier (ADR 0005). A person with an identity has a
public profile: a handle by Mastodon's rules (1 to 30 letters, digits or
underscores, unique whatever its case, a few kept for the instance),
changeable until they first publish, on the instance's account domain
(ACCOUNT_DOMAIN, else the server's host; the instance info names it), so
`raisondetredev@halkyon.eu`; a display name, a bio, up to four links and
an avatar. The table is server only: the app gets a Profile without the
person's id. ProfileEndpoint (identified only) reads, saves and removes
the caller's own; ProfilesEndpoint finds anyone's by handle.

An avatar is a PNG of at most 1 MB that the app draws anew from the
picture chosen, its middle square at 400 pixels, so a photo's metadata
never leaves the device. The server keeps it in the public storage named
by its content, so its URL tells nothing about whose it is, and deletes it
once no profile shows it. Deleting a person deletes their profile and its
picture.

In the app, the Profile tab holds the profile as a form, laid out as the
old app's; anonymous people are told GitHub sign-in gives them one.

Migration 20261009184924904-public-profiles creates public_profile with a
unique index on the person and one on the handle in lower case; it touches
no other table, and the old server never sees it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-09 20:55:16 +02:00
commit 7f6147d689
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1397,6 +1397,133 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get profileSwitchConfirm => 'Wechseln';
@override
String get publicProfileTitle => 'Öffentliches Profil';
@override
String get publicProfileIntro =>
'Wie andere dich sehen, sobald du veröffentlichst: das Handle, unter dem sie dich finden, dein Name, ein paar Worte und Links. Nichts davon ist sichtbar, bevor du es speicherst.';
@override
String get publicProfileAnonymousHint =>
'Melde dich mit GitHub an, um ein öffentliches Profil zu haben und zu veröffentlichen.';
@override
String get publicProfileDisplayName => 'Anzeigename';
@override
String get publicProfileDisplayNameHint => 'Der Name neben deinem Handle';
@override
String get publicProfileHandle => 'Handle';
@override
String get publicProfileHandleHint =>
'Buchstaben, Ziffern und Unterstriche, bis zu 30';
@override
String publicProfileAddress(String address) {
return 'Andere finden dich als @$address';
}
@override
String get publicProfileHandleFixed =>
'Fest, seit du veröffentlicht hast: Andere verweisen darauf.';
@override
String get publicProfileBio => 'Über dich';
@override
String get publicProfileLinks => 'Links';
@override
String get publicProfileLinksHint =>
'Deine Website oder andere Konten, bis zu 4.';
@override
String get publicProfileLinkHint => 'https://…';
@override
String get publicProfileAddLink => 'Link hinzufügen';
@override
String get publicProfileRemoveLink => 'Link entfernen';
@override
String get publicProfileSaved => 'Profil gespeichert.';
@override
String get publicProfileRemove => 'Profil entfernen';
@override
String get publicProfileRemoveTitle => 'Dein öffentliches Profil entfernen?';
@override
String get publicProfileRemoveMessage => 'Dein Handle wird für andere frei.';
@override
String get publicProfileRemoved => 'Profil entfernt.';
@override
String get publicProfileFailed =>
'Die Instanz ist nicht erreichbar. Versuche es erneut.';
@override
String get publicProfileUnreachable =>
'Dein öffentliches Profil ist gerade nicht erreichbar.';
@override
String get publicProfileRetry => 'Erneut versuchen';
@override
String get profileProblemHandleInvalid =>
'Ein Handle besteht aus 1 bis 30 Buchstaben (a bis z), Ziffern oder Unterstrichen.';
@override
String get profileProblemHandleReserved =>
'Dieses Handle ist der Instanz vorbehalten.';
@override
String get profileProblemHandleTaken =>
'Dieses Handle hat schon jemand anderes.';
@override
String get profileProblemPublished =>
'Du hast veröffentlicht: Dein Handle kann sich nicht mehr ändern und dein Profil nicht mehr gehen.';
@override
String get profileProblemDisplayNameTooLong => 'Höchstens 30 Zeichen.';
@override
String get profileProblemBioTooLong => 'Höchstens 500 Zeichen.';
@override
String get profileProblemTooManyLinks => 'Höchstens 4 Links.';
@override
String get profileProblemLinkInvalid =>
'Links beginnen mit https:// oder http://.';
@override
String get profileProblemAvatarInvalid =>
'Dieses Bild lässt sich nicht verwenden: Wähle ein PNG-, JPEG-, WebP- oder GIF-Bild.';
@override
String get publicProfilePicture => 'Bild';
@override
String get publicProfileChoosePicture => 'Bild wählen';
@override
String get publicProfileRemovePicture => 'Bild entfernen';
@override
String get publicProfilePictureType => 'Bilder';
@override
String get publicProfilePictureLater =>
'Speichere dein Profil, um ein Bild hinzuzufügen.';
@override
String get settingsTitle => 'Einstellungen';