diff --git a/server/src/database/boards_observer.go b/server/src/database/boards_observer.go deleted file mode 100644 index f36c052dee..0000000000 --- a/server/src/database/boards_observer.go +++ /dev/null @@ -1,76 +0,0 @@ -package database - -import ( - "context" - "github.com/google/uuid" - "github.com/uptrace/bun" - "scrumlr.io/server/identifiers" -) - -type BoardObserver interface { - Observer - - // UpdatedBoard will be called if the specified board received an update. - UpdatedBoard(board Board) - - // DeletedBoard will be called if the board with the specified id was deleted. - DeletedBoard(board uuid.UUID) - - // UpdatedBoardTimer will be called if the specified board started/deleted a timer - UpdatedBoardTimer(board Board) -} - -var _ bun.AfterUpdateHook = (*BoardUpdate)(nil) -var _ bun.AfterUpdateHook = (*BoardTimerUpdate)(nil) -var _ bun.AfterDeleteHook = (*Board)(nil) - -func (*BoardUpdate) AfterUpdate(ctx context.Context, _ *bun.UpdateQuery) error { - if ctx.Value("Database") == nil { - return nil - } - d := ctx.Value("Database").(*Database) - if len(d.observer) > 0 { - board := ctx.Value("Result").(*Board) - for _, observer := range d.observer { - if o, ok := observer.(BoardObserver); ok { - o.UpdatedBoard(*board) - return nil - } - } - } - return nil -} - -func (*BoardTimerUpdate) AfterUpdate(ctx context.Context, _ *bun.UpdateQuery) error { - if ctx.Value("Database") == nil { - return nil - } - d := ctx.Value("Database").(*Database) - if len(d.observer) > 0 { - board := ctx.Value("Result").(*Board) - for _, observer := range d.observer { - if o, ok := observer.(BoardObserver); ok { - o.UpdatedBoardTimer(*board) - return nil - } - } - } - return nil -} - -func (*Board) AfterDelete(ctx context.Context, _ *bun.DeleteQuery) error { - if ctx.Value("Database") == nil { - return nil - } - d := ctx.Value("Database").(*Database) - if len(d.observer) > 0 { - board := ctx.Value(identifiers.BoardIdentifier).(uuid.UUID) - for _, observer := range d.observer { - if o, ok := observer.(BoardObserver); ok { - o.DeletedBoard(board) - return nil - } - } - } - return nil -} diff --git a/server/src/database/boards_observer_test.go b/server/src/database/boards_observer_test.go deleted file mode 100644 index e4059e8de5..0000000000 --- a/server/src/database/boards_observer_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package database - -import ( - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "testing" -) - -type BoardsObserverForTests struct { - t *testing.T - boardName *string - deleted bool - deletedBoard *uuid.UUID -} - -func (o *BoardsObserverForTests) UpdatedBoard(board Board) { - o.boardName = board.Name -} - -func (o *BoardsObserverForTests) UpdatedBoardTimer(board Board) { -} - -func (o *BoardsObserverForTests) DeletedBoard(board uuid.UUID) { - o.deleted = true - o.deletedBoard = &board -} - -var boardsObserver BoardsObserverForTests - -func TestBoardsObserver(t *testing.T) { - boardsObserver = BoardsObserverForTests{t: t, deleted: false} - testDb.AttachObserver(&boardsObserver) - - t.Run("Test=1", testBoardsObserverOnUpdate) - t.Run("Test=2", testBoardsObserverOnDelete) - - _, _ = testDb.DetachObserver(boardsObserver) -} - -func testBoardsObserverOnUpdate(t *testing.T) { - board := fixture.MustRow("Board.boardsObserverTestBoard").(*Board) - - updatedName := "My updated board name" - updatedBoard, err := testDb.UpdateBoard(BoardUpdate{ - ID: board.ID, - Name: &updatedName, - }) - assert.Nil(t, err) - assert.NotNil(t, boardsObserver.boardName) - assert.Equal(t, *updatedBoard.Name, *boardsObserver.boardName) -} - -func testBoardsObserverOnDelete(t *testing.T) { - board := fixture.MustRow("Board.boardsObserverTestBoard").(*Board) - err := testDb.DeleteBoard(board.ID) - assert.Nil(t, err) - assert.True(t, boardsObserver.deleted) - assert.NotNil(t, boardsObserver.deletedBoard) - assert.Equal(t, board.ID, *boardsObserver.deletedBoard) -} diff --git a/server/src/database/columns_observer.go b/server/src/database/columns_observer.go deleted file mode 100644 index d9f713495e..0000000000 --- a/server/src/database/columns_observer.go +++ /dev/null @@ -1,86 +0,0 @@ -package database - -import ( - "context" - "github.com/google/uuid" - "github.com/uptrace/bun" - "scrumlr.io/server/common/filter" - "scrumlr.io/server/identifiers" -) - -type ColumnsObserver interface { - Observer - - // UpdatedColumns will be called if the columns of the board with the specified id were updated. - UpdatedColumns(board uuid.UUID, columns []Column) - DeletedColumn(user, board, column uuid.UUID, notes []Note, votes []Vote) -} - -var _ bun.AfterInsertHook = (*ColumnInsert)(nil) -var _ bun.AfterUpdateHook = (*ColumnUpdate)(nil) -var _ bun.AfterDeleteHook = (*Column)(nil) - -func (*ColumnInsert) AfterInsert(ctx context.Context, _ *bun.InsertQuery) error { - return notifyColumnsUpdated(ctx) -} - -func (*ColumnUpdate) AfterUpdate(ctx context.Context, _ *bun.UpdateQuery) error { - return notifyColumnsUpdated(ctx) -} - -func (*Column) AfterDelete(ctx context.Context, _ *bun.DeleteQuery) error { - result := ctx.Value("Result").(*[]Column) - if len(*result) > 0 { - return notifyColumnDeleted(ctx) - } - return nil -} - -func notifyColumnsUpdated(ctx context.Context) error { - if ctx.Value("Database") == nil { - return nil - } - d := ctx.Value("Database").(*Database) - if len(d.observer) > 0 { - board := ctx.Value(identifiers.BoardIdentifier).(uuid.UUID) - columns, err := d.GetColumns(board) - if err != nil { - return err - } - for _, observer := range d.observer { - if o, ok := observer.(ColumnsObserver); ok { - o.UpdatedColumns(board, columns) - return nil - } - } - } - return nil -} - -func notifyColumnDeleted(ctx context.Context) error { - if ctx.Value("Database") == nil { - return nil - } - d := ctx.Value("Database").(*Database) - if len(d.observer) > 0 { - user := ctx.Value(identifiers.UserIdentifier).(uuid.UUID) - board := ctx.Value(identifiers.BoardIdentifier).(uuid.UUID) - column := ctx.Value(identifiers.ColumnIdentifier).(uuid.UUID) - notes, err := d.GetNotes(board) - if err != nil { - return err - } - votes, err := d.GetVotes(filter.VoteFilter{Board: board}) - if err != nil { - return err - } - for _, observer := range d.observer { - if o, ok := observer.(ColumnsObserver); ok { - o.DeletedColumn(user, board, column, notes, votes) - return nil - } - } - } - return nil - -} diff --git a/server/src/database/columns_observer_test.go b/server/src/database/columns_observer_test.go deleted file mode 100644 index 6326fb7b0c..0000000000 --- a/server/src/database/columns_observer_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package database - -import ( - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "testing" -) - -type ColumnsObserverForTests struct { - t *testing.T - board *uuid.UUID - columns *[]Column - deletedColumn *uuid.UUID -} - -func (o *ColumnsObserverForTests) UpdatedColumns(board uuid.UUID, columns []Column) { - o.board = &board - o.columns = &columns -} - -func (o *ColumnsObserverForTests) DeletedColumn(user, board, column uuid.UUID, notes []Note, votes []Vote) { - o.board = &board - o.deletedColumn = &column -} - -func (o *ColumnsObserverForTests) Reset() { - o.board = nil - o.columns = nil - o.deletedColumn = nil -} - -var columnsObserver ColumnsObserverForTests -var columnsObserverTestColumn Column - -func TestColumnsObserver(t *testing.T) { - columnsObserver = ColumnsObserverForTests{t: t} - testDb.AttachObserver(&columnsObserver) - - t.Run("Test=1", testColumnsObserverOnCreate) - columnsObserver.Reset() - t.Run("Test=2", testColumnsObserverOnUpdate) - columnsObserver.Reset() - t.Run("Test=3", testColumnsObserverOnDelete) - columnsObserver.Reset() - t.Run("Test=4", testColumnsObserverOnDeleteNotExisting) - - _, _ = testDb.DetachObserver(columnsObserver) -} - -func testColumnsObserverOnCreate(t *testing.T) { - board := fixture.MustRow("Board.columnsObserverTestBoard").(*Board) - column, err := testDb.CreateColumn(ColumnInsert{Board: board.ID, Name: "Created column", Color: "backlog-blue"}) - - assert.Nil(t, err) - assert.NotNil(t, columnsObserver.board) - assert.NotNil(t, columnsObserver.columns) - - assert.Equal(t, 1, len(*columnsObserver.columns)) - assert.Equal(t, column.Board, (*columnsObserver.columns)[0].Board) - assert.Equal(t, column.Name, (*columnsObserver.columns)[0].Name) - - columnsObserverTestColumn = column -} -func testColumnsObserverOnUpdate(t *testing.T) { - column, err := testDb.UpdateColumn(ColumnUpdate{ - ID: columnsObserverTestColumn.ID, - Board: columnsObserverTestColumn.Board, - Name: "A new name", - Color: "backlog-blue", - Visible: true, - Index: 0, - }) - - assert.Nil(t, err) - assert.NotNil(t, columnsObserver.board) - assert.NotNil(t, columnsObserver.columns) - - assert.Equal(t, 1, len(*columnsObserver.columns)) - assert.Equal(t, column.Board, (*columnsObserver.columns)[0].Board) - assert.Equal(t, column.Name, (*columnsObserver.columns)[0].Name) -} -func testColumnsObserverOnDelete(t *testing.T) { - columnsObserverTestUser := fixture.MustRow("User.john").(*User) - err := testDb.DeleteColumn(columnsObserverTestColumn.Board, columnsObserverTestColumn.ID, columnsObserverTestUser.ID) - assert.Nil(t, err) - assert.NotNil(t, columnsObserver.board) - assert.NotNil(t, columnsObserver.deletedColumn) -} -func testColumnsObserverOnDeleteNotExisting(t *testing.T) { - columnsObserverTestUser := fixture.MustRow("User.john").(*User) - err := testDb.DeleteColumn(columnsObserverTestColumn.Board, columnsObserverTestColumn.ID, columnsObserverTestUser.ID) - assert.Nil(t, err) - assert.Nil(t, columnsObserver.board) - assert.Nil(t, columnsObserver.deletedColumn) -} diff --git a/server/src/services/boards/boards.go b/server/src/services/boards/boards.go index 969321167e..a6e9effa80 100644 --- a/server/src/services/boards/boards.go +++ b/server/src/services/boards/boards.go @@ -4,9 +4,10 @@ import ( "context" "errors" "fmt" - "scrumlr.io/server/identifiers" "time" + "scrumlr.io/server/identifiers" + "github.com/google/uuid" "scrumlr.io/server/common/dto" @@ -28,7 +29,6 @@ func NewBoardService(db *database.Database, rt *realtime.Broker) services.Boards b := new(BoardService) b.database = db b.realtime = rt - b.database.AttachObserver((database.BoardObserver)(b)) return b } @@ -133,7 +133,11 @@ func (s *BoardService) BoardOverview(_ context.Context, boardIDs []uuid.UUID, us } func (s *BoardService) Delete(_ context.Context, id uuid.UUID) error { - return s.database.DeleteBoard(id) + err := s.database.DeleteBoard(id) + if err != nil { + logger.Get().Errorw("unable to delete board", "err", err) + } + return err } func (s *BoardService) Update(ctx context.Context, body dto.BoardUpdateRequest) (*dto.Board, error) { @@ -172,8 +176,11 @@ func (s *BoardService) Update(ctx context.Context, body dto.BoardUpdateRequest) board, err := s.database.UpdateBoard(update) if err != nil { + log.Errorw("unable to update board", "err", err) return nil, err } + s.UpdatedBoard(board) + return new(dto.Board).From(board), err } @@ -187,8 +194,11 @@ func (s *BoardService) SetTimer(_ context.Context, id uuid.UUID, minutes uint8) } board, err := s.database.UpdateBoardTimer(update) if err != nil { + logger.Get().Errorw("unable to update board timer", "err", err) return nil, err } + s.UpdatedBoardTimer(board) + return new(dto.Board).From(board), err } @@ -200,8 +210,11 @@ func (s *BoardService) DeleteTimer(_ context.Context, id uuid.UUID) (*dto.Board, } board, err := s.database.UpdateBoardTimer(update) if err != nil { + logger.Get().Errorw("unable to update board timer", "err", err) return nil, err } + s.UpdatedBoardTimer(board) + return new(dto.Board).From(board), err } @@ -232,8 +245,11 @@ func (s *BoardService) IncrementTimer(_ context.Context, id uuid.UUID) (*dto.Boa board, err = s.database.UpdateBoardTimer(update) if err != nil { + logger.Get().Errorw("unable to update board timer", "err", err) return nil, err } + s.UpdatedBoardTimer(board) + return new(dto.Board).From(board), nil } diff --git a/server/src/services/boards/columns.go b/server/src/services/boards/columns.go index da46c11dc4..6401b6cefd 100644 --- a/server/src/services/boards/columns.go +++ b/server/src/services/boards/columns.go @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" "scrumlr.io/server/common" "scrumlr.io/server/common/dto" + "scrumlr.io/server/common/filter" "scrumlr.io/server/realtime" "scrumlr.io/server/database" @@ -16,15 +17,31 @@ import ( func (s *BoardService) CreateColumn(_ context.Context, body dto.ColumnRequest) (*dto.Column, error) { column, err := s.database.CreateColumn(database.ColumnInsert{Board: body.Board, Name: body.Name, Color: body.Color, Visible: body.Visible, Index: body.Index}) + if err != nil { + logger.Get().Errorw("unable to create column", "err", err) + return nil, err + } + s.UpdatedColumns(body.Board) return new(dto.Column).From(column), err } func (s *BoardService) DeleteColumn(_ context.Context, board, column, user uuid.UUID) error { - return s.database.DeleteColumn(board, column, user) + err := s.database.DeleteColumn(board, column, user) + if err != nil { + logger.Get().Errorw("unable to delete column", "err", err) + return err + } + s.DeletedColumn(user, board, column) + return err } func (s *BoardService) UpdateColumn(_ context.Context, body dto.ColumnUpdateRequest) (*dto.Column, error) { column, err := s.database.UpdateColumn(database.ColumnUpdate{ID: body.ID, Board: body.Board, Name: body.Name, Color: body.Color, Visible: body.Visible, Index: body.Index}) + if err != nil { + logger.Get().Errorw("unable to update column", "err", err) + return nil, err + } + s.UpdatedColumns(body.Board) return new(dto.Column).From(column), err } @@ -51,10 +68,15 @@ func (s *BoardService) ListColumns(ctx context.Context, boardID uuid.UUID) ([]*d return dto.Columns(columns), err } -func (s *BoardService) UpdatedColumns(board uuid.UUID, columns []database.Column) { - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ +func (s *BoardService) UpdatedColumns(board uuid.UUID) { + dbColumns, err := s.database.GetColumns(board) + if err != nil { + logger.Get().Errorw("unable to retrieve columns in updated notes", "err", err) + return + } + err = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventColumnsUpdated, - Data: dto.Columns(columns), + Data: dto.Columns(dbColumns), }) if err != nil { logger.Get().Errorw("unable to broadcast updated columns", "err", err) @@ -95,7 +117,7 @@ func (s *BoardService) SyncNotesOnColumnChange(boardID uuid.UUID) (string, error return "", err } -func (s *BoardService) DeletedColumn(user, board, column uuid.UUID, notes []database.Note, votes []database.Vote) { +func (s *BoardService) DeletedColumn(user, board, column uuid.UUID) { err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventColumnDeleted, Data: column, @@ -104,8 +126,13 @@ func (s *BoardService) DeletedColumn(user, board, column uuid.UUID, notes []data logger.Get().Errorw("unable to broadcast updated columns", "err", err) } - eventNotes := make([]dto.Note, len(notes)) - for index, note := range notes { + dbNotes, err := s.database.GetNotes(board) + if err != nil { + logger.Get().Errorw("unable to retrieve notes in deleted column", "err", err) + return + } + eventNotes := make([]dto.Note, len(dbNotes)) + for index, note := range dbNotes { eventNotes[index] = *new(dto.Note).From(note) } err = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ @@ -116,8 +143,13 @@ func (s *BoardService) DeletedColumn(user, board, column uuid.UUID, notes []data logger.Get().Errorw("unable to broadcast updated notes", "err", err) } + boardVotes, err := s.database.GetVotes(filter.VoteFilter{Board: board}) + if err != nil { + logger.Get().Errorw("unable to retrieve votes in deleted column", "err", err) + return + } personalVotes := []*dto.Vote{} - for _, vote := range votes { + for _, vote := range boardVotes { if vote.User == user { personalVotes = append(personalVotes, new(dto.Vote).From(vote)) } @@ -129,5 +161,4 @@ func (s *BoardService) DeletedColumn(user, board, column uuid.UUID, notes []data if err != nil { logger.Get().Errorw("unable to broadcast updated votes", "err", err) } - } diff --git a/server/src/services/notes/notes.go b/server/src/services/notes/notes.go index 1444ddd3ac..0ad271a6ea 100644 --- a/server/src/services/notes/notes.go +++ b/server/src/services/notes/notes.go @@ -23,10 +23,6 @@ type NoteService struct { realtime *realtime.Broker } -type Observer interface { - AttachObserver(observer database.Observer) -} - type DB interface { CreateNote(insert database.NoteInsert) (database.Note, error) GetNote(id uuid.UUID) (database.Note, error)