flc/crewboard
Crewboard - team task board (boards, lists, cards, roles, history, drag and drop, MCP, AI extras). FastAPI + DataK3 + appid + Models.
A remix template · 1 remixes · Remix flc/crewboard
app/boards/infrastructure/memory.py
"""An in-memory repository: the tests use it, and so does `DEV_USER=... uvicorn` on localhost when
no BUCKET is set, so the whole board runs without a Dodil account."""
import copy
from datetime import datetime, timezone
from boards.application.ports import Taken
class MemoryBoards:
"""Stores copies, like a database would, so a use case can't cheat by mutating shared objects."""
def __init__(self, clock=None):
self.boards, self.cards, self.events = {}, {}, []
self.clock = clock or (lambda: datetime.now(timezone.utc).isoformat(timespec="seconds"))
def get(self, board_id):
return copy.deepcopy(self.boards.get(board_id))
def boards_for(self, email):
return [copy.deepcopy(b) for b in self.boards.values()
if b.owner_email == email or email in b.members]
def insert_board(self, board):
if board.id in self.boards:
raise Taken(board.id)
self.boards[board.id] = copy.deepcopy(board)
def save_board(self, board):
stored = self.boards[board.id]
stored.title, stored.lists = board.title, list(board.lists)
def save_members(self, board, removed=()):
self.boards[board.id].members = dict(board.members)
def get_card(self, card_id):
return copy.deepcopy(self.cards.get(card_id))
def cards_on(self, board_id):
return sorted((copy.deepcopy(c) for c in self.cards.values() if c.board_id == board_id),
key=lambda c: c.position)
def cards_on_boards(self, board_ids):
return [c for b in board_ids for c in self.cards_on(b)]
def max_number(self, board_id):
return max((c.number for c in self.cards.values() if c.board_id == board_id), default=0)
def insert_card(self, card):
if card.id in self.cards:
raise Taken(card.id)
self.cards[card.id] = copy.deepcopy(card)
def update_card(self, card, expected_version):
if self.cards[card.id].version != expected_version:
return False
self.cards[card.id] = copy.deepcopy(card)
return True
def log(self, board_id, card_id, actor, action, changes=None):
self.events.append({"board_id": board_id, "card_id": card_id, "actor": actor,
"action": action, "changes": changes,
"at": self.clock()})
def activity(self, board_id, card_id=None, limit=50):
rows = [e for e in self.events if e["board_id"] == board_id
and (card_id is None or e["card_id"] == card_id)]
return list(reversed(rows))[:limit]