Zum Inhalt

🔌 Connector Template System

Übersicht

Dieses System ermöglicht es, sichere und flexible Connectors für verschiedene Datenquellen zu erstellen: - ✅ Sichere Credentials - Verschlüsselt im Google Secret Manager - ✅ Flexible Templates - Einfach pro Kunde anpassbar - ✅ Type-Safe - TypeScript/Dart Typen für Credentials - ✅ Audit Logging - Alle Zugriffe werden geloggt - ✅ Auto-Scheduling - Cron-basierte automatische Ausführung


🎯 Architektur

┌─────────────────────────────────────────────────────────────┐
│                    FLUTTER APP (Client)                      │
│                                                              │
│  ┌────────────────────────────────────────────────────┐    │
│  │  ConnectorSetupWizard (3 Steps)                    │    │
│  │  1. Type & Name                                     │    │
│  │  2. Credentials (type-based form)                  │    │
│  │  3. Schedule                                        │    │
│  └────────────────────────────────────────────────────┘    │
│                          │                                   │
│                          ▼                                   │
│  ┌────────────────────────────────────────────────────┐    │
│  │  Cloud Function: storeConnectorCredentials()      │    │
│  │  → Secret Manager (AES-256 encrypted)             │    │
│  │  → Firestore (nur Metadata)                        │    │
│  └────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│              GOOGLE SECRET MANAGER                           │
│  connector-abc123-credentials: {clientId, clientSecret}     │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│         CLOUD FUNCTIONS (Custom Templates)                   │
│  - business_central_template.js                             │
│  - rest_api_template.js                                     │
│  - ftp_excel_template.js                                    │
│  - database_template.js (TODO)                              │
└─────────────────────────────────────────────────────────────┘

🚀 Quick Start

1. Connector in Flutter App erstellen

import 'package:easy_sale_erp/pages/settings/connector/connector_setup_wizard.dart';

// Wizard öffnen
showDialog(
  context: context,
  builder: (context) => const ConnectorSetupWizard(
    preselectedType: ConnectorType.businessCentral, // Optional
  ),
);

2. Template für Kunden erstellen

# Template kopieren
cp functions/templates/business_central_template.js \
   functions/connectors/customer_acme_bc_import.js

# Anpassen
nano functions/connectors/customer_acme_bc_import.js

Anpassungen:

// 1. Connector ID (aus Firestore nach Erstellung)
const CONNECTOR_ID = 'abc123xyz';

// 2. Tenant ID
const TENANT_ID = 'customer_acme';

// 3. Schedule
const SCHEDULE = '0 2 * * *'; // Täglich um 2 Uhr

// 4. Transform-Funktion
function transformProduct(bcProduct) {
  return {
    id: bcProduct.no,
    articleNumber: bcProduct.no,
    name: bcProduct.description,
    // KUNDE-SPEZIFISCH:
    customField: bcProduct.specialField,
  };
}

3. Deployen

cd functions
npm install
firebase deploy --only functions:customer_acme_bc_import

📦 Verfügbare Templates

1. Business Central Template

File: functions/templates/business_central_template.js

Use Case: Microsoft Dynamics 365 Business Central Import

Credentials: - tenantId - Azure AD Tenant ID - clientId - App Registration Client ID - clientSecret - App Registration Secret - environment - production/sandbox - companyId - BC Company GUID

Funktionen: - importProducts() - Importiert Artikel - importCustomers() - Importiert Kunden - getBCAccessToken() - OAuth2 Token - fetchBCData() - Generic BC API Wrapper

Beispiel:

// functions/connectors/customer_123_bc_import.js
const CONNECTOR_ID = 'abc123';
const TENANT_ID = 'customer_123';
const SCHEDULE = '0 2 * * *';

function transformProduct(bcProduct) {
  return {
    id: bcProduct.no,
    name: bcProduct.description,
    price: bcProduct.unitPrice,
    // Kunde-spezifisch
  };
}


2. REST API Template

File: functions/templates/rest_api_template.js

