Enha: sqlx instead of pgx
This commit is contained in:
		
							
								
								
									
										13
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								go.mod
									
									
									
									
									
								
							| @@ -4,15 +4,14 @@ go 1.24 | |||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/BurntSushi/toml v1.5.0 | 	github.com/BurntSushi/toml v1.5.0 | ||||||
| 	github.com/jackc/pgx/v5 v5.7.5 | 	github.com/jmoiron/sqlx v1.4.0 | ||||||
|  | 	github.com/mattn/go-sqlite3 v1.14.28 | ||||||
| 	github.com/rs/xid v1.6.0 | 	github.com/rs/xid v1.6.0 | ||||||
|  | 	github.com/stretchr/testify v1.10.0 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/jackc/pgpassfile v1.0.0 // indirect | 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||||
| 	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect | 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||||
| 	github.com/jackc/puddle/v2 v2.2.2 // indirect | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
| 	golang.org/x/crypto v0.37.0 // indirect |  | ||||||
| 	golang.org/x/sync v0.13.0 // indirect |  | ||||||
| 	golang.org/x/text v0.24.0 // indirect |  | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										35
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1,32 +1,25 @@ | |||||||
|  | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= | ||||||
|  | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= | ||||||
| github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= | github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= | ||||||
| github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= | github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= | ||||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |  | ||||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= | github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= | ||||||
| github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= | ||||||
| github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= | github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= | ||||||
| github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= | github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= | ||||||
| github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= | ||||||
| github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= | ||||||
| github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= | ||||||
| github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= | github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= | ||||||
|  | github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= | ||||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
| github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= | github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= | ||||||
| github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= | github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= | ||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||||||
| github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= |  | ||||||
| github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= |  | ||||||
| golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= |  | ||||||
| golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= |  | ||||||
| golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= |  | ||||||
| golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= |  | ||||||
| golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= |  | ||||||
| golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= |  | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |  | ||||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								gralias.db
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gralias.db
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -72,6 +72,10 @@ func getStateByCtx(ctx context.Context) (*models.UserState, error) { | |||||||
| 	return us, nil | 	return us, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // func dbCreate(fi *models.FullInfo) error{ | ||||||
|  | // 	repo.CreateRoom() | ||||||
|  | // } | ||||||
|  |  | ||||||
| func saveFullInfo(fi *models.FullInfo) error { | func saveFullInfo(fi *models.FullInfo) error { | ||||||
| 	// INFO: no transactions; so case is possible where first object is updated but the second is not | 	// INFO: no transactions; so case is possible where first object is updated but the second is not | ||||||
| 	if err := saveState(fi.State.Username, fi.State); err != nil { | 	if err := saveState(fi.State.Username, fi.State); err != nil { | ||||||
|   | |||||||
| @@ -43,6 +43,10 @@ func HandleCreateRoom(w http.ResponseWriter, r *http.Request) { | |||||||
| 	fi.State.RoomID = room.ID | 	fi.State.RoomID = room.ID | ||||||
| 	fi.Room = room | 	fi.Room = room | ||||||
| 	fi.Room.IsPublic = true // hardcode for local test; move to form | 	fi.Room.IsPublic = true // hardcode for local test; move to form | ||||||
|  | 	if err := repo.CreateRoom(r.Context(), room); err != nil { | ||||||
|  | 		abortWithError(w, err.Error()) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 	if err := saveFullInfo(fi); err != nil { | 	if err := saveFullInfo(fi); err != nil { | ||||||
| 		msg := "failed to set current room to session" | 		msg := "failed to set current room to session" | ||||||
| 		log.Error(msg, "error", err) | 		log.Error(msg, "error", err) | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import ( | |||||||
| 	"gralias/config" | 	"gralias/config" | ||||||
| 	"gralias/models" | 	"gralias/models" | ||||||
| 	"gralias/pkg/cache" | 	"gralias/pkg/cache" | ||||||
|  | 	"gralias/repos" | ||||||
| 	"html/template" | 	"html/template" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| @@ -17,6 +18,7 @@ var ( | |||||||
| 	cfg      *config.Config | 	cfg      *config.Config | ||||||
| 	memcache cache.Cache | 	memcache cache.Cache | ||||||
| 	Notifier *broker.Broker | 	Notifier *broker.Broker | ||||||
|  | 	repo     repos.AllRepos | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func init() { | func init() { | ||||||
| @@ -30,6 +32,7 @@ func init() { | |||||||
| 	cache.MemCache.StartBackupRoutine(15 * time.Second) // Reduced backup interval | 	cache.MemCache.StartBackupRoutine(15 * time.Second) // Reduced backup interval | ||||||
| 	// bot loader | 	// bot loader | ||||||
| 	// check the rooms if it has bot_{digits} in them, create bots if have | 	// check the rooms if it has bot_{digits} in them, create bots if have | ||||||
|  | 	repo = repos.NewRepoProvider("sqlite3://../gralias.db") | ||||||
| 	recoverBots() | 	recoverBots() | ||||||
| 	// if player has a roomID, but no team and role, try to recover | 	// if player has a roomID, but no team and role, try to recover | ||||||
| 	recoverPlayers() | 	recoverPlayers() | ||||||
| @@ -57,7 +60,11 @@ func HandleHome(w http.ResponseWriter, r *http.Request) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if fi != nil && fi.Room == nil { | 	if fi != nil && fi.Room == nil { | ||||||
| 		fi.List = listRooms(false) | 		rooms, err := repo.ListRooms(r.Context()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error("failed to list rooms;", "error", err) | ||||||
|  | 		} | ||||||
|  | 		fi.List = rooms | ||||||
| 	} | 	} | ||||||
| 	if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil { | 	if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil { | ||||||
| 		log.Error("failed to exec templ;", "error", err, "templ", "base") | 		log.Error("failed to exec templ;", "error", err, "templ", "base") | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								handlers/sqlite:gralias.db
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								handlers/sqlite:gralias.db
									
									
									
									
									
										Normal file
									
								
							| @@ -54,9 +54,9 @@ func StartTurnTimer(roomID string, duration time.Duration) { | |||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
| 				room.Settings.TurnSecondsLeft-- | 				room.Settings.TurnSecondsLeft-- | ||||||
| 				if err := saveRoom(room); err != nil { | 				// if err := saveRoom(room); err != nil { | ||||||
| 					log.Error("failed to save room", "error", err) | 				// 	log.Error("failed to save room", "error", err) | ||||||
| 				} | 				// } | ||||||
| 				notify(models.NotifyRoomUpdatePrefix+room.ID, "") | 				notify(models.NotifyRoomUpdatePrefix+room.ID, "") | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -58,7 +58,8 @@ type Action struct { | |||||||
| 	Word            string    `json:"word" db:"word"` | 	Word            string    `json:"word" db:"word"` | ||||||
| 	WordColor       string    `json:"word_color" db:"word_color"` | 	WordColor       string    `json:"word_color" db:"word_color"` | ||||||
| 	Number          string    `json:"number_associated" db:"number_associated"` | 	Number          string    `json:"number_associated" db:"number_associated"` | ||||||
| 	CreatedAt       time.Time `json:"created_at" db:"created_at"` | 		CreatedAt       time.Time `json:"created_at" db:"-"` | ||||||
|  | 	CreatedAtUnix   int64     `db:"created_at"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type Player struct { | type Player struct { | ||||||
|   | |||||||
| @@ -3,8 +3,7 @@ package repos | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"gralias/models" | 	"gralias/models" | ||||||
|  | 	"time" | ||||||
| 	"github.com/jackc/pgx/v5" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type ActionsRepo interface { | type ActionsRepo interface { | ||||||
| @@ -15,32 +14,33 @@ type ActionsRepo interface { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (p *RepoProvider) ListActions(ctx context.Context, roomID string) ([]models.Action, error) { | func (p *RepoProvider) ListActions(ctx context.Context, roomID string) ([]models.Action, error) { | ||||||
| 	rows, err := p.DB.Query(ctx, `SELECT actor, actor_color, action_type, word, word_color, number_associated FROM actions WHERE room_id = $1 ORDER BY created_at ASC`, roomID) | 	actions := []models.Action{} | ||||||
|  | 	err := p.DB.SelectContext(ctx, &actions, `SELECT actor, actor_color, action_type, word, word_color, number_associated, created_at FROM actions WHERE room_id = ? ORDER BY created_at ASC`, roomID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return pgx.CollectRows(rows, pgx.RowToStructByName[models.Action]) | 	for i := range actions { | ||||||
|  | 		actions[i].CreatedAt = time.Unix(0, actions[i].CreatedAtUnix) | ||||||
|  | 	} | ||||||
|  | 	return actions, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *RepoProvider) CreateAction(ctx context.Context, roomID string, a *models.Action) error { | func (p *RepoProvider) CreateAction(ctx context.Context, roomID string, a *models.Action) error { | ||||||
| 	_, err := p.DB.Exec(ctx, `INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated) VALUES ($1, $2, $3, $4, $5, $6, $7)`, | 	_, err := p.DB.ExecContext(ctx, `INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, a.Actor, a.ActorColor, a.Action, a.Word, a.WordColor, a.Number, a.CreatedAt.UnixNano()) | ||||||
| 		roomID, a.Actor, a.ActorColor, a.Action, a.Word, a.WordColor, a.Number) |  | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *RepoProvider) GetLastClue(ctx context.Context, roomID string) (*models.Action, error) { | func (p *RepoProvider) GetLastClue(ctx context.Context, roomID string) (*models.Action, error) { | ||||||
| 	rows, err := p.DB.Query(ctx, `SELECT actor, actor_color, action_type, word, word_color, number_associated FROM actions WHERE room_id = $1 AND action_type = 'gave_clue' ORDER BY created_at DESC LIMIT 1`, roomID) | 	action := &models.Action{} | ||||||
|  | 	err := p.DB.GetContext(ctx, action, `SELECT actor, actor_color, action_type, word, word_color, number_associated, created_at FROM actions WHERE room_id = ? AND action_type = 'gave_clue' ORDER BY created_at DESC LIMIT 1`, roomID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	action, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[models.Action]) | 	action.CreatedAt = time.Unix(0, action.CreatedAtUnix) | ||||||
| 	if err != nil { | 	return action, nil | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return &action, nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *RepoProvider) DeleteActionsByRoomID(ctx context.Context, roomID string) error { | func (p *RepoProvider) DeleteActionsByRoomID(ctx context.Context, roomID string) error { | ||||||
| 	_, err := p.DB.Exec(ctx, `DELETE FROM actions WHERE room_id = $1`, roomID) | 	_, err := p.DB.ExecContext(ctx, `DELETE FROM actions WHERE room_id = ?`, roomID) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
							
								
								
									
										177
									
								
								repos/actions_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								repos/actions_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | |||||||
|  | package repos | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"gralias/models" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/jmoiron/sqlx" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	_ "github.com/mattn/go-sqlite3" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func setupActionsTestDB(t *testing.T) (*sqlx.DB, func()) { | ||||||
|  | 	db, err := sqlx.Connect("sqlite3", ":memory:") | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	schema := ` | ||||||
|  | 	CREATE TABLE IF NOT EXISTS actions ( | ||||||
|  | 		room_id TEXT, | ||||||
|  | 		actor TEXT, | ||||||
|  | 		actor_color TEXT, | ||||||
|  | 		action_type TEXT, | ||||||
|  | 		word TEXT, | ||||||
|  | 		word_color TEXT, | ||||||
|  | 		number_associated TEXT, | ||||||
|  | 		created_at INTEGER | ||||||
|  | 	); | ||||||
|  | 	` | ||||||
|  | 	_, err = db.Exec(schema) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	return db, func() { | ||||||
|  | 		db.Close() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestActionsRepo_CreateAction(t *testing.T) { | ||||||
|  | 	db, teardown := setupActionsTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	roomID := "test_room_actions_1" | ||||||
|  | 	action := &models.Action{ | ||||||
|  | 		Actor:      "player1", | ||||||
|  | 		ActorColor: "blue", | ||||||
|  | 		Action:     "gave_clue", | ||||||
|  | 		Word:       "apple", | ||||||
|  | 		WordColor:  "red", | ||||||
|  | 		Number:     "3", | ||||||
|  | 		CreatedAt:  time.Now(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := repo.CreateAction(context.Background(), roomID, action) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	var retrievedAction models.Action | ||||||
|  | 	err = db.Get(&retrievedAction, "SELECT * FROM actions WHERE room_id = ? AND actor = ?", roomID, action.Actor) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, action.Word, retrievedAction.Word) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestActionsRepo_ListActions(t *testing.T) { | ||||||
|  | 	db, teardown := setupActionsTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	roomID := "test_room_actions_2" | ||||||
|  | 	action1 := &models.Action{ | ||||||
|  | 		Actor:      "player1", | ||||||
|  | 		ActorColor: "blue", | ||||||
|  | 		Action:     "gave_clue", | ||||||
|  | 		Word:       "apple", | ||||||
|  | 		WordColor:  "red", | ||||||
|  | 		Number:     "3", | ||||||
|  | 		CreatedAt:  time.Now().Add(-2 * time.Second), | ||||||
|  | 	} | ||||||
|  | 	action2 := &models.Action{ | ||||||
|  | 		Actor:      "player2", | ||||||
|  | 		ActorColor: "red", | ||||||
|  | 		Action:     "guessed", | ||||||
|  | 		Word:       "banana", | ||||||
|  | 		WordColor:  "blue", | ||||||
|  | 		Number:     "0", | ||||||
|  | 		CreatedAt:  time.Now().Add(-1 * time.Second), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action1.Actor, action1.ActorColor, action1.Action, action1.Word, action1.WordColor, action1.Number, action1.CreatedAt.UnixNano()) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	_, err = db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action2.Actor, action2.ActorColor, action2.Action, action2.Word, action2.WordColor, action2.Number, action2.CreatedAt.UnixNano()) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	actions, err := repo.ListActions(context.Background(), roomID) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Len(t, actions, 2) | ||||||
|  | 	assert.Equal(t, action1.Word, actions[0].Word) | ||||||
|  | 	assert.Equal(t, action2.Word, actions[1].Word) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestActionsRepo_GetLastClue(t *testing.T) { | ||||||
|  | 	db, teardown := setupActionsTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	roomID := "test_room_actions_3" | ||||||
|  | 	action1 := &models.Action{ | ||||||
|  | 		Actor:      "player1", | ||||||
|  | 		ActorColor: "blue", | ||||||
|  | 		Action:     "gave_clue", | ||||||
|  | 		Word:       "apple", | ||||||
|  | 		WordColor:  "red", | ||||||
|  | 		Number:     "3", | ||||||
|  | 		CreatedAt:  time.Now().Add(-3 * time.Second), | ||||||
|  | 	} | ||||||
|  | 	action2 := &models.Action{ | ||||||
|  | 		Actor:      "player2", | ||||||
|  | 		ActorColor: "red", | ||||||
|  | 		Action:     "gave_clue", | ||||||
|  | 		Word:       "banana", | ||||||
|  | 		WordColor:  "blue", | ||||||
|  | 		Number:     "2", | ||||||
|  | 		CreatedAt:  time.Now().Add(-2 * time.Second), | ||||||
|  | 	} | ||||||
|  | 	// Non-clue action | ||||||
|  | 	action3 := &models.Action{ | ||||||
|  | 		Actor:      "player3", | ||||||
|  | 		ActorColor: "blue", | ||||||
|  | 		Action:     "guessed", | ||||||
|  | 		Word:       "orange", | ||||||
|  | 		WordColor:  "blue", | ||||||
|  | 		Number:     "0", | ||||||
|  | 		CreatedAt:  time.Now().Add(-1 * time.Second), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action1.Actor, action1.ActorColor, action1.Action, action1.Word, action1.WordColor, action1.Number, action1.CreatedAt.UnixNano()) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	_, err = db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action2.Actor, action2.ActorColor, action2.Action, action2.Word, action2.WordColor, action2.Number, action2.CreatedAt.UnixNano()) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	_, err = db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action3.Actor, action3.ActorColor, action3.Action, action3.Word, action3.WordColor, action3.Number, action3.CreatedAt.UnixNano()) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	lastClue, err := repo.GetLastClue(context.Background(), roomID) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.NotNil(t, lastClue) | ||||||
|  | 	assert.Equal(t, action2.Word, lastClue.Word) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestActionsRepo_DeleteActionsByRoomID(t *testing.T) { | ||||||
|  | 	db, teardown := setupActionsTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	roomID := "test_room_actions_4" | ||||||
|  | 	action1 := &models.Action{ | ||||||
|  | 		Actor:      "player1", | ||||||
|  | 		ActorColor: "blue", | ||||||
|  | 		Action:     "gave_clue", | ||||||
|  | 		Word:       "apple", | ||||||
|  | 		WordColor:  "red", | ||||||
|  | 		Number:     "3", | ||||||
|  | 		CreatedAt:  time.Now(), | ||||||
|  | 	} | ||||||
|  | 	_, err := db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action1.Actor, action1.ActorColor, action1.Action, action1.Word, action1.WordColor, action1.Number, action1.CreatedAt.UnixNano()) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	err = repo.DeleteActionsByRoomID(context.Background(), roomID) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	var count int | ||||||
|  | 	err = db.Get(&count, "SELECT COUNT(*) FROM actions WHERE room_id = ?", roomID) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, 0, count) | ||||||
|  | } | ||||||
| @@ -1,11 +1,11 @@ | |||||||
| package repos | package repos | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" |  | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
| 	"github.com/jackc/pgx/v5/pgxpool" | 	"github.com/jmoiron/sqlx" | ||||||
|  | 	_ "github.com/mattn/go-sqlite3" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type AllRepos interface { | type AllRepos interface { | ||||||
| @@ -15,17 +15,17 @@ type AllRepos interface { | |||||||
| } | } | ||||||
|  |  | ||||||
| type RepoProvider struct { | type RepoProvider struct { | ||||||
| 	DB *pgxpool.Pool | 	DB *sqlx.DB | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewRepoProvider(pathToDB string) *RepoProvider { | func NewRepoProvider(pathToDB string) *RepoProvider { | ||||||
| 	dbpool, err := pgxpool.New(context.Background(), pathToDB) | 	db, err := sqlx.Connect("sqlite3", pathToDB) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		slog.Error("Unable to connect to database", "error", err) | 		slog.Error("Unable to connect to database", "error", err) | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| 	slog.Info("Successfully connected to database") | 	slog.Info("Successfully connected to database") | ||||||
| 	return &RepoProvider{ | 	return &RepoProvider{ | ||||||
| 		DB: dbpool, | 		DB: db, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								repos/main_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								repos/main_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | package repos | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestNewRepoProvider(t *testing.T) { | ||||||
|  | 	// Create a temporary SQLite database file for testing | ||||||
|  | 	tmpDBFile := "./test_gralias.db" | ||||||
|  | 	defer os.Remove(tmpDBFile) // Clean up the temporary file after the test | ||||||
|  |  | ||||||
|  | 	// Initialize a new RepoProvider | ||||||
|  | 	repoProvider := NewRepoProvider(tmpDBFile) | ||||||
|  |  | ||||||
|  | 	// Assert that the DB connection is not nil | ||||||
|  | 	assert.NotNil(t, repoProvider.DB, "DB connection should not be nil") | ||||||
|  |  | ||||||
|  | 	// Close the database connection | ||||||
|  | 	err := repoProvider.DB.Close() | ||||||
|  | 	assert.NoError(t, err, "Error closing database connection") | ||||||
|  | } | ||||||
| @@ -13,7 +13,7 @@ type PlayersRepo interface { | |||||||
|  |  | ||||||
| func (p *RepoProvider) GetPlayer(roomID, username string) (*models.Player, error) { | func (p *RepoProvider) GetPlayer(roomID, username string) (*models.Player, error) { | ||||||
| 	var player models.Player | 	var player models.Player | ||||||
| 	err := p.DB.QueryRow(context.Background(), "SELECT id, room_id, username, team, role, is_bot FROM players WHERE room_id = $1 AND username = $2", roomID, username).Scan(&player.ID, &player.RoomID, &player.Username, &player.Team, &player.Role, &player.IsBot) | 	err := p.DB.GetContext(context.Background(), &player, "SELECT id, room_id, username, team, role, is_bot FROM players WHERE room_id = ? AND username = ?", roomID, username) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -21,11 +21,11 @@ func (p *RepoProvider) GetPlayer(roomID, username string) (*models.Player, error | |||||||
| } | } | ||||||
|  |  | ||||||
| func (p *RepoProvider) AddPlayer(player *models.Player) error { | func (p *RepoProvider) AddPlayer(player *models.Player) error { | ||||||
| 	_, err := p.DB.Exec(context.Background(), "INSERT INTO players (room_id, username, team, role, is_bot) VALUES ($1, $2, $3, $4, $5)", player.RoomID, player.Username, player.Team, player.Role, player.IsBot) | 	_, err := p.DB.ExecContext(context.Background(), "INSERT INTO players (room_id, username, team, role, is_bot) VALUES (?, ?, ?, ?, ?)", player.RoomID, player.Username, player.Team, player.Role, player.IsBot) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *RepoProvider) DeletePlayer(roomID, username string) error { | func (p *RepoProvider) DeletePlayer(roomID, username string) error { | ||||||
| 	_, err := p.DB.Exec(context.Background(), "DELETE FROM players WHERE room_id = $1 AND username = $2", roomID, username) | 	_, err := p.DB.ExecContext(context.Background(), "DELETE FROM players WHERE room_id = ? AND username = ?", roomID, username) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										104
									
								
								repos/players_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								repos/players_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | |||||||
|  | package repos | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"gralias/models" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/jmoiron/sqlx" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	_ "github.com/mattn/go-sqlite3" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func setupPlayersTestDB(t *testing.T) (*sqlx.DB, func()) { | ||||||
|  | 	db, err := sqlx.Connect("sqlite3", ":memory:") | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	schema := ` | ||||||
|  | 	CREATE TABLE IF NOT EXISTS players ( | ||||||
|  | 		id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||||
|  | 		room_id TEXT, | ||||||
|  | 		username TEXT, | ||||||
|  | 		team TEXT, | ||||||
|  | 		role TEXT, | ||||||
|  | 		is_bot BOOLEAN | ||||||
|  | 	); | ||||||
|  | 	` | ||||||
|  | 	_, err = db.Exec(schema) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	return db, func() { | ||||||
|  | 		db.Close() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPlayersRepo_AddPlayer(t *testing.T) { | ||||||
|  | 	db, teardown := setupPlayersTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	player := &models.Player{ | ||||||
|  | 		RoomID:   "test_room_player_1", | ||||||
|  | 		Username: "test_player_1", | ||||||
|  | 		Team:     "blue", | ||||||
|  | 		Role:     "player", | ||||||
|  | 		IsBot:    false, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := repo.AddPlayer(player) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	var retrievedPlayer models.Player | ||||||
|  | 	err = db.Get(&retrievedPlayer, "SELECT * FROM players WHERE room_id = ? AND username = ?", player.RoomID, player.Username) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, player.Username, retrievedPlayer.Username) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPlayersRepo_GetPlayer(t *testing.T) { | ||||||
|  | 	db, teardown := setupPlayersTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	player := &models.Player{ | ||||||
|  | 		RoomID:   "test_room_player_2", | ||||||
|  | 		Username: "test_player_2", | ||||||
|  | 		Team:     "red", | ||||||
|  | 		Role:     "player", | ||||||
|  | 		IsBot:    false, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := db.Exec(`INSERT INTO players (room_id, username, team, role, is_bot) VALUES (?, ?, ?, ?, ?)`, player.RoomID, player.Username, player.Team, player.Role, player.IsBot) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	retrievedPlayer, err := repo.GetPlayer(player.RoomID, player.Username) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.NotNil(t, retrievedPlayer) | ||||||
|  | 	assert.Equal(t, player.Username, retrievedPlayer.Username) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPlayersRepo_DeletePlayer(t *testing.T) { | ||||||
|  | 	db, teardown := setupPlayersTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	player := &models.Player{ | ||||||
|  | 		RoomID:   "test_room_player_3", | ||||||
|  | 		Username: "test_player_3", | ||||||
|  | 		Team:     "blue", | ||||||
|  | 		Role:     "player", | ||||||
|  | 		IsBot:    false, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := db.Exec(`INSERT INTO players (room_id, username, team, role, is_bot) VALUES (?, ?, ?, ?, ?)`, player.RoomID, player.Username, player.Team, player.Role, player.IsBot) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	err = repo.DeletePlayer(player.RoomID, player.Username) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	var count int | ||||||
|  | 	err = db.Get(&count, "SELECT COUNT(*) FROM players WHERE room_id = ? AND username = ?", player.RoomID, player.Username) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, 0, count) | ||||||
|  | } | ||||||
| @@ -3,24 +3,19 @@ package repos | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"gralias/models" | 	"gralias/models" | ||||||
|  |  | ||||||
| 	"github.com/jackc/pgx/v5" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type RoomsRepo interface { | type RoomsRepo interface { | ||||||
| 	ListRooms(ctx context.Context) ([]models.Room, error) | 	ListRooms(ctx context.Context) ([]*models.Room, error) | ||||||
| 	GetRoomByID(ctx context.Context, id string) (*models.Room, error) | 	GetRoomByID(ctx context.Context, id string) (*models.Room, error) | ||||||
| 	CreateRoom(ctx context.Context, room *models.Room) error | 	CreateRoom(ctx context.Context, room *models.Room) error | ||||||
| 	DeleteRoomByID(ctx context.Context, id string) error | 	DeleteRoomByID(ctx context.Context, id string) error | ||||||
| 	UpdateRoom(ctx context.Context, room *models.Room) error | 	UpdateRoom(ctx context.Context, room *models.Room) error | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *RepoProvider) ListRooms(ctx context.Context) ([]models.Room, error) { | func (p *RepoProvider) ListRooms(ctx context.Context) ([]*models.Room, error) { | ||||||
| 	rows, err := p.DB.Query(ctx, `SELECT * FROM rooms`) | 	rooms := []*models.Room{} | ||||||
| 	if err != nil { | 	err := p.DB.SelectContext(ctx, &rooms, `SELECT * FROM rooms`) | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	rooms, err := pgx.CollectRows(rows, pgx.RowToStructByName[models.Room]) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -28,28 +23,25 @@ func (p *RepoProvider) ListRooms(ctx context.Context) ([]models.Room, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (p *RepoProvider) GetRoomByID(ctx context.Context, id string) (*models.Room, error) { | func (p *RepoProvider) GetRoomByID(ctx context.Context, id string) (*models.Room, error) { | ||||||
| 	rows, err := p.DB.Query(ctx, `SELECT * FROM rooms WHERE id = $1`, id) | 	room := &models.Room{} | ||||||
|  | 	err := p.DB.GetContext(ctx, room, `SELECT * FROM rooms WHERE id = ?`, id) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	room, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[models.Room]) | 	return room, nil | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return &room, nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *RepoProvider) CreateRoom(ctx context.Context, r *models.Room) error { | func (p *RepoProvider) CreateRoom(ctx context.Context, r *models.Room) error { | ||||||
| 	_, err := p.DB.Exec(ctx, `INSERT INTO rooms (id, created_at, creator_name, team_turn, this_turn_limit, opened_this_turn, blue_counter, red_counter, red_turn, mime_done, is_public, is_running, language, round_time, is_over, team_won, room_pass) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)`, r.ID, r.CreatedAt, r.CreatorName, r.TeamTurn, r.ThisTurnLimit, r.OpenedThisTurn, r.BlueCounter, r.RedCounter, r.RedTurn, r.MimeDone, r.IsPublic, r.IsRunning, r.Language, r.RoundTime, r.IsOver, r.TeamWon, r.Settings.RoomPass) | 	_, err := p.DB.ExecContext(ctx, `INSERT INTO rooms (id, created_at, creator_name, team_turn, this_turn_limit, opened_this_turn, blue_counter, red_counter, red_turn, mime_done, is_public, is_running, language, round_time, is_over, team_won, room_pass) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, r.ID, r.CreatedAt, r.CreatorName, r.TeamTurn, r.ThisTurnLimit, r.OpenedThisTurn, r.BlueCounter, r.RedCounter, r.RedTurn, r.MimeDone, r.IsPublic, r.IsRunning, r.Language, r.RoundTime, r.IsOver, r.TeamWon, r.RoomPass) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *RepoProvider) DeleteRoomByID(ctx context.Context, id string) error { | func (p *RepoProvider) DeleteRoomByID(ctx context.Context, id string) error { | ||||||
| 	_, err := p.DB.Exec(ctx, `DELETE FROM rooms WHERE id = $1`, id) | 	_, err := p.DB.ExecContext(ctx, `DELETE FROM rooms WHERE id = ?`, id) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *RepoProvider) UpdateRoom(ctx context.Context, r *models.Room) error { | func (p *RepoProvider) UpdateRoom(ctx context.Context, r *models.Room) error { | ||||||
| 	_, err := p.DB.Exec(ctx, `UPDATE rooms SET team_turn = $1, this_turn_limit = $2, opened_this_turn = $3, blue_counter = $4, red_counter = $5, red_turn = $6, mime_done = $7, is_public = $8, is_running = $9, language = $10, round_time = $11, is_over = $12, team_won = $13, room_pass = $14 WHERE id = $15`, r.TeamTurn, r.ThisTurnLimit, r.OpenedThisTurn, r.BlueCounter, r.RedCounter, r.RedTurn, r.MimeDone, r.IsPublic, r.IsRunning, r.Language, r.RoundTime, r.IsOver, r.TeamWon, r.Settings.RoomPass, r.ID) | 	_, err := p.DB.ExecContext(ctx, `UPDATE rooms SET team_turn = ?, this_turn_limit = ?, opened_this_turn = ?, blue_counter = ?, red_counter = ?, red_turn = ?, mime_done = ?, is_public = ?, is_running = ?, language = ?, round_time = ?, is_over = ?, team_won = ?, room_pass = ? WHERE id = ?`, r.TeamTurn, r.ThisTurnLimit, r.OpenedThisTurn, r.BlueCounter, r.RedCounter, r.RedTurn, r.MimeDone, r.IsPublic, r.IsRunning, r.Language, r.RoundTime, r.IsOver, r.TeamWon, r.RoomPass, r.ID) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										254
									
								
								repos/rooms_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								repos/rooms_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,254 @@ | |||||||
|  | package repos | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"gralias/models" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/jmoiron/sqlx" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	_ "github.com/mattn/go-sqlite3" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func setupTestDB(t *testing.T) (*sqlx.DB, func()) { | ||||||
|  | 	db, err := sqlx.Connect("sqlite3", ":memory:") | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	schema := ` | ||||||
|  | 	CREATE TABLE IF NOT EXISTS rooms ( | ||||||
|  | 		id TEXT PRIMARY KEY, | ||||||
|  | 		created_at DATETIME, | ||||||
|  | 		creator_name TEXT, | ||||||
|  | 		team_turn TEXT, | ||||||
|  | 		this_turn_limit INTEGER, | ||||||
|  | 		opened_this_turn INTEGER, | ||||||
|  | 		blue_counter INTEGER, | ||||||
|  | 		red_counter INTEGER, | ||||||
|  | 		red_turn BOOLEAN, | ||||||
|  | 		mime_done BOOLEAN, | ||||||
|  | 		is_public BOOLEAN, | ||||||
|  | 		is_running BOOLEAN, | ||||||
|  | 		language TEXT, | ||||||
|  | 		round_time INTEGER, | ||||||
|  | 		is_over BOOLEAN, | ||||||
|  | 		team_won TEXT, | ||||||
|  | 		room_pass TEXT | ||||||
|  | 	); | ||||||
|  | 	` | ||||||
|  | 	_, err = db.Exec(schema) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	return db, func() { | ||||||
|  | 		db.Close() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRoomsRepo_CreateRoom(t *testing.T) { | ||||||
|  | 	db, teardown := setupTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	room := &models.Room{ | ||||||
|  | 		ID:             "test_room_1", | ||||||
|  | 		CreatedAt:      time.Now(), | ||||||
|  | 		CreatorName:    "test_creator", | ||||||
|  | 		TeamTurn:       "blue", | ||||||
|  | 		ThisTurnLimit:  5, | ||||||
|  | 		OpenedThisTurn: 0, | ||||||
|  | 		BlueCounter:    0, | ||||||
|  | 		RedCounter:     0, | ||||||
|  | 		RedTurn:        false, | ||||||
|  | 		MimeDone:       false, | ||||||
|  | 		IsPublic:       true, | ||||||
|  | 		IsRunning:      false, | ||||||
|  | 		Language:       "en", | ||||||
|  | 		RoundTime:      60, | ||||||
|  | 		IsOver:         false, | ||||||
|  | 		TeamWon:        "", | ||||||
|  | 		RoomPass:       "", | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := repo.CreateRoom(context.Background(), room) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	// Verify the room was created | ||||||
|  | 	var retrievedRoom models.Room | ||||||
|  | 	err = db.Get(&retrievedRoom, "SELECT * FROM rooms WHERE id = ?", room.ID) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, room.ID, retrievedRoom.ID) | ||||||
|  | 	assert.Equal(t, room.CreatorName, retrievedRoom.CreatorName) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRoomsRepo_GetRoomByID(t *testing.T) { | ||||||
|  | 	db, teardown := setupTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	room := &models.Room{ | ||||||
|  | 		ID:             "test_room_2", | ||||||
|  | 		CreatedAt:      time.Now(), | ||||||
|  | 		CreatorName:    "test_creator_2", | ||||||
|  | 		TeamTurn:       "red", | ||||||
|  | 		ThisTurnLimit:  5, | ||||||
|  | 		OpenedThisTurn: 0, | ||||||
|  | 		BlueCounter:    0, | ||||||
|  | 		RedCounter:     0, | ||||||
|  | 		RedTurn:        true, | ||||||
|  | 		MimeDone:       false, | ||||||
|  | 		IsPublic:       true, | ||||||
|  | 		IsRunning:      false, | ||||||
|  | 		Language:       "en", | ||||||
|  | 		RoundTime:      60, | ||||||
|  | 		IsOver:         false, | ||||||
|  | 		TeamWon:        "", | ||||||
|  | 		RoomPass:       "", | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Insert a room directly into the database for testing GetRoomByID | ||||||
|  | 	_, err := db.Exec(`INSERT INTO rooms (id, created_at, creator_name, team_turn, this_turn_limit, opened_this_turn, blue_counter, red_counter, red_turn, mime_done, is_public, is_running, language, round_time, is_over, team_won, room_pass) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, room.ID, room.CreatedAt, room.CreatorName, room.TeamTurn, room.ThisTurnLimit, room.OpenedThisTurn, room.BlueCounter, room.RedCounter, room.RedTurn, room.MimeDone, room.IsPublic, room.IsRunning, room.Language, room.RoundTime, room.IsOver, room.TeamWon, room.RoomPass) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	retrievedRoom, err := repo.GetRoomByID(context.Background(), room.ID) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.NotNil(t, retrievedRoom) | ||||||
|  | 	assert.Equal(t, room.ID, retrievedRoom.ID) | ||||||
|  | 	assert.Equal(t, room.CreatorName, retrievedRoom.CreatorName) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRoomsRepo_ListRooms(t *testing.T) { | ||||||
|  | 	db, teardown := setupTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	room1 := &models.Room{ | ||||||
|  | 		ID:             "list_room_1", | ||||||
|  | 		CreatedAt:      time.Now(), | ||||||
|  | 		CreatorName:    "list_creator_1", | ||||||
|  | 		TeamTurn:       "blue", | ||||||
|  | 		ThisTurnLimit:  5, | ||||||
|  | 		OpenedThisTurn: 0, | ||||||
|  | 		BlueCounter:    0, | ||||||
|  | 		RedCounter:     0, | ||||||
|  | 		RedTurn:        false, | ||||||
|  | 		MimeDone:       false, | ||||||
|  | 		IsPublic:       true, | ||||||
|  | 		IsRunning:      false, | ||||||
|  | 		Language:       "en", | ||||||
|  | 		RoundTime:      60, | ||||||
|  | 		IsOver:         false, | ||||||
|  | 		TeamWon:        "", | ||||||
|  | 		RoomPass:       "", | ||||||
|  | 	} | ||||||
|  | 	room2 := &models.Room{ | ||||||
|  | 		ID:             "list_room_2", | ||||||
|  | 		CreatedAt:      time.Now(), | ||||||
|  | 		CreatorName:    "list_creator_2", | ||||||
|  | 		TeamTurn:       "red", | ||||||
|  | 		ThisTurnLimit:  5, | ||||||
|  | 		OpenedThisTurn: 0, | ||||||
|  | 		BlueCounter:    0, | ||||||
|  | 		RedCounter:     0, | ||||||
|  | 		RedTurn:        true, | ||||||
|  | 		MimeDone:       false, | ||||||
|  | 		IsPublic:       true, | ||||||
|  | 		IsRunning:      false, | ||||||
|  | 		Language:       "en", | ||||||
|  | 		RoundTime:      60, | ||||||
|  | 		IsOver:         false, | ||||||
|  | 		TeamWon:        "", | ||||||
|  | 		RoomPass:       "", | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := db.Exec(`INSERT INTO rooms (id, created_at, creator_name, team_turn, this_turn_limit, opened_this_turn, blue_counter, red_counter, red_turn, mime_done, is_public, is_running, language, round_time, is_over, team_won, room_pass) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, room1.ID, room1.CreatedAt, room1.CreatorName, room1.TeamTurn, room1.ThisTurnLimit, room1.OpenedThisTurn, room1.BlueCounter, room1.RedCounter, room1.RedTurn, room1.MimeDone, room1.IsPublic, room1.IsRunning, room1.Language, room1.RoundTime, room1.IsOver, room1.TeamWon, room1.RoomPass) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	_, err = db.Exec(`INSERT INTO rooms (id, created_at, creator_name, team_turn, this_turn_limit, opened_this_turn, blue_counter, red_counter, red_turn, mime_done, is_public, is_running, language, round_time, is_over, team_won, room_pass) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, room2.ID, room2.CreatedAt, room2.CreatorName, room2.TeamTurn, room2.ThisTurnLimit, room2.OpenedThisTurn, room2.BlueCounter, room2.RedCounter, room2.RedTurn, room2.MimeDone, room2.IsPublic, room2.IsRunning, room2.Language, room2.RoundTime, room2.IsOver, room2.TeamWon, room2.RoomPass) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	rooms, err := repo.ListRooms(context.Background()) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Len(t, rooms, 2) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRoomsRepo_DeleteRoomByID(t *testing.T) { | ||||||
|  | 	db, teardown := setupTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	room := &models.Room{ | ||||||
|  | 		ID:             "delete_room_1", | ||||||
|  | 		CreatedAt:      time.Now(), | ||||||
|  | 		CreatorName:    "delete_creator_1", | ||||||
|  | 		TeamTurn:       "blue", | ||||||
|  | 		ThisTurnLimit:  5, | ||||||
|  | 		OpenedThisTurn: 0, | ||||||
|  | 		BlueCounter:    0, | ||||||
|  | 		RedCounter:     0, | ||||||
|  | 		RedTurn:        false, | ||||||
|  | 		MimeDone:       false, | ||||||
|  | 		IsPublic:       true, | ||||||
|  | 		IsRunning:      false, | ||||||
|  | 		Language:       "en", | ||||||
|  | 		RoundTime:      60, | ||||||
|  | 		IsOver:         false, | ||||||
|  | 		TeamWon:        "", | ||||||
|  | 		RoomPass:       "", | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := db.Exec(`INSERT INTO rooms (id, created_at, creator_name, team_turn, this_turn_limit, opened_this_turn, blue_counter, red_counter, red_turn, mime_done, is_public, is_running, language, round_time, is_over, team_won, room_pass) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, room.ID, room.CreatedAt, room.CreatorName, room.TeamTurn, room.ThisTurnLimit, room.OpenedThisTurn, room.BlueCounter, room.RedCounter, room.RedTurn, room.MimeDone, room.IsPublic, room.IsRunning, room.Language, room.RoundTime, room.IsOver, room.TeamWon, room.RoomPass) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	err = repo.DeleteRoomByID(context.Background(), room.ID) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	var count int | ||||||
|  | 	err = db.Get(&count, "SELECT COUNT(*) FROM rooms WHERE id = ?", room.ID) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, 0, count) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRoomsRepo_UpdateRoom(t *testing.T) { | ||||||
|  | 	db, teardown := setupTestDB(t) | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	repo := &RepoProvider{DB: db} | ||||||
|  |  | ||||||
|  | 	room := &models.Room{ | ||||||
|  | 		ID:             "update_room_1", | ||||||
|  | 		CreatedAt:      time.Now(), | ||||||
|  | 		CreatorName:    "update_creator_1", | ||||||
|  | 		TeamTurn:       "blue", | ||||||
|  | 		ThisTurnLimit:  5, | ||||||
|  | 		OpenedThisTurn: 0, | ||||||
|  | 		BlueCounter:    0, | ||||||
|  | 		RedCounter:     0, | ||||||
|  | 		RedTurn:        false, | ||||||
|  | 		MimeDone:       false, | ||||||
|  | 		IsPublic:       true, | ||||||
|  | 		IsRunning:      false, | ||||||
|  | 		Language:       "en", | ||||||
|  | 		RoundTime:      60, | ||||||
|  | 		IsOver:         false, | ||||||
|  | 		TeamWon:        "", | ||||||
|  | 		RoomPass:       "", | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := db.Exec(`INSERT INTO rooms (id, created_at, creator_name, team_turn, this_turn_limit, opened_this_turn, blue_counter, red_counter, red_turn, mime_done, is_public, is_running, language, round_time, is_over, team_won, room_pass) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, room.ID, room.CreatedAt, room.CreatorName, room.TeamTurn, room.ThisTurnLimit, room.OpenedThisTurn, room.BlueCounter, room.RedCounter, room.RedTurn, room.MimeDone, room.IsPublic, room.IsRunning, room.Language, room.RoundTime, room.IsOver, room.TeamWon, room.RoomPass) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	room.TeamTurn = "red" | ||||||
|  | 	room.BlueCounter = 10 | ||||||
|  |  | ||||||
|  | 	err = repo.UpdateRoom(context.Background(), room) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	var updatedRoom models.Room | ||||||
|  | 	err = db.Get(&updatedRoom, "SELECT * FROM rooms WHERE id = ?", room.ID) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 			assert.Equal(t, models.UserTeam("red"), updatedRoom.TeamTurn) | ||||||
|  | 	assert.Equal(t, uint8(10), updatedRoom.BlueCounter) | ||||||
|  | } | ||||||
							
								
								
									
										0
									
								
								sqlite:gralias.db
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								sqlite:gralias.db
									
									
									
									
									
										Normal file
									
								
							
		Reference in New Issue
	
	Block a user
	 Grail Finder
					Grail Finder