349 lines
9.3 KiB
Go
349 lines
9.3 KiB
Go
package room
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type RoomStatus string
|
|
|
|
const (
|
|
RoomActive RoomStatus = "active"
|
|
RoomClosed RoomStatus = "closed"
|
|
RoomBanned RoomStatus = "banned"
|
|
)
|
|
|
|
func (s RoomStatus) IsActive() bool { return s == RoomActive }
|
|
func (s RoomStatus) IsClosed() bool { return s == RoomClosed || s == RoomBanned }
|
|
func (s RoomStatus) CanTransitionTo(next RoomStatus) bool {
|
|
if s == next {
|
|
return true
|
|
}
|
|
switch s {
|
|
case RoomActive:
|
|
return next == RoomClosed || next == RoomBanned
|
|
case RoomClosed:
|
|
return next == RoomActive
|
|
case RoomBanned:
|
|
return next == RoomActive
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func ParseRoomStatus(raw string) (RoomStatus, error) {
|
|
switch strings.ToLower(strings.TrimSpace(raw)) {
|
|
case "active":
|
|
return RoomActive, nil
|
|
case "closed":
|
|
return RoomClosed, nil
|
|
case "banned":
|
|
return RoomBanned, nil
|
|
default:
|
|
return "", fmt.Errorf("unknown room status %q", raw)
|
|
}
|
|
}
|
|
|
|
type PlayMode string
|
|
|
|
const (
|
|
PlaySequential PlayMode = "sequential"
|
|
PlayRepeatOne PlayMode = "repeat_one"
|
|
PlayRepeatAll PlayMode = "repeat_all"
|
|
PlayShuffle PlayMode = "shuffle"
|
|
)
|
|
|
|
func ParsePlayMode(s string) (PlayMode, error) {
|
|
switch strings.ToLower(strings.TrimSpace(s)) {
|
|
case "sequential", "":
|
|
return PlaySequential, nil
|
|
case "repeat_one", "repeatone":
|
|
return PlayRepeatOne, nil
|
|
case "repeat_all", "repeatall":
|
|
return PlayRepeatAll, nil
|
|
case "shuffle", "random":
|
|
return PlayShuffle, nil
|
|
default:
|
|
return "", fmt.Errorf("unknown play mode %q", s)
|
|
}
|
|
}
|
|
|
|
type AutoPlaySettings struct {
|
|
Enabled bool `json:"enabled"`
|
|
Mode PlayMode `json:"mode"`
|
|
Delay uint32 `json:"delay"` // seconds
|
|
}
|
|
|
|
func DefaultAutoPlay() AutoPlaySettings {
|
|
return AutoPlaySettings{Enabled: true, Mode: PlaySequential, Delay: 3}
|
|
}
|
|
|
|
type RoomCategory struct {
|
|
ID int64 `json:"id"`
|
|
Key string `json:"key"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
SortOrder int32 `json:"sort_order"`
|
|
IsEnabled bool `json:"is_enabled"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type RoomLabel struct {
|
|
ID int64 `json:"id"`
|
|
Key string `json:"key"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Color string `json:"color"`
|
|
CategoryID *int64 `json:"category_id,omitempty"`
|
|
SortOrder int32 `json:"sort_order"`
|
|
IsEnabled bool `json:"is_enabled"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type Room struct {
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"display_name,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
Creator string `json:"creator"`
|
|
Status RoomStatus `json:"status"`
|
|
IsPublic bool `json:"is_public"`
|
|
PasswordHash string `json:"-"`
|
|
RequireApproval bool `json:"require_approval"`
|
|
MaxMembers int `json:"max_members"`
|
|
MemberCount int `json:"member_count"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
ClosedAt *time.Time `json:"closed_at,omitempty"`
|
|
Settings *RoomSettings `json:"settings,omitempty"`
|
|
Version int64 `json:"version"`
|
|
CategoryID *int64 `json:"category_id,omitempty"`
|
|
LabelIDs []int64 `json:"label_ids,omitempty"`
|
|
CoverFileID *string `json:"cover_file_id,omitempty"`
|
|
AutoPlay AutoPlaySettings `json:"auto_play"`
|
|
}
|
|
|
|
type RoomWithCount struct {
|
|
Room `json:",inline"`
|
|
MemberCount int `json:"member_count"`
|
|
}
|
|
|
|
type SortDirection string
|
|
|
|
const (
|
|
SortAsc SortDirection = "asc"
|
|
SortDesc SortDirection = "desc"
|
|
)
|
|
|
|
type PageParams struct {
|
|
Page int `json:"page"`
|
|
PageSize int `json:"page_size"`
|
|
}
|
|
|
|
func (p PageParams) Offset() int {
|
|
if p.Page <= 1 {
|
|
return 0
|
|
}
|
|
return (p.Page - 1) * p.Limit()
|
|
}
|
|
func (p PageParams) Limit() int {
|
|
if p.PageSize <= 0 {
|
|
return 20
|
|
}
|
|
if p.PageSize > 100 {
|
|
return 100
|
|
}
|
|
return p.PageSize
|
|
}
|
|
|
|
type RoomListSortBy string
|
|
|
|
const (
|
|
SortByCreatedAt RoomListSortBy = "created_at"
|
|
SortByUpdatedAt RoomListSortBy = "updated_at"
|
|
SortByName RoomListSortBy = "name"
|
|
SortByMemberCount RoomListSortBy = "member_count"
|
|
)
|
|
|
|
func (s RoomListSortBy) AsSQL() string {
|
|
switch s {
|
|
case SortByName:
|
|
return "name"
|
|
case SortByMemberCount:
|
|
return "member_count"
|
|
case SortByUpdatedAt:
|
|
return "updated_at"
|
|
default:
|
|
return "created_at"
|
|
}
|
|
}
|
|
|
|
type RoomListQuery struct {
|
|
Pagination PageParams `json:"pagination"`
|
|
Status *RoomStatus `json:"status,omitempty"`
|
|
Search *string `json:"search,omitempty"`
|
|
IsBanned *bool `json:"is_banned,omitempty"`
|
|
IsPublic *bool `json:"is_public,omitempty"`
|
|
Creator *string `json:"creator,omitempty"`
|
|
CategoryID *int64 `json:"category_id,omitempty"`
|
|
LabelIDs []int64 `json:"label_ids,omitempty"`
|
|
SortBy RoomListSortBy `json:"sort_by"`
|
|
SortDirection SortDirection `json:"sort_direction"`
|
|
}
|
|
|
|
func DefaultRoomListQuery() RoomListQuery {
|
|
active := RoomActive
|
|
return RoomListQuery{
|
|
Pagination: PageParams{Page: 1, PageSize: 20},
|
|
Status: &active,
|
|
SortBy: SortByCreatedAt,
|
|
SortDirection: SortDesc,
|
|
}
|
|
}
|
|
|
|
func NewRoom(name, creator string) *Room {
|
|
now := time.Now()
|
|
return &Room{
|
|
Name: name,
|
|
DisplayName: name,
|
|
Creator: creator,
|
|
Status: RoomActive,
|
|
IsPublic: true,
|
|
MaxMembers: 100,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
Version: 1,
|
|
Settings: DefaultSettings(),
|
|
AutoPlay: DefaultAutoPlay(),
|
|
}
|
|
}
|
|
|
|
type RoomSettings struct {
|
|
IsPublic bool `json:"is_public"`
|
|
RequireApproval bool `json:"require_approval"`
|
|
AllowGuestJoin bool `json:"allow_guest_join"`
|
|
ChatEnabled bool `json:"chat_enabled"`
|
|
MaxMembers int `json:"max_members"`
|
|
AllowAutoJoin bool `json:"allow_auto_join"`
|
|
AdminAddedPerms uint64 `json:"admin_added_permissions"`
|
|
AdminRemovedPerms uint64 `json:"admin_removed_permissions"`
|
|
MemberAddedPerms uint64 `json:"member_added_permissions"`
|
|
MemberRemovedPerms uint64 `json:"member_removed_permissions"`
|
|
GuestAddedPerms uint64 `json:"guest_added_permissions"`
|
|
GuestRemovedPerms uint64 `json:"guest_removed_permissions"`
|
|
AutoPlayEnabled bool `json:"auto_play_enabled"`
|
|
PlayMode PlayMode `json:"play_mode"`
|
|
Distributors *string `json:"distributors,omitempty"`
|
|
}
|
|
|
|
|
|
// Distributors helpers: per-room distribution override with fallback to global.
|
|
|
|
// HasCustomDistributors returns true when room explicitly sets distributors (even if empty string means disable all).
|
|
func (s *RoomSettings) HasCustomDistributors() bool {
|
|
return s != nil && s.Distributors != nil
|
|
}
|
|
|
|
// EffectiveDistributors returns parsed per-room distributors if set, otherwise fallback to global.
|
|
func (s *RoomSettings) EffectiveDistributors(global []string) []string {
|
|
if s != nil && s.Distributors != nil {
|
|
return ParseDistributors(*s.Distributors)
|
|
}
|
|
return global
|
|
}
|
|
|
|
// SetDistributors sets the per-room distributors override. Use nil to clear (fallback to global).
|
|
func (s *RoomSettings) SetDistributors(v *string) {
|
|
s.Distributors = v
|
|
}
|
|
|
|
// ParseDistributors parses a comma-separated distributors string into normalized list.
|
|
// Supported values: srs-hls (alias srs/hls), cf (alias cloudflare/cf-sfu), cdn.
|
|
// Empty input yields empty list.
|
|
func ParseDistributors(raw string) []string {
|
|
if raw == "" {
|
|
return []string{}
|
|
}
|
|
parts := strings.Split(raw, ",")
|
|
out := make([]string, 0, len(parts))
|
|
seen := make(map[string]struct{}, len(parts))
|
|
for _, p := range parts {
|
|
p = strings.TrimSpace(strings.ToLower(p))
|
|
if p == "" {
|
|
continue
|
|
}
|
|
if p == "srs" || p == "hls" {
|
|
p = "srs-hls"
|
|
}
|
|
if p == "cloudflare" || p == "cf-sfu" {
|
|
p = "cf"
|
|
}
|
|
if p != "srs-hls" && p != "cf" && p != "cdn" {
|
|
continue
|
|
}
|
|
if _, ok := seen[p]; !ok {
|
|
seen[p] = struct{}{}
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func NormalizeDistributorsString(raw string) string {
|
|
return strings.Join(ParseDistributors(raw), ",")
|
|
}
|
|
|
|
func ValidateDistributorsList(list []string) error {
|
|
for _, d := range list {
|
|
if d != "srs-hls" && d != "cf" && d != "cdn" {
|
|
return fmt.Errorf("unsupported distributor %q", d)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DefaultSettings() *RoomSettings {
|
|
return &RoomSettings{
|
|
IsPublic: true,
|
|
AllowGuestJoin: false,
|
|
ChatEnabled: true,
|
|
MaxMembers: 100,
|
|
AllowAutoJoin: true,
|
|
RequireApproval: false,
|
|
AutoPlayEnabled: true,
|
|
PlayMode: PlaySequential,
|
|
}
|
|
}
|
|
|
|
func (s *RoomSettings) AdminPermissions() PermissionSet {
|
|
base := PermDefaultAdmin
|
|
base |= PermissionSet(s.AdminAddedPerms)
|
|
base &^= PermissionSet(s.AdminRemovedPerms)
|
|
return base
|
|
}
|
|
func (s *RoomSettings) MemberPermissions() PermissionSet {
|
|
base := PermDefaultMember
|
|
base |= PermissionSet(s.MemberAddedPerms)
|
|
base &^= PermissionSet(s.MemberRemovedPerms)
|
|
return base
|
|
}
|
|
func (s *RoomSettings) GuestPermissions() PermissionSet {
|
|
base := PermDefaultGuest
|
|
base |= PermissionSet(s.GuestAddedPerms)
|
|
base &^= PermissionSet(s.GuestRemovedPerms)
|
|
return base & PermGuestAssignable
|
|
}
|
|
|
|
func (s *RoomSettings) IsChatEnabled() bool { return s.ChatEnabled }
|
|
|
|
func (s *RoomSettings) EffectiveAutoPlay() AutoPlaySettings {
|
|
enabled := s.AutoPlayEnabled
|
|
mode := s.PlayMode
|
|
if mode == "" {
|
|
mode = PlaySequential
|
|
}
|
|
return AutoPlaySettings{Enabled: enabled, Mode: mode, Delay: 3}
|
|
}
|