Use Case: Generische REST APIs (Shopify, WooCommerce, Custom APIs)

Credentials: - apiUrl - Base URL (z.B. https://api.example.com) - authType - Basic Auth, Bearer Token, API Key, OAuth2 - authValue - Token/Key/Credentials

Funktionen: - createApiClient() - Authenticated Axios Client - fetchFromApi() - Generic API Wrapper - importProducts(), importCustomers(), importOrders()

Unterstützt verschiedene Auth-Methoden:

switch (credentials.authType) {
  case 'Bearer Token':
    headers['Authorization'] = `Bearer ${credentials.authValue}`;
  case 'API Key':
    headers['X-API-Key'] = credentials.authValue;
  case 'Basic Auth':
    headers['Authorization'] = `Basic ${btoa(credentials.authValue)}`;
}


3. FTP/Excel Template

File: functions/templates/ftp_excel_template.js

Use Case: Excel-Import von FTP/SFTP Server

Credentials: - protocol - FTP oder SFTP - host - FTP Server (z.B. ftp.example.com) - port - 21 (FTP) oder 22 (SFTP) - username - FTP Username - password - FTP Password - remotePath - Pfad zur Datei (z.B. /exports/) - filePattern - Dateiname-Pattern (z.B. products_*.xlsx)

Funktionen: - connectFTP() - FTP/SFTP Client - findLatestFile() - Neueste Datei nach Pattern - downloadFile() - Download von FTP - parseExcelFile() - XLSX zu JSON - mapExcelRow() - Excel-Zeile zu internem Format

Excel-Konfiguration:

const EXCEL_CONFIG = {
  sheetName: 'Products', // oder null für erste
  headerRow: 1,
  startRow: 2,
  deleteAfterImport: false,
};

function mapExcelRow(row, rowNumber) {
  return {
    id: row['Artikelnummer'],
    name: row['Produktname'],
    price: parseFloat(row['Preis']),
    // Kunde-spezifisch
  };
}


4. Database Template (TODO)

File: functions/templates/database_template.js

Use Case: Direkter Datenbankzugriff (MySQL, PostgreSQL, MSSQL)

Credentials: - dbType - MySQL, PostgreSQL, MSSQL - host - Datenbank-Host - port - Port (3306, 5432, 1433) - database - Datenbankname - username - DB-User - password - DB-Password


🛠️ Shared Utils

connector_utils.js

Credentials laden:

const { getCredentials } = require('../utils/connector_utils');

const credentials = await getCredentials('connector-id');
// { clientId: '...', clientSecret: '...', ... }

In Firestore speichern:

const { saveToFirestore } = require('../utils/connector_utils');

await saveToFirestore('customer_123', 'articles', {
  id: 'P123',
  name: 'Produkt',
  price: 99.99,
});

Batch-Speicherung:

const { batchSaveToFirestore } = require('../utils/connector_utils');

const products = [/* ... */];
const saved = await batchSaveToFirestore(
  'customer_123',
  'articles',
  products,
  'id' // ID-Field
);

Logging:

const { logConnectorExecution } = require('../utils/connector_utils');

await logConnectorExecution(
  'connector-id',
  'success', // oder 'error'
  150 // Records processed
);

Error Handling:

const { withErrorHandling } = require('../utils/connector_utils');

exports.myFunction = onSchedule({
  schedule: '0 2 * * *',
}, withErrorHandling(async () => {
  // Errors werden automatisch geloggt und Admins benachrichtigt
  await importData();
}));


🔒 Security Best Practices

✅ DO's

  1. Credentials NUR im Secret Manager

    // ✅ RICHTIG
    const credentials = await getCredentials(connectorId);
    
    // ❌ FALSCH
    const clientSecret = 'hardcoded-secret';
    

  2. Nur Server-seitig verwenden

    // ✅ Cloud Functions = OK
    exports.importData = onSchedule(...);
    
    // ❌ Flutter Client = NICHT OK
    // Credentials niemals im Client laden!
    

  3. Audit Logging aktivieren

    // ✅ Automatisch in getCredentials()
    await logCredentialAccess(connectorId, 'read', true);
    

  4. Error Handling

    // ✅ withErrorHandling sendet Alerts an Admins
    withErrorHandling(async () => { ... });
    

❌ DON'Ts

  1. Niemals Credentials in Firestore (Client-lesbar)

    // ❌ FALSCH
    await firestore.collection('connectors').doc(id).set({
      clientSecret: '...' // NIEMALS!
    });
    

  2. Niemals Credentials im Code

    // ❌ FALSCH
    const clientSecret = '6el8Q~...';
    

  3. Niemals Credentials im Git

    # ❌ FALSCH
    git add functions/credentials.json
    
    # ✅ RICHTIG - .gitignore
    echo "*.json" >> functions/.gitignore
    


📊 Monitoring & Debugging

Logs ansehen

# Alle Logs
firebase functions:log

# Spezifische Function
firebase functions:log --only customer_acme_bc_import

# Live-Logs
firebase functions:log --follow

Audit Logs (Firestore)

// Collection: auditLogs
{
  type: 'credential_access',
  connectorId: 'abc123',
  action: 'read',
  success: true,
  timestamp: Timestamp,
}

// Collection: connectorLogs
{
  connectorId: 'abc123',
  status: 'success', // 'error', 'warning'
  recordsProcessed: 150,
  error: null,
  timestamp: Timestamp,
}

Dashboard (Firestore)

// Collection: connectors
{
  id: 'abc123',
  name: 'ACME BC Import',
  type: 'businessCentral',
  isActive: true,
  lastRun: Timestamp,
  lastRunStatus: 'success',
  lastRunRecords: 150,
}

🧪 Testing

Connection Test (Flutter)

final functions = FirebaseFunctions.instanceFor(region: 'europe-west1');
final result = await functions
  .httpsCallable('testConnectorConnection')
  .call({'connectorId': 'abc123'});

if (result.data['success']) {
  print('✅ Connection OK');
} else {
  print('❌ Error: ${result.data['error']}');
}

Manual Trigger (Flutter)

final result = await functions
  .httpsCallable('manualBCImport')
  .call({});

print('Imported ${result.data['products']} products');

Dry Run (Template anpassen)

// In Template hinzufügen
exports.dryRun = onCall(async (request) => {
  const credentials = await getCredentials(CONNECTOR_ID);
  const products = await fetchBCProducts(credentials);

  // NICHT speichern, nur Preview
  return {
    wouldImport: products.length,
    sampleData: products.slice(0, 5),
  };
});

📦 Dependencies

package.json

{
  "dependencies": {
    "@google-cloud/secret-manager": "^5.0.0",
    "firebase-admin": "^12.0.0",
    "firebase-functions": "^5.0.0",
    "axios": "^1.6.0",
    "basic-ftp": "^5.0.0",
    "xlsx": "^0.18.0",
    "mysql2": "^3.6.0"
  }
}

Installation

cd functions
npm install

🚀 Deployment

Einzelne Function

firebase deploy --only functions:customer_acme_bc_import

Alle Functions

firebase deploy --only functions

Nur Connector-Management

firebase deploy --only functions:storeConnectorCredentials,functions:updateConnectorCredentials,functions:deleteConnector

💰 Kosten

Google Secret Manager

  • Aktive Secrets: $0.06/Secret/Monat
  • Versionen: $0.03/Version/Monat
  • Zugriffe: $0.03/10.000 Zugriffe

Beispiel (50 Connectors): - 50 Secrets × $0.06 = $3/Monat - 100 Versions × $0.03 = $3/Monat - 50.000 Zugriffe × $0.03/10k = $0.15/Monat - TOTAL: ~$6-7/Monat

Cloud Functions

  • Invocations: $0.40/Million (erste 2M gratis)
  • Compute Time: $0.0000025/GB-second

Beispiel (50 Connectors, täglich): - 50 × 30 Tage = 1.500 Invocations (GRATIS) - Compute: ~$5-10/Monat

GESAMT: ~$15-20/Monat für 50 Connectors


📚 Weitere Dokumentationen


✅ Checkliste für neuen Connector

  • [ ] Connector im Flutter App erstellen (ConnectorSetupWizard)
  • [ ] Connector ID aus Firestore kopieren
  • [ ] Template kopieren und umbenennen
  • [ ] CONNECTOR_ID, TENANT_ID, SCHEDULE anpassen
  • [ ] Transform-Funktionen kunde-spezifisch anpassen
  • [ ] firebase deploy --only functions:...
  • [ ] Connection Test im Dashboard
  • [ ] Manual Trigger Test
  • [ ] Scheduled Run abwarten und Logs prüfen
  • [ ] Firestore Daten validieren

🎉 Fertig! Connector läuft automatisch nach Schedule.

🔌 Connector Template System - Quick Reference

📁 Dateistruktur

easy_sale_erp/
├── lib/
│   ├── models/connector/
│   │   ├── connector_type.dart          # Connector-Typen & Credential-Felder
│   │   ├── connector_config.dart        # Connector-Konfiguration & Schedule
│   │   └── connector_credentials.dart   # (ALT - nicht mehr verwendet)
│   │
│   └── pages/settings/connector/
│       └── connector_setup_wizard.dart  # 3-Step-Wizard UI
├── functions/
│   ├── index.js                         # Main Export (+ Connector Management)
│   ├── connector_management.js          # Store/Update/Delete Credentials
│   │
│   ├── utils/
│   │   └── connector_utils.js           # Shared Utils (getCredentials, etc.)
│   │
│   ├── templates/                       # Templates zum Kopieren
│   │   ├── business_central_template.js
│   │   ├── rest_api_template.js
│   │   └── ftp_excel_template.js
│   │
│   └── connectors/                      # Kunde-spezifische Functions
│       └── customer_acme_bc_import.js   # Beispiel
├── firestore.rules                      # Security Rules (updated)
├── setup_connectors.sh                  # Setup-Script
├── CONNECTOR_TEMPLATE_SYSTEM.md         # Vollständige Dokumentation
└── CONNECTOR_QUICK_REFERENCE.md         # Diese Datei

🚀 Workflow: Neuer Connector

1️⃣ Setup (Einmalig)

# Secret Manager aktivieren & Functions deployen
./setup_connectors.sh

2️⃣ Connector in Flutter erstellen

// lib/main.dart oder settings page
import 'package:easy_sale_erp/pages/settings/connector/connector_setup_wizard.dart';

// Button/Tile zum Öffnen
ElevatedButton(
  onPressed: () {
    showDialog(
      context: context,
      builder: (context) => const ConnectorSetupWizard(),
    );
  },
  child: const Text('Neuer Connector'),
);

Wizard-Schritte: 1. Connector-Typ wählen (BC, Shopify, REST API, FTP/Excel, DB) 2. Name & Credentials eingeben 3. Schedule konfigurieren (täglich, stündlich, manuell) 4. ✅ Erstellen → Credentials landen im Secret Manager

3️⃣ Template kopieren & anpassen

# Beispiel: Business Central
cp functions/templates/business_central_template.js \
   functions/connectors/customer_acme_bc_import.js

# Editor öffnen
nano functions/connectors/customer_acme_bc_import.js

Anpassungen:

// 1. IDs setzen (aus Firestore nach Wizard-Erstellung)
const CONNECTOR_ID = 'abc123xyz';  // connectors/{id}
const TENANT_ID = 'customer_acme';

// 2. Schedule
const SCHEDULE = '0 2 * * *';  // Cron Expression

// 3. Transform-Funktion
function transformProduct(bcProduct) {
  return {
    id: bcProduct.no,
    name: bcProduct.description,
    price: parseFloat(bcProduct.unitPrice),

    // KUNDE-SPEZIFISCH:
    customField: bcProduct.someField,
    calculated: calculateSomething(bcProduct),
  };
}

4️⃣ Deployen

# Einzelne Function
firebase deploy --only functions:scheduledAcmeBCImport

# Oder alle
firebase deploy --only functions

5️⃣ Testen

// Manual Trigger aus Flutter
final functions = FirebaseFunctions.instanceFor(region: 'europe-west1');

final result = await functions
  .httpsCallable('manualAcmeBCImport')
  .call({});

print('Imported ${result.data['products']} products');

6️⃣ Überwachen

# Logs ansehen
firebase functions:log --only scheduledAcmeBCImport

# Live-Logs
firebase functions:log --follow

📋 Verfügbare Connector-Typen

Typ Template Use Case Credentials
Business Central business_central_template.js MS Dynamics 365 tenantId, clientId, clientSecret, environment, companyId
REST API rest_api_template.js Generic APIs, Shopify, WooCommerce apiUrl, authType, authValue
FTP/Excel ftp_excel_template.js Excel-Import via FTP/SFTP host, port, username, password, remotePath, filePattern
Database (TODO) MySQL, PostgreSQL, MSSQL host, port, database, username, password

🔧 Wichtigste Functions

Utils (connector_utils.js)

const {
  getCredentials,           // Lädt Credentials aus Secret Manager
  saveToFirestore,          // Speichert einzelnes Dokument
  batchSaveToFirestore,     // Batch-Speicherung (500 Docs)
  logConnectorExecution,    // Logged Ausführung
  withErrorHandling,        // Error-Middleware (sendet Alerts)
} = require('../utils/connector_utils');

Management (connector_management.js)

// Wird automatisch von Flutter Wizard aufgerufen
exports.storeConnectorCredentials   // Erstellt Connector + Secret
exports.updateConnectorCredentials  // Aktualisiert Credentials
exports.deleteConnector             // Löscht Connector + Secret
exports.testConnectorConnection     // Testet Verbindung

💾 Firestore Collections

connectors/{connectorId}

{
  name: 'ACME BC Import',
  type: 'businessCentral',
  credentialId: 'abc123',
  secretName: 'projects/.../secrets/connector-abc123-credentials',
  isActive: true,
  schedule: {
    type: 'daily',
    cronExpression: '0 2 * * *',
  },
  createdAt: Timestamp,
  createdBy: 'uid',
  lastRun: Timestamp,
  lastRunStatus: 'success',
  lastRunRecords: 150,
}

connectorLogs/{logId}

{
  connectorId: 'abc123',
  status: 'success',  // 'error', 'warning'
  recordsProcessed: 150,
  error: null,
  timestamp: Timestamp,
}

auditLogs/{logId}

{
  type: 'credential_access',
  connectorId: 'abc123',
  action: 'read',
  success: true,
  timestamp: Timestamp,
}

🔒 Security Checklist

  • [x] Credentials nur im Secret Manager (AES-256)
  • [x] Keine Credentials in Firestore (nur Metadata)
  • [x] Firestore Rules: Nur Admins können Connectors erstellen
  • [x] Audit Logs für alle Credential-Zugriffe
  • [x] Error Alerts an SuperAdmins
  • [x] Cloud Functions = Server-only (nie Client)
  • [ ] App Check aktivieren (nach Setup)
  • [ ] Service Account Keys aus Git entfernen
  • [ ] Alte Credentials rotieren

🐛 Troubleshooting

Problem: "Secret not found"

# Prüfe ob Secret existiert
gcloud secrets list --project=YOUR_PROJECT_ID

# Connector ID prüfen
firebase firestore:get connectors/abc123

Problem: "Permission denied"

# Service Account Permissions
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
  --member="serviceAccount:YOUR_PROJECT_ID@appspot.gserviceaccount.com" \
  --role="roles/secretmanager.admin"

Problem: Function timeout

// In Template: timeoutSeconds erhöhen
exports.myFunction = onSchedule({
  schedule: '0 2 * * *',
  timeoutSeconds: 540,  // 9 Minuten (max)
  memory: '1GiB',       // Mehr Memory
}, ...)

Problem: "Too many writes"

// Batch-Speicherung verwenden
await batchSaveToFirestore(tenantId, collection, dataArray);
// Statt einzelner saves in Schleife

📊 Monitoring Dashboard (TODO)

// lib/pages/settings/connector_dashboard.dart
class ConnectorDashboard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: FirebaseFirestore.instance
        .collection('connectors')
        .snapshots(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) return CircularProgressIndicator();

        return ListView.builder(
          itemCount: snapshot.data!.docs.length,
          itemBuilder: (context, index) {
            final connector = snapshot.data!.docs[index];
            return ConnectorCard(
              name: connector['name'],
              type: connector['type'],
              status: connector['lastRunStatus'],
              lastRun: connector['lastRun'],
              records: connector['lastRunRecords'],
            );
          },
        );
      },
    );
  }
}

