🔌 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¶
📦 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¶
-
Credentials NUR im Secret Manager
-
Nur Server-seitig verwenden
-
Audit Logging aktivieren
-
Error Handling
❌ DON'Ts¶
-
Niemals Credentials in Firestore (Client-lesbar)
-
Niemals Credentials im Code
-
Niemals Credentials im Git
📊 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¶
🚀 Deployment¶
Einzelne Function¶
Alle 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)¶
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 |
📚 Weiterführende Links¶
- Vollständige Dokumentation
- Security Best Practices
- Google Secret Manager Docs
- Firebase Functions v2 Docs
✅ Next Steps¶
- [ ]
./setup_connectors.shausführen - [ ] Ersten Connector im Flutter App erstellen
- [ ] Template kopieren und anpassen
- [ ] Deployen und testen
- [ ] Dashboard für Connector-Übersicht bauen
- [ ] App Check aktivieren
- [ ] 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 JobdeleteSchedulerJob()- Löscht Scheduler JobtoggleSchedulerJob()- Pausiert/aktiviert JobgenerateCronExpression()- Konvertiert Schedule-Objekt zu CronupdateConnectorSchedule- 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¶
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¶
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¶
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¶
UI-Test Workflow¶
- Connector erstellen → Scheduler Job wird erstellt
- Schedule ändern → Job wird aktualisiert
- Connector pausieren → Job wird pausiert
- Connector aktivieren → Job wird resumed
- 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¶
- Prüfe isActive Status in Firestore
- Prüfe Job State in Cloud Scheduler Console
- Prüfe Function Logs für Fehler
- Verify OIDC Token Configuration
Timezone Issues¶
- Jobs laufen in
Europe/BerlinTimezone - Firestore Timestamps sind UTC
- UI zeigt lokale Zeit