managarten/apps/uload/docs/database-refactoring-plan.md
Wuesteon ff80aeec1f refactor: restructure
monorepo with apps/ and services/
  directories
2025-11-26 03:03:24 +01:00

8.7 KiB

Database Refactoring Plan - Card System

Aktuelle Problematik

Derzeit existieren 3 separate Collections für Cards:

  • cards - Allgemeine Cards
  • user_cards - User-spezifische Cards
  • card_templates - Vordefinierte Templates

Dies führt zu:

  • Redundanz in der Datenhaltung
  • Komplexität bei Queries
  • Inkonsistenzen zwischen Collections
  • Wartungsprobleme bei Schema-Änderungen

Vorschlag 1: Unified Cards Collection (Empfohlen)

Schema Design

{
  collection: "cards",
  type: "base",
  schema: {
    // Core Fields
    id: "auto",
    user_id: "relation:users",      // null für System-Templates

    // Card Type & Origin
    type: "select",                  // "user" | "template" | "system"
    source: "select",                // "created" | "duplicated" | "imported"
    template_id: "relation:cards",   // Verweis auf Original-Template

    // Card Configuration (Vereinheitlicht!)
    config: "json",                  // Discriminated Union: {mode, ...config}
    metadata: "json",                // Name, description, tags, etc.
    constraints: "json",             // aspectRatio, limits, etc.

    // Organization
    page: "text",                    // Für User-Cards: welche Seite
    position: "number",              // Sortierung
    folder_id: "relation:folders",  // Optional: Ordner-Zuordnung

    // Visibility & Sharing
    visibility: "select",            // "private" | "public" | "unlisted"
    is_featured: "bool",            // Für Template-Store
    allow_duplication: "bool",      // Kann als Template verwendet werden

    // Statistics
    usage_count: "number",          // Wie oft als Template verwendet
    likes_count: "number",          // Für Template-Store

    // Timestamps
    created: "autodate",
    updated: "autodate"
  },

  indexes: [
    "user_id",
    "type",
    "page",
    "visibility",
    "template_id"
  ],

  rules: {
    list: "@request.auth.id = user_id || visibility = 'public'",
    view: "@request.auth.id = user_id || visibility != 'private'",
    create: "@request.auth.id != ''",
    update: "@request.auth.id = user_id",
    delete: "@request.auth.id = user_id && type != 'system'"
  }
}

Vorteile

  1. Eine Quelle der Wahrheit - Alle Cards in einer Collection
  2. Flexibilität - User-Cards können zu Templates werden
  3. Einfache Queries - Keine JOINs zwischen Collections
  4. Konsistenz - Gleiches Schema für alle Card-Typen
  5. Performance - Bessere Indizierung möglich

Migration Strategy

// 1. Neue Collection erstellen
await createUnifiedCardsCollection();

// 2. Daten migrieren
await migrateUserCards(); // user_cards → cards (type: "user")
await migrateTemplates(); // card_templates → cards (type: "template")
await migrateSystemCards(); // cards → cards (vereinheitlichen)

// 3. Alte Collections archivieren
await archiveOldCollections();

Vorschlag 2: Optimierte Separate Collections

Falls eine Trennung gewünscht ist, dann mit klarer Hierarchie:

// 1. card_definitions - Alle Card-Definitionen
{
  collection: "card_definitions",
  fields: [
    { name: "config", type: "json" },       // Card-Konfiguration
    { name: "metadata", type: "json" },     // Meta-Informationen
    { name: "constraints", type: "json" },  // Beschränkungen
    { name: "checksum", type: "text" }      // Für Deduplizierung
  ]
}

// 2. card_instances - Konkrete Verwendungen
{
  collection: "card_instances",
  fields: [
    { name: "definition_id", type: "relation:card_definitions" },
    { name: "user_id", type: "relation:users" },
    { name: "type", type: "select" },       // "personal" | "template"
    { name: "overrides", type: "json" },    // Lokale Anpassungen
    { name: "page", type: "text" },
    { name: "position", type: "number" }
  ]
}

Vorschlag 3: Hybrid-Ansatz mit Smart References

// Haupttabelle für aktive Cards
{
  collection: "cards",
  fields: [
    { name: "user_id", type: "relation" },
    { name: "data", type: "json" },         // Komplette Card-Daten
    { name: "template_ref", type: "text" }, // Optional: Template-ID
    { name: "is_template", type: "bool" },  // Kann als Template genutzt werden
    { name: "cache_key", type: "text" }     // Für Caching
  ]
}

