diff --git a/README.md b/README.md index 8ef4c87..ec67cb8 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,23 @@ This project is API-first, meaning all the types frome from the `asyncapi.yml` f * [ ] Un mensaje que envĂ­e el estado actual del board al cliente * [ ] Refactorizar el agregado a event sourcing * [ ] Implementar tests e2e + + +Cual es el main aggregate? +- Column +- Card +- Los dos + + + +Agregados: +Board{ + Columns: Column[]{ + - columnId + - name: ColumnName + - Cards:CardId[] + } + } + +Card + - Info de card \ No newline at end of file diff --git a/main.ts b/main.ts index 8936fe8..dd00d99 100644 --- a/main.ts +++ b/main.ts @@ -1,34 +1,33 @@ import { createServer } from 'node:http' -import { WebSocketServer, type WebSocket } from 'ws' -import { AddColumnHandler } from './src/application/AddColumnHandler.ts' -import { AddCardHandler } from './src/application/AddCardHandler.ts' -import { EventBusWebSocket } from './src/infrastructure/EventBusWebSocket.ts' -import { Controller } from './src/infrastructure/server/Controller.ts' -import { Router } from './src/infrastructure/server/Router.ts' +import { type WebSocket, WebSocketServer } from 'ws' +import { AddColumnHandler } from './src/task-management/application/AddColumnHandler.ts' +import { AddCardHandler } from './src/task-management/application/AddCardHandler.ts' +import { EventBusWebSocket } from './src/task-management/infrastructure/EventBusWebSocket.ts' +import { Controller } from './src/task-management/infrastructure/server/Controller.ts' +import { Router } from './src/task-management/infrastructure/server/Router.ts' import { BoardRepositoryFake } from './tests/BoardRepositoryFake.ts' -import { BoardId } from './src/domain/BoardId.ts' +import { BoardId } from './src/task-management/domain/BoardId.ts' -const server = createServer( - (req, res) => { - const boardUrl = req.url?.split('/')[2] - if (boardUrl === undefined) { +const server = createServer((req, res) => { + const boardUrl = req.url?.split('/')[2] + if (boardUrl === undefined) { + res.writeHead(404, { 'Content-Type': 'text/plain' }) + res.end('Not found') + return + } + const boardId: BoardId = new BoardId(boardUrl) + console.log(`Received request for board ${boardId}`) + boardRepositoryFake + .findOrThrowBy(boardId) + .then((board) => { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify(board)) + }) + .catch(() => { res.writeHead(404, { 'Content-Type': 'text/plain' }) res.end('Not found') - return - } - const boardId: BoardId = new BoardId(boardUrl) - console.log(`Received request for board ${boardId}`) - boardRepositoryFake.findOrThrowBy(boardId) - .then((board) => { - res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end(JSON.stringify(board)) - }) - .catch(() => { - res.writeHead(404, { 'Content-Type': 'text/plain' }) - res.end('Not found') - }) - } -) + }) +}) const commandsServer = new WebSocketServer({ noServer: true }) const eventsServer = new WebSocketServer({ noServer: true }) diff --git a/src/domain/ErrorCode.ts b/src/domain/ErrorCode.ts deleted file mode 100644 index fd6057d..0000000 --- a/src/domain/ErrorCode.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const ErrorCode = { - BOARD_NOT_FOUND : 'BOARD_NOT_FOUND', - INVALID_COLUMN_NAME : 'INVALID_COLUMN_NAME', - INVALID_CARD_NAME : 'INVALID_CARD_NAME', - DUPLICATED_CARD_ERROR : 'DUPLICATED_CARD_ERROR' -} as const; - -export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode]; diff --git a/src/application/AddCardHandler.test.ts b/src/task-management/application/AddCardHandler.test.ts similarity index 83% rename from src/application/AddCardHandler.test.ts rename to src/task-management/application/AddCardHandler.test.ts index ccc5f6e..6265dbb 100644 --- a/src/application/AddCardHandler.test.ts +++ b/src/task-management/application/AddCardHandler.test.ts @@ -1,12 +1,13 @@ import assert from 'node:assert' import { beforeEach, describe, it, mock } from 'node:test' -import { WALLBOX_BOARD_ID } from '../../tests/BoardIdMother.ts' -import { BoardRepositoryFake } from '../../tests/BoardRepositoryFake.ts' -import { TODO_COLUMN_ID } from '../../tests/ColumnIdMother.ts' -import { AddCardHandler } from './AddCardHandler.ts' +import { WALLBOX_BOARD_ID } from '../../../tests/BoardIdMother.ts' +import { BoardRepositoryFake } from '../../../tests/BoardRepositoryFake.ts' +import * as CardMother from '../../../tests/CardIdMother.ts' +import { TODO_COLUMN_ID } from '../../../tests/ColumnIdMother.ts' +import type { CardAdded } from '../../../types/types.ts' import { Board } from '../domain/Board.ts' -import * as CardMother from '../../tests/CardIdMother.ts' -import type { CardAdded } from '../../types/types.ts' +import { CardId } from '../domain/CardId.ts' +import { AddCardHandler } from './AddCardHandler.ts' describe('AddCardHandler', () => { let eventBus = { emit: mock.fn() } @@ -30,7 +31,7 @@ describe('AddCardHandler', () => { }) const savedBoard = boardRepository.getLatestSaved() - assert.ok(savedBoard.hasCard('Test features')) + assert.ok(savedBoard.hasCard(CardId.fromString(CardMother.REFACTOR_REFINERY.ID))) }) it('cannot add a card with the same id', async () => { diff --git a/src/application/AddCardHandler.ts b/src/task-management/application/AddCardHandler.ts similarity index 66% rename from src/application/AddCardHandler.ts rename to src/task-management/application/AddCardHandler.ts index 64f2fa3..8074405 100644 --- a/src/application/AddCardHandler.ts +++ b/src/task-management/application/AddCardHandler.ts @@ -1,10 +1,12 @@ -import type { AddCard } from '../../types/types.ts' -import { AddCardSchema } from '../../types/types.zod.ts' +import { WALLBOX_BOARD_ID } from '../../../tests/BoardIdMother.ts' +import { TODO_COLUMN_ID } from '../../../tests/ColumnIdMother.ts' +import type { AddCard } from '../../../types/types.ts' +import { AddCardSchema } from '../../../types/types.zod.ts' import { BoardId } from '../domain/BoardId.ts' import type { BoardRepository } from '../domain/BoardRepository.ts' -import type { Handler } from './Handler.ts' import { Card } from '../domain/Card.ts' import type { EventBus } from '../domain/EventBus.ts' +import type { Handler } from './Handler.ts' export class AddCardHandler implements Handler { private readonly eventBus: EventBus @@ -22,7 +24,10 @@ export class AddCardHandler implements Handler { async handle(command: AddCard) { const board = await this.boardRepository.findOrThrowBy(new BoardId(command.boardId)) - board.addCard(command.columnId, Card.create({ id: command.cardId, name: command.name })) + board.addCard( + command.columnId, + Card.create({ id: command.cardId, name: command.name, columnId: TODO_COLUMN_ID, boardId: WALLBOX_BOARD_ID }) + ) await this.boardRepository.save(board) this.eventBus.emit(board.pullDomainEvents()) diff --git a/src/application/AddColumnHandler.test.ts b/src/task-management/application/AddColumnHandler.test.ts similarity index 93% rename from src/application/AddColumnHandler.test.ts rename to src/task-management/application/AddColumnHandler.test.ts index 82d2c8e..1b409a0 100644 --- a/src/application/AddColumnHandler.test.ts +++ b/src/task-management/application/AddColumnHandler.test.ts @@ -1,11 +1,11 @@ import { describe, it, mock } from 'node:test' import assert from 'node:assert' import { AddColumnHandler } from './AddColumnHandler.ts' -import { BoardRepositoryFake } from '../../tests/BoardRepositoryFake.ts' -import { NOT_EXISTENT_BOARD_ID, notExistentBoardId, WALLBOX_BOARD_ID } from '../../tests/BoardIdMother.ts' +import { BoardRepositoryFake } from '../../../tests/BoardRepositoryFake.ts' +import { NOT_EXISTENT_BOARD_ID, notExistentBoardId, WALLBOX_BOARD_ID } from '../../../tests/BoardIdMother.ts' import { BoardNotFound } from '../domain/BoardNotFound.ts' -import type { AddColumn, ColumnAdded, ColumnAddedType } from '../../types/types.ts' -import { DOING_COLUMN_ID, TODO_COLUMN_ID, todoColumnId } from '../../tests/ColumnIdMother.ts' +import type { ColumnAdded } from '../../../types/types.ts' +import { DOING_COLUMN_ID, TODO_COLUMN_ID, todoColumnId } from '../../../tests/ColumnIdMother.ts' describe('AddColumnHandler', () => { it('should add a column', async () => { diff --git a/src/application/AddColumnHandler.ts b/src/task-management/application/AddColumnHandler.ts similarity index 87% rename from src/application/AddColumnHandler.ts rename to src/task-management/application/AddColumnHandler.ts index ca1bcf6..7c5f877 100644 --- a/src/application/AddColumnHandler.ts +++ b/src/task-management/application/AddColumnHandler.ts @@ -1,5 +1,5 @@ -import type { AddColumn } from '../../types/types.ts' -import { AddColumnSchema } from '../../types/types.zod.ts' +import type { AddColumn } from '../../../types/types.ts' +import { AddColumnSchema } from '../../../types/types.zod.ts' import type { EventBus } from '../domain/EventBus.ts' import type { Handler } from './Handler.ts' import { BoardId } from '../domain/BoardId.ts' diff --git a/src/task-management/application/FancyAddCardHandler.ts b/src/task-management/application/FancyAddCardHandler.ts new file mode 100644 index 0000000..5df3a7f --- /dev/null +++ b/src/task-management/application/FancyAddCardHandler.ts @@ -0,0 +1,43 @@ +import type { AddCard } from '../../../types/types.ts' +import { AddCardSchema } from '../../../types/types.zod.ts' +import { BoardId } from '../domain/BoardId.ts' +import type { BoardRepository } from '../domain/BoardRepository.ts' +import type { Handler } from './Handler.ts' +import { Card } from '../domain/Card.ts' +import type { EventBus } from '../domain/EventBus.js' +import { ColumnId } from '../domain/ColumnId.ts' +import type { CardRepository } from '../domain/CardRepository.ts' + +export class FancyAddCardHandler implements Handler { + private readonly eventBus: EventBus + private readonly boardRepository: BoardRepository + private readonly cardRepository: CardRepository + + constructor(eventBus: EventBus, boardRepository: BoardRepository, cardRepository: CardRepository) { + this.eventBus = eventBus + this.boardRepository = boardRepository + this.cardRepository = cardRepository + } + + schema() { + return AddCardSchema + } + + async handle(command: AddCard) { + this.ensureBoardAndColumnExists(command.boardId, command.columnId) + + const card: Card = Card.create({ + id: command.cardId, + name: command.name, + boardId: command.boardId, + columnId: command.columnId + }) + await this.cardRepository.save(card) + this.eventBus.emit(card.pullDomainEvents()) + } + + private async ensureBoardAndColumnExists(boardId: string, columnId: string) { + const board = await this.boardRepository.findOrThrowBy(new BoardId(boardId)) + board.ensureColumnExists(ColumnId.fromString(columnId)) + } +} diff --git a/src/application/Handler.ts b/src/task-management/application/Handler.ts similarity index 100% rename from src/application/Handler.ts rename to src/task-management/application/Handler.ts diff --git a/src/domain/Board.test.ts b/src/task-management/domain/Board.test.ts similarity index 74% rename from src/domain/Board.test.ts rename to src/task-management/domain/Board.test.ts index f7a4428..cf1e7e1 100644 --- a/src/domain/Board.test.ts +++ b/src/task-management/domain/Board.test.ts @@ -1,10 +1,11 @@ import assert from 'node:assert/strict' import { describe, it } from 'node:test' -import { DOING_COLUMN_ID, TODO_COLUMN_ID, todoColumnId } from '../../tests/ColumnIdMother.ts' +import { WALLBOX_BOARD_ID } from '../../../tests/BoardIdMother.ts' +import { notExistentCardId } from '../../../tests/CardIdMother.ts' +import { DOING_COLUMN_ID, TODO_COLUMN_ID, todoColumnId } from '../../../tests/ColumnIdMother.ts' import { Board } from './Board.ts' import { Card } from './Card.ts' -import { WALLBOX_BOARD_ID } from '../../tests/BoardIdMother.ts' -import { notExistentCardId } from '../../tests/CardIdMother.ts' +import { CardId } from './CardId.ts' describe('Board', () => { it('does not have column on creation', () => { @@ -54,16 +55,21 @@ describe('Board', () => { it('can not find any card in an empty board', () => { const board = new Board('ecc81f64-7925-4004-b7e1-4f1f26dbbba5') - assert(!board.hasCard('NOT-EXISTANT')) + assert(!board.hasCard(CardId.fromString('NOT-EXISTANT'))) }) it('checks if has a card by name', () => { const board = new Board('ecc81f64-7925-4004-b7e1-4f1f26dbbba5') - const card = Card.create({ id: 'random', name: 'Example card' }) + const card = Card.create({ + id: 'random', + name: 'Example card', + columnId: TODO_COLUMN_ID, + boardId: WALLBOX_BOARD_ID + }) board.addColumn(TODO_COLUMN_ID, 'TODO') board.addCard(TODO_COLUMN_ID, card) - const hasCard = board.hasCard('not existent') + const hasCard = board.hasCard(CardId.fromString('NOT-EXISTANT')) assert(!hasCard) }) @@ -71,7 +77,7 @@ describe('Board', () => { it('checks if has a card by id', () => { const board = new Board(WALLBOX_BOARD_ID) const cardName = 'Example card' - const card = Card.create({ id: 'random', name: cardName }) + const card = Card.create({ id: 'random', name: cardName, columnId: TODO_COLUMN_ID, boardId: WALLBOX_BOARD_ID }) board.addColumn(TODO_COLUMN_ID, 'TODO') board.addCard(TODO_COLUMN_ID, card) @@ -81,13 +87,13 @@ describe('Board', () => { it('finds a card in any column', () => { const board = new Board('ecc81f64-7925-4004-b7e1-4f1f26dbbba5') const cardName = 'Example card' - const card = Card.create({ id: 'random', name: cardName }) + const card = Card.create({ id: 'random', name: cardName, columnId: TODO_COLUMN_ID, boardId: WALLBOX_BOARD_ID }) board.addColumn(TODO_COLUMN_ID, 'TODO') board.addColumn(DOING_COLUMN_ID, 'DOING') board.addCard(DOING_COLUMN_ID, card) - assert(board.hasCard(cardName)) + assert(board.hasCard(card.getId())) }) }) @@ -95,17 +101,22 @@ describe('Board', () => { it('can add a card', () => { const board = new Board('ecc81f64-7925-4004-b7e1-4f1f26dbbba5') const cardName = 'Example card' - const card = Card.create({ id: 'random', name: cardName }) + const card = Card.create({ id: 'random', name: cardName, columnId: TODO_COLUMN_ID, boardId: WALLBOX_BOARD_ID }) board.addColumn(TODO_COLUMN_ID, 'TODO') board.addCard(TODO_COLUMN_ID, card) - assert(board.hasCard(cardName)) + assert(board.hasCard(card.getId())) }) it('throws an error if column does not exists', () => { const board = new Board('ecc81f64-7925-4004-b7e1-4f1f26dbbba5') - const card = Card.create({ id: 'random', name: 'not important' }) + const card = Card.create({ + id: 'random', + name: 'not important', + columnId: TODO_COLUMN_ID, + boardId: WALLBOX_BOARD_ID + }) assert.throws(() => board.addCard('not important', card)) }) @@ -113,14 +124,14 @@ describe('Board', () => { it('can add multiple a cards', () => { const board = new Board('ecc81f64-7925-4004-b7e1-4f1f26dbbba5') const cardName = 'Example card' - const card = Card.create({ id: 'random', name: cardName }) + const card = Card.create({ id: 'random', name: cardName, columnId: TODO_COLUMN_ID, boardId: WALLBOX_BOARD_ID }) board.addColumn(TODO_COLUMN_ID, 'TODO') board.addColumn(DOING_COLUMN_ID, 'DOING') board.addCard(DOING_COLUMN_ID, card) board.delete(todoColumnId) - assert(board.hasCard(cardName)) + assert(board.hasCard(card.getId())) }) }) diff --git a/src/domain/Board.ts b/src/task-management/domain/Board.ts similarity index 82% rename from src/domain/Board.ts rename to src/task-management/domain/Board.ts index a6240f4..fea66a6 100644 --- a/src/domain/Board.ts +++ b/src/task-management/domain/Board.ts @@ -3,7 +3,8 @@ import type { Card } from './Card.ts' import { Column } from './Column.ts' import { ColumnId } from './ColumnId.ts' import { CardId } from './CardId.ts' -import type { BoardEvent, CardAdded } from '../../types/types.ts' +import type { BoardEvent } from '../../../types/types.ts' +import { ColumnNotFoundError } from './errors/ColumnNotFoundError.ts' export class Board { private readonly id: BoardId @@ -47,7 +48,7 @@ export class Board { throw new Error('Column not found') } - column.addCard(card) + column.addCard(card.getId()) this.domainEvents.push({ type: 'CardAddedType', cardId: card.getId().getValue(), @@ -61,7 +62,7 @@ export class Board { this.columns = this.columns.filter((c) => !c.hasId(columnId)) } - hasCard(cardName: string | CardId) { + hasCard(cardName: CardId) { return this.columns.some((c) => c.hasCard(cardName)) } @@ -72,4 +73,10 @@ export class Board { flushDomainEvents() { this.domainEvents = [] } + + ensureColumnExists(columnId: ColumnId) { + if (!this.hasColumn(columnId)) { + throw new ColumnNotFoundError(columnId, this.id) + } + } } diff --git a/src/domain/BoardId.ts b/src/task-management/domain/BoardId.ts similarity index 100% rename from src/domain/BoardId.ts rename to src/task-management/domain/BoardId.ts diff --git a/src/domain/BoardNotFound.ts b/src/task-management/domain/BoardNotFound.ts similarity index 100% rename from src/domain/BoardNotFound.ts rename to src/task-management/domain/BoardNotFound.ts diff --git a/src/domain/BoardRepository.ts b/src/task-management/domain/BoardRepository.ts similarity index 100% rename from src/domain/BoardRepository.ts rename to src/task-management/domain/BoardRepository.ts diff --git a/src/domain/Card.test.ts b/src/task-management/domain/Card.test.ts similarity index 59% rename from src/domain/Card.test.ts rename to src/task-management/domain/Card.test.ts index 7d3812b..caf56d5 100644 --- a/src/domain/Card.test.ts +++ b/src/task-management/domain/Card.test.ts @@ -1,15 +1,17 @@ +import assert from 'node:assert' +import { randomUUID } from 'node:crypto' import { beforeEach, describe, it } from 'node:test' import { Card } from './Card.ts' -import assert from 'node:assert' import { InvalidCardNameError } from './errors/InvalidCardNameError.ts' -import { randomUUID } from 'node:crypto' +import { TODO_COLUMN_ID } from '../../../tests/ColumnIdMother.ts' +import { WALLBOX_BOARD_ID } from '../../../tests/BoardIdMother.ts' describe('Card', () => { let card: Card // TODO: add mother card beforeEach(() => { - card = Card.create({ id: randomUUID(), name: 'name' }) + card = Card.create({ id: randomUUID(), name: 'name', columnId: TODO_COLUMN_ID, boardId: WALLBOX_BOARD_ID }) }) it('is created with a name', () => { @@ -23,11 +25,17 @@ describe('Card', () => { }) it('cannot be created with empty name', () => { - assert.throws(() => Card.create({ id: randomUUID(), name: '' }), new InvalidCardNameError('')) + assert.throws( + () => Card.create({ id: randomUUID(), name: '', columnId: TODO_COLUMN_ID, boardId: WALLBOX_BOARD_ID }), + new InvalidCardNameError('') + ) }) it('cannot be created with blank name', () => { - assert.throws(() => Card.create({ id: randomUUID(), name: ' ' }), new InvalidCardNameError(' ')) + assert.throws( + () => Card.create({ id: randomUUID(), name: ' ', columnId: TODO_COLUMN_ID, boardId: WALLBOX_BOARD_ID }), + new InvalidCardNameError(' ') + ) }) it('is created with empty description', () => { diff --git a/src/domain/Card.ts b/src/task-management/domain/Card.ts similarity index 76% rename from src/domain/Card.ts rename to src/task-management/domain/Card.ts index e63e54c..b9c5064 100644 --- a/src/domain/Card.ts +++ b/src/task-management/domain/Card.ts @@ -1,11 +1,14 @@ +import type { BoardId } from './BoardId.ts' import { CardDescription } from './CardDescription.ts' import { CardId } from './CardId.ts' import { CardName } from './CardName.ts' +import type { ColumnId } from './ColumnId.ts' export class Card { private id: CardId public name: CardName private description: CardDescription + private domainEvents: Array = [] private constructor(values: { id: CardId; name: CardName; description: CardDescription }) { this.id = values.id @@ -13,7 +16,8 @@ export class Card { this.description = values.description } - static create(values: { id: string; name: string }): Card { + static create(values: { id: string; name: string; columnId: string; boardId: string }): Card { + //Add event return new Card({ id: CardId.fromString(values.id), name: CardName.fromString(values.name), @@ -48,4 +52,12 @@ export class Card { hasId(id: CardId) { return this.id.equals(id) } + + pullDomainEvents() { + return this.domainEvents + } + + flushDomainEvents() { + this.domainEvents = [] + } } diff --git a/src/domain/CardDescription.ts b/src/task-management/domain/CardDescription.ts similarity index 100% rename from src/domain/CardDescription.ts rename to src/task-management/domain/CardDescription.ts diff --git a/src/domain/CardId.ts b/src/task-management/domain/CardId.ts similarity index 100% rename from src/domain/CardId.ts rename to src/task-management/domain/CardId.ts diff --git a/src/domain/CardName.ts b/src/task-management/domain/CardName.ts similarity index 100% rename from src/domain/CardName.ts rename to src/task-management/domain/CardName.ts diff --git a/src/task-management/domain/CardRepository.ts b/src/task-management/domain/CardRepository.ts new file mode 100644 index 0000000..375c9dd --- /dev/null +++ b/src/task-management/domain/CardRepository.ts @@ -0,0 +1,8 @@ +import { BoardId } from './BoardId.ts' +import { Board } from './Board.ts' +import { BoardNotFound } from './BoardNotFound.ts' +import type { Card } from './Card.ts' + +export abstract class CardRepository { + abstract save(card: Card): Promise +} diff --git a/src/domain/Column.test.ts b/src/task-management/domain/Column.test.ts similarity index 65% rename from src/domain/Column.test.ts rename to src/task-management/domain/Column.test.ts index 93d49ed..819b951 100644 --- a/src/domain/Column.test.ts +++ b/src/task-management/domain/Column.test.ts @@ -1,12 +1,13 @@ import assert from 'node:assert' import { randomUUID } from 'node:crypto' import { beforeEach, describe, it } from 'node:test' -import { TODO_COLUMN_ID } from '../../tests/ColumnIdMother.ts' +import { TODO_COLUMN_ID } from '../../../tests/ColumnIdMother.ts' import { Card } from './Card.ts' import { CardId } from './CardId.ts' import { Column } from './Column.ts' import { DuplicatedCardError } from './errors/DuplicatedCardError.ts' import { InvalidColumnNameError } from './errors/InvalidColumnNameError.ts' +import { WALLBOX_BOARD_ID } from '../../../tests/BoardIdMother.ts' describe('Column', () => { let column: Column @@ -44,25 +45,25 @@ describe('Column', () => { }) it('can add cards', () => { - column.addCard(Card.create({ id: randomUUID(), name: 'name' })) + column.addCard(CardId.fromString(randomUUID())) assert.ok(!column.isEmpty()) }) it('cannot add the same card twice', () => { const cardId = randomUUID() - const card = Card.create({ id: cardId, name: 'name' }) + const card = Card.create({ id: cardId, name: 'name', columnId: TODO_COLUMN_ID, boardId: WALLBOX_BOARD_ID }) - column.addCard(card) - assert.throws(() => column.addCard(card), new DuplicatedCardError(CardId.fromString(cardId))) + column.addCard(card.getId()) + assert.throws(() => column.addCard(card.getId()), new DuplicatedCardError(CardId.fromString(cardId))) }) it('can add two cards', () => { const cardId = randomUUID() - const card1 = Card.create({ id: cardId, name: 'name' }) - const card2 = Card.create({ id: cardId + '1', name: 'name' }) + const card1 = Card.create({ id: cardId, name: 'name', columnId: TODO_COLUMN_ID, boardId: WALLBOX_BOARD_ID }) + const card2 = Card.create({ id: cardId + '1', name: 'name', columnId: TODO_COLUMN_ID, boardId: WALLBOX_BOARD_ID }) - column.addCard(card1) - assert.doesNotThrow(() => column.addCard(card2)) + column.addCard(card1.getId()) + assert.doesNotThrow(() => column.addCard(card2.getId())) }) }) diff --git a/src/domain/Column.ts b/src/task-management/domain/Column.ts similarity index 67% rename from src/domain/Column.ts rename to src/task-management/domain/Column.ts index b01e033..6e3f528 100644 --- a/src/domain/Column.ts +++ b/src/task-management/domain/Column.ts @@ -7,7 +7,7 @@ import { CardId } from './CardId.ts' export class Column { private id: ColumnId private name: ColumnName - private cards: Card[] = [] + private cards: CardId[] = [] private constructor(id: string, name: string) { this.id = ColumnId.fromString(id) @@ -34,22 +34,20 @@ export class Column { return this.cards.length === 0 } - addCard(card: Card): void { - this.ensureCardIsNotDuplicated(card) - this.cards.push(card) + addCard(cardId: CardId): void { + this.ensureCardIsNotDuplicated(cardId) + this.cards.push(cardId) } - ensureCardIsNotDuplicated(card: Card): void { - if (this.cards.some((c) => c.hasSameId(card))) { - throw new DuplicatedCardError(card.getId()) + ensureCardIsNotDuplicated(card: CardId): void { + if (this.cards.some((c) => c.equals(card))) { + throw new DuplicatedCardError(card) } } - hasCard(cardName: string | CardId) { + hasCard(cardName: CardId) { if (cardName instanceof CardId) { - return this.cards.some((c) => c.hasId(cardName)) + return this.cards.some((c) => c.equals(cardName)) } - - return this.cards.some((c) => c.hasName(cardName)) } } diff --git a/src/domain/ColumnId.ts b/src/task-management/domain/ColumnId.ts similarity index 100% rename from src/domain/ColumnId.ts rename to src/task-management/domain/ColumnId.ts diff --git a/src/domain/ColumnName.ts b/src/task-management/domain/ColumnName.ts similarity index 100% rename from src/domain/ColumnName.ts rename to src/task-management/domain/ColumnName.ts diff --git a/src/domain/DomainError.ts b/src/task-management/domain/DomainError.ts similarity index 100% rename from src/domain/DomainError.ts rename to src/task-management/domain/DomainError.ts diff --git a/src/task-management/domain/ErrorCode.ts b/src/task-management/domain/ErrorCode.ts new file mode 100644 index 0000000..e215c49 --- /dev/null +++ b/src/task-management/domain/ErrorCode.ts @@ -0,0 +1,9 @@ +export const ErrorCode = { + BOARD_NOT_FOUND: 'BOARD_NOT_FOUND', + INVALID_COLUMN_NAME: 'INVALID_COLUMN_NAME', + INVALID_CARD_NAME: 'INVALID_CARD_NAME', + DUPLICATED_CARD_ERROR: 'DUPLICATED_CARD_ERROR', + COLUMN_NOT_FOUND: 'COLUMN_NOT_FOUND' +} as const + +export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode] diff --git a/src/domain/EventBus.ts b/src/task-management/domain/EventBus.ts similarity index 52% rename from src/domain/EventBus.ts rename to src/task-management/domain/EventBus.ts index 3df14d6..246f44f 100644 --- a/src/domain/EventBus.ts +++ b/src/task-management/domain/EventBus.ts @@ -1,4 +1,4 @@ -import type { BoardEvent } from '../../types/types.ts' +import type { BoardEvent } from '../../../types/types.ts' export interface EventBus { emit(event: BoardEvent[]): void diff --git a/src/task-management/domain/errors/ColumnNotFoundError.ts b/src/task-management/domain/errors/ColumnNotFoundError.ts new file mode 100644 index 0000000..e8ce7e4 --- /dev/null +++ b/src/task-management/domain/errors/ColumnNotFoundError.ts @@ -0,0 +1,10 @@ +import type { BoardId } from '../BoardId.ts' +import type { ColumnId } from '../ColumnId.ts' +import { DomainError } from '../DomainError.ts' +import { ErrorCode } from '../ErrorCode.ts' + +export class ColumnNotFoundError extends DomainError { + constructor(columnId: ColumnId, boardId: BoardId) { +super(`Column with Id: ${columnId} not found in Board: ${boardId}`, ErrorCode.COLUMN_NOT_FOUND) + } +} diff --git a/src/domain/errors/DuplicatedCardError.ts b/src/task-management/domain/errors/DuplicatedCardError.ts similarity index 100% rename from src/domain/errors/DuplicatedCardError.ts rename to src/task-management/domain/errors/DuplicatedCardError.ts diff --git a/src/domain/errors/InvalidCardNameError.ts b/src/task-management/domain/errors/InvalidCardNameError.ts similarity index 100% rename from src/domain/errors/InvalidCardNameError.ts rename to src/task-management/domain/errors/InvalidCardNameError.ts diff --git a/src/domain/errors/InvalidColumnNameError.ts b/src/task-management/domain/errors/InvalidColumnNameError.ts similarity index 100% rename from src/domain/errors/InvalidColumnNameError.ts rename to src/task-management/domain/errors/InvalidColumnNameError.ts diff --git a/src/infrastructure/EventBusWebSocket.ts b/src/task-management/infrastructure/EventBusWebSocket.ts similarity index 100% rename from src/infrastructure/EventBusWebSocket.ts rename to src/task-management/infrastructure/EventBusWebSocket.ts diff --git a/src/infrastructure/server/Controller.ts b/src/task-management/infrastructure/server/Controller.ts similarity index 82% rename from src/infrastructure/server/Controller.ts rename to src/task-management/infrastructure/server/Controller.ts index 1287678..3101b13 100644 --- a/src/infrastructure/server/Controller.ts +++ b/src/task-management/infrastructure/server/Controller.ts @@ -1,7 +1,7 @@ import { type WebSocket } from 'ws' -import { StringToJSONSchema } from '../../../schema.ts' -import type { BoardCommand } from '../../../types/types.ts' -import { BoardCommandSchema } from '../../../types/types.zod.ts' +import { StringToJSONSchema } from '../../../../schema.ts' +import type { BoardCommand } from '../../../../types/types.ts' +import { BoardCommandSchema } from '../../../../types/types.zod.ts' import type { Handler } from '../../application/Handler.ts' export class Controller { diff --git a/src/infrastructure/server/Router.ts b/src/task-management/infrastructure/server/Router.ts similarity index 100% rename from src/infrastructure/server/Router.ts rename to src/task-management/infrastructure/server/Router.ts diff --git a/tests/BoardIdMother.ts b/tests/BoardIdMother.ts index 34848cd..57df37a 100644 --- a/tests/BoardIdMother.ts +++ b/tests/BoardIdMother.ts @@ -1,4 +1,4 @@ -import { BoardId } from '../src/domain/BoardId.ts' +import { BoardId } from '../src/task-management/domain/BoardId.ts' export const WALLBOX_BOARD_ID = 'abfe40bf-22b8-4692-8585-cea01b809493' diff --git a/tests/BoardRepositoryFake.ts b/tests/BoardRepositoryFake.ts index e95db66..c6110d1 100644 --- a/tests/BoardRepositoryFake.ts +++ b/tests/BoardRepositoryFake.ts @@ -1,7 +1,7 @@ -import { Board } from '../src/domain/Board.ts' -import { BoardId } from '../src/domain/BoardId.ts' -import { BoardRepository } from '../src/domain/BoardRepository.ts' -import { WALLBOX_BOARD_ID, wallboxBoardId } from './BoardIdMother.ts' +import { Board } from '../src/task-management/domain/Board.ts' +import { BoardId } from '../src/task-management/domain/BoardId.ts' +import { BoardRepository } from '../src/task-management/domain/BoardRepository.ts' +import { WALLBOX_BOARD_ID } from './BoardIdMother.ts' export class BoardRepositoryFake extends BoardRepository { private boards: Map = new Map() diff --git a/tests/CardIdMother.ts b/tests/CardIdMother.ts index 67d0766..a13c8e5 100644 --- a/tests/CardIdMother.ts +++ b/tests/CardIdMother.ts @@ -1,4 +1,4 @@ -import { CardId } from '../src/domain/CardId.ts' +import { CardId } from '../src/task-management/domain/CardId.ts' export const REFACTOR_REFINERY_ID = '74a36d29-5faf-4561-a669-dec2e4024146' diff --git a/tests/CardRepositoryFake.ts b/tests/CardRepositoryFake.ts new file mode 100644 index 0000000..db4bf5b --- /dev/null +++ b/tests/CardRepositoryFake.ts @@ -0,0 +1,27 @@ +import { Board } from '../src/task-management/domain/Board.ts' +import { Card } from '../src/task-management/domain/Card.ts' +import { CardRepository } from '../src/task-management/domain/CardRepository.ts' +import { wallboxBoardId } from './BoardIdMother.ts' +import { REFACTOR_REFINERY } from './CardIdMother.ts' + +export class CardRepositoryFake extends CardRepository { + private cards: Map = new Map() + + private latestSavedBoard?: Board + + constructor( + card: Card = Card.create({ + id: REFACTOR_REFINERY.ID, + name: REFACTOR_REFINERY.NAME, + boardId: wallboxBoardId.getValue(), + columnId: 'columnId' + }) + ) { + super() + this.cards.set(card.getId().toString(), card) + } + + async save(card: Card) { + this.cards.set(card.getId().getValue(), card) + } +} diff --git a/tests/ColumnIdMother.ts b/tests/ColumnIdMother.ts index 3023e37..4cfdb4e 100644 --- a/tests/ColumnIdMother.ts +++ b/tests/ColumnIdMother.ts @@ -1,4 +1,4 @@ -import { ColumnId } from '../src/domain/ColumnId.ts' +import { ColumnId } from '../src/task-management/domain/ColumnId.ts' export const TODO_COLUMN_ID = 'f7b83013-5cb5-4972-ac7f-a55e2016f18e' diff --git a/tsconfig.json b/tsconfig.json index bcade7a..1087872 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,6 @@ "strict": true, "skipLibCheck": true, "noUncheckedIndexedAccess": true, - "erasableSyntaxOnly": true + //"erasableSyntaxOnly": true } }