💰 Kosten (50 Connectors)

Service Kosten/Monat
Secret Manager $3 (Secrets) + $3 (Versions) = $6
Cloud Functions 1.500 Invocations (GRATIS) + Compute = $5-10
Firestore Reads/Writes = $2-5
TOTAL $13-21/Monat


✅ Next Steps

  1. [ ] ./setup_connectors.sh ausführen
  2. [ ] Ersten Connector im Flutter App erstellen
  3. [ ] Template kopieren und anpassen
  4. [ ] Deployen und testen
  5. [ ] Dashboard für Connector-Übersicht bauen
  6. [ ] App Check aktivieren
  7. [ ] Service Account Keys aus Git entfernen

🎉 System ist einsatzbereit!

Schedule Management für Connectors

Übersicht

Das Schedule-Management ermöglicht die vollständige Verwaltung von automatisierten Import-Zeitplänen direkt aus der UI. Cloud Scheduler Jobs werden automatisch erstellt, aktualisiert und gelöscht.

Architektur

Flutter UI (lib/pages/settings/connector/widgets/schedule_edit_dialog.dart)

  • Interaktiver Dialog für Schedule-Bearbeitung
  • 5 Schedule-Typen: Manual, Hourly, Daily, Weekly, Custom
  • Visuelle Auswahl mit Intervall-/Zeitpicker
  • Live-Validation der Cron Expressions

