@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
142 lines (119 loc) • 4.08 kB
text/typescript
import { KanbanStore, KanbanBoard, Task } from './types.js';
import { ConfigManager } from '../../config/config-manager.js';
export class KanbanDataStore {
private store: KanbanStore = { boards: {} };
private configManager: ConfigManager;
private readonly MODULE_NAME = 'kanban';
private readonly DATA_FILE = 'kanban.json';
constructor(configManager?: ConfigManager) {
this.configManager = configManager || new ConfigManager();
}
async init(): Promise<void> {
const storageManager = this.configManager.getStorageManager();
await storageManager.ensureStorageDirectories();
const data = await storageManager.loadData(this.MODULE_NAME, this.DATA_FILE);
if (data) {
this.store = data;
} else {
// If file doesn't exist, create it with empty store
await this.save();
}
}
async save(): Promise<void> {
const storageManager = this.configManager.getStorageManager();
await storageManager.saveData(this.MODULE_NAME, this.DATA_FILE, this.store);
}
createBoard(name: string, columns: string[] = ['Backlog', 'To Do', 'In Progress', 'Review', 'Done']): KanbanBoard {
const board: KanbanBoard = {
id: this.generateId(),
name,
columns,
tasks: [],
createdAt: new Date(),
updatedAt: new Date(),
};
this.store.boards[board.id] = board;
return board;
}
getBoard(nameOrId: string): KanbanBoard | undefined {
// Try to find by ID first
if (this.store.boards[nameOrId]) {
return this.store.boards[nameOrId];
}
// Try to find by name
return Object.values(this.store.boards).find(board => board.name === nameOrId);
}
getAllBoards(): KanbanBoard[] {
return Object.values(this.store.boards);
}
addTask(boardNameOrId: string, task: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Task | null {
const board = this.getBoard(boardNameOrId);
if (!board) {
return null;
}
const newTask: Task = {
...task,
id: this.generateId(),
createdAt: new Date(),
updatedAt: new Date(),
// Initialize new enhanced properties with defaults if not provided
acceptanceCriteria: task.acceptanceCriteria || [],
dependencies: task.dependencies || [],
timeTracking: task.timeTracking || undefined,
attachments: task.attachments || [],
comments: task.comments || [],
customFields: task.customFields || [],
subtasks: task.subtasks || [],
watchers: task.watchers || [],
// Ensure tags is used instead of labels for consistency
tags: task.tags || (task as any).labels || [],
};
board.tasks.push(newTask);
board.updatedAt = new Date();
return newTask;
}
moveTask(boardNameOrId: string, taskId: string, newColumn: string): boolean {
const board = this.getBoard(boardNameOrId);
if (!board) {
return false;
}
const task = board.tasks.find(t => t.id === taskId);
if (!task || !board.columns.includes(newColumn)) {
return false;
}
task.column = newColumn;
task.updatedAt = new Date();
board.updatedAt = new Date();
return true;
}
updateTask(boardNameOrId: string, taskId: string, updates: Partial<Omit<Task, 'id' | 'createdAt' | 'updatedAt'>>): boolean {
const board = this.getBoard(boardNameOrId);
if (!board) {
return false;
}
const task = board.tasks.find(t => t.id === taskId);
if (!task) {
return false;
}
Object.assign(task, updates);
task.updatedAt = new Date();
board.updatedAt = new Date();
return true;
}
deleteTask(boardNameOrId: string, taskId: string): boolean {
const board = this.getBoard(boardNameOrId);
if (!board) {
return false;
}
const taskIndex = board.tasks.findIndex(t => t.id === taskId);
if (taskIndex === -1) {
return false;
}
board.tasks.splice(taskIndex, 1);
board.updatedAt = new Date();
return true;
}
private generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
}