updates
All checks were successful
build / build (push) Successful in 28s
release-tag / release-image (push) Successful in 17m36s

This commit is contained in:
Tijl 2024-09-04 23:03:23 +02:00
parent 18c3bf3dd8
commit 5ad1da76b1
Signed by: tijl
GPG Key ID: DAE24BFCD722F053
14 changed files with 219 additions and 191 deletions

View File

@ -169,7 +169,7 @@ func questionHandler(c *fiber.Ctx, newGame NullableUUID, prevError NullableStrin
data["Errors"] = gameSession.QuestionsErrors data["Errors"] = gameSession.QuestionsErrors
data["PreviousError"] = prevError.String data["PreviousError"] = prevError.String
data["ShortcutKeys"] = []string{"d", "f", "h", "j", "k"} data["ShortcutKeys"] = []string{"d", "f", "h", "j", "k"}
return c.Render("apps/flags/question", data, "layouts/base") return web.Render(c, "apps/flags/question", data, "layouts/base")
} }
func sharedGameHandler(c *fiber.Ctx) error { func sharedGameHandler(c *fiber.Ctx) error {
@ -242,7 +242,7 @@ func createSharedGameData(c *fiber.Ctx, tags []string, maxQuestions int, seconds
data := *web.Common(c) data := *web.Common(c)
data["Title"] = "tmp" data["Title"] = "tmp"
data["ShareKey"] = shareKey data["ShareKey"] = shareKey
return c.Render("apps/flags/shared", data, "layouts/base") return web.Render(c, "apps/flags/shared", data, "layouts/base")
} }
func setupGame(c *fiber.Ctx, tags []string, maxQuestions int, seconds int, gameSeed NullableUUID) error { func setupGame(c *fiber.Ctx, tags []string, maxQuestions int, seconds int, gameSeed NullableUUID) error {
@ -291,7 +291,7 @@ func gameEndHandler(c *fiber.Ctx) error {
data := *web.Common(c) data := *web.Common(c)
data["Title"] = "tmp" data["Title"] = "tmp"
data["Errors"] = gameSession.QuestionsErrors data["Errors"] = gameSession.QuestionsErrors
return c.Render("apps/flags/end", data, "layouts/base") return web.Render(c, "apps/flags/end", data, "layouts/base")
} }
@ -304,5 +304,5 @@ func gameStartHandler(c *fiber.Ctx) error {
data := *web.Common(c) data := *web.Common(c)
data["Title"] = "Flags Game | tijl.dev" data["Title"] = "Flags Game | tijl.dev"
data["SupportedTags"] = supportedTags data["SupportedTags"] = supportedTags
return c.Render("apps/flags/start", data, "layouts/base") return web.Render(c, "apps/flags/start", data, "layouts/base")
} }

View File

@ -5,6 +5,8 @@
"max_questions": "Max Questions", "max_questions": "Max Questions",
"time_limit": "Time limit (seconds)", "time_limit": "Time limit (seconds)",
"share_game": "Save", "share_game": "Save",
"deselect_all": "Deselect all",
"select_all": "Select all",
"Asia": "Asia", "Asia": "Asia",
"MiddleEast": "Middle East", "MiddleEast": "Middle East",
"SoutheastAsia": "Southeast Asia", "SoutheastAsia": "Southeast Asia",

View File

@ -5,6 +5,8 @@
"time_limit": "Tijdlimit (seconden)", "time_limit": "Tijdlimit (seconden)",
"max_questions": "Maximaal aantal vragen", "max_questions": "Maximaal aantal vragen",
"share_game": "Opslaan", "share_game": "Opslaan",
"deselect_all": "Alles deselecteren",
"select_all": "Alles selecteren",
"Asia": "Azië", "Asia": "Azië",
"MiddleEast": "Midden-Oosten", "MiddleEast": "Midden-Oosten",
"SoutheastAsia": "Zuidoost-Azië", "SoutheastAsia": "Zuidoost-Azië",

View File

@ -1,200 +1,200 @@
package uploader package uploader
import ( import (
"context" "context"
"database/sql" "database/sql"
"embed" "embed"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"time" "time"
"git.tijl.dev/tijl/tijl.dev-core/internal/config" "git.tijl.dev/tijl/tijl.dev-core/internal/config"
"git.tijl.dev/tijl/tijl.dev-core/internal/queries" "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/user"
"git.tijl.dev/tijl/tijl.dev-core/modules/db" "git.tijl.dev/tijl/tijl.dev-core/modules/db"
"git.tijl.dev/tijl/tijl.dev-core/modules/i18n" "git.tijl.dev/tijl/tijl.dev-core/modules/i18n"
"git.tijl.dev/tijl/tijl.dev-core/modules/web" "git.tijl.dev/tijl/tijl.dev-core/modules/web"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
//go:embed locales/* //go:embed locales/*
var Embed embed.FS var Embed embed.FS
func Setup() { func Setup() {
i18n.RegisterTranslations(Embed, "locales") i18n.RegisterTranslations(Embed, "locales")
var uploadDir string = filepath.Join(config.Config.DataLocation, "./uploader") var uploadDir string = filepath.Join(config.Config.DataLocation, "./uploader")
web.RegisterAppSetupFunc(func(a *fiber.App) {
a.Get("/app/uploader", func(c *fiber.Ctx) error {
_, err := user.GetSession(c)
if err != nil {
return c.Redirect("/auth?redirect=/app/uploader", http.StatusUnauthorized)
}
data := *web.Common(c)
data["Title"] = "tmp"
return c.Render("apps/uploader/index", data, "layouts/base")
})
a.Get("/app/uploader/:key", func(c *fiber.Ctx) error {
encryptionKey, err := hex.DecodeString(c.Params("key"))
if err != nil {
return c.Next()
}
storageKey := hashKey(encryptionKey)
web.RegisterAppSetupFunc(func(a *fiber.App) { row, err := db.Queries.AppUploaderGet(context.TODO(), storageKey)
a.Get("/app/uploader", func(c *fiber.Ctx) error { if err != nil {
_, err := user.GetSession(c) return c.Next()
if err != nil { }
return c.Redirect("/auth?redirect=/app/uploader", http.StatusUnauthorized)
}
data := *web.Common(c)
data["Title"] = "tmp"
return c.Render("apps/uploader/index", data, "layouts/base")
})
a.Get("/app/uploader/:key", func(c *fiber.Ctx) error {
encryptionKey, err := hex.DecodeString(c.Params("key"))
if err != nil {
return c.Next()
}
storageKey := hashKey(encryptionKey)
row, err := db.Queries.AppUploaderGet(context.TODO(), storageKey) if row.Expire.UnixMilli() < time.Now().UnixMilli() {
if err != nil { return c.Next()
return c.Next() }
}
if row.Expire.UnixMilli() < time.Now().UnixMilli() { accessCount, err := db.Queries.AppUploaderAccessCount(context.TODO(), row.ID)
return c.Next() if err != nil {
} return err
}
accessCount, err := db.Queries.AppUploaderAccessCount(context.TODO(), row.ID) if accessCount == int64(row.MaxVisits) || accessCount > int64(row.MaxVisits) {
if err != nil { return c.Next()
return err }
}
if accessCount == int64(row.MaxVisits) || accessCount > int64(row.MaxVisits) { createLog := queries.AppUploaderAccessCreateParams{
return c.Next() FileID: row.ID,
} IpAddress: c.IP(),
Agent: string(c.Context().UserAgent()),
}
createLog := queries.AppUploaderAccessCreateParams{ user, err := user.GetSession(c)
FileID: row.ID, if err == nil {
IpAddress: c.IP(), createLog.Uid = sql.NullString{
Agent: string(c.Context().UserAgent()), Valid: true,
} String: user,
}
}
user, err := user.GetSession(c) db.Queries.AppUploaderAccessCreate(context.TODO(), createLog)
if err == nil {
createLog.Uid = sql.NullString{
Valid: true,
String: user,
}
}
db.Queries.AppUploaderAccessCreate(context.TODO(), createLog) encryptedContent, err := os.ReadFile(filepath.Join(uploadDir, row.ID.String()))
if err != nil {
return err
}
encryptedContent, err := os.ReadFile(filepath.Join(uploadDir, row.ID.String())) encryptedMetadata, err := os.ReadFile(filepath.Join(uploadDir, row.ID.String()+".meta"))
if err != nil { if err != nil {
return err return err
} }
encryptedMetadata, err := os.ReadFile(filepath.Join(uploadDir, row.ID.String()+".meta")) decryptedContent, err := decryptData(encryptedContent, encryptionKey)
if err != nil { if err != nil {
return err return err
} }
decryptedContent, err := decryptData(encryptedContent, encryptionKey) decryptedMetadata, err := decryptData(encryptedMetadata, encryptionKey)
if err != nil { if err != nil {
return err return err
} }
decryptedMetadata, err := decryptData(encryptedMetadata, encryptionKey) var metadata map[string]interface{}
if err != nil { if err := json.Unmarshal(decryptedMetadata, &metadata); err != nil {
return err return c.Status(fiber.StatusInternalServerError).SendString("Failed to parse metadata")
} }
var metadata map[string]interface{} c.Set("Content-Disposition", "attachment; filename="+metadata["filename"].(string))
if err := json.Unmarshal(decryptedMetadata, &metadata); err != nil { c.Set("Content-Type", metadata["content_type"].(string))
return c.Status(fiber.StatusInternalServerError).SendString("Failed to parse metadata")
}
c.Set("Content-Disposition", "attachment; filename="+metadata["filename"].(string)) return c.Send(decryptedContent)
c.Set("Content-Type", metadata["content_type"].(string)) })
return c.Send(decryptedContent) a.Post("/app/uploader", func(c *fiber.Ctx) error {
}) uid, err := user.GetSession(c)
if err != nil {
return c.SendStatus(http.StatusUnauthorized)
}
expireDays, err := strconv.Atoi(c.FormValue("expire_days"))
if err != nil {
return err
}
maxDownloads, err := strconv.Atoi(c.FormValue("max_downloads"))
if err != nil {
return err
}
file, err := c.FormFile("file")
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString("Failed to read file")
}
a.Post("/app/uploader", func(c *fiber.Ctx) error { src, err := file.Open()
uid, err := user.GetSession(c) if err != nil {
if err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to open file")
return c.SendStatus(http.StatusUnauthorized) }
} defer src.Close()
expireDays, err := strconv.Atoi(c.FormValue("expire_days")) // Read file content into a byte slice
if err != nil {return err} fileContent := make([]byte, file.Size)
maxDownloads, err := strconv.Atoi(c.FormValue("max_downloads")) if _, err := src.Read(fileContent); err != nil {
if err != nil {return err} return c.Status(fiber.StatusInternalServerError).SendString("Failed to read file content")
}
file, err := c.FormFile("file") // Generate encryption key
if err != nil { encryptionKey, err := generateEncryptionKey()
return c.Status(fiber.StatusBadRequest).SendString("Failed to read file") if err != nil {
} return c.Status(fiber.StatusInternalServerError).SendString("Failed to generate encryption key")
}
src, err := file.Open() // Encrypt the file content
if err != nil { encryptedContent, err := encryptData(fileContent, encryptionKey)
return c.Status(fiber.StatusInternalServerError).SendString("Failed to open file") if err != nil {
} return c.Status(fiber.StatusInternalServerError).SendString("Failed to encrypt file content")
defer src.Close() }
// Read file content into a byte slice storageKey := hashKey(encryptionKey)
fileContent := make([]byte, file.Size)
if _, err := src.Read(fileContent); err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Failed to read file content")
}
// Generate encryption key id, err := db.Queries.AppUploaderCreate(context.TODO(), queries.AppUploaderCreateParams{
encryptionKey, err := generateEncryptionKey() Uid: uid,
if err != nil { FileCrypto: storageKey,
return c.Status(fiber.StatusInternalServerError).SendString("Failed to generate encryption key") Expire: time.Now().AddDate(0, 0, expireDays),
} MaxVisits: int32(maxDownloads),
})
if err != nil {
return err
}
// Encrypt the file content // Save encrypted file content
encryptedContent, err := encryptData(fileContent, encryptionKey) if err := os.WriteFile(filepath.Join(uploadDir, id.String()), encryptedContent, 0644); err != nil {
if err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to save encrypted file")
return c.Status(fiber.StatusInternalServerError).SendString("Failed to encrypt file content") }
}
storageKey := hashKey(encryptionKey) metadata := map[string]interface{}{
"filename": file.Filename,
"content_type": file.Header.Get("Content-Type"),
}
id, err := db.Queries.AppUploaderCreate(context.TODO(), queries.AppUploaderCreateParams{ metadataBytes, err := json.Marshal(metadata)
Uid: uid, if err != nil {
FileCrypto: storageKey, return c.Status(fiber.StatusInternalServerError).SendString("Failed to marshal metadata")
Expire: time.Now().AddDate(0, 0, expireDays), }
MaxVisits: int32(maxDownloads),
})
if err != nil {
return err
}
// Save encrypted file content encryptedMetadata, err := encryptData(metadataBytes, encryptionKey)
if err := os.WriteFile(filepath.Join(uploadDir, id.String()), encryptedContent, 0644); err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Failed to save encrypted file") return c.Status(fiber.StatusInternalServerError).SendString("Failed to encrypt metadata")
} }
metadata := map[string]interface{}{ if err := os.WriteFile(filepath.Join(uploadDir, id.String()+".meta"), encryptedMetadata, 0644); err != nil {
"filename": file.Filename, return c.Status(fiber.StatusInternalServerError).SendString("Failed to save encrypted metadata")
"content_type": file.Header.Get("Content-Type"), }
}
metadataBytes, err := json.Marshal(metadata) data := *web.Common(c)
if err != nil { data["Title"] = "tmp"
return c.Status(fiber.StatusInternalServerError).SendString("Failed to marshal metadata") data["Key"] = hex.EncodeToString(encryptionKey)
} return c.Render("apps/uploader/uploaded", data, "layouts/base")
})
encryptedMetadata, err := encryptData(metadataBytes, encryptionKey) }, 1000)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Failed to encrypt metadata")
}
if err := os.WriteFile(filepath.Join(uploadDir, id.String()+".meta"), encryptedMetadata, 0644); err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Failed to save encrypted metadata")
}
data := *web.Common(c)
data["Title"] = "tmp"
data["Key"] = hex.EncodeToString(encryptionKey)
return c.Render("apps/uploader/uploaded", data, "layouts/base")
})
}, 1000)
} }

