Feat: add cleaner cron
This commit is contained in:
		| @@ -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" | ||||||
|   | |||||||
| @@ -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
									
								
							
							
						
						
									
										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() { | 	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 | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								main.go
									
									
									
									
									
								
							| @@ -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 { | ||||||
|   | |||||||
| @@ -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() | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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 | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Grail Finder
					Grail Finder