flags
All checks were successful
build / build (push) Successful in 40s
release-tag / release-image (push) Successful in 15m1s

This commit is contained in:
Tijl 2024-08-26 14:22:24 +02:00
parent 4a268ef8c7
commit d246f5e270
Signed by: tijl
GPG Key ID: DAE24BFCD722F053
13 changed files with 527 additions and 314 deletions

1
go.mod
View File

@ -20,6 +20,7 @@ require (
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/enescakir/emoji v1.0.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect

2
go.sum
View File

@ -21,6 +21,8 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/enescakir/emoji v1.0.0 h1:W+HsNql8swfCQFtioDGDHCHri8nudlK1n5p2rHCJoog=
github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkKp+WKFD0=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=

120
internal/apps/flags/game.go Normal file
View File

@ -0,0 +1,120 @@
package flags
import (
"context"
"database/sql"
"strconv"
"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"
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"
"github.com/google/uuid"
)
func answerHandler(c *fiber.Ctx) error {
return questionHandler(c, NewGameUUID{})
}
type NewGameUUID struct {
uuid.UUID
used bool
}
func questionHandler(c *fiber.Ctx, newGame NewGameUUID) error {
var gameId uuid.UUID
var err error
if newGame.used {
gameId = newGame.UUID
} else {
gameId, err = uuid.Parse(c.Cookies("app_flags_game_session"))
if err != nil {
return err
}
}
gameSession, err := db.Queries.GetFlagsGame(context.TODO(), gameId)
if err != nil {
return err
}
if (gameSession.QuestionAmount != 0) && (gameSession.QuestionAmount+1 == gameSession.QuestionCurrent) {
// TODO: game is over
return c.SendString("game is over")
}
err, countries := filterCountriesByTags(gameSession.Tags)
if err != nil {
return err
}
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
if gameSession.QuestionAmount != 0 {
shuffledCountries = shuffledCountries[0:gameSession.QuestionAmount]
}
log.Debug().Interface("shuffledCountries", shuffledCountries).Interface("shuffledAnswers", shuffledAnswers).Msg("data")
flag, err := emoji.CountryFlag(shuffledCountries[gameSession.QuestionCurrent-1])
if err != nil {
return err
}
data := *web.Common(c)
data["Title"] = "tmp"
data["Answers"] = shuffledAnswers
data["Flag"] = flag
return c.Render("apps/flags/question", data, "layouts/base")
}
func startNewGameHandler(c *fiber.Ctx) error {
values := c.Request().PostArgs().PeekMulti("tags")
var selectedTags []string
for _, v := range values {
selectedTags = append(selectedTags, string(v))
}
maxQuestions, err := strconv.Atoi(c.FormValue("max_questions"))
if err != nil {
return err
}
var Quid = sql.NullString{}
uid, err := user.GetSession(c)
if err == nil {
Quid.Valid = true
Quid.String = uid
}
row, err := db.Queries.CreateFlagsGame(context.TODO(), queries.CreateFlagsGameParams{
Uid: Quid,
Tags: selectedTags,
QuestionAmount: int32(maxQuestions),
})
if err != nil {
return err
}
c.Cookie(&fiber.Cookie{
Name: "app_flags_game_session",
Value: row.GameID.String(),
Secure: true,
})
return questionHandler(c, NewGameUUID{used: true, UUID: row.GameID})
}
func stopGameHandler(c *fiber.Ctx) error {
utils.ClearCookie(c, "app_flags_game_session")
return EntryPageHandler(c)
}

View File

@ -1 +0,0 @@
package flags

View File

@ -3,14 +3,11 @@ package flags
// WIP
import (
"context"
"database/sql"
"embed"
"encoding/json"
"strconv"
"errors"
"fmt"
"git.tijl.dev/tijl/tijl.dev-core/internal/queries"
"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"
@ -25,6 +22,34 @@ type CountryCode struct {
Tags []string `json:"tags"`
}
func filterCountriesByTags(tags []string) (error, []string) {
var result []string
for _, tag := range tags {
isSupported := false
for _, supportedTag := range supportedTags {
if tag == supportedTag {
isSupported = true
}
}
if isSupported == false {
return errors.New(fmt.Sprintf("unsupported tag %v", tag)), result
}
}
for _, country := range countryCodes {
for _, tag := range tags {
for _, cTag := range country.Tags {
if cTag == tag {
result = append(result, country.Code)
}
}
}
}
return nil, result
}
//go:embed locales/* data/*
var Embed embed.FS
@ -38,47 +63,26 @@ func Setup() {
web.RegisterAppSetupFunc(func(a *fiber.App) {
a.Get("/app/flags", func(c *fiber.Ctx) error {
data := *web.Common(c)
data["Title"] = "tmp"
data["SupportedTags"] = supportedTags
return c.Render("apps/flags/start", data, "layouts/base")
if c.Cookies("app_flags_game_session") != "" {
return questionHandler(c, NewGameUUID{})
} else {
return EntryPageHandler(c)
}
})
a.Post("/app/flags", func(c *fiber.Ctx) error {
recType := c.FormValue("type")
if recType == "start" {
values := c.Request().PostArgs().PeekMulti("tags")
var selectedTags []string
for _, v := range values {
selectedTags = append(selectedTags, string(v))
}
maxQuestions, err := strconv.Atoi(c.FormValue("max_questions"))
if err != nil {
return err
}
_, err = db.Queries.CreateFlagsGame(context.TODO(), queries.CreateFlagsGameParams{
Uid: sql.NullString{},
Tags: selectedTags,
QuestionAmount: int32(maxQuestions),
})
if err != nil {
return err
}
c.Cookie(&fiber.Cookie{
Name: "app_flags_game_session",
Value: "",
})
data := *web.Common(c)
data["Title"] = "tmp"
return c.Render("apps/flags/question", data, "layouts/base")
switch recType {
case "start":
return startNewGameHandler(c)
case "answer":
return c.SendString("wip")
case "cancel":
return stopGameHandler(c)
}
return nil
@ -111,3 +115,10 @@ func loadData() error {
return nil
}
func EntryPageHandler(c *fiber.Ctx) error {
data := *web.Common(c)
data["Title"] = "tmp"
data["SupportedTags"] = supportedTags
return c.Render("apps/flags/start", data, "layouts/base")
}

View File

@ -0,0 +1,38 @@
package flags
import (
"crypto/sha256"
"encoding/binary"
"math/rand"
)
func shuffleSliceRandom(slice []string) []string {
shuffled := make([]string, len(slice))
copy(shuffled, slice)
for i := len(shuffled) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
}
return shuffled
}
func shuffleSlice(slice []string, seed string) []string {
hasher := sha256.New()
hasher.Write([]byte(seed))
hash := hasher.Sum(nil)
seedInt := int64(binary.LittleEndian.Uint64(hash[:8]))
r := rand.New(rand.NewSource(seedInt))
shuffled := make([]string, len(slice))
copy(shuffled, slice)
r.Shuffle(len(shuffled), func(i, j int) {
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
})
return shuffled
}

View File

@ -85,7 +85,8 @@ func HandleCallback(c *fiber.Ctx) error {
}
redirect := c.Cookies("internal_redirect")
c.ClearCookie("internal_redirect", "state")
utils.ClearCookie(c, "internal_redirect")
utils.ClearCookie(c, "state")
if redirect != "" {
return c.Redirect(redirect, http.StatusFound)
}

View File

@ -26,7 +26,7 @@ type AppFlagsGame struct {
type AppFlagsGamesAnswer struct {
GameID uuid.UUID
Question int32
Correct bool
Errors int32
}
type Session struct {

15
internal/utils/cookie.go Normal file
View File

@ -0,0 +1,15 @@
package utils
import (
"time"
"github.com/gofiber/fiber/v2"
)
func ClearCookie(c *fiber.Ctx, key string) {
c.Cookie(&fiber.Cookie{
Name: key,
Value: "",
Expires: time.Now().Add(-1 * time.Hour),
})
}

View File

@ -4,7 +4,7 @@ CREATE TABLE app_flags_games (
uid VARCHAR DEFAULT NULL,
tags VARCHAR[] NOT NULL,
question_amount INT NOT NULL,
question_current INT DEFAULT 0 NOT NULL,
question_current INT DEFAULT 1 NOT NULL,
question_correct INT DEFAULT 0 NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
@ -14,7 +14,7 @@ CREATE TABLE app_flags_games (
CREATE TABLE app_flags_games_answers (
game_id UUID NOT NULL,
question INT NOT NULL,
correct BOOLEAN NOT NULL,
errors INT NOT NULL,
FOREIGN KEY (game_id) REFERENCES app_flags_games (game_id),
CONSTRAINT app_flags_games_answers_unique UNIQUE (game_id, question)
);

View File

@ -0,0 +1,26 @@
<div class="container">
<div>
<div class="flex justify-center p-4 mt-4">
<span style="font-size: 105px;">{{.Flag}}</span>
</div>
<div hx-boost="true" class="flex justify-center flex-wrap">
{{range .Answers}}
<form method="post" class="m-1">
<input class="hidden" name="type" value="answer" />
<input class="hidden" name="answer" value="{{.}}" />
<button type="submit" class="btn btn-lg rounded-2xl">{{index $.T .}}</button>
</form>
{{end}}
</div>
<form hx-boost="true" method="post">
<input class="hidden" name="type" value="cancel" />
<button type="submit" class="btn hover:btn-error">Stop</button>
</form>
</div>
</div>

View File

@ -11,10 +11,10 @@
{{end}}
</div>
<label class="cursor-pointer label">
<!--<label class="cursor-pointer label">
<span class="label-text">Aantal vragen</span>
<input name="tags" value="{{.}}" type="checkbox" class="checkbox checkbox-primary" />
</label>
</label>-->
<input value="0" name="max_questions" type="number" class="input input-bordered" />
<button type="submit" class="btn btn-primary">Submit</button>