This commit is contained in:
2026-03-05 22:41:16 +02:00
parent e879703041
commit 61417dbf92
6 changed files with 196 additions and 7 deletions

View File

@@ -2,11 +2,17 @@ package state
import (
"encoding/json"
"fmt"
"slices"
"strings"
"sync"
)
const (
maxActivityLogEntries = 400
adminLogBroadcastLimit = 200
)
type Manager struct {
mu sync.RWMutex
rooms map[string]*Room
@@ -111,8 +117,10 @@ func (m *Manager) CreateRoom(input CreateRoomInput) (CreateRoomResult, error) {
Participants: map[string]*Participant{
creatorID: creator,
},
ActivityLog: []ActivityLogEntry{},
subscribers: map[string]*subscriber{},
}
m.appendActivityLogLocked(room, "%s created the room as admin.", creator.Username)
m.mu.Lock()
m.rooms[roomID] = room
@@ -176,12 +184,16 @@ func (m *Manager) JoinRoom(roomID string, input JoinRoomInput) (JoinRoomResult,
return JoinRoomResult{}, ErrParticipantNotFound
}
wasConnected := existing.Connected
existing.Username = username
existing.Connected = true
existing.UpdatedAt = now
if isAdminByToken {
existing.IsAdmin = true
}
if !wasConnected {
m.appendActivityLogLocked(room, "%s joined as %s.", existing.Username, existing.Role)
}
room.UpdatedAt = now
if err := m.store.Save(room); err != nil {
@@ -225,6 +237,7 @@ func (m *Manager) JoinRoom(roomID string, input JoinRoomInput) (JoinRoomResult,
}
room.Participants[participant.ID] = participant
m.appendActivityLogLocked(room, "%s joined as %s.", participant.Username, participant.Role)
room.UpdatedAt = now
if err := m.store.Save(room); err != nil {
@@ -254,9 +267,11 @@ func (m *Manager) LeaveRoom(roomID, participantID string) error {
return ErrParticipantNotFound
}
participant.Connected = false
participant.UpdatedAt = nowUTC()
room.UpdatedAt = nowUTC()
if !participant.Connected {
return nil
}
m.disconnectParticipantLocked(room, participant)
m.appendActivityLogLocked(room, "%s left the room.", participant.Username)
if err := m.store.Save(room); err != nil {
return err
@@ -300,9 +315,11 @@ func (m *Manager) CastVote(roomID, participantID, card string) error {
participant.VoteValue = normalizedCard
participant.UpdatedAt = nowUTC()
room.UpdatedAt = nowUTC()
m.appendActivityLogLocked(room, "%s voted %s.", participant.Username, normalizedCard)
if room.Settings.RevealMode == RevealModeAutoAll && allActiveParticipantsVoted(room) {
room.Round.Revealed = true
m.appendActivityLogLocked(room, "Votes auto-revealed after all active participants voted.")
}
if err := m.store.Save(room); err != nil {
@@ -332,6 +349,7 @@ func (m *Manager) RevealVotes(roomID, participantID string) error {
room.Round.Revealed = true
room.UpdatedAt = nowUTC()
m.appendActivityLogLocked(room, "%s revealed the votes.", participant.Username)
if err := m.store.Save(room); err != nil {
return err
@@ -360,6 +378,7 @@ func (m *Manager) ResetVotes(roomID, participantID string) error {
m.resetVotesLocked(room)
room.UpdatedAt = nowUTC()
m.appendActivityLogLocked(room, "%s reset all votes.", participant.Username)
if err := m.store.Save(room); err != nil {
return err
@@ -421,10 +440,11 @@ func (m *Manager) Subscribe(roomID, participantID string) (<-chan []byte, []byte
delete(roomRef.subscribers, subscriberID)
if p, participantOK := roomRef.Participants[participantID]; participantOK {
p.Connected = false
p.UpdatedAt = nowUTC()
roomRef.UpdatedAt = nowUTC()
_ = m.store.Save(roomRef)
if p.Connected {
m.disconnectParticipantLocked(roomRef, p)
m.appendActivityLogLocked(roomRef, "%s disconnected.", p.Username)
_ = m.store.Save(roomRef)
}
}
roomRef.mu.Unlock()
@@ -461,6 +481,7 @@ func (m *Manager) loadFromDisk() error {
Settings: persisted.Settings,
Round: persisted.Round,
Participants: make(map[string]*Participant, len(persisted.Participants)),
ActivityLog: append([]ActivityLogEntry(nil), persisted.ActivityLog...),
subscribers: map[string]*subscriber{},
}
for _, participant := range persisted.Participants {
@@ -489,6 +510,7 @@ func (room *Room) toPersisted() persistedRoom {
Settings: room.Settings,
Round: room.Round,
Participants: participants,
ActivityLog: append([]ActivityLogEntry(nil), room.ActivityLog...),
}
}
@@ -572,6 +594,18 @@ func (m *Manager) marshalRoomState(room *Room, viewerParticipantID string) ([]by
if viewer.IsAdmin {
state.Links.AdminLink = "/room/" + room.ID + "?adminToken=" + room.AdminToken
start := 0
if len(room.ActivityLog) > adminLogBroadcastLimit {
start = len(room.ActivityLog) - adminLogBroadcastLimit
}
state.AdminLogs = make([]PublicActivityLogEntry, 0, len(room.ActivityLog)-start)
for _, item := range room.ActivityLog[start:] {
state.AdminLogs = append(state.AdminLogs, PublicActivityLogEntry{
At: item.At,
Message: item.Message,
})
}
}
return json.Marshal(state)
@@ -606,3 +640,20 @@ func (m *Manager) broadcastRoom(roomID string) {
}
}
}
func (m *Manager) appendActivityLogLocked(room *Room, format string, args ...any) {
room.ActivityLog = append(room.ActivityLog, ActivityLogEntry{
At: nowUTC(),
Message: fmt.Sprintf(format, args...),
})
if len(room.ActivityLog) > maxActivityLogEntries {
room.ActivityLog = append([]ActivityLogEntry(nil), room.ActivityLog[len(room.ActivityLog)-maxActivityLogEntries:]...)
}
}
func (m *Manager) disconnectParticipantLocked(room *Room, participant *Participant) {
participant.Connected = false
participant.UpdatedAt = nowUTC()
room.UpdatedAt = nowUTC()
}