Feat: add cleaner cron

This commit is contained in:
Grail Finder
2025-07-04 10:34:08 +03:00
parent 83215f5c14
commit 71a2d9d747
8 changed files with 116 additions and 5 deletions

View File

@ -1,6 +1,7 @@
BASE_URL = "https://localhost:3000" BASE_URL = "https://localhost:3000"
SESSION_LIFETIME_SECONDS = 30000 SESSION_LIFETIME_SECONDS = 30000
COOKIE_SECRET = "test" COOKIE_SECRET = "test"
DB_PATH = "sqlite3://gralias.db"
[SERVICE] [SERVICE]
HOST = "localhost" HOST = "localhost"

View File

@ -13,6 +13,7 @@ type Config struct {
SessionLifetime int64 `toml:"SESSION_LIFETIME_SECONDS"` SessionLifetime int64 `toml:"SESSION_LIFETIME_SECONDS"`
CookieSecret string `toml:"COOKIE_SECRET"` CookieSecret string `toml:"COOKIE_SECRET"`
LLMConfig LLMConfig `toml:"LLM"` LLMConfig LLMConfig `toml:"LLM"`
DBPath string `toml:"DB_PATH"`
} }
type ServerConfig struct { type ServerConfig struct {
@ -38,6 +39,7 @@ func LoadConfigOrDefault(fn string) *Config {
config.CookieSecret = "test" config.CookieSecret = "test"
config.ServerConfig.Host = "localhost" config.ServerConfig.Host = "localhost"
config.ServerConfig.Port = "3000" config.ServerConfig.Port = "3000"
config.DBPath = "sqlite3://gralias.db"
} }
fmt.Printf("config debug; config.LLMConfig.URL: %s\n", config.LLMConfig.URL) fmt.Printf("config debug; config.LLMConfig.URL: %s\n", config.LLMConfig.URL)
return config return config

76
crons/main.go Normal file
View File

@ -0,0 +1,76 @@
package crons
import (
"context"
"gralias/repos"
"log/slog"
"time"
)
type CronManager struct {
repo repos.AllRepos
log *slog.Logger
}
func NewCronManager(repo repos.AllRepos, log *slog.Logger) *CronManager {
return &CronManager{
repo: repo,
log: log,
}
}
func (cm *CronManager) Start() {
ticker := time.NewTicker(30 * time.Second)
go func() {
for range ticker.C {
cm.CleanupRooms()
}
}()
}
func (cm *CronManager) CleanupRooms() {
ctx := context.Background()
rooms, err := cm.repo.RoomList(ctx)
if err != nil {
cm.log.Error("failed to get rooms list", "err", err)
return
}
for _, room := range rooms {
players, err := cm.repo.PlayerListByRoom(ctx, room.ID)
if err != nil {
cm.log.Error("failed to get players for room", "room_id", room.ID, "err", err)
continue
}
if len(players) == 0 {
cm.log.Info("deleting empty room", "room_id", room.ID)
if err := cm.repo.RoomDeleteByID(ctx, room.ID); err != nil {
cm.log.Error("failed to delete empty room", "room_id", room.ID, "err", err)
}
continue
}
creatorInRoom := false
for _, player := range players {
if player.Username == room.CreatorName {
creatorInRoom = true
break
}
}
if !creatorInRoom {
cm.log.Info("deleting room because creator left", "room_id", room.ID)
for _, player := range players {
if player.IsBot {
if err := cm.repo.PlayerDelete(ctx, room.ID, player.Username); err != nil {
cm.log.Error("failed to delete bot player", "room_id", room.ID, "username", player.Username, "err", err)
}
} else {
if err := cm.repo.PlayerExitRoom(ctx, player.Username); err != nil {
cm.log.Error("failed to update player room", "room_id", room.ID, "username", player.Username, "err", err)
}
}
}
if err := cm.repo.RoomDeleteByID(ctx, room.ID); err != nil {
cm.log.Error("failed to delete room after creator left", "room_id", room.ID, "err", err)
}
}
}
}

View File

@ -168,7 +168,9 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) {
} }
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
tx.Rollback() if err := tx.Rollback(); err != nil {
log.Error("failed to rollback transaction", "error", err)
}
panic(r) panic(r)
} }
}() }()
@ -189,7 +191,9 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) {
fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) fi.Room.ActionHistory = append(fi.Room.ActionHistory, action)
// Use the new context with transaction // Use the new context with transaction
if err := saveFullInfo(ctx, fi); err != nil { if err := saveFullInfo(ctx, fi); err != nil {
tx.Rollback() if err := tx.Rollback(); err != nil {
log.Error("failed to rollback transaction", "error", err)
}
abortWithError(w, err.Error()) abortWithError(w, err.Error())
return return
} }
@ -197,7 +201,9 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) {
action.RoomID = fi.Room.ID action.RoomID = fi.Room.ID
action.CreatedAt = time.Now() action.CreatedAt = time.Now()
if err := repo.CreateAction(ctx, fi.Room.ID, &action); err != nil { if err := repo.CreateAction(ctx, fi.Room.ID, &action); err != nil {
tx.Rollback() if err := tx.Rollback(); err != nil {
log.Error("failed to rollback transaction", "error", err)
}
log.Error("failed to save action", "error", err) log.Error("failed to save action", "error", err)
abortWithError(w, err.Error()) abortWithError(w, err.Error())
return return
@ -206,7 +212,9 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) {
for _, card := range fi.Room.Cards { for _, card := range fi.Room.Cards {
card.RoomID = fi.Room.ID // Ensure RoomID is set for each card card.RoomID = fi.Room.ID // Ensure RoomID is set for each card
if err := repo.WordCardsCreate(ctx, &card); err != nil { if err := repo.WordCardsCreate(ctx, &card); err != nil {
tx.Rollback() if err := tx.Rollback(); err != nil {
log.Error("failed to rollback transaction", "error", err)
}
log.Error("failed to save word card", "error", err) log.Error("failed to save word card", "error", err)
abortWithError(w, err.Error()) abortWithError(w, err.Error())
return return

View File

@ -70,7 +70,9 @@ func GetSession(next http.Handler) http.Handler {
return return
} }
if userSession.IsExpired() { if userSession.IsExpired() {
repo.SessionDelete(r.Context(), sessionToken) if err := repo.SessionDelete(r.Context(), sessionToken); err != nil {
log.Error("failed to delete session", "error", err)
}
// cache.MemCache.RemoveKey(sessionToken) // cache.MemCache.RemoveKey(sessionToken)
msg := "session is expired" msg := "session is expired"
log.Debug(msg, "error", err, "token", sessionToken) log.Debug(msg, "error", err, "token", sessionToken)

View File

@ -3,7 +3,9 @@ package main
import ( import (
"context" "context"
"gralias/config" "gralias/config"
"gralias/crons"
"gralias/handlers" "gralias/handlers"
"gralias/repos"
"log/slog" "log/slog"
"net/http" "net/http"
"os" "os"
@ -61,6 +63,12 @@ func main() {
stop := make(chan os.Signal, 1) stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM) signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
repo := repos.NewRepoProvider(cfg.DBPath)
defer repo.Close()
cm := crons.NewCronManager(repo, slog.Default())
cm.Start()
server := ListenToRequests(cfg.ServerConfig.Port) server := ListenToRequests(cfg.ServerConfig.Port)
go func() { go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {

View File

@ -104,3 +104,7 @@ func (p *RepoProvider) InitTx(ctx context.Context) (context.Context, *sqlx.Tx, e
} }
return context.WithValue(ctx, "tx", tx), tx, nil return context.WithValue(ctx, "tx", tx), tx, nil
} }
func (p *RepoProvider) Close() {
p.DB.Close()
}

View File

@ -16,6 +16,7 @@ type PlayersRepo interface {
PlayerExitRoom(ctx context.Context, username string) error PlayerExitRoom(ctx context.Context, username string) error
PlayerListNames(ctx context.Context) ([]string, error) PlayerListNames(ctx context.Context) ([]string, error)
PlayerList(ctx context.Context, isBot bool) ([]models.Player, error) PlayerList(ctx context.Context, isBot bool) ([]models.Player, error)
PlayerListByRoom(ctx context.Context, roomID string) ([]models.Player, error)
} }
func (p *RepoProvider) PlayerListNames(ctx context.Context) ([]string, error) { func (p *RepoProvider) PlayerListNames(ctx context.Context) ([]string, error) {
@ -85,3 +86,12 @@ func (p *RepoProvider) PlayerList(ctx context.Context, isBot bool) ([]models.Pla
} }
return players, nil return players, nil
} }
func (p *RepoProvider) PlayerListByRoom(ctx context.Context, roomID string) ([]models.Player, error) {
var players []models.Player
err := sqlx.SelectContext(ctx, p.DB, &players, "SELECT id, room_id, username, team, role, is_bot FROM players WHERE room_id = ?", roomID)
if err != nil {
return nil, err
}
return players, nil
}