feat(chat): Sprint 3 -- message handlers, real-time features, permissions
- Implement full MessageHandler dispatch with all 18 incoming message types
- Add handler_messages.go: SendMessage, EditMessage, DeleteMessage with ownership checks
- Add handler_rooms.go: JoinConversation, LeaveConversation
- Add handler_history.go: FetchHistory (cursor-based), SearchMessages (ILIKE), SyncMessages
- Add handler_realtime.go: Typing, MarkAsRead, Delivered, AddReaction, RemoveReaction
- Add handler_calls.go: WebRTC signaling relay (CallOffer/Answer/ICE/Hangup/Reject)
- Add PermissionService: CanRead/CanSend/CanJoin/CanModerate based on room_members
- Add RateLimiter: per-user per-action sliding window (in-memory)
- Wire all dependencies in router.go setupChatWebSocket
2026-02-22 19:43:44 +00:00
|
|
|
package chat
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type PermissionService struct {
|
|
|
|
|
db *gorm.DB
|
|
|
|
|
logger *zap.Logger
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewPermissionService(db *gorm.DB, logger *zap.Logger) *PermissionService {
|
|
|
|
|
if logger == nil {
|
|
|
|
|
logger = zap.NewNop()
|
|
|
|
|
}
|
|
|
|
|
return &PermissionService{
|
|
|
|
|
db: db,
|
|
|
|
|
logger: logger,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type roomMemberCheck struct {
|
|
|
|
|
Role string `gorm:"column:role"`
|
|
|
|
|
IsMuted bool `gorm:"column:is_muted"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *PermissionService) getMembership(ctx context.Context, userID, roomID uuid.UUID) (*roomMemberCheck, bool) {
|
|
|
|
|
var member roomMemberCheck
|
|
|
|
|
err := p.db.WithContext(ctx).
|
|
|
|
|
Table("room_members").
|
|
|
|
|
Select("role, is_muted").
|
|
|
|
|
Where("user_id = ? AND room_id = ?", userID, roomID).
|
|
|
|
|
First(&member).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
return &member, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *PermissionService) CanRead(ctx context.Context, userID, roomID uuid.UUID) bool {
|
feat(v0.703): Go Live & Streaming Complet
- Backend: room creation for live streams, permissions CanJoin/CanSend/CanRead for stream rooms
- LiveViewChat: useLiveStreamChat hook, WebSocket connection, stream_id as room
- LiveViewPlayer: real-time viewer count via polling (5s)
- Media Session: seekbackward/seekforward handlers (10s step)
- GoLiveView.stories.tsx: Default, Loading, Error, StreamKeyVisible
- Docs: API_REFERENCE, CHANGELOG, PROJECT_STATE, FEATURE_STATUS, RETROSPECTIVE_V0703
- SCOPE_CONTROL, .cursorrules: update to v0.801
- Archive V0_703_RELEASE_SCOPE.md
2026-02-25 08:35:22 +00:00
|
|
|
// GL3-01: Live stream chat — allow read when roomID is a live_streams.id (public)
|
|
|
|
|
var count int64
|
|
|
|
|
if err := p.db.WithContext(ctx).
|
|
|
|
|
Table("live_streams").
|
|
|
|
|
Where("id = ?", roomID).
|
|
|
|
|
Count(&count).Error; err == nil && count > 0 {
|
|
|
|
|
return true
|
|
|
|
|
}
|
feat(chat): Sprint 3 -- message handlers, real-time features, permissions
- Implement full MessageHandler dispatch with all 18 incoming message types
- Add handler_messages.go: SendMessage, EditMessage, DeleteMessage with ownership checks
- Add handler_rooms.go: JoinConversation, LeaveConversation
- Add handler_history.go: FetchHistory (cursor-based), SearchMessages (ILIKE), SyncMessages
- Add handler_realtime.go: Typing, MarkAsRead, Delivered, AddReaction, RemoveReaction
- Add handler_calls.go: WebRTC signaling relay (CallOffer/Answer/ICE/Hangup/Reject)
- Add PermissionService: CanRead/CanSend/CanJoin/CanModerate based on room_members
- Add RateLimiter: per-user per-action sliding window (in-memory)
- Wire all dependencies in router.go setupChatWebSocket
2026-02-22 19:43:44 +00:00
|
|
|
_, isMember := p.getMembership(ctx, userID, roomID)
|
|
|
|
|
return isMember
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *PermissionService) CanSend(ctx context.Context, userID, roomID uuid.UUID) bool {
|
feat(v0.703): Go Live & Streaming Complet
- Backend: room creation for live streams, permissions CanJoin/CanSend/CanRead for stream rooms
- LiveViewChat: useLiveStreamChat hook, WebSocket connection, stream_id as room
- LiveViewPlayer: real-time viewer count via polling (5s)
- Media Session: seekbackward/seekforward handlers (10s step)
- GoLiveView.stories.tsx: Default, Loading, Error, StreamKeyVisible
- Docs: API_REFERENCE, CHANGELOG, PROJECT_STATE, FEATURE_STATUS, RETROSPECTIVE_V0703
- SCOPE_CONTROL, .cursorrules: update to v0.801
- Archive V0_703_RELEASE_SCOPE.md
2026-02-25 08:35:22 +00:00
|
|
|
// GL3-01: Live stream chat — allow send when roomID is a live_streams.id (public)
|
|
|
|
|
var count int64
|
|
|
|
|
if err := p.db.WithContext(ctx).
|
|
|
|
|
Table("live_streams").
|
|
|
|
|
Where("id = ?", roomID).
|
|
|
|
|
Count(&count).Error; err == nil && count > 0 {
|
|
|
|
|
return true
|
|
|
|
|
}
|
feat(chat): Sprint 3 -- message handlers, real-time features, permissions
- Implement full MessageHandler dispatch with all 18 incoming message types
- Add handler_messages.go: SendMessage, EditMessage, DeleteMessage with ownership checks
- Add handler_rooms.go: JoinConversation, LeaveConversation
- Add handler_history.go: FetchHistory (cursor-based), SearchMessages (ILIKE), SyncMessages
- Add handler_realtime.go: Typing, MarkAsRead, Delivered, AddReaction, RemoveReaction
- Add handler_calls.go: WebRTC signaling relay (CallOffer/Answer/ICE/Hangup/Reject)
- Add PermissionService: CanRead/CanSend/CanJoin/CanModerate based on room_members
- Add RateLimiter: per-user per-action sliding window (in-memory)
- Wire all dependencies in router.go setupChatWebSocket
2026-02-22 19:43:44 +00:00
|
|
|
member, isMember := p.getMembership(ctx, userID, roomID)
|
|
|
|
|
if !isMember {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return !member.IsMuted
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *PermissionService) CanJoin(ctx context.Context, userID, roomID uuid.UUID) bool {
|
feat(v0.703): Go Live & Streaming Complet
- Backend: room creation for live streams, permissions CanJoin/CanSend/CanRead for stream rooms
- LiveViewChat: useLiveStreamChat hook, WebSocket connection, stream_id as room
- LiveViewPlayer: real-time viewer count via polling (5s)
- Media Session: seekbackward/seekforward handlers (10s step)
- GoLiveView.stories.tsx: Default, Loading, Error, StreamKeyVisible
- Docs: API_REFERENCE, CHANGELOG, PROJECT_STATE, FEATURE_STATUS, RETROSPECTIVE_V0703
- SCOPE_CONTROL, .cursorrules: update to v0.801
- Archive V0_703_RELEASE_SCOPE.md
2026-02-25 08:35:22 +00:00
|
|
|
// GL3-01: Live stream chat — allow join when roomID is a live_streams.id (public)
|
|
|
|
|
var count int64
|
|
|
|
|
if err := p.db.WithContext(ctx).
|
|
|
|
|
Table("live_streams").
|
|
|
|
|
Where("id = ?", roomID).
|
|
|
|
|
Count(&count).Error; err == nil && count > 0 {
|
|
|
|
|
return true
|
|
|
|
|
}
|
feat(chat): Sprint 3 -- message handlers, real-time features, permissions
- Implement full MessageHandler dispatch with all 18 incoming message types
- Add handler_messages.go: SendMessage, EditMessage, DeleteMessage with ownership checks
- Add handler_rooms.go: JoinConversation, LeaveConversation
- Add handler_history.go: FetchHistory (cursor-based), SearchMessages (ILIKE), SyncMessages
- Add handler_realtime.go: Typing, MarkAsRead, Delivered, AddReaction, RemoveReaction
- Add handler_calls.go: WebRTC signaling relay (CallOffer/Answer/ICE/Hangup/Reject)
- Add PermissionService: CanRead/CanSend/CanJoin/CanModerate based on room_members
- Add RateLimiter: per-user per-action sliding window (in-memory)
- Wire all dependencies in router.go setupChatWebSocket
2026-02-22 19:43:44 +00:00
|
|
|
_, isMember := p.getMembership(ctx, userID, roomID)
|
|
|
|
|
return isMember
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 09:21:57 +00:00
|
|
|
// IsLiveRoom returns true if roomID is a live_streams.id (F474).
|
|
|
|
|
func (p *PermissionService) IsLiveRoom(ctx context.Context, roomID uuid.UUID) bool {
|
|
|
|
|
var count int64
|
|
|
|
|
if err := p.db.WithContext(ctx).
|
|
|
|
|
Table("live_streams").
|
|
|
|
|
Where("id = ?", roomID).
|
|
|
|
|
Count(&count).Error; err != nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return count > 0
|
|
|
|
|
}
|
|
|
|
|
|
feat(chat): Sprint 3 -- message handlers, real-time features, permissions
- Implement full MessageHandler dispatch with all 18 incoming message types
- Add handler_messages.go: SendMessage, EditMessage, DeleteMessage with ownership checks
- Add handler_rooms.go: JoinConversation, LeaveConversation
- Add handler_history.go: FetchHistory (cursor-based), SearchMessages (ILIKE), SyncMessages
- Add handler_realtime.go: Typing, MarkAsRead, Delivered, AddReaction, RemoveReaction
- Add handler_calls.go: WebRTC signaling relay (CallOffer/Answer/ICE/Hangup/Reject)
- Add PermissionService: CanRead/CanSend/CanJoin/CanModerate based on room_members
- Add RateLimiter: per-user per-action sliding window (in-memory)
- Wire all dependencies in router.go setupChatWebSocket
2026-02-22 19:43:44 +00:00
|
|
|
func (p *PermissionService) CanModerate(ctx context.Context, userID, roomID uuid.UUID) bool {
|
|
|
|
|
member, isMember := p.getMembership(ctx, userID, roomID)
|
|
|
|
|
if !isMember {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return member.Role == "admin" || member.Role == "moderator" || member.Role == "owner"
|
|
|
|
|
}
|