package store import ( "context" "math/rand/v2" "slices" "time" "forge.cadoles.com/wpetit/kouiz/internal/timex" "github.com/pkg/errors" "gorm.io/gorm" ) func (s *Store) UpsertQuizCategory(ctx context.Context, category *QuizCategory) error { return errors.WithStack(s.Do(ctx, func(db *gorm.DB) error { var existing *QuizCategory err := db.Find(&existing, "name = ? and theme = ?", category.Name, category.Theme).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return errors.WithStack(err) } if existing != nil { category.Model = existing.Model } if err := db.Save(category).Error; err != nil { return errors.WithStack(err) } return nil })) } func (s *Store) UpsertQuizEntry(ctx context.Context, entry *QuizEntry) error { return errors.WithStack(s.Do(ctx, func(db *gorm.DB) error { var existing *QuizEntry err := db.Find(&existing, "provider = ? and provider_id = ?", entry.Provider, entry.ProviderID).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return errors.WithStack(err) } if existing != nil { entry.Model = existing.Model } if err := db.Save(entry).Error; err != nil { return errors.WithStack(err) } return nil })) } func (s *Store) GetQuizTurn(ctx context.Context, playInterval time.Duration, period timex.PeriodType) (*QuizTurn, error) { var quizTurn *QuizTurn err := s.Tx(ctx, func(tx *gorm.DB) error { now := time.Now().UTC() err := tx.Model(&quizTurn).Preload("Entries").Preload("Entries.Category").Order("ended_at DESC").First(&quizTurn, "ended_at >= ?", now).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return errors.WithStack(err) } if quizTurn != nil && quizTurn.ID != 0 { return nil } interval := timex.NewInterval(playInterval, period) current := interval.GetCurrent() start, end := interval.GetIntervalBounds(current, time.Now().UTC()) quizTurn = &QuizTurn{ StartedAt: start.UTC(), EndedAt: end.UTC(), } alreadyUsed := make([]uint, 0) err = tx.Table("quiz_turn_entries").Pluck("quiz_entry_id", &alreadyUsed).Error if err != nil { return errors.WithStack(err) } query := tx.Model(&QuizEntry{}) if len(alreadyUsed) > 0 { query = query.Where("id NOT IN ?", alreadyUsed) } var entryIDs []uint err = query.Pluck("id", &entryIDs).Error if err != nil { return errors.WithStack(err) } for range 3 { index := rand.IntN(len(entryIDs)) quizTurn.Entries = append(quizTurn.Entries, &QuizEntry{ Model: gorm.Model{ ID: entryIDs[index], }, }) entryIDs = slices.Delete(entryIDs, index, index+1) } if err := tx.Save(quizTurn).Error; err != nil { return errors.WithStack(err) } err = tx.Model(&QuizTurn{}).Preload("Entries").Preload("Entries.Category").First(&quizTurn).Error if err != nil { return errors.WithStack(err) } return nil }) if err != nil { return nil, errors.WithStack(err) } return quizTurn, nil }