View File

@ -9,6 +9,7 @@ import (
type Service struct { type Service struct {
Name string Name string
Slug string
Description string Description string
Url string Url string
InfoUrl string InfoUrl string
@ -16,38 +17,46 @@ type Service struct {
Scale float64 Scale float64
} }
var services = map[string]Service{ var services = []Service{
"immich": { {
Name: "Immich", Name: "Immich",
Slug: "immich",
Description: "...", Description: "...",
Url: "https://fotos.tijl.dev/photos", Url: "https://fotos.tijl.dev/photos",
InfoUrl: "https://immich.app", InfoUrl: "https://immich.app",
Color: "white", Color: "white",
Scale: 0, Scale: 0,
}, },
"element": { {
Name: "Element", Name: "Element",
Slug: "element",
Description: "...", Description: "...",
Url: "https://element.tijl.dev", Url: "https://element.tijl.dev",
InfoUrl: "https://matrix.org", InfoUrl: "https://matrix.org",
Color: "#0DBD8B", Color: "#0DBD8B",
Scale: 0.1, Scale: 0.1,
}, },
"nextcloud": { {
Name: "Nextcloud", Name: "Nextcloud",
Slug: "nextcloud",
Description: "...", Description: "...",
Url: "https://cloud.tijl.dev", Url: "https://cloud.tijl.dev",
InfoUrl: "https://nextcloud.com", InfoUrl: "https://nextcloud.com",
Color: "#00679e", Color: "#00679e",
Scale: -0.2, Scale: -0.2,
}, },
{
Name: "Gitea",
Slug: "gitea",
Description: "...",
Url: "https://git.tijl.dev",
InfoUrl: "https://about.gitea.com",
Color: "white",
Scale: -0.2,
},
} }
func servicesHandler(c *fiber.Ctx) error { func servicesHandler(c *fiber.Ctx) error {
_, err := user.GetSession(c)
if err != nil {
return c.Next()
}
data := *web.Common(c) data := *web.Common(c)
data["Title"] = i18n.Translate(c, "services") data["Title"] = i18n.Translate(c, "services")
data["Services"] = services data["Services"] = services
@ -59,9 +68,11 @@ func serviceHandler(c *fiber.Ctx) error {
if err != nil { if err != nil {
return c.Next() return c.Next()
} }
if services[c.Params("service")].Url != "" { /*
return c.Redirect(services[c.Params("service")].Url) if services[c.Params("service")].Url != "" {
} return c.Redirect(services[c.Params("service")].Url)
}
*/
return c.Next() return c.Next()
} }
@ -70,8 +81,10 @@ func serviceInfoHandler(c *fiber.Ctx) error {
if err != nil { if err != nil {
return c.Next() return c.Next()
} }
if services[c.Params("service")].Url != "" { /*
return c.Redirect(services[c.Params("service")].InfoUrl) if services[c.Params("service")].Url != "" {
} return c.Redirect(services[c.Params("service")].InfoUrl)
}
*/
return c.Next() return c.Next()
} }

View File

@ -70,6 +70,7 @@ func Listen() {
app := fiber.New(fiber.Config{ app := fiber.New(fiber.Config{
Views: engine, Views: engine,
DisableStartupMessage: true, DisableStartupMessage: true,
BodyLimit: 4 * 1024 * 1024 * 1024,
}) })
app.Use(fiberzerolog.New(fiberzerolog.Config{ app.Use(fiberzerolog.New(fiberzerolog.Config{
Logger: &log.Logger, Logger: &log.Logger,

10
modules/web/render.go Normal file
View File

@ -0,0 +1,10 @@
package web
import "github.com/gofiber/fiber/v2"
func Render(c *fiber.Ctx, template string, data fiber.Map, layout string) error {
if c.Get("HX-Request") != "" && c.Get("HX-Target") == "main-content" {
return c.Render(template, data)
}
return c.Render(template, data, layout)
}

View File

@ -6,7 +6,7 @@
<div>game is over</div> <div>game is over</div>
<form method="post" hx-boost="true"> <form method="post" hx-boost="true" hx-target="#main-content">
<input class="hidden" name="type" value="exit" /> <input class="hidden" name="type" value="exit" />
<button type="submit" class="btn">exit</button> <button type="submit" class="btn">exit</button>
</form> </form>

View File

@ -18,7 +18,7 @@
<span style="font-size: 105px;">{{.Flag}}</span> <span style="font-size: 105px;">{{.Flag}}</span>
</div> </div>
<div hx-boost="true" id="answers" class="flex justify-center flex-wrap"> <div hx-target="#main-content" hx-boost="true" id="answers" class="flex justify-center flex-wrap">
{{range $index, $answer := .Answers}} {{range $index, $answer := .Answers}}
<form method="post" class="m-1"> <form method="post" class="m-1">
<input class="hidden" name="type" value="answer" /> <input class="hidden" name="type" value="answer" />
@ -34,7 +34,7 @@
{{end}} {{end}}
</div> </div>
<form hx-boost="true" method="post"> <form hx-boost="true" hx-target="#main-content" method="post">
<input class="hidden" name="type" value="exit" /> <input class="hidden" name="type" value="exit" />
<button type="submit" class="btn hover:btn-error">Stop</button> <button type="submit" class="btn hover:btn-error">Stop</button>
</form> </form>

View File

@ -1,7 +1,7 @@
<div> <div>
<div class="text-center text-2xl my-4">{{.ShareKey}}</div> <div class="text-center text-2xl my-4">{{.ShareKey}}</div>
<form hx-boost="true" method="get" class="justify-center flex mt-4"> <form hx-target="#main-content" hx-boost="true" method="get" class="justify-center flex mt-4">
<button type="submit" class="btn text-center">OK</button> <button type="submit" class="btn text-center">OK</button>
</form> </form>
</div> </div>

View File

@ -1,4 +1,4 @@
<div hx-boost="true"> <div hx-target="#main-content" hx-boost="true">
<div id="error-message" class="text-red-500 mb-4"></div> <div id="error-message" class="text-red-500 mb-4"></div>

View File

@ -1,4 +1,4 @@
<form enctype="multipart/form-data" method="post" hx-boost="true"> <form enctype="multipart/form-data" method="post">
<div> <div>
<label class="input input-bordered flex items-center gap-2 mt-2"> <label class="input input-bordered flex items-center gap-2 mt-2">
Max downloaders Max downloaders

View File

@ -16,7 +16,7 @@
<header> <header>
{{template "partials/menu" .}} {{template "partials/menu" .}}
</header> </header>
<main class="mx-6 pt-24 pb-4"> <main id="main-content" class="mx-6 pt-24 pb-4">
{{embed}} {{embed}}
</main> </main>
</body> </body>

View File

@ -5,18 +5,18 @@
<div class="mt-4 flex justify-center"> <div class="mt-4 flex justify-center">
<div class="flex gap-2"> <div class="flex gap-2">
{{range $key, $value := .Services}} {{range .Services}}
<a href="#{{$key}}" id="service-{{$key}}" class="group" <a href="#{{.Slug}}" id="service-{{.Slug}}" class="group"
onmouseover="this.querySelector('img').style.transform='scale({{incfloat .Scale 1.1}})';" onmouseover="this.querySelector('img').style.transform='scale({{incfloat .Scale 1.1}})';"
onmouseout="this.querySelector('img').style.transform='scale({{incfloat .Scale 1}})';"> onmouseout="this.querySelector('img').style.transform='scale({{incfloat .Scale 1}})';">
<div class="relative w-24 h-24 rounded-2xl rounded-3xl shadow-2xl" style="background-color: {{.Color}};"> <div class="relative w-24 h-24 rounded-2xl rounded-3xl shadow-2xl" style="background-color: {{.Color}};">
<div class="absolute inset-0 flex items-center justify-center"> <div class="absolute inset-0 flex items-center justify-center">
<img alt="{{.Name}} logo" <img alt="{{.Name}} logo"
class="text-white transform transition-transform duration-300 drop-shadow-2xl" class="text-white transform transition-transform duration-300 drop-shadow-2xl"
style="transform: scale({{incfloat .Scale 1}});" src="/static/assets/{{$key}}.svg" /> style="transform: scale({{incfloat .Scale 1}});" src="/static/assets/{{.Slug}}.svg" />
</div> </div>
</div> </div>
<div id="service-{{$key}}-label" class="w-24 text-center mt-2 rounded-2xl rounded-b-none py-1"> <div id="service-{{.Slug}}-label" class="w-24 text-center mt-2 rounded-2xl rounded-b-none py-1">
{{.Name}} {{.Name}}
</div> </div>
</a> </a>
@ -25,13 +25,13 @@
</div> </div>
<div class="card bg-base-300" id="services-info"> <div class="card bg-base-300" id="services-info">
{{range $key, $value := .Services}} {{range .Services}}
<div class="card-body hidden" id="service-{{$key}}-info"> <div class="card-body hidden" id="service-{{.Slug}}-info">
<h2 class="card-title">{{.Name}}</h2> <h2 class="card-title">{{.Name}}</h2>
<p>{{.Description}}</p> <p>{{.Description}}</p>
<div class="card-actions justify-end"> <div class="card-actions justify-end">
<a href="/service/{{$key}}/info" class="btn bg-base-300">Info</a> <a href="/service/{{.Slug}}/info" class="btn bg-base-300">Info</a>
<a href="/service/{{$key}}" class="btn btn-primary">Open</a> <a href="/service/{{.Slug}}" class="btn btn-primary">Open</a>
</div> </div>
</div> </div>
{{end}} {{end}}