dodil/boards-challenge
Dodil Boards Challenge: build the Trello replacement on Dodil. Instructions for you and your AI assistant.
A remix template · 18 remixes · Remix dodil/boards-challenge
app/boards/application/use_cases.py
"""Every action a person can take. The web routes and the MCP tools both call these and nothing
else, so a rule cannot be skipped by using the other door.
Add a use case here, then expose it in doors/web.py and doors/mcp.py (one line each).
"""
import uuid
from dataclasses import asdict
from typing import Optional
from boards.application.ports import Assistant, BoardRepository
from boards.domain.model import Board, Card, Forbidden, Invalid, Person, Role
class NotFound(Exception):
pass
def _card(c: Card) -> dict:
d = asdict(c)
d["due_date"] = c.due_date.isoformat() if c.due_date else None
return d
class Boards:
def __init__(self, repo: BoardRepository, assistant: Optional[Assistant] = None):
self.repo, self.assistant = repo, assistant
def _board(self, board_id: str, person: Person) -> Board:
board = self.repo.get(board_id)
if board is None:
raise NotFound("no such board")
try:
board.require_view(person)
except Forbidden:
raise NotFound("no such board") # a board you can't see doesn't exist for you
return board
def _find_card(self, card_id: str, person: Person) -> tuple:
card = self.repo.get_card(card_id)
if card is None:
raise NotFound("no such card")
return self._board(card.board_id, person), card
def create_board(self, person: Person, title: str) -> dict:
if not title.strip():
raise Invalid("a board needs a title")
board = Board(id=uuid.uuid4().hex[:12], title=title.strip(), owner_email=person.email)
self.repo.save_board(board)
self.repo.log(board.id, None, person.email, f"created board {board.title!r}")
return {"id": board.id, "title": board.title}
def my_boards(self, person: Person) -> list:
return [{"id": b.id, "title": b.title, "role": b.role_of(person).value}
for b in self.repo.boards_for(person.email)]
def show_board(self, person: Person, board_id: str) -> dict:
board = self._board(board_id, person)
cards = [_card(c) for c in self.repo.cards_on(board.id)]
return {"id": board.id, "title": board.title, "role": board.role_of(person).value,
"cards": cards}
def add_member(self, person: Person, board_id: str, email: str, role: str) -> dict:
board = self._board(board_id, person)
board.add_member(person, email, Role(role))
self.repo.save_board(board)
self.repo.log(board.id, None, person.email, f"added {email} as {role}")
return {"board_id": board.id, "email": email, "role": role}
def add_card(self, person: Person, board_id: str, title: str,
list_name: str = "To do") -> dict:
board = self._board(board_id, person)
board.require_edit(person)
card = Card(id=uuid.uuid4().hex[:12], board_id=board.id, title=title.strip())
card.move_to(list_name)
self.repo.save_card(card)
self.repo.log(board.id, card.id, person.email, f"added card {card.title!r}")
return _card(card)
def move_card(self, person: Person, card_id: str, list_name: str) -> dict:
board, card = self._find_card(card_id, person)
board.require_edit(person)
before = card.list_name
card.move_to(list_name)
self.repo.save_card(card)
self.repo.log(board.id, card.id, person.email, f"moved from {before} to {list_name}")
return _card(card)
def suggest_checklist(self, person: Person, card_id: str) -> dict:
"""An AI suggestion only: it changes nothing. A person decides what to keep."""
_, card = self._find_card(card_id, person)
if self.assistant is None:
raise Invalid("no AI assistant configured")
return {"card_id": card.id, "suggested_checklist": self.assistant.checklist(card.title)}