# Extensions (API-Erweiterungen)

Mit dem Extension-System kannst du die SeminarPlan API um eigene Routen, Cronjobs und Logik erweitern, ohne den Core-Code zu verändern.

# Übersicht

Das Extension-System erlaubt es, die API modular zu erweitern. Extensions können:

  • Neue Routen registrieren (V1 und V2)
  • Bestehende Routen überschreiben (Route Overrides)
  • Cronjobs registrieren
  • Öffentliche Routen definieren (ohne Authentifizierung)
  • Eigene Logik beim Startup ausführen

Extensions werden als Verzeichnisse mit einer plugin.js-Datei angelegt.

# Verzeichnisstruktur

extensions/                  # Standard-Verzeichnis (konfigurierbar)
├── meine-erweiterung/
│   ├── plugin.js            # Extension-Definition (Pflicht)
│   ├── routes/              # Automatisch geladene Routen (optional)
│   │   ├── contacts.js
│   │   └── webhooks.js
│   └── services/            # Eigene Services (optional)
│       └── MyService.js
└── andere-erweiterung/
    └── plugin.js

Das Extensions-Verzeichnis kann per Umgebungsvariable konfiguriert werden:

EXTENSIONS_DIR=/pfad/zu/extensions

# plugin.js -- Extension-Definition

Jede Extension muss eine plugin.js-Datei im Wurzelverzeichnis haben:

module.exports = {
  // Metadaten
  meta: {
    name: 'meine-erweiterung',      // Eindeutiger Name (Pflicht)
    enabled: true,                    // Extension aktiv? (Standard: true)
    priority: 100,                    // Ladereihenfolge (niedriger = früher)
    routesPrefix: 'v1/',              // Prefix für automatisch geladene Routen
  },

  // Öffentliche Routen (kein Token nötig)
  auth: {
    publicRoutes: ['/v1/webhooks/stripe'],
    publicPrefixes: ['/v1/public/custom/'],
  },

  // Bestehende Routen überschreiben
  overrides: [
    { method: 'GET', url: '/api/v2/contacts' },
  ],

  // Cronjobs registrieren
  cron: [
    {
      name: 'nightly-sync',
      cronTime: '0 2 * * *',          // Jeden Tag um 02:00
      onTick: async () => {
        // Synchronisations-Logik
      },
    },
  ],

  // Registrierungs-Funktion (optional)
  register: async (fastify, { app, extension, manager, meta, paths }) => {
    // Eigene Logik, Decorators, Hooks etc.
    fastify.decorate('myService', new MyService());
  },
};

# meta-Felder

Feld Typ Standard Beschreibung
name string Verzeichnisname Eindeutiger Name der Extension
enabled boolean true Extension aktiv/inaktiv
priority number 100 Ladereihenfolge (niedrigere Zahl = früher)
routesPrefix string 'v1/' Prefix für Auto-Loaded Routes

# Routen hinzufügen

# Automatisch (routes-Verzeichnis)

Dateien im routes/-Verzeichnis der Extension werden automatisch mit @fastify/autoload geladen. Das Format ist identisch zu normalen Fastify-Routen:

// extensions/meine-erweiterung/routes/webhooks.js
module.exports = async function (fastify, opts) {
  fastify.post('/webhooks/stripe', {
    schema: {
      body: { type: 'object' },
    },
  }, async (request, reply) => {
    // Webhook-Logik
    return { ok: true };
  });
};

# Manuell (in register)

Du kannst Routen auch direkt in der register-Funktion registrieren:

register: async (fastify, { app }) => {
  fastify.get('/api/v2/custom/stats', async (request) => {
    // Eigene Statistik-Logik
    return { items: [], meta: { total: 0 } };
  });
},

# Route Overrides

Du kannst bestehende Core-Routen ersetzen. Das ist nützlich, wenn du das Verhalten eines Standard-Endpunkts ändern möchtest:

module.exports = {
  meta: { name: 'custom-contacts' },

  // Diese Core-Route wird unterdrückt
  overrides: [
    { method: 'GET', url: '/api/v2/contacts' },
  ],

  register: async (fastify) => {
    // Deine eigene Implementierung
    fastify.get('/api/v2/contacts', async (request) => {
      // Custom-Logik
      return { items: [], meta: { total: 0, limit: 20, offset: 0, hasMore: false } };
    });
  },
};

Vorsicht bei Route Overrides

  • Zwei Extensions können nicht dieselbe Route überschreiben (Fehlermeldung bei Konflikten)
  • Du musst das gleiche Response-Format einhalten, sonst bricht das Frontend
  • Teste Override-Routen gründlich, da sie Core-Funktionalität ersetzen

# Cronjobs

Extensions können zeitgesteuerte Aufgaben registrieren:

cron: [
  // Einfacher Cronjob
  {
    name: 'cleanup-old-data',
    cronTime: '0 3 * * 0',       // Sonntags um 03:00
    onTick: async () => {
      console.log('Bereinigung läuft...');
    },
  },

  // Factory-Funktion (Zugriff auf Fastify-Instanz)
  {
    factory: async ({ app, fastify }) => ({
      name: 'sync-external',
      cronTime: '*/30 * * * *',  // Alle 30 Minuten
      onTick: async () => {
        const db = app.mongo.db;
        // Synchronisations-Logik mit DB-Zugriff
      },
    }),
  },
],

# Öffentliche Routen

Standardmäßig benötigen alle Routen ein gültiges Token. Extensions können Routen als öffentlich markieren:

auth: {
  // Exakte Routen ohne Auth
  publicRoutes: ['/v1/webhooks/stripe', '/v1/webhooks/paypal'],

  // Alle Routen unter diesem Prefix ohne Auth
  publicPrefixes: ['/v1/public/integration/'],
},

# Register-Funktion

Die register-Funktion erhält folgende Parameter:

Parameter Beschreibung
fastify Gekapselte Fastify-Instanz (für Routen, Decorators, Hooks)
app Root Fastify-Instanz (für globale Services)
extension Das vollständige Extension-Objekt
manager Der ExtensionManager (für fortgeschrittene Fälle)
meta Die Metadaten der Extension
paths.dir Verzeichnis der Extension
paths.file Pfad zur plugin.js
paths.routesDir Pfad zum routes/-Verzeichnis (oder null)

# Lebenszyklus

  1. Discover -- ExtensionManager durchsucht das Extensions-Verzeichnis nach plugin.js-Dateien
  2. Load -- Jede Extension wird geladen und validiert
  3. Sort -- Extensions werden nach priority sortiert (niedrig zuerst)
  4. Collect -- Öffentliche Routen und Overrides werden gesammelt
  5. Guard -- Core-Route-Guards werden installiert (überschriebene Routen werden übersprungen)
  6. Register -- Extensions werden der Reihe nach registriert (register-Funktion + Auto-Load Routes)
  7. Cron -- Cronjobs werden materialisiert und gestartet

# Best Practices

Empfehlungen

  • Gib deiner Extension einen eindeutigen, beschreibenden Namen
  • Verwende priority nur wenn die Ladereihenfolge wichtig ist
  • Halte Route Overrides minimal -- erweitere lieber, als zu ersetzen
  • Teste Extensions isoliert, bevor du sie in die Produktion bringst
  • Nutze die register-Funktion für komplexe Initialisierung
  • Dokumentiere öffentliche Routen sorgfältig (Sicherheit!)

# Siehe auch

Last Updated: 4/16/2026, 8:54:41 AM