Cloud Functions (functions/scheduler_management.js)

  • createOrUpdateSchedulerJob() - Erstellt/aktualisiert Cloud Scheduler Job
  • deleteSchedulerJob() - Löscht Scheduler Job
  • toggleSchedulerJob() - Pausiert/aktiviert Job
  • generateCronExpression() - Konvertiert Schedule-Objekt zu Cron
  • updateConnectorSchedule - HTTP Callable Function für UI

Integration (functions/connector_management.js)

  • Create: Scheduler Job wird bei Connector-Erstellung erstellt
  • Update: Schedule-Änderungen updaten den Job automatisch
  • Delete: Job wird beim Löschen entfernt
  • Toggle: Active/Pause wird an Scheduler weitergegeben

Verwendung

1. Schedule in UI bearbeiten

// In ConnectorSettingsPage - Schedule-Stat ist anklickbar
InkWell(
  onTap: () => _editSchedule(connector),
  child: _buildMinimalStat(
    context,
    'Schedule',
    connector.schedule.displayName,
    CupertinoIcons.clock,
  ),
)

2. Schedule-Typen

Manual: Nur manuelle Ausführung - Kein Scheduler Job - Button "Manuell starten" in UI

Hourly: Stündliche Ausführung - Intervalle: 15min, 30min, 60min - Cron: */15 * * * *, */30 * * * *, 0 * * * *

