From 1848af3547f287327b182f6d9bce8e3b9eb251b5 Mon Sep 17 00:00:00 2001 From: Mateo Ivankovic Date: Fri, 8 Nov 2024 12:30:56 +0100 Subject: [PATCH 1/7] remove personal votes --- server/src/services/boards/boards.go | 526 +++++++++++++-------------- 1 file changed, 258 insertions(+), 268 deletions(-) diff --git a/server/src/services/boards/boards.go b/server/src/services/boards/boards.go index afa983b812..0b45f0b795 100644 --- a/server/src/services/boards/boards.go +++ b/server/src/services/boards/boards.go @@ -1,324 +1,314 @@ package boards import ( - "context" - "errors" - "fmt" - "time" + "context" + "errors" + "fmt" + "time" - "scrumlr.io/server/identifiers" + "github.com/google/uuid" - "github.com/google/uuid" + "scrumlr.io/server/common/dto" + "scrumlr.io/server/realtime" + "scrumlr.io/server/services" - "scrumlr.io/server/common/dto" - "scrumlr.io/server/realtime" - "scrumlr.io/server/services" - - "scrumlr.io/server/common" - "scrumlr.io/server/database" - "scrumlr.io/server/database/types" - "scrumlr.io/server/logger" + "scrumlr.io/server/common" + "scrumlr.io/server/database" + "scrumlr.io/server/database/types" + "scrumlr.io/server/logger" ) type BoardService struct { - database *database.Database - realtime *realtime.Broker + database *database.Database + realtime *realtime.Broker } func NewBoardService(db *database.Database, rt *realtime.Broker) services.Boards { - b := new(BoardService) - b.database = db - b.realtime = rt - return b + b := new(BoardService) + b.database = db + b.realtime = rt + return b } func (s *BoardService) Get(ctx context.Context, id uuid.UUID) (*dto.Board, error) { - log := logger.FromContext(ctx) - board, err := s.database.GetBoard(id) - if err != nil { - log.Errorw("unable to get board", "boardID", id, "err", err) - return nil, err - } - return new(dto.Board).From(board), err + log := logger.FromContext(ctx) + board, err := s.database.GetBoard(id) + if err != nil { + log.Errorw("unable to get board", "boardID", id, "err", err) + return nil, err + } + return new(dto.Board).From(board), err } // get all associated boards of a given user func (s *BoardService) GetBoards(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error) { - log := logger.FromContext(ctx) - boards, err := s.database.GetBoards(userID) - if err != nil { - log.Errorw("unable to get boards of user", "userID", userID, "err", err) - return nil, err - } - - result := make([]uuid.UUID, len(boards)) - for i, board := range boards { - result[i] = board.ID - } - return result, nil + log := logger.FromContext(ctx) + boards, err := s.database.GetBoards(userID) + if err != nil { + log.Errorw("unable to get boards of user", "userID", userID, "err", err) + return nil, err + } + + result := make([]uuid.UUID, len(boards)) + for i, board := range boards { + result[i] = board.ID + } + return result, nil } func (s *BoardService) Create(ctx context.Context, body dto.CreateBoardRequest) (*dto.Board, error) { - log := logger.FromContext(ctx) - // map request on board object to insert into database - var board database.BoardInsert - switch body.AccessPolicy { - case types.AccessPolicyPublic, types.AccessPolicyByInvite: - board = database.BoardInsert{Name: body.Name, Description: body.Description, AccessPolicy: body.AccessPolicy} - case types.AccessPolicyByPassphrase: - if body.Passphrase == nil || len(*body.Passphrase) == 0 { - return nil, errors.New("passphrase must be set on access policy 'BY_PASSPHRASE'") - } - - encodedPassphrase, salt, _ := common.Sha512WithSalt(*body.Passphrase) - board = database.BoardInsert{ - Name: body.Name, - Description: body.Description, - AccessPolicy: body.AccessPolicy, - Passphrase: encodedPassphrase, - Salt: salt, - } - } - - // map request on column objects to insert into database - columns := make([]database.ColumnInsert, 0, len(body.Columns)) - for index, value := range body.Columns { - var currentIndex = index - columns = append(columns, database.ColumnInsert{Name: value.Name, Color: value.Color, Visible: value.Visible, Index: ¤tIndex}) - } - - // create the board - b, err := s.database.CreateBoard(body.Owner, board, columns) - if err != nil { - log.Errorw("unable to create board", "owner", body.Owner, "policy", body.AccessPolicy, "error", err) - return nil, err - } - - return new(dto.Board).From(b), nil + log := logger.FromContext(ctx) + // map request on board object to insert into database + var board database.BoardInsert + switch body.AccessPolicy { + case types.AccessPolicyPublic, types.AccessPolicyByInvite: + board = database.BoardInsert{Name: body.Name, Description: body.Description, AccessPolicy: body.AccessPolicy} + case types.AccessPolicyByPassphrase: + if body.Passphrase == nil || len(*body.Passphrase) == 0 { + return nil, errors.New("passphrase must be set on access policy 'BY_PASSPHRASE'") + } + + encodedPassphrase, salt, _ := common.Sha512WithSalt(*body.Passphrase) + board = database.BoardInsert{ + Name: body.Name, + Description: body.Description, + AccessPolicy: body.AccessPolicy, + Passphrase: encodedPassphrase, + Salt: salt, + } + } + + // map request on column objects to insert into database + columns := make([]database.ColumnInsert, 0, len(body.Columns)) + for index, value := range body.Columns { + var currentIndex = index + columns = append(columns, database.ColumnInsert{Name: value.Name, Color: value.Color, Visible: value.Visible, Index: ¤tIndex}) + } + + // create the board + b, err := s.database.CreateBoard(body.Owner, board, columns) + if err != nil { + log.Errorw("unable to create board", "owner", body.Owner, "policy", body.AccessPolicy, "error", err) + return nil, err + } + + return new(dto.Board).From(b), nil } func (s *BoardService) FullBoard(ctx context.Context, boardID uuid.UUID) (*dto.FullBoard, error) { - log := logger.FromContext(ctx) - fullBoard, err := s.database.Get(boardID) - if err != nil { - log.Errorw("unable to get full board", "boardID", boardID, "err", err) - return nil, err - } - - personalVotes := []database.Vote{} - for _, vote := range fullBoard.Votes { - if vote.User == ctx.Value(identifiers.UserIdentifier).(uuid.UUID) { - personalVotes = append(personalVotes, vote) - } - } - fullBoard.Votes = personalVotes - - return new(dto.FullBoard).From(fullBoard), err + log := logger.FromContext(ctx) + fullBoard, err := s.database.Get(boardID) + if err != nil { + log.Errorw("unable to get full board", "boardID", boardID, "err", err) + return nil, err + } + + return new(dto.FullBoard).From(fullBoard), err } func (s *BoardService) BoardOverview(ctx context.Context, boardIDs []uuid.UUID, user uuid.UUID) ([]*dto.BoardOverview, error) { - log := logger.FromContext(ctx) - OverviewBoards := make([]*dto.BoardOverview, len(boardIDs)) - for i, id := range boardIDs { - board, sessions, columns, err := s.database.GetBoardOverview(id) - if err != nil { - log.Errorw("unable to get board overview", "board", id, "err", err) - return nil, err - } - participantNum := len(sessions) - columnNum := len(columns) - dtoBoard := new(dto.Board).From(board) - for _, session := range sessions { - if session.User == user { - sessionCreated := session.CreatedAt - OverviewBoards[i] = &dto.BoardOverview{ - Board: dtoBoard, - Participants: participantNum, - CreatedAt: sessionCreated, - Columns: columnNum, - } - } - } - } - return OverviewBoards, nil + log := logger.FromContext(ctx) + OverviewBoards := make([]*dto.BoardOverview, len(boardIDs)) + for i, id := range boardIDs { + board, sessions, columns, err := s.database.GetBoardOverview(id) + if err != nil { + log.Errorw("unable to get board overview", "board", id, "err", err) + return nil, err + } + participantNum := len(sessions) + columnNum := len(columns) + dtoBoard := new(dto.Board).From(board) + for _, session := range sessions { + if session.User == user { + sessionCreated := session.CreatedAt + OverviewBoards[i] = &dto.BoardOverview{ + Board: dtoBoard, + Participants: participantNum, + CreatedAt: sessionCreated, + Columns: columnNum, + } + } + } + } + return OverviewBoards, nil } func (s *BoardService) Delete(ctx context.Context, id uuid.UUID) error { - log := logger.FromContext(ctx) - err := s.database.DeleteBoard(id) - if err != nil { - log.Errorw("unable to delete board", "err", err) - } - return err + log := logger.FromContext(ctx) + err := s.database.DeleteBoard(id) + if err != nil { + log.Errorw("unable to delete board", "err", err) + } + return err } func (s *BoardService) Update(ctx context.Context, body dto.BoardUpdateRequest) (*dto.Board, error) { - log := logger.FromContext(ctx) - update := database.BoardUpdate{ - ID: body.ID, - Name: body.Name, - Description: body.Description, - ShowAuthors: body.ShowAuthors, - ShowNotesOfOtherUsers: body.ShowNotesOfOtherUsers, - ShowNoteReactions: body.ShowNoteReactions, - AllowStacking: body.AllowStacking, - IsLocked: body.IsLocked, - TimerStart: body.TimerStart, - TimerEnd: body.TimerEnd, - SharedNote: body.SharedNote, - } - - if body.AccessPolicy != nil { - update.AccessPolicy = body.AccessPolicy - if *body.AccessPolicy == types.AccessPolicyByPassphrase { - if body.Passphrase == nil { - return nil, common.BadRequestError(errors.New("passphrase must be set if policy 'BY_PASSPHRASE' is selected")) - } - - passphrase, salt, err := common.Sha512WithSalt(*body.Passphrase) - if err != nil { - log.Error("failed to encode passphrase") - return nil, fmt.Errorf("failed to encode passphrase: %w", err) - } - - update.Passphrase = passphrase - update.Salt = salt - } - } - - 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 + log := logger.FromContext(ctx) + update := database.BoardUpdate{ + ID: body.ID, + Name: body.Name, + Description: body.Description, + ShowAuthors: body.ShowAuthors, + ShowNotesOfOtherUsers: body.ShowNotesOfOtherUsers, + ShowNoteReactions: body.ShowNoteReactions, + AllowStacking: body.AllowStacking, + IsLocked: body.IsLocked, + TimerStart: body.TimerStart, + TimerEnd: body.TimerEnd, + SharedNote: body.SharedNote, + } + + if body.AccessPolicy != nil { + update.AccessPolicy = body.AccessPolicy + if *body.AccessPolicy == types.AccessPolicyByPassphrase { + if body.Passphrase == nil { + return nil, common.BadRequestError(errors.New("passphrase must be set if policy 'BY_PASSPHRASE' is selected")) + } + + passphrase, salt, err := common.Sha512WithSalt(*body.Passphrase) + if err != nil { + log.Error("failed to encode passphrase") + return nil, fmt.Errorf("failed to encode passphrase: %w", err) + } + + update.Passphrase = passphrase + update.Salt = salt + } + } + + 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 } func (s *BoardService) SetTimer(ctx context.Context, id uuid.UUID, minutes uint8) (*dto.Board, error) { - log := logger.FromContext(ctx) - timerStart := time.Now().Local() - timerEnd := timerStart.Add(time.Minute * time.Duration(minutes)) - update := database.BoardTimerUpdate{ - ID: id, - TimerStart: &timerStart, - TimerEnd: &timerEnd, - } - board, err := s.database.UpdateBoardTimer(update) - if err != nil { - log.Errorw("unable to update board timer", "err", err) - return nil, err - } - s.UpdatedBoardTimer(board) - - return new(dto.Board).From(board), err + log := logger.FromContext(ctx) + timerStart := time.Now().Local() + timerEnd := timerStart.Add(time.Minute * time.Duration(minutes)) + update := database.BoardTimerUpdate{ + ID: id, + TimerStart: &timerStart, + TimerEnd: &timerEnd, + } + board, err := s.database.UpdateBoardTimer(update) + if err != nil { + log.Errorw("unable to update board timer", "err", err) + return nil, err + } + s.UpdatedBoardTimer(board) + + return new(dto.Board).From(board), err } func (s *BoardService) DeleteTimer(ctx context.Context, id uuid.UUID) (*dto.Board, error) { - log := logger.FromContext(ctx) - update := database.BoardTimerUpdate{ - ID: id, - TimerStart: nil, - TimerEnd: nil, - } - board, err := s.database.UpdateBoardTimer(update) - if err != nil { - log.Errorw("unable to update board timer", "err", err) - return nil, err - } - s.UpdatedBoardTimer(board) - - return new(dto.Board).From(board), err + log := logger.FromContext(ctx) + update := database.BoardTimerUpdate{ + ID: id, + TimerStart: nil, + TimerEnd: nil, + } + board, err := s.database.UpdateBoardTimer(update) + if err != nil { + log.Errorw("unable to update board timer", "err", err) + return nil, err + } + s.UpdatedBoardTimer(board) + + return new(dto.Board).From(board), err } func (s *BoardService) IncrementTimer(ctx context.Context, id uuid.UUID) (*dto.Board, error) { - log := logger.FromContext(ctx) - board, err := s.database.GetBoard(id) - if err != nil { - log.Errorw("unable to get board", "boardID", id, "err", err) - return nil, err - } - - var timerStart time.Time - var timerEnd time.Time - - currentTime := time.Now().Local() - - if board.TimerEnd.After(currentTime) { - timerStart = *board.TimerStart - timerEnd = board.TimerEnd.Add(time.Minute * time.Duration(1)) - } else { - timerStart = currentTime - timerEnd = currentTime.Add(time.Minute * time.Duration(1)) - } - - update := database.BoardTimerUpdate{ - ID: board.ID, - TimerStart: &timerStart, - TimerEnd: &timerEnd, - } - - board, err = s.database.UpdateBoardTimer(update) - if err != nil { - log.Errorw("unable to update board timer", "err", err) - return nil, err - } - s.UpdatedBoardTimer(board) - - return new(dto.Board).From(board), nil + log := logger.FromContext(ctx) + board, err := s.database.GetBoard(id) + if err != nil { + log.Errorw("unable to get board", "boardID", id, "err", err) + return nil, err + } + + var timerStart time.Time + var timerEnd time.Time + + currentTime := time.Now().Local() + + if board.TimerEnd.After(currentTime) { + timerStart = *board.TimerStart + timerEnd = board.TimerEnd.Add(time.Minute * time.Duration(1)) + } else { + timerStart = currentTime + timerEnd = currentTime.Add(time.Minute * time.Duration(1)) + } + + update := database.BoardTimerUpdate{ + ID: board.ID, + TimerStart: &timerStart, + TimerEnd: &timerEnd, + } + + board, err = s.database.UpdateBoardTimer(update) + if err != nil { + log.Errorw("unable to update board timer", "err", err) + return nil, err + } + s.UpdatedBoardTimer(board) + + return new(dto.Board).From(board), nil } func (s *BoardService) UpdatedBoardTimer(board database.Board) { - _ = s.realtime.BroadcastToBoard(board.ID, realtime.BoardEvent{ - Type: realtime.BoardEventBoardTimerUpdated, - Data: new(dto.Board).From(board), - }) + _ = s.realtime.BroadcastToBoard(board.ID, realtime.BoardEvent{ + Type: realtime.BoardEventBoardTimerUpdated, + Data: new(dto.Board).From(board), + }) } func (s *BoardService) UpdatedBoard(board database.Board) { - _ = s.realtime.BroadcastToBoard(board.ID, realtime.BoardEvent{ - Type: realtime.BoardEventBoardUpdated, - Data: new(dto.Board).From(board), - }) - - err_msg, err := s.SyncBoardSettingChange(board.ID) - if err != nil { - logger.Get().Errorw(err_msg, "err", err) - } + _ = s.realtime.BroadcastToBoard(board.ID, realtime.BoardEvent{ + Type: realtime.BoardEventBoardUpdated, + Data: new(dto.Board).From(board), + }) + + err_msg, err := s.SyncBoardSettingChange(board.ID) + if err != nil { + logger.Get().Errorw(err_msg, "err", err) + } } func (s *BoardService) SyncBoardSettingChange(boardID uuid.UUID) (string, error) { - var err_msg string - columns, err := s.database.GetColumns(boardID) - if err != nil { - err_msg = "unable to retrieve columns, following a updated board call" - return err_msg, err - } - - var columnsID []uuid.UUID - for _, column := range columns { - columnsID = append(columnsID, column.ID) - } - notes, err := s.database.GetNotes(boardID, columnsID...) - if err != nil { - err_msg = "unable to retrieve notes, following a updated board call" - return err_msg, err - } - - err = s.realtime.BroadcastToBoard(boardID, realtime.BoardEvent{ - Type: realtime.BoardEventNotesSync, - Data: dto.Notes(notes), - }) - if err != nil { - err_msg = "unable to broadcast notes, following a updated board call" - return err_msg, err - } - return "", err + var err_msg string + columns, err := s.database.GetColumns(boardID) + if err != nil { + err_msg = "unable to retrieve columns, following a updated board call" + return err_msg, err + } + + var columnsID []uuid.UUID + for _, column := range columns { + columnsID = append(columnsID, column.ID) + } + notes, err := s.database.GetNotes(boardID, columnsID...) + if err != nil { + err_msg = "unable to retrieve notes, following a updated board call" + return err_msg, err + } + + err = s.realtime.BroadcastToBoard(boardID, realtime.BoardEvent{ + Type: realtime.BoardEventNotesSync, + Data: dto.Notes(notes), + }) + if err != nil { + err_msg = "unable to broadcast notes, following a updated board call" + return err_msg, err + } + return "", err } func (s *BoardService) DeletedBoard(board uuid.UUID) { - _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ - Type: realtime.BoardEventBoardDeleted, - }) + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + Type: realtime.BoardEventBoardDeleted, + }) } From 7de9f60161f41cf13171ff6b0a4ed2d47112bdad Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 11 Nov 2024 17:20:06 +0100 Subject: [PATCH 2/7] fix display of votes --- src/components/Votes/Votes.tsx | 2 ++ src/store/features/votes/reducer.ts | 5 ++++- src/store/features/votings/reducer.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/Votes/Votes.tsx b/src/components/Votes/Votes.tsx index d116bd8ec4..37f304c5f9 100644 --- a/src/components/Votes/Votes.tsx +++ b/src/components/Votes/Votes.tsx @@ -41,7 +41,9 @@ export const Votes: FC = (props) => { return voting || allPastVotes > 0 ? (
e.stopPropagation()}> + {/* standard display for votes */} {!voting && allPastVotes > 0 && } + {/* display for votes when voting is open */} {voting && ongoingVotes.note > 0 && } {voting && (isModerator || !boardLocked) && ( 0 && !voting.allowMultipleVotes)} /> diff --git a/src/store/features/votes/reducer.ts b/src/store/features/votes/reducer.ts index 4b4912cc9e..0bbc4405ad 100644 --- a/src/store/features/votes/reducer.ts +++ b/src/store/features/votes/reducer.ts @@ -2,6 +2,7 @@ import {createReducer} from "@reduxjs/toolkit"; import {VotesState} from "./types"; import {initializeBoard} from "../board"; import {createdVote, deletedVote, updatedVotes} from "./actions"; +import {createdVoting, updatedVoting} from "../votings"; const initialState: VotesState = []; @@ -9,8 +10,10 @@ export const votesReducer = createReducer(initialState, (builder) => builder .addCase(initializeBoard, (_state, action) => action.payload.fullBoard.votes) .addCase(createdVote, (state, action) => { - state.push(action.payload); + state.unshift(action.payload); }) + .addCase(updatedVoting, (_state, action) => action.payload.notes?.map((n) => ({voting: action.payload.voting.id, note: n.id}))) .addCase(updatedVotes, (_state, action) => action.payload) .addCase(deletedVote, (state, action) => state.filter((v) => !(v.voting === action.payload.voting && v.note === action.payload.note))) + .addCase(createdVoting, () => []) ); diff --git a/src/store/features/votings/reducer.ts b/src/store/features/votings/reducer.ts index 1d9aab8c24..f2b38cc8d5 100644 --- a/src/store/features/votings/reducer.ts +++ b/src/store/features/votings/reducer.ts @@ -25,6 +25,6 @@ export const votingsReducer = createReducer(initialState, (builder) => }) .addCase(updatedVoting, (state, action) => { state.open = undefined; - state.past.push(action.payload.voting); + state.past.unshift(action.payload.voting); }) ); From 6fd91d99ac09ada260e034a22f3f38ac6beb1a63 Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 18 Nov 2024 11:25:39 +0100 Subject: [PATCH 3/7] only send the latests votes and voting in InitEvent --- server/src/api/event_filter.go | 28 +++++++++++++++++++--------- src/store/features/votes/reducer.ts | 2 +- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/server/src/api/event_filter.go b/server/src/api/event_filter.go index 7c03a552ac..305c8a09cd 100644 --- a/server/src/api/event_filter.go +++ b/server/src/api/event_filter.go @@ -2,7 +2,6 @@ package api import ( "encoding/json" - "github.com/google/uuid" "scrumlr.io/server/common/dto" "scrumlr.io/server/database/types" @@ -312,6 +311,18 @@ func (boardSubscription *BoardSubscription) eventFilter(event *realtime.BoardEve func eventInitFilter(event InitEvent, clientID uuid.UUID) InitEvent { isMod := isModerator(clientID, event.Data.BoardSessions) + latestVoting := event.Data.Votings[0] + event.Data.Votings = []*dto.Voting{latestVoting} + + latestVotes := make([]*dto.Vote, 0) + for _, v := range event.Data.Votes { + if v.Voting == latestVoting.ID { + latestVotes = append(latestVotes, v) + } + } + + event.Data.Votes = latestVotes + if isMod { return event } @@ -335,9 +346,9 @@ func eventInitFilter(event InitEvent, clientID uuid.UUID) InitEvent { // Notes filteredNotes := filterNotes(event.Data.Notes, clientID, event.Data.Board, event.Data.Columns) - // Votes + // Votes -> only from the latest voting visibleVotes := make([]*dto.Vote, 0) - for _, v := range event.Data.Votes { + for _, v := range latestVotes { for _, n := range filteredNotes { if v.Note == n.ID { aVote := dto.Vote{ @@ -348,17 +359,16 @@ func eventInitFilter(event InitEvent, clientID uuid.UUID) InitEvent { } } } + // Votings - visibleVotings := make([]*dto.Voting, 0) - for _, v := range event.Data.Votings { - filteredVoting := filterVoting(v, filteredNotes, clientID) - visibleVotings = append(visibleVotings, filteredVoting) - } + visibleVoting := make([]*dto.Voting, 0) + filteredVoting := filterVoting(event.Data.Votings[0], filteredNotes, clientID) + visibleVoting = append(visibleVoting, filteredVoting) retEvent.Data.Columns = filteredColumns retEvent.Data.Notes = filteredNotes retEvent.Data.Votes = visibleVotes - retEvent.Data.Votings = visibleVotings + retEvent.Data.Votings = visibleVoting return retEvent } diff --git a/src/store/features/votes/reducer.ts b/src/store/features/votes/reducer.ts index 0bbc4405ad..7d8dc5e767 100644 --- a/src/store/features/votes/reducer.ts +++ b/src/store/features/votes/reducer.ts @@ -10,7 +10,7 @@ export const votesReducer = createReducer(initialState, (builder) => builder .addCase(initializeBoard, (_state, action) => action.payload.fullBoard.votes) .addCase(createdVote, (state, action) => { - state.unshift(action.payload); + state.push(action.payload); }) .addCase(updatedVoting, (_state, action) => action.payload.notes?.map((n) => ({voting: action.payload.voting.id, note: n.id}))) .addCase(updatedVotes, (_state, action) => action.payload) From 35b169c2a6ddc6918bc2b8d1adfe716b12e05e78 Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 18 Nov 2024 11:29:54 +0100 Subject: [PATCH 4/7] remove line left from debugging --- src/store/features/votes/reducer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/store/features/votes/reducer.ts b/src/store/features/votes/reducer.ts index 7d8dc5e767..0fc80452c6 100644 --- a/src/store/features/votes/reducer.ts +++ b/src/store/features/votes/reducer.ts @@ -2,7 +2,7 @@ import {createReducer} from "@reduxjs/toolkit"; import {VotesState} from "./types"; import {initializeBoard} from "../board"; import {createdVote, deletedVote, updatedVotes} from "./actions"; -import {createdVoting, updatedVoting} from "../votings"; +import {createdVoting} from "../votings"; const initialState: VotesState = []; @@ -12,7 +12,6 @@ export const votesReducer = createReducer(initialState, (builder) => .addCase(createdVote, (state, action) => { state.push(action.payload); }) - .addCase(updatedVoting, (_state, action) => action.payload.notes?.map((n) => ({voting: action.payload.voting.id, note: n.id}))) .addCase(updatedVotes, (_state, action) => action.payload) .addCase(deletedVote, (state, action) => state.filter((v) => !(v.voting === action.payload.voting && v.note === action.payload.note))) .addCase(createdVoting, () => []) From 4d5efb586e7d730dd18e093175c2b66a4af20a9c Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 18 Nov 2024 11:39:37 +0100 Subject: [PATCH 5/7] undo changes in votings/reducer --- src/store/features/votings/reducer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/features/votings/reducer.ts b/src/store/features/votings/reducer.ts index f2b38cc8d5..1d9aab8c24 100644 --- a/src/store/features/votings/reducer.ts +++ b/src/store/features/votings/reducer.ts @@ -25,6 +25,6 @@ export const votingsReducer = createReducer(initialState, (builder) => }) .addCase(updatedVoting, (state, action) => { state.open = undefined; - state.past.unshift(action.payload.voting); + state.past.push(action.payload.voting); }) ); From 01177c3bbe6ae049db68b6b03303bf6ff20b973f Mon Sep 17 00:00:00 2001 From: Mateo Date: Wed, 20 Nov 2024 16:02:49 +0100 Subject: [PATCH 6/7] fix error when no voting too place befor sending init event --- server/src/api/event_filter.go | 39 ++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/server/src/api/event_filter.go b/server/src/api/event_filter.go index 305c8a09cd..7eda6a5c66 100644 --- a/server/src/api/event_filter.go +++ b/server/src/api/event_filter.go @@ -311,18 +311,24 @@ func (boardSubscription *BoardSubscription) eventFilter(event *realtime.BoardEve func eventInitFilter(event InitEvent, clientID uuid.UUID) InitEvent { isMod := isModerator(clientID, event.Data.BoardSessions) - latestVoting := event.Data.Votings[0] - event.Data.Votings = []*dto.Voting{latestVoting} - latestVotes := make([]*dto.Vote, 0) - for _, v := range event.Data.Votes { - if v.Voting == latestVoting.ID { - latestVotes = append(latestVotes, v) + // filter to only respond with the latest voting and its votes + latestVoting := event.Data.Votings + if len(event.Data.Votings) != 0 { + latestVoting = make([]*dto.Voting, 0) + activeNotes := make([]*dto.Vote, 0) + + latestVoting = append(latestVoting, event.Data.Votings[0]) + + for _, v := range event.Data.Votes { + if v.Voting == latestVoting[0].ID { + activeNotes = append(activeNotes, v) + } } + event.Data.Votings = latestVoting + event.Data.Votes = activeNotes } - event.Data.Votes = latestVotes - if isMod { return event } @@ -340,15 +346,15 @@ func eventInitFilter(event InitEvent, clientID uuid.UUID) InitEvent { Votes: event.Data.Votes, }, } + // Columns filteredColumns := filterColumns(event.Data.Columns) - // Notes filteredNotes := filterNotes(event.Data.Notes, clientID, event.Data.Board, event.Data.Columns) - // Votes -> only from the latest voting + // Votes visibleVotes := make([]*dto.Vote, 0) - for _, v := range latestVotes { + for _, v := range event.Data.Votes { for _, n := range filteredNotes { if v.Note == n.ID { aVote := dto.Vote{ @@ -359,16 +365,17 @@ func eventInitFilter(event InitEvent, clientID uuid.UUID) InitEvent { } } } - // Votings - visibleVoting := make([]*dto.Voting, 0) - filteredVoting := filterVoting(event.Data.Votings[0], filteredNotes, clientID) - visibleVoting = append(visibleVoting, filteredVoting) + visibleVotings := make([]*dto.Voting, 0) + for _, v := range event.Data.Votings { + filteredVoting := filterVoting(v, filteredNotes, clientID) + visibleVotings = append(visibleVotings, filteredVoting) + } retEvent.Data.Columns = filteredColumns retEvent.Data.Notes = filteredNotes retEvent.Data.Votes = visibleVotes - retEvent.Data.Votings = visibleVoting + retEvent.Data.Votings = visibleVotings return retEvent } From d07bb4adca0f404741e02f75d9b05294e2c7512d Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 25 Nov 2024 12:04:11 +0100 Subject: [PATCH 7/7] added userID to note dto -> to filter votes --- server/src/api/boards_listen_on_board.go | 32 ++++++++++++------------ server/src/api/event_filter.go | 5 +++- server/src/common/dto/vote.go | 2 ++ 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/server/src/api/boards_listen_on_board.go b/server/src/api/boards_listen_on_board.go index 8005871650..d1d2e7d31c 100644 --- a/server/src/api/boards_listen_on_board.go +++ b/server/src/api/boards_listen_on_board.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/websocket" - dto2 "scrumlr.io/server/common/dto" + "scrumlr.io/server/common/dto" "scrumlr.io/server/logger" "scrumlr.io/server/realtime" ) @@ -16,27 +16,27 @@ import ( type BoardSubscription struct { subscription chan *realtime.BoardEvent clients map[uuid.UUID]*websocket.Conn - boardParticipants []*dto2.BoardSession - boardSettings *dto2.Board - boardColumns []*dto2.Column - boardNotes []*dto2.Note - boardReactions []*dto2.Reaction + boardParticipants []*dto.BoardSession + boardSettings *dto.Board + boardColumns []*dto.Column + boardNotes []*dto.Note + boardReactions []*dto.Reaction } type InitEvent struct { Type realtime.BoardEventType `json:"type"` - Data dto2.FullBoard `json:"data"` + Data dto.FullBoard `json:"data"` } type EventData struct { - Board *dto2.Board `json:"board"` - Columns []*dto2.Column `json:"columns"` - Notes []*dto2.Note `json:"notes"` - Reactions []*dto2.Reaction `json:"reactions"` - Votings []*dto2.Voting `json:"votings"` - Votes []*dto2.Vote `json:"votes"` - Sessions []*dto2.BoardSession `json:"participants"` - Requests []*dto2.BoardSessionRequest `json:"requests"` + Board *dto.Board `json:"board"` + Columns []*dto.Column `json:"columns"` + Notes []*dto.Note `json:"notes"` + Reactions []*dto.Reaction `json:"reactions"` + Votings []*dto.Voting `json:"votings"` + Votes []*dto.Vote `json:"votes"` + Sessions []*dto.BoardSession `json:"participants"` + Requests []*dto.BoardSessionRequest `json:"requests"` } func (s *Server) openBoardSocket(w http.ResponseWriter, r *http.Request) { @@ -97,7 +97,7 @@ func (s *Server) openBoardSocket(w http.ResponseWriter, r *http.Request) { } } -func (s *Server) listenOnBoard(boardID, userID uuid.UUID, conn *websocket.Conn, initEventData dto2.FullBoard) { +func (s *Server) listenOnBoard(boardID, userID uuid.UUID, conn *websocket.Conn, initEventData dto.FullBoard) { if _, exist := s.boardSubscriptions[boardID]; !exist { s.boardSubscriptions[boardID] = &BoardSubscription{ clients: make(map[uuid.UUID]*websocket.Conn), diff --git a/server/src/api/event_filter.go b/server/src/api/event_filter.go index 7eda6a5c66..417716e4ad 100644 --- a/server/src/api/event_filter.go +++ b/server/src/api/event_filter.go @@ -361,7 +361,10 @@ func eventInitFilter(event InitEvent, clientID uuid.UUID) InitEvent { Voting: v.Voting, Note: n.ID, } - visibleVotes = append(visibleVotes, &aVote) + if clientID == v.User { + visibleVotes = append(visibleVotes, &aVote) + } + } } } diff --git a/server/src/common/dto/vote.go b/server/src/common/dto/vote.go index 6d296ef296..c5e9af63cf 100644 --- a/server/src/common/dto/vote.go +++ b/server/src/common/dto/vote.go @@ -10,11 +10,13 @@ import ( type Vote struct { Voting uuid.UUID `json:"voting"` Note uuid.UUID `json:"note"` + User uuid.UUID `json:"user"` } func (v *Vote) From(vote database.Vote) *Vote { v.Voting = vote.Voting v.Note = vote.Note + v.User = vote.User return v }