twitch-bot-streak/pkg/database/query.go
Knut Ahlers f1d35ce7c0
Switch to properly tested database interface
and with that support all databases the bot does support

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-24 13:29:57 +01:00

139 lines
3.4 KiB
Go

package database
import (
"errors"
"fmt"
"time"
"gorm.io/gorm"
)
var defaultMetaTime = time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
func (d DB) CountStreak(twitchID uint64, username string) (user StreakUser, err error) {
if err = d.db.Transaction(func(tx *gorm.DB) (err error) {
if err = tx.First(&user, "twitch_id = ?", twitchID).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("getting user: %w", err)
}
// User was not yet inserted
user = StreakUser{
TwitchID: twitchID,
Username: username,
StreamsCount: 0,
CurrentStreak: 0,
MaxStreak: 0,
StreakStatus: StatusBroken,
}
}
switch user.StreakStatus {
case StatusActive:
// User has an active streak, do nothing
return nil
case StatusBroken:
// User needs a new streak
user.CurrentStreak = 1
case StatusPending:
// User can prolong their streak
user.CurrentStreak += 1
}
// In any case set the streak active and count the current stream
user.StreamsCount++
user.StreakStatus = StatusActive
if user.CurrentStreak > user.MaxStreak {
user.MaxStreak = user.CurrentStreak
}
if err = tx.Save(&user).Error; err != nil {
return fmt.Errorf("saving user: %w", err)
}
return nil
}); err != nil {
return user, fmt.Errorf("counting streak for user: %w", err)
}
return user, nil
}
func (d DB) SetStreamOffline() (err error) {
return d.storeTimeToMeta(d.db, "stream_offline", time.Now())
}
func (d DB) StartStream(streamOfflineGrace time.Duration) (err error) {
if err = d.db.Transaction(func(tx *gorm.DB) (err error) {
lastOffline, err := d.getTimeFromMeta(tx, "stream_offline")
if err != nil {
return fmt.Errorf("getting offline time: %w", err)
}
lastOnline, err := d.getTimeFromMeta(tx, "stream_online")
if err != nil {
return fmt.Errorf("getting online time: %w", err)
}
if err = d.storeTimeToMeta(tx, "stream_online", time.Now()); err != nil {
return fmt.Errorf("storing stream start: %w", err)
}
if time.Since(lastOffline) < streamOfflineGrace || lastOnline.After(lastOffline) {
// We only had a short break or the stream was already started
return nil
}
if err = tx.Model(&StreakUser{}).
Where("streak_status = ?", StatusPending).
Update("streak_status", StatusBroken).
Error; err != nil {
return fmt.Errorf("breaking streaks for pending users: %w", err)
}
if err = tx.Model(&StreakUser{}).
Where("streak_status = ?", StatusActive).
Update("streak_status", StatusPending).
Error; err != nil {
return fmt.Errorf("breaking streaks for pending users: %w", err)
}
return nil
}); err != nil {
return fmt.Errorf("starting stream: %w", err)
}
return nil
}
func (d DB) getTimeFromMeta(db *gorm.DB, key string) (t time.Time, err error) {
var meta StreakMeta
if err = db.First(&meta, "name = ?", key).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return t, fmt.Errorf("getting last %s time: %w", key, err)
}
meta.Value = defaultMetaTime.Format(time.RFC3339Nano)
}
t, err = time.Parse(time.RFC3339Nano, meta.Value)
if err != nil {
return t, fmt.Errorf("parsing %s time: %w", key, err)
}
return t, nil
}
func (d DB) storeTimeToMeta(db *gorm.DB, key string, t time.Time) (err error) {
if err = db.Save(&StreakMeta{
Name: key,
Value: t.Format(time.RFC3339Nano),
}).Error; err != nil {
return fmt.Errorf("updating stream meta: %w", err)
}
return nil
}