Daily: Tägliche Ausführung - Wählbare Uhrzeit (Standard: 02:00) - Cron: 0 2 * * *

Weekly: Wöchentliche Ausführung - Jeden Montag zur gewählten Zeit - Cron: 0 2 * * 1

Custom: Eigene Cron Expression - Für spezielle Anforderungen - Beispiel: 0 */4 * * * (alle 4 Stunden)

3. Cloud Scheduler Job

Der erstellte Job hat folgende Eigenschaften:

{
  name: `connector-${connectorId}`,
  schedule: cronExpression,
  timeZone: 'Europe/Berlin',
  httpTarget: {
    uri: `https://europe-west1-${PROJECT}.cloudfunctions.net/manual${type}Import`,
    httpMethod: 'POST',
    body: { connectorId },
    oidcToken: { serviceAccountEmail }
  }
}

Deployment

1. Dependencies installieren

cd functions
npm install @google-cloud/scheduler

2. IAM Permissions prüfen

# Service Account benötigt:
# - cloudscheduler.jobs.create
# - cloudscheduler.jobs.update
# - cloudscheduler.jobs.delete
# - cloudscheduler.jobs.pause
# - cloudscheduler.jobs.resume

3. Cloud Functions deployen

firebase deploy --only functions:storeConnectorCredentials
firebase deploy --only functions:updateConnectorCredentials
firebase deploy --only functions:updateConnectorSchedule
firebase deploy --only functions:deleteConnector