// Separate Template-Registry
{
  collection: "template_registry",
  fields: [
    { name: "card_id", type: "relation:cards" },
    { name: "category", type: "select" },
    { name: "tags", type: "json" },
    { name: "featured", type: "bool" },
    { name: "stats", type: "json" }
  ]
}

Empfohlene Implementierung

Phase 1: Schema-Vereinfachung (1-2 Tage)

// Neue unified cards collection
const unifiedCardSchema = {
	// User & System Fields kombiniert
	user_id: { type: 'relation', required: false }, // null = System-Template
	type: { type: 'select', options: ['user', 'template', 'system'] },

	// Vereinheitlichte Datenstruktur
	data: {
		type: 'json',
		schema: {
			config: CardConfig, // Discriminated Union
			metadata: CardMetadata,
			constraints: CardConstraints
		}
	},

	// Gemeinsame Features
	visibility: { type: 'select', default: 'private' },
	tags: { type: 'json', default: [] },
	stats: { type: 'json', default: {} }
};

Phase 2: Service-Layer Anpassung (1 Tag)

class UnifiedCardService {
	// Vereinfachte API
	async getCard(id: string): Promise<Card> {
		const record = await pb.collection('cards').getOne(id);
		return this.parseCard(record);
	}

	async getUserCards(userId: string): Promise<Card[]> {
		return pb.collection('cards').getList({
			filter: `user_id = "${userId}" && type = "user"`
		});
	}

	async getTemplates(category?: string): Promise<Card[]> {
		return pb.collection('cards').getList({
			filter: `type = "template" && visibility = "public"`
		});
	}

	async createFromTemplate(templateId: string): Promise<Card> {
		const template = await this.getCard(templateId);
		return this.createCard({
			...template,
			type: 'user',
			template_id: templateId,
			user_id: pb.authStore.model?.id
		});
	}
}

Phase 3: Migration (1-2 Tage)

// Migrations-Script
async function migrateToUnifiedCards() {
	// 1. Backup erstellen
	await backupDatabase();

	// 2. User Cards migrieren
	const userCards = await pb.collection('user_cards').getFullList();
	for (const card of userCards) {
		await pb.collection('cards').create({
			user_id: card.user_id,
			type: 'user',
			data: {
				config: card.config || card.modules,
				metadata: card.metadata,
				constraints: card.constraints
			},
			page: card.page,
			position: card.position
		});
	}

	// 3. Templates migrieren
	const templates = await pb.collection('card_templates').getFullList();
	for (const template of templates) {
		await pb.collection('cards').create({
			user_id: null,
			type: 'template',
			data: {
				config: template.config,
				metadata: {
					name: template.name,
					description: template.description,
					category: template.category
				}
			},
			visibility: 'public',
			is_featured: template.featured
		});
	}

	// 4. Alte Collections deaktivieren
	await archiveCollection('user_cards');
	await archiveCollection('card_templates');
}

Vorteile der Vereinheitlichung

1. Weniger Komplexität

  • Eine Collection statt drei
  • Einheitliches Schema
  • Einfachere Queries

2. Bessere Performance

  • Weniger JOINs
  • Effizientere Indizes
  • Besseres Caching

3. Flexibilität

  • User-Cards können Templates werden
  • Templates können personalisiert werden
  • Einfaches Sharing

4. Wartbarkeit

  • Ein Schema zu pflegen
  • Konsistente Validierung
  • Einfachere Backups

Risiken & Mitigation

Risiko Mitigation
Datenverlust bei Migration Vollständiges Backup, Staging-Test
Performance bei großen Datenmengen Indizes optimieren, Pagination
Breaking Changes in API Compatibility Layer während Übergang
Komplexere Permissions Rule-based Access Control

Zeitplan

  • Woche 1: Schema-Design finalisieren, Tests schreiben
  • Woche 2: Migration implementieren, Staging-Test
  • Woche 3: Production-Migration, Monitoring
  • Woche 4: Alte Collections entfernen, Cleanup

Metriken für Erfolg

  • Reduzierung der Datenbankgröße um ~20%
  • Query-Performance Verbesserung um ~30%
  • Weniger Code (-40% in Services)
  • Einfachere API für Frontend
  • Bessere Testbarkeit

Fazit

Empfehlung: Vorschlag 1 - Unified Cards Collection

Dies bietet die beste Balance aus:

  • Einfachheit
  • Performance
  • Flexibilität
  • Zukunftssicherheit

Die Migration ist überschaubar und die Vorteile überwiegen deutlich die einmaligen Migrationskosten.