package db import ( "errors" "strings" "sync" "gorm.io/gorm" "hightube/internal/model" ) type userCache struct { mutex sync.RWMutex byID map[uint]model.User byUsername map[string]uint } type roomCache struct { mutex sync.RWMutex byID map[uint]model.Room byUserID map[uint]uint byStreamKey map[string]uint activeRoomIDs map[uint]struct{} activeRoomsLoaded bool } var users = &userCache{ byID: make(map[uint]model.User), byUsername: make(map[string]uint), } var rooms = &roomCache{ byID: make(map[uint]model.Room), byUserID: make(map[uint]uint), byStreamKey: make(map[string]uint), activeRoomIDs: make(map[uint]struct{}), } func cacheUser(user model.User) { usernameKey := strings.ToLower(strings.TrimSpace(user.Username)) users.mutex.Lock() users.byID[user.ID] = user if usernameKey != "" { users.byUsername[usernameKey] = user.ID } users.mutex.Unlock() } func removeUserFromCache(user model.User) { usernameKey := strings.ToLower(strings.TrimSpace(user.Username)) users.mutex.Lock() delete(users.byID, user.ID) if usernameKey != "" { delete(users.byUsername, usernameKey) } users.mutex.Unlock() } func cacheRoom(room model.Room) { rooms.mutex.Lock() rooms.byID[room.ID] = room rooms.byUserID[room.UserID] = room.ID if room.StreamKey != "" { rooms.byStreamKey[room.StreamKey] = room.ID } if room.IsActive { rooms.activeRoomIDs[room.ID] = struct{}{} } else { delete(rooms.activeRoomIDs, room.ID) } rooms.mutex.Unlock() } func removeRoomFromCache(room model.Room) { rooms.mutex.Lock() delete(rooms.byID, room.ID) delete(rooms.byUserID, room.UserID) if room.StreamKey != "" { delete(rooms.byStreamKey, room.StreamKey) } delete(rooms.activeRoomIDs, room.ID) rooms.mutex.Unlock() } func LoadUserByID(id uint) (model.User, error) { users.mutex.RLock() if user, ok := users.byID[id]; ok { users.mutex.RUnlock() return user, nil } users.mutex.RUnlock() var user model.User if err := DB.First(&user, id).Error; err != nil { return model.User{}, err } cacheUser(user) return user, nil } func LoadUserByUsername(username string) (model.User, error) { key := strings.ToLower(strings.TrimSpace(username)) if key == "" { return model.User{}, gorm.ErrRecordNotFound } users.mutex.RLock() if id, ok := users.byUsername[key]; ok { if user, found := users.byID[id]; found { users.mutex.RUnlock() return user, nil } } users.mutex.RUnlock() var user model.User if err := DB.Where("username = ?", strings.TrimSpace(username)).First(&user).Error; err != nil { return model.User{}, err } cacheUser(user) return user, nil } func LoadRoomByUserID(userID uint) (model.Room, error) { rooms.mutex.RLock() if roomID, ok := rooms.byUserID[userID]; ok { if room, found := rooms.byID[roomID]; found { rooms.mutex.RUnlock() return room, nil } } rooms.mutex.RUnlock() var room model.Room if err := DB.Where("user_id = ?", userID).First(&room).Error; err != nil { return model.Room{}, err } cacheRoom(room) return room, nil } func LoadRoomByStreamKey(streamKey string) (model.Room, error) { rooms.mutex.RLock() if roomID, ok := rooms.byStreamKey[streamKey]; ok { if room, found := rooms.byID[roomID]; found { rooms.mutex.RUnlock() return room, nil } } rooms.mutex.RUnlock() var room model.Room if err := DB.Where("stream_key = ?", streamKey).First(&room).Error; err != nil { return model.Room{}, err } cacheRoom(room) return room, nil } func ListActiveRooms() ([]model.Room, error) { rooms.mutex.RLock() loaded := rooms.activeRoomsLoaded if loaded { result := make([]model.Room, 0, len(rooms.activeRoomIDs)) for roomID := range rooms.activeRoomIDs { if room, ok := rooms.byID[roomID]; ok { result = append(result, room) } } complete := len(result) == len(rooms.activeRoomIDs) rooms.mutex.RUnlock() if complete { return result, nil } } else { rooms.mutex.RUnlock() } var result []model.Room if err := DB.Where("is_active = ?", true).Find(&result).Error; err != nil { return nil, err } rooms.mutex.Lock() for _, room := range result { rooms.byID[room.ID] = room rooms.byUserID[room.UserID] = room.ID if room.StreamKey != "" { rooms.byStreamKey[room.StreamKey] = room.ID } rooms.activeRoomIDs[room.ID] = struct{}{} } rooms.activeRoomsLoaded = true rooms.mutex.Unlock() return result, nil } func CreateUserAndRoom(user *model.User, room *model.Room) error { if user == nil || room == nil { return errors.New("user and room are required") } if err := DB.Transaction(func(tx *gorm.DB) error { if err := tx.Create(user).Error; err != nil { return err } room.UserID = user.ID if err := tx.Create(room).Error; err != nil { return err } return nil }); err != nil { return err } cacheUser(*user) cacheRoom(*room) return nil } func UpdateUserRole(userID uint, role string) error { if err := DB.Model(&model.User{}).Where("id = ?", userID).Update("role", role).Error; err != nil { return err } user, err := LoadUserByID(userID) if err != nil { return err } user.Role = role cacheUser(user) return nil } func UpdateUserEnabled(userID uint, enabled bool) error { if err := DB.Model(&model.User{}).Where("id = ?", userID).Update("enabled", enabled).Error; err != nil { return err } user, err := LoadUserByID(userID) if err != nil { return err } user.Enabled = enabled cacheUser(user) return nil } func UpdateUserPassword(userID uint, hash string) error { if err := DB.Model(&model.User{}).Where("id = ?", userID).Update("password", hash).Error; err != nil { return err } user, err := LoadUserByID(userID) if err != nil { return err } user.Password = hash cacheUser(user) return nil } func SetRoomActive(roomID uint, active bool) error { if err := DB.Model(&model.Room{}).Where("id = ?", roomID).Update("is_active", active).Error; err != nil { return err } rooms.mutex.Lock() room, ok := rooms.byID[roomID] if ok { room.IsActive = active rooms.byID[roomID] = room if active { rooms.activeRoomIDs[roomID] = struct{}{} } else { delete(rooms.activeRoomIDs, roomID) } rooms.activeRoomsLoaded = true rooms.mutex.Unlock() return nil } rooms.mutex.Unlock() if !active { rooms.mutex.Lock() delete(rooms.activeRoomIDs, roomID) rooms.activeRoomsLoaded = true rooms.mutex.Unlock() return nil } var roomFromDB model.Room if err := DB.First(&roomFromDB, roomID).Error; err != nil { return err } roomFromDB.IsActive = active cacheRoom(roomFromDB) rooms.mutex.Lock() rooms.activeRoomsLoaded = true rooms.mutex.Unlock() return nil } func DeleteUserCascade(userID uint) error { user, userErr := LoadUserByID(userID) room, roomErr := LoadRoomByUserID(userID) if err := DB.Transaction(func(tx *gorm.DB) error { if err := tx.Where("user_id = ?", userID).Delete(&model.Room{}).Error; err != nil { return err } if err := tx.Delete(&model.User{}, userID).Error; err != nil { return err } return nil }); err != nil { return err } if roomErr == nil { removeRoomFromCache(room) } if userErr == nil { removeUserFromCache(user) } return nil }