4. Scheduler API aktivieren

gcloud services enable cloudscheduler.googleapis.com --project=YOUR_PROJECT_ID

Fehlerbehandlung

Scheduler Job Creation Failed

// Nicht kritisch - Job kann manuell erstellt werden
console.error('⚠️ Scheduler job creation failed:', error);
// Connector wird trotzdem erstellt

Job Not Found beim Delete

// Normal wenn Job bereits gelöscht oder nie erstellt wurde
if (error.code === 5) { // NOT_FOUND
  console.log('⚠️ Scheduler job not found');
}

Migration existierender Connectors

Für bereits vorhandene Connectors ohne Scheduler Job:

// Einmalig ausführen
const connectors = await admin.firestore()
  .collection('connectors')
  .where('isActive', '==', true)
  .get();

for (const doc of connectors.docs) {
  const connector = doc.data();
  await createOrUpdateSchedulerJob(
    doc.id,
    connector.schedule,
    connector.type
  );
}

Monitoring

Cloud Scheduler Console

https://console.cloud.google.com/cloudscheduler?project=YOUR_PROJECT_ID

Logs prüfen

# Scheduler Job Ausführungen
gcloud scheduler jobs describe connector-CONNECTOR_ID --location=europe-west1

# Function Logs
gcloud functions logs read manual${Type}Import --region=europe-west1

