package bossraid import ( "strings" "gorm.io/gorm" "gorm.io/gorm/clause" ) type Repository struct { db *gorm.DB } func NewRepository(db *gorm.DB) *Repository { return &Repository{db: db} } func (r *Repository) Create(room *BossRoom) error { return r.db.Create(room).Error } func (r *Repository) Update(room *BossRoom) error { return r.db.Save(room).Error } func (r *Repository) FindBySessionName(sessionName string) (*BossRoom, error) { var room BossRoom if err := r.db.Where("session_name = ?", sessionName).First(&room).Error; err != nil { return nil, err } return &room, nil } // FindBySessionNameForUpdate acquires a row-level lock (SELECT ... FOR UPDATE) // to prevent concurrent state transitions. func (r *Repository) FindBySessionNameForUpdate(sessionName string) (*BossRoom, error) { var room BossRoom if err := r.db.Clauses(clause.Locking{Strength: "UPDATE"}).Where("session_name = ?", sessionName).First(&room).Error; err != nil { return nil, err } return &room, nil } // Transaction wraps a function in a database transaction. func (r *Repository) Transaction(fn func(txRepo *Repository) error) error { return r.db.Transaction(func(tx *gorm.DB) error { return fn(&Repository{db: tx}) }) } // CountActiveByUsername checks if a player is already in an active boss raid. func (r *Repository) CountActiveByUsername(username string) (int64, error) { var count int64 // LIKE 특수문자 이스케이프 escaped := strings.NewReplacer("%", "\\%", "_", "\\_").Replace(username) search := `"` + escaped + `"` err := r.db.Model(&BossRoom{}). Where("status IN ? AND players LIKE ?", []RoomStatus{StatusWaiting, StatusInProgress}, "%"+search+"%", ).Count(&count).Error return count, err }