diff --git a/internal/apps/flags/handlers.go b/internal/apps/flags/handlers.go index 0798582..fe5ede5 100644 --- a/internal/apps/flags/handlers.go +++ b/internal/apps/flags/handlers.go @@ -5,13 +5,13 @@ import ( "database/sql" "net/http" "strconv" + "time" "git.tijl.dev/tijl/tijl.dev-core/internal/queries" "git.tijl.dev/tijl/tijl.dev-core/internal/user" "git.tijl.dev/tijl/tijl.dev-core/internal/utils" "git.tijl.dev/tijl/tijl.dev-core/modules/db" "git.tijl.dev/tijl/tijl.dev-core/modules/i18n" - log "git.tijl.dev/tijl/tijl.dev-core/modules/logger" "git.tijl.dev/tijl/tijl.dev-core/modules/web" "github.com/enescakir/emoji" "github.com/gofiber/fiber/v2" @@ -29,7 +29,7 @@ func answerHandler(c *fiber.Ctx) error { answer := c.FormValue("answer") - gameSession, err := db.Queries.GetFlagsGame(context.TODO(), gameId) + gameSession, err := db.Queries.AppFlagsGetGame(context.TODO(), gameId) if err != nil { return err } @@ -39,29 +39,29 @@ func answerHandler(c *fiber.Ctx) error { return err } - shuffledCountries := shuffleSlice(countries, gameSession.GameSeed.UUID.String()) + shuffledCountries := shuffleSlice(countries, gameSession.GameSeed.String()) correctAnswer := shuffledCountries[gameSession.QuestionCurrent-1] if answer == correctAnswer { - db.Queries.UpdateFlagsGame(context.TODO(), queries.UpdateFlagsGameParams{ + db.Queries.AppFlagsUpdateGame(context.TODO(), queries.AppFlagsUpdateGameParams{ GameID: gameId, QuestionCurrent: gameSession.QuestionCurrent + 1, }) - db.Queries.UpsertGameAnswer(context.TODO(), queries.UpsertGameAnswerParams{ + db.Queries.AppFlagsUpsertGameAnswer(context.TODO(), queries.AppFlagsUpsertGameAnswerParams{ GameID: gameId, Question: gameSession.QuestionCurrent, Errors: 0, }) } else { - db.Queries.UpsertGameAnswer(context.TODO(), queries.UpsertGameAnswerParams{ + db.Queries.AppFlagsUpsertGameAnswer(context.TODO(), queries.AppFlagsUpsertGameAnswerParams{ GameID: gameId, Question: gameSession.QuestionCurrent, Errors: 1, }) } - err = db.Queries.UpdateQuestionCorrect(context.TODO(), gameId) + err = db.Queries.AppFlagsUpdateQuestionCorrect(context.TODO(), gameId) if err != nil { return err } @@ -87,7 +87,7 @@ func questionHandler(c *fiber.Ctx, newGame NewGameUUID) error { } } - gameSession, err := db.Queries.GetFlagsGame(context.TODO(), gameId) + gameSession, err := db.Queries.AppFlagsGetGame(context.TODO(), gameId) if err != nil { return err } @@ -111,17 +111,20 @@ func questionHandler(c *fiber.Ctx, newGame NewGameUUID) error { return gameEndHandler(c) } - shuffledCountries := shuffleSlice(countries, gameSession.GameSeed.UUID.String()) - shuffledAnswers := shuffleSlice(countries, gameSession.GameSeed.UUID.String()+string(gameSession.QuestionCurrent)) - shuffledAnswers = shuffledAnswers[0:4] // 4 random aswers - shuffledAnswers = append(shuffledAnswers, shuffledCountries[gameSession.QuestionCurrent-1]) // add correct answer - shuffledAnswers = shuffleSlice(shuffledAnswers, gameSession.GameSeed.UUID.String()+string(gameSession.QuestionCurrent)) // shuffle again + shuffledCountries := shuffleSlice(countries, gameSession.GameSeed.String()) + shuffledAnswers := shuffleSlice(countries, gameSession.GameSeed.String()+string(gameSession.QuestionCurrent)) + shuffledAnswers = shuffledAnswers[0:4] // 4 random aswers + shuffledAnswers = append(shuffledAnswers, shuffledCountries[gameSession.QuestionCurrent-1]) // add correct answer + shuffledAnswers = shuffleSlice(shuffledAnswers, gameSession.GameSeed.String()+string(gameSession.QuestionCurrent)) // shuffle again if gameSession.QuestionAmount != 0 && int(gameSession.QuestionAmount) < len(countries) { shuffledCountries = shuffledCountries[0:gameSession.QuestionAmount] } - log.Debug().Interface("shuffledCountries", shuffledCountries).Interface("shuffledAnswers", shuffledAnswers).Msg("data") + var timeleft = []string{} + if gameSession.Seconds != 0 { + timeleft = append(timeleft, strconv.Itoa(int(gameSession.Seconds)-int(time.Since(gameSession.CreatedAt).Seconds()))) + } flag, err := emoji.CountryFlag(shuffledCountries[gameSession.QuestionCurrent-1]) if err != nil { @@ -130,13 +133,23 @@ func questionHandler(c *fiber.Ctx, newGame NewGameUUID) error { data := *web.Common(c) data["Title"] = "tmp" + data["TimeLeft"] = timeleft + data["QuestionsLeft"] = len(shuffledCountries) - int(gameSession.QuestionCurrent) + 1 data["Answers"] = shuffledAnswers data["Flag"] = flag data["Errors"] = gameSession.QuestionsErrors return c.Render("apps/flags/question", data, "layouts/base") } -func getGameData() { +func sharedGameHandler(c *fiber.Ctx) error { + + shareKey := c.FormValue("sharekey") + data, err := db.Queries.AppFlagsGetSharedData(context.TODO(), shareKey) + if err != nil { + return err + } + + return setupGame(c, data.Tags, int(data.Questions), int(data.Seconds), true, data.GameSeed) } func startNewGameHandler(c *fiber.Ctx) error { @@ -164,6 +177,37 @@ func startNewGameHandler(c *fiber.Ctx) error { return err } + seconds, err := strconv.Atoi(c.FormValue("seconds")) + if err != nil { + return err + } + + if c.FormValue("share") != "" { + return createSharedGameData(c, selectedTags, maxQuestions, seconds) + } + + return setupGame(c, selectedTags, maxQuestions, seconds, false, uuid.UUID{}) +} + +func createSharedGameData(c *fiber.Ctx, tags []string, maxQuestions int, seconds int) error { + shareKey := utils.RandString(4) + _, err := db.Queries.AppFlagsNewSharedData(context.TODO(), queries.AppFlagsNewSharedDataParams{ + ShareKey: shareKey, + Tags: tags, + Questions: int32(maxQuestions), + Seconds: int32(seconds), + }) + if err != nil { + return err + } + + data := *web.Common(c) + data["Title"] = "tmp" + data["ShareKey"] = shareKey + return c.Render("apps/flags/shared", data, "layouts/base") +} + +func setupGame(c *fiber.Ctx, tags []string, maxQuestions int, seconds int, useGameSeed bool, gameSeed uuid.UUID) error { var Quid = sql.NullString{} uid, err := user.GetSession(c) if err == nil { @@ -171,22 +215,30 @@ func startNewGameHandler(c *fiber.Ctx) error { Quid.String = uid } - row, err := db.Queries.CreateFlagsGame(context.TODO(), queries.CreateFlagsGameParams{ + createGameParams := queries.AppFlagsCreateGameWithSeedParams{ Uid: Quid, - Tags: selectedTags, + Tags: tags, QuestionAmount: int32(maxQuestions), - }) + Seconds: int32(seconds), + GameSeed: uuid.New(), + } + + if useGameSeed { + createGameParams.GameSeed = gameSeed + } + + gameID, err := db.Queries.AppFlagsCreateGameWithSeed(context.TODO(), createGameParams) if err != nil { return err } c.Cookie(&fiber.Cookie{ Name: flagSessionCookie, - Value: row.GameID.String(), + Value: gameID.String(), //Secure: true, }) - return questionHandler(c, NewGameUUID{used: true, UUID: row.GameID}) + return questionHandler(c, NewGameUUID{used: true, UUID: gameID}) } func gameEndHandler(c *fiber.Ctx) error { @@ -194,7 +246,7 @@ func gameEndHandler(c *fiber.Ctx) error { if err != nil { return err } - gameSession, err := db.Queries.GetFlagsGame(context.TODO(), gameId) + gameSession, err := db.Queries.AppFlagsGetGame(context.TODO(), gameId) if err != nil { return err } diff --git a/internal/apps/flags/main.go b/internal/apps/flags/main.go index b4d6c4c..f87e4de 100644 --- a/internal/apps/flags/main.go +++ b/internal/apps/flags/main.go @@ -51,6 +51,8 @@ func Setup() { return answerHandler(c) case "exit": return stopGameHandler(c) + case "shared": + return sharedGameHandler(c) } return nil diff --git a/internal/queries/app_flags.sql b/internal/queries/app_flags.sql index 3ca3005..6e7465c 100644 --- a/internal/queries/app_flags.sql +++ b/internal/queries/app_flags.sql @@ -1,28 +1,39 @@ --- name: CreateFlagsGame :one -INSERT INTO app_flags_games (uid, tags, question_amount) -VALUES ($1, $2, $3) +-- name: AppFlagsCreateGame :one +INSERT INTO app_flags_games (uid, tags, question_amount, seconds) +VALUES ($1, $2, $3, $4) RETURNING game_id, game_seed; --- name: GetFlagsGame :one +-- name: AppFlagsCreateGameWithSeed :one +INSERT INTO app_flags_games (uid, tags, question_amount, seconds, game_seed) +VALUES ($1, $2, $3, $4, $5) +RETURNING game_id; + +-- name: AppFlagsGetGame :one SELECT * FROM app_flags_games WHERE game_id = $1 LIMIT 1; --- name: UpdateFlagsGame :exec +-- name: AppFlagsUpdateGame :exec UPDATE app_flags_games SET question_current = $1, last_activity = CURRENT_TIMESTAMP WHERE game_id = $2; --- name: UpdateQuestionCorrect :exec +-- name: AppFlagsUpdateQuestionCorrect :exec UPDATE app_flags_games SET questions_errors = ( SELECT COALESCE(SUM(errors), 0) FROM app_flags_games_answers WHERE app_flags_games_answers.game_id = app_flags_games.game_id -) +), last_activity = CURRENT_TIMESTAMP WHERE app_flags_games.game_id = $1; --- name: UpsertGameAnswer :exec +-- name: AppFlagsUpsertGameAnswer :exec INSERT INTO app_flags_games_answers (game_id, question, errors) VALUES ($1, $2, $3) ON CONFLICT (game_id, question) DO UPDATE SET errors = app_flags_games_answers.errors + EXCLUDED.errors; +-- name: AppFlagsNewSharedData :one +INSERT INTO app_flags_games_shared_data (share_key, game_seed, tags, questions, seconds) VALUES ($1, $2, $3, $4, $5) RETURNING game_seed; + +-- name: AppFlagsGetSharedData :one +SELECT * FROM app_flags_games_shared_data WHERE share_key = $1 LIMIT 1; + diff --git a/internal/queries/app_flags.sql.go b/internal/queries/app_flags.sql.go index 9c93366..8133021 100644 --- a/internal/queries/app_flags.sql.go +++ b/internal/queries/app_flags.sql.go @@ -13,42 +13,76 @@ import ( "github.com/lib/pq" ) -const createFlagsGame = `-- name: CreateFlagsGame :one -INSERT INTO app_flags_games (uid, tags, question_amount) -VALUES ($1, $2, $3) +const appFlagsCreateGame = `-- name: AppFlagsCreateGame :one +INSERT INTO app_flags_games (uid, tags, question_amount, seconds) +VALUES ($1, $2, $3, $4) RETURNING game_id, game_seed ` -type CreateFlagsGameParams struct { +type AppFlagsCreateGameParams struct { Uid sql.NullString Tags []string QuestionAmount int32 + Seconds int32 } -type CreateFlagsGameRow struct { +type AppFlagsCreateGameRow struct { GameID uuid.UUID - GameSeed uuid.NullUUID + GameSeed uuid.UUID } -func (q *Queries) CreateFlagsGame(ctx context.Context, arg CreateFlagsGameParams) (CreateFlagsGameRow, error) { - row := q.db.QueryRowContext(ctx, createFlagsGame, arg.Uid, pq.Array(arg.Tags), arg.QuestionAmount) - var i CreateFlagsGameRow +func (q *Queries) AppFlagsCreateGame(ctx context.Context, arg AppFlagsCreateGameParams) (AppFlagsCreateGameRow, error) { + row := q.db.QueryRowContext(ctx, appFlagsCreateGame, + arg.Uid, + pq.Array(arg.Tags), + arg.QuestionAmount, + arg.Seconds, + ) + var i AppFlagsCreateGameRow err := row.Scan(&i.GameID, &i.GameSeed) return i, err } -const getFlagsGame = `-- name: GetFlagsGame :one -SELECT game_id, game_seed, uid, tags, question_amount, question_current, questions_errors, created_at, last_activity FROM app_flags_games WHERE game_id = $1 LIMIT 1 +const appFlagsCreateGameWithSeed = `-- name: AppFlagsCreateGameWithSeed :one +INSERT INTO app_flags_games (uid, tags, question_amount, seconds, game_seed) +VALUES ($1, $2, $3, $4, $5) +RETURNING game_id ` -func (q *Queries) GetFlagsGame(ctx context.Context, gameID uuid.UUID) (AppFlagsGame, error) { - row := q.db.QueryRowContext(ctx, getFlagsGame, gameID) +type AppFlagsCreateGameWithSeedParams struct { + Uid sql.NullString + Tags []string + QuestionAmount int32 + Seconds int32 + GameSeed uuid.UUID +} + +func (q *Queries) AppFlagsCreateGameWithSeed(ctx context.Context, arg AppFlagsCreateGameWithSeedParams) (uuid.UUID, error) { + row := q.db.QueryRowContext(ctx, appFlagsCreateGameWithSeed, + arg.Uid, + pq.Array(arg.Tags), + arg.QuestionAmount, + arg.Seconds, + arg.GameSeed, + ) + var game_id uuid.UUID + err := row.Scan(&game_id) + return game_id, err +} + +const appFlagsGetGame = `-- name: AppFlagsGetGame :one +SELECT game_id, game_seed, uid, tags, seconds, question_amount, question_current, questions_errors, created_at, last_activity FROM app_flags_games WHERE game_id = $1 LIMIT 1 +` + +func (q *Queries) AppFlagsGetGame(ctx context.Context, gameID uuid.UUID) (AppFlagsGame, error) { + row := q.db.QueryRowContext(ctx, appFlagsGetGame, gameID) var i AppFlagsGame err := row.Scan( &i.GameID, &i.GameSeed, &i.Uid, pq.Array(&i.Tags), + &i.Seconds, &i.QuestionAmount, &i.QuestionCurrent, &i.QuestionsErrors, @@ -58,51 +92,95 @@ func (q *Queries) GetFlagsGame(ctx context.Context, gameID uuid.UUID) (AppFlagsG return i, err } -const updateFlagsGame = `-- name: UpdateFlagsGame :exec +const appFlagsGetSharedData = `-- name: AppFlagsGetSharedData :one +SELECT id, share_key, game_seed, questions, tags, seconds, created_at FROM app_flags_games_shared_data WHERE share_key = $1 LIMIT 1 +` + +func (q *Queries) AppFlagsGetSharedData(ctx context.Context, shareKey string) (AppFlagsGamesSharedDatum, error) { + row := q.db.QueryRowContext(ctx, appFlagsGetSharedData, shareKey) + var i AppFlagsGamesSharedDatum + err := row.Scan( + &i.ID, + &i.ShareKey, + &i.GameSeed, + &i.Questions, + pq.Array(&i.Tags), + &i.Seconds, + &i.CreatedAt, + ) + return i, err +} + +const appFlagsNewSharedData = `-- name: AppFlagsNewSharedData :one +INSERT INTO app_flags_games_shared_data (share_key, game_seed, tags, questions, seconds) VALUES ($1, $2, $3, $4, $5) RETURNING game_seed +` + +type AppFlagsNewSharedDataParams struct { + ShareKey string + GameSeed uuid.UUID + Tags []string + Questions int32 + Seconds int32 +} + +func (q *Queries) AppFlagsNewSharedData(ctx context.Context, arg AppFlagsNewSharedDataParams) (uuid.UUID, error) { + row := q.db.QueryRowContext(ctx, appFlagsNewSharedData, + arg.ShareKey, + arg.GameSeed, + pq.Array(arg.Tags), + arg.Questions, + arg.Seconds, + ) + var game_seed uuid.UUID + err := row.Scan(&game_seed) + return game_seed, err +} + +const appFlagsUpdateGame = `-- name: AppFlagsUpdateGame :exec UPDATE app_flags_games SET question_current = $1, last_activity = CURRENT_TIMESTAMP WHERE game_id = $2 ` -type UpdateFlagsGameParams struct { +type AppFlagsUpdateGameParams struct { QuestionCurrent int32 GameID uuid.UUID } -func (q *Queries) UpdateFlagsGame(ctx context.Context, arg UpdateFlagsGameParams) error { - _, err := q.db.ExecContext(ctx, updateFlagsGame, arg.QuestionCurrent, arg.GameID) +func (q *Queries) AppFlagsUpdateGame(ctx context.Context, arg AppFlagsUpdateGameParams) error { + _, err := q.db.ExecContext(ctx, appFlagsUpdateGame, arg.QuestionCurrent, arg.GameID) return err } -const updateQuestionCorrect = `-- name: UpdateQuestionCorrect :exec +const appFlagsUpdateQuestionCorrect = `-- name: AppFlagsUpdateQuestionCorrect :exec UPDATE app_flags_games SET questions_errors = ( SELECT COALESCE(SUM(errors), 0) FROM app_flags_games_answers WHERE app_flags_games_answers.game_id = app_flags_games.game_id -) +), last_activity = CURRENT_TIMESTAMP WHERE app_flags_games.game_id = $1 ` -func (q *Queries) UpdateQuestionCorrect(ctx context.Context, gameID uuid.UUID) error { - _, err := q.db.ExecContext(ctx, updateQuestionCorrect, gameID) +func (q *Queries) AppFlagsUpdateQuestionCorrect(ctx context.Context, gameID uuid.UUID) error { + _, err := q.db.ExecContext(ctx, appFlagsUpdateQuestionCorrect, gameID) return err } -const upsertGameAnswer = `-- name: UpsertGameAnswer :exec +const appFlagsUpsertGameAnswer = `-- name: AppFlagsUpsertGameAnswer :exec INSERT INTO app_flags_games_answers (game_id, question, errors) VALUES ($1, $2, $3) ON CONFLICT (game_id, question) DO UPDATE SET errors = app_flags_games_answers.errors + EXCLUDED.errors ` -type UpsertGameAnswerParams struct { +type AppFlagsUpsertGameAnswerParams struct { GameID uuid.UUID Question int32 Errors int32 } -func (q *Queries) UpsertGameAnswer(ctx context.Context, arg UpsertGameAnswerParams) error { - _, err := q.db.ExecContext(ctx, upsertGameAnswer, arg.GameID, arg.Question, arg.Errors) +func (q *Queries) AppFlagsUpsertGameAnswer(ctx context.Context, arg AppFlagsUpsertGameAnswerParams) error { + _, err := q.db.ExecContext(ctx, appFlagsUpsertGameAnswer, arg.GameID, arg.Question, arg.Errors) return err } diff --git a/internal/queries/models.go b/internal/queries/models.go index 3c483f6..d9ca983 100644 --- a/internal/queries/models.go +++ b/internal/queries/models.go @@ -13,9 +13,10 @@ import ( type AppFlagsGame struct { GameID uuid.UUID - GameSeed uuid.NullUUID + GameSeed uuid.UUID Uid sql.NullString Tags []string + Seconds int32 QuestionAmount int32 QuestionCurrent int32 QuestionsErrors int32 @@ -24,9 +25,20 @@ type AppFlagsGame struct { } type AppFlagsGamesAnswer struct { - GameID uuid.UUID - Question int32 - Errors int32 + GameID uuid.UUID + Question int32 + Errors int32 + CreatedAt time.Time +} + +type AppFlagsGamesSharedDatum struct { + ID int32 + ShareKey string + GameSeed uuid.UUID + Questions int32 + Tags []string + Seconds int32 + CreatedAt time.Time } type Session struct { diff --git a/migrations/00000002_flags.up.sql b/migrations/00000002_flags.up.sql index 8cf9317..9efa3da 100644 --- a/migrations/00000002_flags.up.sql +++ b/migrations/00000002_flags.up.sql @@ -1,8 +1,9 @@ CREATE TABLE app_flags_games ( - game_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - game_seed UUID DEFAULT gen_random_uuid(), + game_id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(), + game_seed UUID NOT NULL DEFAULT gen_random_uuid(), uid VARCHAR DEFAULT NULL, tags VARCHAR[] NOT NULL, + seconds INT NOT NULL, question_amount INT NOT NULL, question_current INT DEFAULT 1 NOT NULL, questions_errors INT DEFAULT 0 NOT NULL, @@ -11,10 +12,21 @@ CREATE TABLE app_flags_games ( FOREIGN KEY (uid) REFERENCES users (uid) ); +CREATE TABLE app_flags_games_shared_data ( + id SERIAL PRIMARY KEY, + share_key VARCHAR NOT NULL, + game_seed UUID NOT NULL DEFAULT gen_random_uuid(), + questions INT DEFAULT 0 NOT NULL, + tags VARCHAR[] NOT NULL, + seconds INT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL +); + CREATE TABLE app_flags_games_answers ( game_id UUID NOT NULL, question INT NOT NULL, errors INT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, FOREIGN KEY (game_id) REFERENCES app_flags_games (game_id), CONSTRAINT app_flags_games_answers_unique UNIQUE (game_id, question) ); diff --git a/web/views/apps/flags/question.html b/web/views/apps/flags/question.html index 9f765d6..b09f73c 100644 --- a/web/views/apps/flags/question.html +++ b/web/views/apps/flags/question.html @@ -2,9 +2,17 @@
+
+ Questions left: {{.QuestionsLeft}} +
Errors: {{.Errors}}
+ {{range .TimeLeft}} +
+ Seconds left: {{.}} +
+ {{end}}
{{.Flag}} @@ -28,3 +36,18 @@
+ +{{range .TimeLeft}} + +{{end}} diff --git a/web/views/apps/flags/shared.html b/web/views/apps/flags/shared.html new file mode 100644 index 0000000..4b3c45a --- /dev/null +++ b/web/views/apps/flags/shared.html @@ -0,0 +1 @@ +
{{.ShareKey}}
diff --git a/web/views/apps/flags/start.html b/web/views/apps/flags/start.html index 733ffa1..3b964fb 100644 --- a/web/views/apps/flags/start.html +++ b/web/views/apps/flags/start.html @@ -14,12 +14,32 @@ {{end}} - + + +
+ +
+ +
+ +
- +
+ + + +