Testing

Manueller Job-Test

gcloud scheduler jobs run connector-CONNECTOR_ID --location=europe-west1

UI-Test Workflow

  1. Connector erstellen → Scheduler Job wird erstellt
  2. Schedule ändern → Job wird aktualisiert
  3. Connector pausieren → Job wird pausiert
  4. Connector aktivieren → Job wird resumed
  5. Connector löschen → Job wird gelöscht

Cron Expression Referenz

Typ Beispiel Beschreibung
Hourly 0 * * * * Jede volle Stunde
Every 30min */30 * * * * Alle 30 Minuten
Daily 2am 0 2 * * * Täglich um 02:00
Weekly Mon 0 2 * * 1 Montags 02:00
Monthly 0 2 1 * * 1. des Monats 02:00
Custom 0 */6 * * * Alle 6 Stunden

Format: minute hour day month weekday - minute: 0-59 - hour: 0-23 - day: 1-31 - month: 1-12 - weekday: 0-6 (0=Sonntag)

Troubleshooting

"Permission denied" beim Job-Create

# Service Account Role hinzufügen
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
  --member="serviceAccount:YOUR_PROJECT@appspot.gserviceaccount.com" \
  --role="roles/cloudscheduler.admin"

Job läuft nicht

  1. Prüfe isActive Status in Firestore
  2. Prüfe Job State in Cloud Scheduler Console
  3. Prüfe Function Logs für Fehler
  4. Verify OIDC Token Configuration

Timezone Issues

  • Jobs laufen in Europe/Berlin Timezone
  • Firestore Timestamps sind UTC
  • UI zeigt lokale Zeit