#!/usr/bin/env python3 from __future__ import annotations import argparse import json import os import sys from typing import Iterable import requests from common import ( add_domain_and_auth_args, build_session, resolve_arg, resolve_domain_and_auth, ) DEFAULT_STACKS: list[str] = [ "🐞 Bugs", "📋 To Do", "🚧 In Progress", "✅ Done", "📄 Backlog", ] def get_stack_names() -> list[str]: """ Get stack names from NEW_BOARD_COLUMNS env var (comma-separated), or fall back to DEFAULT_STACKS if not set. """ columns_env = os.getenv("NEW_BOARD_COLUMNS") if columns_env: # Split by comma and strip whitespace from each column name return [col.strip() for col in columns_env.split(",") if col.strip()] return DEFAULT_STACKS def create_board(session: requests.Session, base_url: str, title: str) -> dict: """ POST /boards -> { id, title, ... } """ url = f"{base_url}/index.php/apps/deck/api/v1.0/boards" headers = {"OCS-APIRequest": "true", "Content-Type": "application/json"} payload = {"title": title} resp = session.post(url, headers=headers, data=json.dumps(payload)) if resp.status_code not in (200, 201): raise RuntimeError( f"Deck API error creating board: {resp.status_code} {resp.text}" ) return resp.json() def create_stack( session: requests.Session, base_url: str, board_id: int, title: str, order: int ) -> dict: """ POST /boards/{board_id}/stacks -> { id, title, order, ... } """ url = f"{base_url}/index.php/apps/deck/api/v1.0/boards/{board_id}/stacks" headers = {"OCS-APIRequest": "true", "Content-Type": "application/json"} payload = {"title": title, "order": order} resp = session.post(url, headers=headers, data=json.dumps(payload)) if resp.status_code not in (200, 201): raise RuntimeError( f"Deck API error creating stack '{title}': {resp.status_code} {resp.text}" ) return resp.json() def create_stacks_in_order( session: requests.Session, base_url: str, board_id: int, stacks: Iterable[str], ) -> list[dict]: created = [] for idx, title in enumerate(stacks): created.append(create_stack(session, base_url, board_id, title, idx)) print(f" ✔️ Created stack [{idx}]: {title}") return created def main() -> None: parser = argparse.ArgumentParser( description="Create a Nextcloud Deck board and pre-populate it with standard stacks." ) # Only domain/username/password (no board-id since we are creating it) add_domain_and_auth_args(parser) # Script-specific parser.add_argument( "--board-name", help="New board name/title (e.g., 'Team Kanban')", ) parser.add_argument( "--no-default-stacks", action="store_true", help="Create the board without the default stacks.", ) args = parser.parse_args() # Resolve domain + auth (ENV -> CLI -> prompt) try: base_url, username, password = resolve_domain_and_auth( cli_domain=args.domain, cli_username=args.username, cli_password=args.password, ) except Exception as e: print(f"❌ {e}", file=sys.stderr) sys.exit(2) # Resolve board name try: board_name = resolve_arg( args.board_name, ["NEXTCLOUD_BOARD_NAME", "BOARD_NAME"], prompt_text="New board name: ", cast=str, ) except Exception as e: print(f"❌ {e}", file=sys.stderr) sys.exit(2) session = build_session(username, password) # Create board try: board = create_board(session, base_url, board_name) board_id = board.get("id") if board_id is None: raise RuntimeError(f"Board created but no id returned: {board}") print(f"✔️ Created board '{board_name}' (id: {board_id}) at {base_url}") except Exception as e: print(f"❌ Failed to create board: {e}", file=sys.stderr) sys.exit(1) # Create default stacks (unless suppressed) created_stacks = [] if not args.no_default_stacks: try: stack_names = get_stack_names() print("→ Creating stacks in order:") created_stacks = create_stacks_in_order( session, base_url, int(board_id), stack_names ) except Exception as e: print(f"❌ Failed to create stacks: {e}", file=sys.stderr) sys.exit(1) # Summary print("\nSummary") print("-------") print(f"Board ID: {board_id}") if created_stacks: print("Stacks (in order):") for s in created_stacks: print(f" - [{s.get('id')}] {s.get('title')}") if __name__ == "__main__": main()