mirror of
https://github.com/wekan/wekan.git
synced 2026-11-07 02:52:33 -05:00
Sandstorm username races. - #2345: the Card Title Filter is now SAVED by every board trigger builder (a trigger doc missing the field could never satisfy the matcher's $in - those rules fired for nothing), SHOWN in the rule details, and ENFORCED for archive/restore triggers by resolving the title from the card when the activity lacks it; legacy field-less triggers keep matching; the filter popup prefills its current value. - #2674: rules created via POST/PUT /api/boards/:boardId/rules never fired - missing matching fields are now normalized to '*' wildcards and trigger/action types validated; the addMember/removeMember rule actions no longer crash on unresolvable usernames or member-less cards and their writes are awaited. New docs/API/Rules.md (endpoints, matching fields, wildcard semantics, the exact two-rule add-on-move-to/remove-on-move-from workflow), linked from REST-API.md; api.py help now lists the rule commands with the same example. - #574: Sandstorm unique-username generation probes case-insensitively (anchored, regex-escaped) and retries the next number when a concurrent insert wins the unique-index race, instead of aborting the login hook with E11000. Tests: tests/rulesCardTitleFilter.test.cjs (12), tests/rulesApiTriggerNormalize.test.cjs (19), tests/sandstormUsername.test.cjs (11) - positive and negative cases. Thanks to InfoSec812, sfahrenholz, mitar and xet7.
186 lines
7.2 KiB
Markdown
186 lines
7.2 KiB
Markdown
# Rules (Board Automation / IFTTT) REST API
|
|
|
|
Board automation rules ("IFTTT rules") link a **trigger** (an event on the
|
|
board) to an **action** (what happens to the card/board). The REST API embeds
|
|
the full trigger and action inline, so a rule is self-contained.
|
|
|
|
This API makes the workflow of
|
|
[issue #2674](https://github.com/wekan/wekan/issues/2674) scriptable: add a
|
|
user to a card when it is moved **to** a list, and remove the user again when
|
|
the card is moved **away from** that list.
|
|
|
|
## Summary
|
|
|
|
| HTTP Method | Url | Short Description |
|
|
| :--- | :--- | :--- |
|
|
| `GET` | `/api/boards/:boardId/rules` | List all automation rules of a board |
|
|
| `GET` | `/api/boards/:boardId/rules/:ruleId` | Get one rule (with its trigger and action) |
|
|
| `POST` | `/api/boards/:boardId/rules` | Add an automation rule |
|
|
| `PUT` | `/api/boards/:boardId/rules/:ruleId` | Edit a rule's title, trigger and/or action |
|
|
| `DELETE` | `/api/boards/:boardId/rules/:ruleId` | Remove a rule (and its trigger and action) |
|
|
|
|
All endpoints require a `Bearer` token; write endpoints require board write
|
|
access (BoardAdmin).
|
|
|
|
## Trigger matching and the `*` wildcard
|
|
|
|
The rule engine matches an incoming board activity against **every** matching
|
|
field of the trigger's `activityType` (for example `moveCard` matches on
|
|
`boardId`, `listName`, `oldListName`, `userId`, `swimlaneName`, `cardTitle`).
|
|
A field set to `'*'` matches anything.
|
|
|
|
You only need to supply the fields you want to constrain: any matching field
|
|
missing from the trigger you POST/PUT is stored as the `'*'` wildcard
|
|
automatically. (Before this normalization existed, an API-created trigger
|
|
that omitted a field such as `userId` could never match any activity, so the
|
|
rule silently never fired — the bug reported in issue #2674.)
|
|
|
|
### Trigger `activityType` values
|
|
|
|
| `activityType` | fires when | matching fields |
|
|
| :--- | :--- | :--- |
|
|
| `createCard` | a card is created | `boardId`, `listName`, `userId`, `swimlaneName`, `cardTitle` |
|
|
| `moveCard` | a card is moved to/from a list or swimlane | `boardId`, `listName` (destination), `oldListName` (source), `userId`, `swimlaneName`, `cardTitle` |
|
|
| `archivedCard` / `restoredCard` | a card is archived / unarchived | `boardId`, `userId`, `cardTitle` |
|
|
| `joinMember` / `unjoinMember` | a member is added to / removed from a card | `boardId`, `username`, `userId` |
|
|
| `addChecklist` / `removeChecklist` / `completeChecklist` / `uncompleteChecklist` | checklist events | `boardId`, `checklistName`, `userId` |
|
|
| `addedChecklistItem` / `removedChecklistItem` / `checkedItem` / `uncheckedItem` | checklist item events | `boardId`, `checklistItemName`, `userId` |
|
|
| `addAttachment` / `deleteAttachment` | attachment events | `boardId`, `userId` |
|
|
| `addedLabel` / `removedLabel` | label events | `boardId`, `labelId`, `userId` |
|
|
| `scheduledTrigger` | time-based (due/aging/recurring); see `scheduleKind`, `days`, `atTime` | evaluated by the scheduler, not by activities |
|
|
|
|
For a card moved **to** a list, constrain `listName` (the destination list's
|
|
title). For a card moved **away from** a list, constrain `oldListName` (the
|
|
source list's title) and leave `listName` as `'*'`.
|
|
|
|
### Action `actionType` values
|
|
|
|
`moveCardToTop`, `moveCardToBottom`, `sendEmail`, `setDate`, `updateDate`,
|
|
`removeDate`, `setDateRelative`, `archive`, `unarchive`, `setColor`,
|
|
`addLabel`, `removeLabel`, `addMember`, `removeMember`, `checkAll`,
|
|
`uncheckAll`, `checkItem`, `uncheckItem`, `addChecklist`, `removeChecklist`,
|
|
`addChecklistWithItems`, `addSwimlane`, `createCard`, `linkCard`,
|
|
`markCardComplete`, `markCardIncomplete`, `sortList`, `moveAllCardsInList`.
|
|
|
|
The member actions take a `username`:
|
|
|
|
* `{"actionType": "addMember", "username": "someuser"}` — add the user to the card
|
|
* `{"actionType": "removeMember", "username": "someuser"}` — remove the user from the card
|
|
* `{"actionType": "removeMember", "username": "*"}` — remove **all** members from the card
|
|
|
|
## List rules
|
|
|
|
```shell
|
|
curl -H "Authorization: Bearer TOKEN" \
|
|
-X GET \
|
|
http://localhost:3000/api/boards/BOARDID/rules
|
|
```
|
|
|
|
## Get one rule
|
|
|
|
```shell
|
|
curl -H "Authorization: Bearer TOKEN" \
|
|
-X GET \
|
|
http://localhost:3000/api/boards/BOARDID/rules/RULEID
|
|
```
|
|
|
|
Result example:
|
|
|
|
```json
|
|
{
|
|
"_id": "8gDW2AtwbpNbqSyEw",
|
|
"title": "Remove member when moved away from Doing",
|
|
"trigger": {
|
|
"activityType": "moveCard",
|
|
"listName": "*",
|
|
"oldListName": "Doing",
|
|
"swimlaneName": "*",
|
|
"cardTitle": "*",
|
|
"userId": "*"
|
|
},
|
|
"action": {
|
|
"actionType": "removeMember",
|
|
"username": "someuser"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Add a rule
|
|
|
|
```shell
|
|
curl -H "Authorization: Bearer TOKEN" \
|
|
-H "Content-type: application/json" \
|
|
-X POST \
|
|
http://localhost:3000/api/boards/BOARDID/rules \
|
|
-d '{
|
|
"title": "Add member when moved to Doing",
|
|
"trigger": { "activityType": "moveCard", "listName": "Doing" },
|
|
"action": { "actionType": "addMember", "username": "someuser" }
|
|
}'
|
|
```
|
|
|
|
### Full example: issue #2674 workflow
|
|
|
|
Add the user when a card is moved **to** the list "Doing", remove the user
|
|
again when the card is moved **away from** "Doing":
|
|
|
|
```shell
|
|
# Rule 1: card moved TO "Doing" -> add member
|
|
curl -H "Authorization: Bearer TOKEN" -H "Content-type: application/json" -X POST \
|
|
http://localhost:3000/api/boards/BOARDID/rules \
|
|
-d '{ "title": "Add member on move to Doing",
|
|
"trigger": { "activityType": "moveCard", "listName": "Doing" },
|
|
"action": { "actionType": "addMember", "username": "someuser" } }'
|
|
|
|
# Rule 2: card moved AWAY FROM "Doing" -> remove member
|
|
curl -H "Authorization: Bearer TOKEN" -H "Content-type: application/json" -X POST \
|
|
http://localhost:3000/api/boards/BOARDID/rules \
|
|
-d '{ "title": "Remove member on move from Doing",
|
|
"trigger": { "activityType": "moveCard", "oldListName": "Doing" },
|
|
"action": { "actionType": "removeMember", "username": "someuser" } }'
|
|
```
|
|
|
|
The omitted trigger fields (`listName` in rule 2, `oldListName` in rule 1,
|
|
plus `userId`, `swimlaneName`, `cardTitle`) are stored as `'*'` wildcards.
|
|
|
|
## Edit a rule
|
|
|
|
Any of `title`, `trigger` and `action` may be supplied; a supplied trigger or
|
|
action document replaces the stored fields. When the supplied trigger includes
|
|
an `activityType`, its missing matching fields are defaulted to `'*'` too.
|
|
|
|
```shell
|
|
curl -H "Authorization: Bearer TOKEN" \
|
|
-H "Content-type: application/json" \
|
|
-X PUT \
|
|
http://localhost:3000/api/boards/BOARDID/rules/RULEID \
|
|
-d '{ "action": { "actionType": "removeMember", "username": "*" } }'
|
|
```
|
|
|
|
## Delete a rule
|
|
|
|
Removes the rule together with its trigger and action documents.
|
|
|
|
```shell
|
|
curl -H "Authorization: Bearer TOKEN" \
|
|
-X DELETE \
|
|
http://localhost:3000/api/boards/BOARDID/rules/RULEID
|
|
```
|
|
|
|
## Python CLI (api.py)
|
|
|
|
The bundled `api.py` exposes the same capability:
|
|
|
|
```shell
|
|
python3 api.py listrules BOARDID
|
|
python3 api.py getrule BOARDID RULEID
|
|
python3 api.py addrule BOARDID 'TITLE' 'TRIGGER_JSON' 'ACTION_JSON'
|
|
python3 api.py editrule BOARDID RULEID 'PATCH_JSON'
|
|
python3 api.py removerule BOARDID RULEID
|
|
|
|
# Issue #2674: remove a user from the card when it is moved away from a list
|
|
python3 api.py addrule BOARDID 'Remove member on move from Doing' \
|
|
'{"activityType":"moveCard","oldListName":"Doing"}' \
|
|
'{"actionType":"removeMember","username":"someuser"}'
|
|
```
|