Files
Hightube/backend/internal/db/cache.go

333 lines
7.1 KiB
Go

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
}