Build package / php-lint (8.2) (push) Successful in 49s
Build package / php-lint (8.3) (push) Successful in 42s
Build package / php-lint (8.4) (push) Successful in 37s
Build package / xml-lint (push) Successful in 13s
Build package / unit-tests (push) Successful in 44s
Build package / package (push) Successful in 59s
Workflows were being switched off with "its board no longer exists or is
no longer available to you" for boards that were perfectly intact.
Deck does not read the session for permissions. PermissionService -- the
class behind every check, including the ones inside CardService::reorder()
-- takes the current user as a plain `private ?string $userId`, filled
from the app container's `userId` service (ISession::get('user_id'),
registered shared). Pimple resolves that once per process and caches it,
and ServerContainer caches Deck's app container just as long. In cron the
value is null whenever a Deck-owned job ran earlier in the same pass, and
otherwise the first workflow owner touched -- never the user being
impersonated. null fails every check, so Deck answered NoPermissionException
for an untouched board and findStackIds() read that as "the board is gone".
IUserSession::setUser() never had any effect on this path.
- DeckIntegrationService::beginUserContext()/endUserContext() pin
PermissionService (mandatory), CardService, BoardService and
ActivityManager (best effort) to the workflow owner, and restore them
afterwards. If PermissionService cannot be pinned, the runner skips that
user instead of acting under someone else's permissions.
- findStackIds() now takes the uid as an argument and is assembled from
pieces that cannot answer for the wrong user: BoardMapper and StackMapper
carry no user state, and getPermissions() is handed the uid explicitly.
It no longer goes through StackService::findAll().
- NoPermissionException is no longer treated as "board missing". Unknown
failures throw, which leaves the workflow enabled.
This also fixes the second half of the same defect: card moves silently
failed for every user except the first one processed in a cron pass.
Separately, RunWorkflowsJob is now a plain Job instead of a TimedJob and
runs on every cron pass. The workflow_deck_automation.interval config key
is gone. Time sensitivity is no longer merely declared but structurally
unreachable: JobList::add() leaves the column at its TIME_SENSITIVE
default, and the ratchet in setLastRun() only fires for TimedJob.
139 lines
9.3 KiB
Markdown
139 lines
9.3 KiB
Markdown
# Deck Workflow-Automatisierung
|
||
|
||
Nextcloud-App für **Nextcloud Hub 26 Spring (Server 34.x)**, die überfällige Karten der [Deck](https://github.com/nextcloud/deck)-App automatisch von einem Stapel in einen anderen verschiebt und optional eine E-Mail-Benachrichtigung verschickt.
|
||
|
||
Ersetzt das früher genutzte eigenständige PHP-Cron-Skript durch eine vollwertige App mit Oberfläche in den **persönlichen Einstellungen** — jeder Nutzer kann dort seine eigenen Regeln ("Workflows") anlegen, ohne Admin-Rechte oder Server-Zugriff zu benötigen.
|
||
|
||
## Funktionsumfang
|
||
|
||
Pro Workflow lässt sich konfigurieren:
|
||
|
||
- Quell-Board und Quell-Stapel
|
||
- Ziel-Stapel, in den überfällige Karten verschoben werden
|
||
- optionaler Filter auf zugewiesene Benutzer (Karte muss **mindestens einem** der gewählten Benutzer zugewiesen sein)
|
||
- optionaler Filter auf Labels/Tags (Karte muss **mindestens eines** der gewählten Labels haben)
|
||
- Checkbox: E-Mail-Benachrichtigung an den Workflow-Besitzer, sobald eine Karte verschoben wurde
|
||
|
||
Ein Nutzer kann beliebig viele Workflows anlegen, bearbeiten, deaktivieren oder löschen.
|
||
|
||
## Architektur
|
||
|
||
- **Keine HTTP/OCS-Aufrufe gegen Deck.** Alles Lesen (Boards, Stapel, Karten, Labels, zugewiesene Benutzer) und das Verschieben von Karten läuft ausschließlich über Decks eigene interne PHP-Klassen (`OCA\Deck\Service\CardService`, `StackService`, `BoardService`, `OCA\Deck\Db\CardMapper`, …), aufgelöst per Dependency Injection direkt im selben PHP-Prozess. Sämtlicher Deck-Zugriff ist in [`lib/Service/DeckIntegrationService.php`](lib/Service/DeckIntegrationService.php) gebündelt.
|
||
- **Hintergrundjob statt Seitenaufruf.** [`lib/BackgroundJob/RunWorkflowsJob.php`](lib/BackgroundJob/RunWorkflowsJob.php) ist ein einfacher `Job` ohne eigenes Intervall: Er läuft bei jedem Cron-Durchlauf von Nextcloud (üblicherweise alle 5 Minuten) und ruft [`lib/Service/WorkflowRunner.php`](lib/Service/WorkflowRunner.php) auf.
|
||
- **Rechte-Kontext je Nutzer.** Ein Hintergrundjob hat standardmäßig keinen eingeloggten Nutzer, muss aber die Regeln mehrerer Nutzer in einem einzigen Lauf auswerten. Der `WorkflowRunner` "verkörpert" deshalb für die Dauer der jeweiligen Workflows kurzzeitig den entsprechenden Besitzer. Dazu gehört mehr als `IUserSession::setUser()`: Decks Berechtigungsprüfungen lesen die Session gar nicht, sondern eine Benutzer-ID, die beim Bau des Deck-Containers einmal pro Prozess eingefroren wird. `DeckIntegrationService::beginUserContext()` setzt diesen Wert für die Dauer der Verkörperung auf den Workflow-Besitzer und stellt ihn danach wieder her; schlägt das fehl, wird der Nutzer in diesem Lauf übersprungen, statt unter fremden Rechten zu arbeiten.
|
||
- **E-Mail** läuft über Nextclouds eigenen `IMailer` (nutzt also den in der Nextcloud-Administration hinterlegten Mailserver) und geht an die im Profil des Workflow-Besitzers hinterlegte Adresse.
|
||
|
||
### Wichtiger Hinweis zu Decks internen Klassen
|
||
|
||
`OCA\Deck\*` ist keine dokumentierte, stabile öffentliche API von Deck (keine `@since`-Markierungen, keine offizielle Zusicherung von Abwärtskompatibilität). Diese App verwendet sie trotzdem bewusst direkt, wie es die Vorgabe verlangt — jeder Zugriff läuft defensiv abgesichert über `DeckIntegrationService` (Prüfung, ob Deck aktiviert ist, `class_exists()`-Checks, try/catch mit Logging statt Absturz). Nach größeren Deck-Updates lohnt sich ein Blick ins Nextcloud-Log, falls Workflows plötzlich nicht mehr greifen.
|
||
|
||
## Installation
|
||
|
||
1. App in `apps/workflow_deck_automation` des Nextcloud-Servers ablegen (oder über den Appstore-Build aus der CI, siehe unten).
|
||
2. In der Nextcloud-Administration unter *Apps* aktivieren.
|
||
3. Die [Deck-App](https://apps.nextcloud.com/apps/deck) muss installiert und für die jeweiligen Nutzer aktiviert sein.
|
||
4. Sicherstellen, dass der [Hintergrundjob-Modus](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/background_jobs_configuration.html) auf *Cron (empfohlen)* steht, damit `RunWorkflowsJob` regelmäßig läuft.
|
||
|
||
## Benutzung
|
||
|
||
Jeder Nutzer findet die Einstellungen unter **Persönliche Einstellungen → Deck Workflow-Automatisierung**. Dort können neue Workflows angelegt, bestehende bearbeitet oder gelöscht werden. Die Dropdowns für Board/Stapel/Label/Benutzer werden live aus Deck geladen. Zur Auswahl stehen nur aktive Boards — archivierte und im Papierkorb liegende Boards sowie gelöschte Stapel blendet Deck bzw. die App aus.
|
||
|
||
### Gelöschte Boards und Stapel
|
||
|
||
Ein Workflow verweist per ID auf ein Board und zwei Stapel. Wird eines davon in Deck gelöscht, passiert Folgendes:
|
||
|
||
- **Beim Speichern** werden Board und Stapel geprüft; ein Workflow auf nicht mehr existierende Ziele lässt sich gar nicht erst anlegen.
|
||
- **In der Übersicht** wird ein betroffener Workflow rot markiert („Board nicht mehr vorhanden", „Quell-Stapel nicht mehr vorhanden" …).
|
||
- **Im Hintergrundjob** wird der Workflow automatisch deaktiviert und der Besitzer einmalig per E-Mail informiert — unabhängig davon, ob für den Workflow „E-Mail senden" aktiviert ist, denn hier geht es nicht um verschobene Karten, sondern darum, dass die Automatisierung nicht mehr läuft. Danach bleibt es still: Ein deaktivierter Workflow wird nicht erneut ausgewertet.
|
||
|
||
Ist Deck vorübergehend nicht erreichbar, wird **nicht** deaktiviert — der Job überspringt den Workflow und versucht es beim nächsten Lauf erneut. Archivierte Boards lösen die Deaktivierung ebenfalls nicht aus, sie sind nur in den Auswahlfeldern ausgeblendet.
|
||
|
||
## Ausführungstakt
|
||
|
||
Der Hintergrundjob hat **kein eigenes Intervall** und ist auch nicht konfigurierbar: Er läuft bei **jedem** Cron-Durchlauf von Nextcloud. Der Takt ist damit exakt der des System-Crons — üblicherweise alle 5 Minuten, bei einem häufiger eingerichteten Cron entsprechend öfter.
|
||
|
||
> Bis einschließlich v0.1.0 gab es dafür die `config.php`-Option `workflow_deck_automation.interval`. Sie ist entfallen und wird ignoriert; ein vorhandener Eintrag kann aus der `config.php` entfernt werden.
|
||
|
||
### Der Job läuft nur nachts bzw. gar nicht
|
||
|
||
Nextcloud merkt sich in der Spalte `time_sensitive` der Tabelle `oc_jobs`, ob ein Job zeitkritisch ist, und überspringt „unkritische" Jobs außerhalb des Wartungsfensters. `JobList::setLastRun()` setzt diese Spalte nur in eine Richtung — von „zeitkritisch" auf „unkritisch", nie zurück. Wer eine ältere Version dieser App installiert hatte (bis einschließlich v0.1.0 war der Job `TIME_INSENSITIVE`), hat diesen Stempel noch in der Datenbank. Er wird weder durch ein App-Update noch durch `app:enable` zurückgesetzt.
|
||
|
||
Symptom: Ist in der `config.php` ein `maintenance_window_start` gesetzt, läuft der Job nur in diesem 4-Stunden-Fenster und den Rest des Tages gar nicht.
|
||
|
||
Einmalig korrigieren:
|
||
|
||
```sql
|
||
UPDATE oc_jobs SET time_sensitive = 0
|
||
WHERE class = 'OCA\\WorkflowDeckAutomation\\BackgroundJob\\RunWorkflowsJob';
|
||
```
|
||
|
||
Ohne Datenbankzugriff geht es auch über `occ`, indem der Job gelöscht und durch Aus-/Einschalten der App neu registriert wird:
|
||
|
||
```bash
|
||
php occ background-job:list --class 'OCA\WorkflowDeckAutomation\BackgroundJob\RunWorkflowsJob'
|
||
php occ background-job:delete <id>
|
||
php occ app:disable workflow_deck_automation
|
||
php occ app:enable workflow_deck_automation
|
||
```
|
||
|
||
Neuinstallationen sind nicht betroffen.
|
||
|
||
## Entwicklung
|
||
|
||
Voraussetzungen: PHP 8.2+, Composer, Node.js 24+, npm 11+.
|
||
|
||
```bash
|
||
composer install
|
||
npm install
|
||
npm run build # einmaliger Produktions-Build
|
||
npm run watch # Entwicklung mit automatischem Rebuild
|
||
```
|
||
|
||
### Tests & Linting
|
||
|
||
```bash
|
||
composer run lint # php -l über lib/ und tests/
|
||
composer run cs:check # nextcloud/coding-standard (php-cs-fixer)
|
||
composer run test:unit # PHPUnit — reine Filter-Logik, benötigt keine Deck-Installation
|
||
```
|
||
|
||
Ein echter End-to-End-Test (Karte anlegen, Workflow konfigurieren, Hintergrundjob auslösen, Verschiebung + Mail prüfen) lässt sich nur gegen eine echte Nextcloud-34-Instanz mit installierter Deck-App durchführen, z. B. per:
|
||
|
||
```bash
|
||
# Job-ID und letzten Lauf anzeigen
|
||
php occ background-job:list --class 'OCA\WorkflowDeckAutomation\BackgroundJob\RunWorkflowsJob'
|
||
|
||
# Sofort ausführen, ohne auf den nächsten Cron-Durchlauf zu warten
|
||
php occ background-job:execute <id> --force-execute
|
||
|
||
# Oder dauerhaft als Worker laufen lassen (Argument ist die Job-Klasse, keine App-ID)
|
||
php occ background-job:worker 'OCA\WorkflowDeckAutomation\BackgroundJob\RunWorkflowsJob'
|
||
```
|
||
|
||
### Release-Paket bauen
|
||
|
||
```bash
|
||
make appstore
|
||
```
|
||
|
||
Erzeugt `build/artifacts/appstore/workflow_deck_automation.tar.gz`.
|
||
|
||
## CI (Gitea Actions)
|
||
|
||
In [`.gitea/workflows/`](.gitea/workflows/):
|
||
|
||
| Workflow | Zweck |
|
||
| --- | --- |
|
||
| `lint-php.yml` | `php -l` über eine PHP-8.2–8.4-Matrix |
|
||
| `lint-info-xml.yml` | validiert `appinfo/info.xml` gegen das Appstore-XML-Schema |
|
||
| `phpunit.yml` | führt die PHPUnit-Tests aus (SQLite/rein logisch, kein DB-Service nötig) |
|
||
| `build-main.yml` | baut Frontend + Appstore-Archiv und veröffentlicht es als Release-Asset — bei Push auf `main` als rollendes Pre-Release `latest-main`, bei Push eines `v*`-Tags als reguläres Release |
|
||
|
||
## Datenmodell
|
||
|
||
Workflows werden in der Tabelle `wfda_workflows` gespeichert (siehe [`lib/Migration/Version1000Date20260813120000.php`](lib/Migration/Version1000Date20260813120000.php)): eine Zeile pro Workflow, mit `user_id`-Bezug, Board-/Stapel-IDs, JSON-kodierten Filterlisten sowie `enabled`/`notify_email`/`last_run`.
|
||
|
||
## Lizenz
|
||
|
||
AGPL-3.0-or-later
|