Feat: add cleaner cron
This commit is contained in:
		| @@ -1,6 +1,7 @@ | ||||
| BASE_URL = "https://localhost:3000" | ||||
| SESSION_LIFETIME_SECONDS = 30000 | ||||
| COOKIE_SECRET = "test" | ||||
| DB_PATH = "sqlite3://gralias.db" | ||||
|  | ||||
| [SERVICE] | ||||
| HOST = "localhost" | ||||
|   | ||||
| @@ -13,6 +13,7 @@ type Config struct { | ||||
| 	SessionLifetime int64        `toml:"SESSION_LIFETIME_SECONDS"` | ||||
| 	CookieSecret    string       `toml:"COOKIE_SECRET"` | ||||
| 	LLMConfig       LLMConfig    `toml:"LLM"` | ||||
| 	DBPath          string       `toml:"DB_PATH"` | ||||
| } | ||||
|  | ||||
| type ServerConfig struct { | ||||
| @@ -38,6 +39,7 @@ func LoadConfigOrDefault(fn string) *Config { | ||||
| 		config.CookieSecret = "test" | ||||
| 		config.ServerConfig.Host = "localhost" | ||||
| 		config.ServerConfig.Port = "3000" | ||||
| 		config.DBPath = "sqlite3://gralias.db" | ||||
| 	} | ||||
| 	fmt.Printf("config debug; config.LLMConfig.URL: %s\n", config.LLMConfig.URL) | ||||
| 	return config | ||||
|   | ||||
							
								
								
									
										76
									
								
								crons/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								crons/main.go
									
									
									
									
									
										Normal 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) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -168,7 +168,9 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) { | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if r := recover(); r != nil { | ||||
| 			tx.Rollback() | ||||
| 			if err := tx.Rollback(); err != nil { | ||||
| 				log.Error("failed to rollback transaction", "error", err) | ||||
| 			} | ||||
| 			panic(r) | ||||
| 		} | ||||
| 	}() | ||||
| @@ -189,7 +191,9 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) { | ||||
| 	fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) | ||||
| 	// Use the new context with transaction | ||||
| 	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()) | ||||
| 		return | ||||
| 	} | ||||
| @@ -197,7 +201,9 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) { | ||||
| 	action.RoomID = fi.Room.ID | ||||
| 	action.CreatedAt = time.Now() | ||||
| 	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) | ||||
| 		abortWithError(w, err.Error()) | ||||
| 		return | ||||
| @@ -206,7 +212,9 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) { | ||||
| 	for _, card := range fi.Room.Cards { | ||||
| 		card.RoomID = fi.Room.ID // Ensure RoomID is set for each card | ||||
| 		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) | ||||
| 			abortWithError(w, err.Error()) | ||||
| 			return | ||||
|   | ||||
| @@ -70,7 +70,9 @@ func GetSession(next http.Handler) http.Handler { | ||||
| 			return | ||||
| 		} | ||||
| 		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) | ||||
| 			msg := "session is expired" | ||||
| 			log.Debug(msg, "error", err, "token", sessionToken) | ||||
|   | ||||
							
								
								
									
										8
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								main.go
									
									
									
									
									
								
							| @@ -3,7 +3,9 @@ package main | ||||
| import ( | ||||
| 	"context" | ||||
| 	"gralias/config" | ||||
| 	"gralias/crons" | ||||
| 	"gralias/handlers" | ||||
| 	"gralias/repos" | ||||
| 	"log/slog" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| @@ -61,6 +63,12 @@ func main() { | ||||
| 	stop := make(chan os.Signal, 1) | ||||
| 	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) | ||||
| 	go func() { | ||||
| 		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { | ||||
|   | ||||
| @@ -104,3 +104,7 @@ func (p *RepoProvider) InitTx(ctx context.Context) (context.Context, *sqlx.Tx, e | ||||
| 	} | ||||
| 	return context.WithValue(ctx, "tx", tx), tx, nil | ||||
| } | ||||
|  | ||||
| func (p *RepoProvider) Close() { | ||||
| 	p.DB.Close() | ||||
| } | ||||
|   | ||||
| @@ -16,6 +16,7 @@ type PlayersRepo interface { | ||||
| 	PlayerExitRoom(ctx context.Context, username string) error | ||||
| 	PlayerListNames(ctx context.Context) ([]string, 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) { | ||||
| @@ -85,3 +86,12 @@ func (p *RepoProvider) PlayerList(ctx context.Context, isBot bool) ([]models.Pla | ||||
| 	} | ||||
| 	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 | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Grail Finder
					Grail Finder