feat: add Planka service provider with full notification and command support

Webhook-based provider for Planka (self-hosted Kanban board) with:
- 15 event types (cards, boards, lists, comments, tasks, attachments, labels)
- Bearer token webhook authentication
- Async API client for boards/cards/lists
- 30 notification templates (en/ru) + 26 command templates (en/ru)
- Bot commands: /status, /boards, /cards, /lists
- Default tracking config, template config, command config seeded on startup
- DB migration for 15 new tracking_config columns
- Frontend: provider config UI with auto-name, Planka-specific hints
- Frontend: tracking config event toggles for all 15 Planka events
This commit is contained in:
2026-03-23 15:54:00 +03:00
parent 39bac828fd
commit 0fde3c6b3d
83 changed files with 1827 additions and 3 deletions
@@ -2,6 +2,7 @@
from notify_bridge_core.providers.immich import ImmichServiceProvider
from notify_bridge_core.providers.gitea import GiteaServiceProvider
from notify_bridge_core.providers.planka import PlankaServiceProvider
from ..database.models import ServiceProvider
@@ -27,3 +28,14 @@ def make_gitea_provider(http_session, provider: ServiceProvider) -> GiteaService
config.get("api_token", ""),
provider.name,
)
def make_planka_provider(http_session, provider: ServiceProvider) -> PlankaServiceProvider:
"""Create a PlankaServiceProvider from a DB provider model."""
config = provider.config or {}
return PlankaServiceProvider(
http_session,
config.get("url", ""),
config.get("api_key", ""),
provider.name,
)
@@ -62,6 +62,22 @@ def event_allowed_by_config(event: ServiceEvent, tc: TrackingConfig) -> bool:
"pr_merged": tc.track_pr_merged,
"pr_commented": tc.track_pr_commented,
"release_published": tc.track_release_published,
# Planka events
"card_created": tc.track_card_created,
"card_updated": tc.track_card_updated,
"card_moved": tc.track_card_moved,
"card_deleted": tc.track_card_deleted,
"card_commented": tc.track_card_commented,
"comment_updated": tc.track_comment_updated,
"board_created": tc.track_board_created,
"board_updated": tc.track_board_updated,
"board_deleted": tc.track_board_deleted,
"list_created": tc.track_list_created,
"list_updated": tc.track_list_updated,
"list_deleted": tc.track_list_deleted,
"attachment_created": tc.track_attachment_created,
"card_label_added": tc.track_card_label_added,
"task_completed": tc.track_task_completed,
# Scheduler events
"scheduled_message": tc.track_scheduled_message,
}
@@ -142,6 +142,30 @@ _SAMPLE_CONTEXT = {
"release_body": "Initial release",
"release_draft": False,
"release_prerelease": False,
# Planka variables (for planka provider templates)
"board_name": "My Project",
"board_id": "123456",
"board_url": "https://planka.example.com/boards/123456",
"card_name": "Fix login bug",
"card_id": "789012",
"card_url": "https://planka.example.com/cards/789012",
"card_description": "Users cannot log in with SSO",
"card_due_date": "2026-04-01T00:00:00.000Z",
"list_name": "In Progress",
"list_id": "list-1",
"old_list_name": "To Do",
"new_list_name": "In Progress",
"old_list_id": "list-0",
"new_list_id": "list-1",
"comment_text": "Looks good, ready for review!",
"comment_id": "comment-1",
"task_name": "Write unit tests",
"task_id": "task-1",
"task_completed": True,
"attachment_name": "screenshot.png",
"attachment_id": "att-1",
"label_name": "bug",
"label_color": "berry-red",
# Scheduler variables (for scheduler provider templates)
"schedule_name": "Daily Reminder",
"fire_count": 42,
@@ -111,6 +111,9 @@ async def check_tracker(tracker_id: int) -> dict[str, Any]:
# Gitea is webhook-based — events arrive via /api/webhooks/gitea endpoint.
# The scheduler still calls check_tracker but there's nothing to poll.
return {"status": "ok", "events_detected": 0, "collections_checked": 0}
elif provider_type == "planka":
# Planka is webhook-based — events arrive via /api/webhooks/planka endpoint.
return {"status": "ok", "events_detected": 0, "collections_checked": 0}
elif provider_type == "scheduler":
from notify_bridge_core.providers.scheduler import SchedulerServiceProvider
custom_vars = tracker_filters.get("custom_variables", {})