dmslanes/lanes
Lanes - team task boards with roles, history and an MCP endpoint for Claude and ChatGPT
A remix template · 0 remixes · Remix dmslanes/lanes
app/infrastructure/memory.py
"""In-memory Repository: the tests and `LANES_STORAGE=memory` local runs use it."""
from __future__ import annotations
import threading
from dataclasses import replace
from app.domain.model import Activity, Board, BoardList, Card, Conflict, Membership, Workspace
class MemoryRepository:
def __init__(self):
self._lock = threading.Lock()
self.workspaces: dict[str, Workspace] = {}
self.members: dict[tuple[str, str], Membership] = {}
self.boards: dict[str, Board] = {}
self.lists: dict[str, BoardList] = {}
self.cards: dict[str, Card] = {}
self.activity: list[Activity] = []
self._card_number = 0
def add_workspace(self, ws):
self.workspaces[ws.id] = ws
def get_workspace(self, workspace_id):
return self.workspaces.get(workspace_id)
def get_membership(self, workspace_id, email):
return self.members.get((workspace_id, email))
def memberships_of_workspace(self, workspace_id):
return [m for (w, _), m in self.members.items() if w == workspace_id]
def memberships_of_person(self, email):
return [m for (_, e), m in self.members.items() if e == email]
def save_membership(self, m):
self.members[(m.workspace_id, m.email)] = m
def delete_membership(self, workspace_id, email):
self.members.pop((workspace_id, email), None)
def add_board(self, board):
self.boards[board.id] = board
def get_board(self, board_id):
return self.boards.get(board_id)
def boards_of_workspaces(self, workspace_ids):
ids = set(workspace_ids)
return [b for b in self.boards.values() if b.workspace_id in ids]
def add_list(self, lst):
self.lists[lst.id] = lst
def get_list(self, list_id):
return self.lists.get(list_id)
def lists_of_board(self, board_id):
return sorted([l for l in self.lists.values() if l.board_id == board_id], key=lambda l: l.position)
def claim_card_number(self):
with self._lock:
self._card_number += 1
return self._card_number
def add_card(self, card):
self.cards[card.id] = replace(card)
def save_card(self, card, expected_version):
with self._lock:
current = self.cards.get(card.id)
if current is None or current.version != expected_version:
raise Conflict("this card changed since you opened it; reload and try again")
self.cards[card.id] = replace(card)
def delete_card(self, card_id):
self.cards.pop(card_id, None)
def get_card(self, card_id):
c = self.cards.get(card_id)
return replace(c) if c else None
def get_card_by_number(self, number):
for c in self.cards.values():
if c.number == number:
return replace(c)
return None
def cards_of_board(self, board_id):
return [replace(c) for c in self.cards.values() if c.board_id == board_id]
def cards_of_workspaces(self, workspace_ids):
ids = set(workspace_ids)
return [replace(c) for c in self.cards.values() if c.workspace_id in ids]
def add_activity(self, a):
self.activity.append(a)
def activity_of_card(self, card_id):
return [a for a in self.activity if a.card_id == card_id]
def activity_of_board(self, board_id, limit):
return sorted([a for a in self.activity if a.board_id == board_id], key=lambda a: a.at, reverse=True)[:limit]