feat(appwrite): Schema für Community-Logos, Herzen und Logo-Meldungen

- Setup-Skript: Tabellen `logos`, `logo_likes` (Mehrspalten-Index `user_logo` über userId, logoId)
  und `logo_reports` sowie der Bucket `logo-images` (nur PNG, höchstens 1 MiB); der Kopfkommentar
  nennt die neuen Objekte. Der Index-Request schickt `columns` schon als Liste, dort war nichts zu ändern.
- Tests: DESIRED mit genau diesen Werten, Anlegen samt Indizes gegen den Fake-Server, --check meldet jedes
  fehlende Objekt (51 statt 28) mit Exit 1, bestehende Tabellen und Buckets bleiben unberührt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-04 18:51:36 +00:00
1 parent e1fb632cdd
commit 88abc2061d
2 files changed
+315 -20

No files matched your search

+63 -3
View File
@@ -3,8 +3,9 @@
* AirCard – Appwrite-Schema-Setup (idempotent)
*
* Legt im selbst gehosteten Appwrite (Projekt `aircard`) die Datenbank `aircard`,
* die Tabellen `cards`, `gallery` und `reports` (TablesDB, mit Spalten und
* Indizes) und die Buckets `card-images` und `gallery-images` an.
* die Tabellen `cards`, `gallery`, `reports`, `logos`, `logo_likes` und
* `logo_reports` (TablesDB, mit Spalten und Indizes) und die Buckets
* `card-images`, `gallery-images` und `logo-images` an.
* Reines `fetch`, keine Abhängigkeiten, Node 22+.
*
* Aufruf (vom Repo-Wurzelverzeichnis):
@@ -40,7 +41,7 @@ const str = (key, size, required) => ({ key, type: 'string', size, required });
export const DESIRED = {
database: { id: 'aircard', name: 'AirCard' },
// Reihenfolge ist Ablauf: cards, gallery, reports (je Tabelle: Spalten, dann Indizes).
// Reihenfolge ist Ablauf: cards, gallery, reports, logos, logo_likes, logo_reports (je Tabelle: Spalten, dann Indizes).
tables: [
{
id: 'cards',
@@ -88,6 +89,54 @@ export const DESIRED = {
],
indexes: [{ key: 'gallery', type: 'key', columns: ['galleryId'] }],
},
{
// Community-Logos. Wie `gallery`: Zeilenrechte setzt nur die API, Clients dürfen nichts auf Tabellenebene.
id: 'logos',
name: 'Logos',
permissions: [],
rowSecurity: true,
columns: [
str('name', 40, true),
str('displayName', 30, false),
str('fileId', 36, true),
str('ownerId', 36, true),
{ key: 'mono', type: 'boolean', required: false, default: false },
{ key: 'likeCount', type: 'integer', required: false, default: 0, min: 0 },
{ key: 'hidden', type: 'boolean', required: false, default: false },
{ key: 'reportCount', type: 'integer', required: false, default: 0, min: 0 },
],
indexes: [
{ key: 'owner', type: 'key', columns: ['ownerId'] },
{ key: 'hidden', type: 'key', columns: ['hidden'] },
{ key: 'likes', type: 'key', columns: ['likeCount'] },
],
},
{
// Ein Herz je Konto und Logo: Zeilen-ID hash32("<logoId>|<userId>"), lesen darf nur das Konto selbst; geschrieben wird nur über die API.
id: 'logo_likes',
name: 'Logo Likes',
permissions: [],
rowSecurity: true,
columns: [str('logoId', 36, true), str('userId', 36, true)],
indexes: [
{ key: 'logo', type: 'key', columns: ['logoId'] },
// Mehrspalten-Index: die Reihenfolge der Spalten zählt (und wird verglichen).
{ key: 'user_logo', type: 'key', columns: ['userId', 'logoId'] },
],
},
{
// Nur Admins lesen (Label `admin`); geschrieben wird ausschließlich über die API.
id: 'logo_reports',
name: 'Logo Reports',
permissions: ['read("label:admin")'],
rowSecurity: false,
columns: [
str('logoId', 36, true),
{ key: 'reason', type: 'enum', elements: ['unangemessen', 'kartendaten', 'urheberrecht', 'spam', 'sonstiges'], required: true },
str('reporterHash', 64, true),
],
indexes: [{ key: 'logo', type: 'key', columns: ['logoId'] }],
},
],
buckets: [
{
@@ -110,6 +159,17 @@ export const DESIRED = {
compression: 'none',
antivirus: false,
},
{
// Logo-PNG (höchstens 1 MiB); die Dateirechte setzt die API je Datei.
id: 'logo-images',
name: 'Logo Images',
permissions: ['create("users")'],
fileSecurity: true,
maximumFileSize: 1048576,
allowedFileExtensions: ['png'],
compression: 'none',
antivirus: false,
},
],
};