Files
nextcloud-workflow-deck-aut…/DEVELOPMENT.md
T
Patrick Niebeling 8268f1068d
Build package / xml-lint (push) Successful in 3m32s
Build package / unit-tests (push) Successful in 4m49s
Build package / php-lint (8.5) (push) Successful in 4m52s
Build package / php-lint (8.4) (push) Successful in 4m56s
Build package / package (push) Successful in 2m28s
Let workflows move cards by start date instead of due date
Each workflow now has a trigger date (due or start): the runner compares
either the card's due date or its start date against now, read straight
from Deck's Card entity. Mails and Nextcloud notifications say whether
the card was overdue or reached its start date. Cards without the chosen
date stay put.

Deck only has a start date since 1.18 and Entity getters are magic, so
DeckIntegrationService::supportsStartdate() checks the property; without
it the form rejects start-date workflows and the runner skips them
without disabling them.

WorkflowRunner::isOverdue() becomes hasPassed(), as it now covers both
dates. Existing workflows keep the due date (migration Version1005).
Bumps to 35.4.0 so the migration runs.
2026-10-01 13:19:32 +02:00

136 lines
7.9 KiB
Markdown

# Entwicklung
Informationen für Entwickler und Admins, die tiefer einsteigen. Die Nutzer-Doku steht in der [README](README.md). Ausführliche Hintergründe zu den Stolperfallen, also warum etwas genau so gebaut ist, stehen in [CLAUDE.md](CLAUDE.md).
## Aufbau
- **Keine HTTP/OCS-Aufrufe gegen Deck.** Lesen (Boards, Stapel, Karten, Board-Mitglieder) und das Verschieben laufen ausschließlich über Decks interne PHP-Klassen im selben Prozess. Sämtlicher Deck-Zugriff ist in [`lib/Service/DeckIntegrationService.php`](lib/Service/DeckIntegrationService.php) gebündelt.
- `OCA\Deck\*` ist keine stabile öffentliche API. Jeder Zugriff ist deshalb abgesichert: `class_exists()`-Checks, Prüfung ob Deck aktiviert ist, try/catch mit Logging.
- Nach größeren Deck-Updates lohnt sich ein Blick ins Nextcloud-Log.
- **Hintergrundjob:** [`RunWorkflowsJob`](lib/BackgroundJob/RunWorkflowsJob.php) ist ein einfacher `Job` ohne Intervall. Er läuft bei jedem Cron-Durchlauf und ruft [`WorkflowRunner`](lib/Service/WorkflowRunner.php) auf.
- **Rechte-Kontext:** Der Runner arbeitet die Workflows nach Eigentümer gruppiert ab und setzt Deck jeweils auf diesen Nutzer, per `DeckIntegrationService::beginUserContext()`. Decks Berechtigungsprüfungen lesen nämlich nicht die Session, sondern eine pro Prozess festgelegte Nutzer-ID.
- **E-Mail:** [`NotificationMailer`](lib/Service/NotificationMailer.php) verschickt über Nextclouds `IMailer`. Die Empfänger ermittelt `DeckIntegrationService::findNotificationRecipients()`, die Entscheidungsregel steckt in [`NotificationPolicy`](lib/Service/NotificationPolicy.php).
- **Nextcloud-Benachrichtigungen:** [`NotificationPusher`](lib/Service/NotificationPusher.php) legt sie über `OCP\Notification\IManager` an, eine pro Karte, an dieselben Empfänger wie die Mail. Er speichert nur Rohwerte. Den deutschen Text baut [`Notifier`](lib/Notification/Notifier.php), registriert in `Application::register()`. Ist die App `notifications` für einen Empfänger nicht aktiv, wird nichts angelegt. Push aufs Handy übernimmt die Notifications-App selbst.
- **Oberfläche:** [`src/PersonalSettings.vue`](src/PersonalSettings.vue) (Vue 3, `@nextcloud/vue` 9) spricht über [`src/api.js`](src/api.js) mit dem OCS-Controller [`WorkflowController`](lib/Controller/WorkflowController.php).
## Datenmodell
Die Tabelle `wfda_workflows` enthält eine Zeile pro Workflow:
| Spalte | Bedeutung |
| --- | --- |
| `board_id`, `source_stack_id`, `target_stack_id` | Deck-IDs |
| `user_id` | Board-Eigentümer, in dessen Namen der Job läuft. Wird bei jedem Lauf aus Deck aktualisiert. |
| `trigger_date` | `due` oder `start`: Fälligkeits- oder Startdatum löst das Verschieben aus |
| `notify_mode` | `off`, `owner` oder `board` (wer) |
| `notify_channel` | `mail`, `notification` oder `both` (wie) |
| `enabled`, `last_run` | aktiv? / letzter Lauf |
Migrationen in [`lib/Migration/`](lib/Migration/):
- `Version1000…` legt die Tabelle an.
- `Version1001…` entfernt die früheren Filter-Spalten.
- `Version1002…` und `Version1003…` ersetzen `notify_email` durch `notify_mode`. Ein gesetztes Häkchen wird dabei zu `owner`.
- `Version1004…` fügt `notify_channel` hinzu. Bestehende Workflows bekommen `mail`.
- `Version1005…` fügt `trigger_date` hinzu. Bestehende Workflows bekommen `due`.
Migrationen laufen nur, wenn die App-Version steigt.
## Lokales Setup
Voraussetzungen: PHP 8.4+, Composer, Node.js 24 mit npm.
```bash
composer install
npm install # nicht `npm ci`: Lockfiles werden bewusst nicht committet
npm run build # Produktions-Build
npm run watch # Entwicklung mit automatischem Rebuild
```
## Tests und Linting
```bash
composer run lint # php -l über lib/ und tests/
composer run cs:check # nextcloud/coding-standard (cs:fix korrigiert)
composer run test:unit # PHPUnit
```
Die Unit-Tests decken nur reine Logik ab, ohne Deck-Installation:
- Datumsvergleich für Fälligkeits- und Startdatum (`WorkflowRunner::hasPassed()`)
- Auslöser und Begründungstext (`TriggerDate`)
- Empfängerregel (`NotificationPolicy::shouldNotify()`)
- Benachrichtigungsweg (`NotificationPolicy::sendsMail()`/`sendsNotification()`)
Die Deck-Anbindung lässt sich nur auf einer echten Nextcloud-Instanz mit Deck testen:
```bash
# Job-ID und letzten Lauf anzeigen
php occ background-job:list --class 'OCA\WorkflowDeckAutomation\BackgroundJob\RunWorkflowsJob'
# Sofort ausführen, ohne auf Cron zu warten
php occ background-job:execute <id> --force-execute
```
## Build, CI und Release
`make appstore` erzeugt `build/artifacts/appstore/workflow_deck_automation.tar.gz`.
Gebaut und veröffentlicht wird in [`.gitea/workflows/build-main.yml`](.gitea/workflows/build-main.yml):
| Job | Zweck |
| --- | --- |
| `php-lint` | `php -l` mit PHP 8.4 und 8.5 |
| `xml-lint` | prüft `appinfo/info.xml` gegen das Appstore-Schema |
| `unit-tests` | PHPUnit |
| `package` | Frontend-Build und `make appstore`. Läuft nur, wenn die Checks grün sind. |
Das Paket wird als Release veröffentlicht:
- Bei jedem Push auf `main` als Pre-Release **`latest-main`**, das jedes Mal neu erzeugt wird.
- Bei einem `v*`-Tag als reguläres Release.
- Bei Pull Requests wird nur gebaut, nicht veröffentlicht.
**Versionen** folgen dem Schema `<Nextcloud-Major>.<minor>.<patch>`. Die unterstützte Nextcloud-Version steht in `appinfo/info.xml`.
Ablauf für ein Release:
1. Version in `appinfo/info.xml` und `package.json` gleich anheben.
2. Auf `main` pushen und warten, bis die Pipeline grün ist.
3. `git tag -a vX.Y.Z -m "…" && git push origin vX.Y.Z`.
4. Optional in den App Store: in Gitea unter *Actions* den Workflow „Publish to App Store“ von Hand starten und den Tag angeben.
### App Store
[`.gitea/workflows/appstore.yml`](.gitea/workflows/appstore.yml) läuft nur von Hand (`workflow_dispatch`). Er baut nichts selbst. Er lädt den Tarball eines vorhandenen `vX.Y.Z`-Release ohne Anmeldung herunter und prüft, ob die Version in `info.xml` zum Tag passt. Dann signiert er das Archiv (SHA-512) und meldet die Download-URL samt Signatur an die App-Store-API. Der Store lädt den Tarball danach selbst von Gitea. Die Releases müssen deshalb öffentlich erreichbar sein, und ein Tag darf nach dem Store-Upload nicht mehr verschoben werden.
Benötigte Repository-Secrets:
| Secret | Inhalt |
| --- | --- |
| `APPSTORE_PRIVATE_KEY` | privater Schlüssel (PEM), zu dem Nextcloud das App-Zertifikat ausgestellt hat |
| `APPSTORE_TOKEN` | API-Token von <https://apps.nextcloud.com/account/token> |
Einmalig vorher: Zertifikat über [nextcloud/app-certificate-requests](https://github.com/nextcloud/app-certificate-requests) beantragen und die App auf apps.nextcloud.com registrieren. Die App wird nicht per Code-Signing (`appinfo/signature.json`) signiert. Nextcloud prüft Apps, die nicht zum Core gehören, nur dann, wenn diese Datei vorhanden ist.
Einzelheiten, etwa zum Verschieben eines Tags nach einem fehlgeschlagenen Build, stehen in [CLAUDE.md](CLAUDE.md).
## Fehlersuche: Der Job läuft nur nachts oder gar nicht
Nextcloud überspringt „nicht zeitkritische“ Jobs außerhalb des Wartungsfensters (`maintenance_window_start` in der `config.php`). Bis einschließlich v0.1.0 war dieser Job als nicht zeitkritisch markiert. Diese Markierung bleibt in der Spalte `oc_jobs.time_sensitive` stehen, auch nach App-Updates.
Symptom: Der Job läuft nur im 4-Stunden-Wartungsfenster, oder `occ background-job:list` zeigt als letzten Lauf `1970-01-01`.
Einmalig korrigieren:
```sql
UPDATE oc_jobs SET time_sensitive = 0
WHERE class = 'OCA\\WorkflowDeckAutomation\\BackgroundJob\\RunWorkflowsJob';
```
Oder ohne Datenbankzugriff den Job löschen und durch Aus- und Einschalten der App neu registrieren:
```bash
php occ background-job:delete <id>
php occ app:disable workflow_deck_automation
php occ app:enable workflow_deck_automation
```
Die frühere `config.php`-Option `workflow_deck_automation.interval` (bis v0.1.0) wird ignoriert und kann entfernt werden.