feat(api): Benutzernamen (Profil-Tabelle, /api/profil, Pflicht beim Hochladen und Veröffentlichen)
- Schema: Tabelle `profiles` (Zeilen-ID = Konto-ID, `username` und `usernameKey`
je 20 Zeichen, Unique-Index `username_key` auf `usernameKey`, rowSecurity an,
keine Tabellenrechte) im Setup-Skript.
- Neues Modul `deploy/api/profiles.js`: `POST /api/profil` legt das Profil an
oder ändert es (Zeilenrecht genau read("any"), Sperre je Konto). Muster,
Sperrliste und Schlüssel (`usernameKey`) stehen in `common.js`; vergeben ->
409 `usernameTaken`, auch wenn erst der Unique-Index bei einem Rennen
entscheidet. Dazu die MSG-Texte `usernameInvalid`, `usernameReserved`,
`usernameTaken`, `usernameRequired`.
- `POST /api/logos/hochladen` und `POST /api/veroeffentlichen` antworten 403
`usernameRequired`, wenn das Konto kein Profil hat (`hasProfile`, nach der
Anmeldung und vor allen anderen Prüfungen).
- Fake-Appwrite: Tabelle `profiles` mit Unique-Index (409), `addProfile`,
`holdNext`. API-Dienst besteht jetzt aus sechs Dateien (READMEs).
- Client: die vier API-Texte in `API_MESSAGES` und `en.ts` (Gleichlauf mit MSG).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a65c075004
commit
19c8151c7e
15 files changed
+980
-53
No files matched your search
+61
-2
@@ -1,8 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Gemeinsame Helfer des API-Dienstes `aircard-api` für die Karten (app.js) und die Logos (logos.js): Fehlertexte und
|
||||
* -codes, Prüfungen, Rechte-Strings, Melder-Hash, Bildmaße. Nur reine Funktionen und Konstanten, kein Zustand.
|
||||
* Gemeinsame Helfer des API-Dienstes `aircard-api` für die Karten (app.js), die Logos (logos.js) und die Benutzernamen
|
||||
* (profiles.js): Fehlertexte und -codes, Prüfungen, Rechte-Strings, Melder-Hash, Bildmaße, Muster und Sperrliste der Benutzernamen.
|
||||
* Nur reine Funktionen und Konstanten, kein Zustand.
|
||||
*
|
||||
* Fehlerantworten sind immer {error: <deutscher Text>, code: <Schlüssel in MSG>}. Texte ohne festen Schlüssel (dynamische
|
||||
* Prüftexte wie „Der Titel ist zu lang (höchstens 60 Zeichen).“) haben bei Status 400 den code `badRequest`, sonst `error`.
|
||||
@@ -23,6 +24,41 @@ const IMAGE_HEAD_BYTES = 64;
|
||||
const REASONS = ['unangemessen', 'kartendaten', 'urheberrecht', 'spam', 'sonstiges'];
|
||||
|
||||
const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,35}$/;
|
||||
/** Benutzername: 3-20 Zeichen, das erste ein Buchstabe oder eine Zahl, danach Buchstaben, Zahlen, Punkt, Unterstrich, Bindestrich (nur ASCII). */
|
||||
const USERNAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{2,19}$/;
|
||||
/**
|
||||
* Gesperrte Benutzernamen als Schlüssel (klein geschrieben, siehe `usernameKey`): Rollen, der Dienst selbst und Platzhalter, die nach
|
||||
* einem Konto aussehen. Jeder Eintrag passt zu USERNAME_PATTERN (sonst wäre er wirkungslos: der Name scheiterte schon als ungültig).
|
||||
*/
|
||||
const RESERVED_USERNAMES = Object.freeze([
|
||||
'admin',
|
||||
'administrator',
|
||||
'root',
|
||||
'system',
|
||||
'support',
|
||||
'moderator',
|
||||
'mod',
|
||||
'aircard',
|
||||
'aircardios',
|
||||
'aircard-ios',
|
||||
'aircard.ios',
|
||||
'aircard_ios',
|
||||
'webklar',
|
||||
'anonym',
|
||||
'anonymous',
|
||||
'null',
|
||||
'undefined',
|
||||
'team',
|
||||
'staff',
|
||||
'official',
|
||||
'offiziell',
|
||||
'hilfe',
|
||||
'help',
|
||||
'kontakt',
|
||||
'contact',
|
||||
'info',
|
||||
]);
|
||||
const RESERVED_USERNAME_SET = new Set(RESERVED_USERNAMES);
|
||||
// Steuerzeichen (C0, DEL, C1), Nullbreiten- und Bidi-Zeichen (U+200B-200F, U+202A-202E, U+2060-206F) und das BOM (U+FEFF):
|
||||
// sie sind unsichtbar, ordnen Text um oder lassen Titel leer aussehen. Dasselbe Muster steht in publish-dialog.ts.
|
||||
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff]/;
|
||||
@@ -63,6 +99,12 @@ const MSG = {
|
||||
tooManyLogos: `Du hast schon ${MAX_LOGOS} Logos hochgeladen. Lösche eins, um ein neues hochzuladen.`,
|
||||
badHeart: 'Ungültige Angabe zum Herz.',
|
||||
badMono: 'Ungültige Angabe zu „einfarbig“.',
|
||||
// Benutzernamen
|
||||
usernameInvalid:
|
||||
'Der Benutzername muss 3–20 Zeichen lang sein, mit einem Buchstaben oder einer Zahl beginnen und darf nur Buchstaben, Zahlen, Punkt, Unterstrich und Bindestrich enthalten.',
|
||||
usernameReserved: 'Dieser Benutzername ist nicht erlaubt.',
|
||||
usernameTaken: 'Dieser Benutzername ist schon vergeben.',
|
||||
usernameRequired: 'Bitte wähle zuerst einen Benutzernamen.',
|
||||
tooLarge: 'Die Anfrage ist zu groß.',
|
||||
jsonOnly: 'Nur application/json wird unterstützt.',
|
||||
unavailable: 'Der Dienst ist gerade nicht erreichbar.',
|
||||
@@ -97,6 +139,19 @@ function isId(value) {
|
||||
return typeof value === 'string' && ID_PATTERN.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Der Schlüssel eines Benutzernamens (Spalte `usernameKey`): der Name klein geschrieben. Über ihn ist ein Name nur einmal
|
||||
* vergeben, egal wie er geschrieben ist („Kenso“ und „kenso“ sind derselbe). Für Zeichenketten, die schon USERNAME_PATTERN erfüllen.
|
||||
*/
|
||||
function usernameKey(name) {
|
||||
return name.toLowerCase();
|
||||
}
|
||||
|
||||
/** Steht der Schlüssel dieses Namens auf der Sperrliste? (In jeder Schreibweise: „Admin“, „AirCard-IOS“.) */
|
||||
function isReservedUsername(name) {
|
||||
return RESERVED_USERNAME_SET.has(usernameKey(name));
|
||||
}
|
||||
|
||||
function assertOwner(owner) {
|
||||
if (!isId(owner)) throw new TypeError('Ungültige Nutzer-ID');
|
||||
return owner;
|
||||
@@ -309,11 +364,15 @@ module.exports = {
|
||||
IMAGE_HEAD_BYTES,
|
||||
REASONS,
|
||||
ID_PATTERN,
|
||||
USERNAME_PATTERN,
|
||||
RESERVED_USERNAMES,
|
||||
CONTROL_CHARACTERS,
|
||||
MSG,
|
||||
codeFor,
|
||||
HttpError,
|
||||
isId,
|
||||
usernameKey,
|
||||
isReservedUsername,
|
||||
isPlainObject,
|
||||
isDuplicate,
|
||||
parseJsonObject,
|
||||
|
||||
Reference in new issue
Block a user