Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
51 changes: 25 additions & 26 deletions main.ts
Original file line number Diff line number Diff line change
@@ -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 })

Expand Down
8 changes: 0 additions & 8 deletions src/domain/ErrorCode.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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() }
Expand All @@ -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 () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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())
Expand Down
Original file line number Diff line number Diff line change
@@ -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 () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
43 changes: 43 additions & 0 deletions src/task-management/application/FancyAddCardHandler.ts
Original file line number Diff line number Diff line change
@@ -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))
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -54,24 +55,29 @@ 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)
})

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)

Expand All @@ -81,46 +87,51 @@ 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()))
})
})

describe('addCard', () => {
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))
})

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()))
})
})

Expand Down
13 changes: 10 additions & 3 deletions src/domain/Board.ts → src/task-management/domain/Board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand All @@ -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))
}

Expand All @@ -72,4 +73,10 @@ export class Board {
flushDomainEvents() {
this.domainEvents = []
}

ensureColumnExists(columnId: ColumnId) {
if (!this.hasColumn(columnId)) {
throw new ColumnNotFoundError(columnId, this.id)
}
}
}
File renamed without changes.
Loading