From 42f9b974b207e44f4c71cbe56997318e54fa583e Mon Sep 17 00:00:00 2001
From: KartoffelChips <104089082+KartoffelChipss@users.noreply.github.com>
Date: Mon, 30 Mar 2026 18:26:45 +0200
Subject: [PATCH] Basic REST app
---
.gitignore | 2 +
.idea/.gitignore | 10 +++
.idea/copilot.data.migration.ask2agent.xml | 6 ++
.idea/go.imports.xml | 11 +++
.idea/modules.xml | 8 +++
.idea/quay.iml | 9 +++
.idea/vcs.xml | 6 ++
app/handlers/health.go | 9 +++
app/models/api_error.go | 5 ++
app/routes/routes.go | 14 ++++
config.example.yaml | 21 ++++++
go.mod | 24 +++++++
go.sum | 38 ++++++++++
internal/config/config.go | 80 ++++++++++++++++++++++
internal/envconfig/envconfig.go | 18 +++++
internal/fiberconfig/fiberconfig.go | 18 +++++
main.go | 30 ++++++++
17 files changed, 309 insertions(+)
create mode 100644 .gitignore
create mode 100644 .idea/.gitignore
create mode 100644 .idea/copilot.data.migration.ask2agent.xml
create mode 100644 .idea/go.imports.xml
create mode 100644 .idea/modules.xml
create mode 100644 .idea/quay.iml
create mode 100644 .idea/vcs.xml
create mode 100644 app/handlers/health.go
create mode 100644 app/models/api_error.go
create mode 100644 app/routes/routes.go
create mode 100644 config.example.yaml
create mode 100644 go.mod
create mode 100644 go.sum
create mode 100644 internal/config/config.go
create mode 100644 internal/envconfig/envconfig.go
create mode 100644 internal/fiberconfig/fiberconfig.go
create mode 100644 main.go
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..65cbde7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.env
+config.yaml
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..ab1f416
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml
new file mode 100644
index 0000000..1f2ea11
--- /dev/null
+++ b/.idea/copilot.data.migration.ask2agent.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml
new file mode 100644
index 0000000..d7202f0
--- /dev/null
+++ b/.idea/go.imports.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..9ad177a
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/quay.iml b/.idea/quay.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/quay.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/handlers/health.go b/app/handlers/health.go
new file mode 100644
index 0000000..053278b
--- /dev/null
+++ b/app/handlers/health.go
@@ -0,0 +1,9 @@
+package handlers
+
+import "github.com/gofiber/fiber/v3"
+
+func HealthCheck(c fiber.Ctx) error {
+ return c.Status(200).JSON(fiber.Map{
+ "status": "ok",
+ })
+}
diff --git a/app/models/api_error.go b/app/models/api_error.go
new file mode 100644
index 0000000..9934619
--- /dev/null
+++ b/app/models/api_error.go
@@ -0,0 +1,5 @@
+package models
+
+type APIError struct {
+ Error string `json:"error"`
+}
diff --git a/app/routes/routes.go b/app/routes/routes.go
new file mode 100644
index 0000000..97b9436
--- /dev/null
+++ b/app/routes/routes.go
@@ -0,0 +1,14 @@
+package routes
+
+import (
+ "quay/app/handlers"
+ "quay/internal/config"
+
+ "github.com/gofiber/fiber/v3"
+)
+
+func Register(app *fiber.App, cfg *config.Config) {
+ api := app.Group("/api")
+
+ api.Get("/health", handlers.HealthCheck)
+}
diff --git a/config.example.yaml b/config.example.yaml
new file mode 100644
index 0000000..40e47d8
--- /dev/null
+++ b/config.example.yaml
@@ -0,0 +1,21 @@
+global:
+ token: "${DEPLOY_TOKEN}"
+ github_pat: "${GITHUB_PAT}"
+ storage_path: /var/www/sites
+
+sites:
+ - repo: my-lib-docs
+ owner: yourname
+ branch: gh-pages
+ domain: docs.my-lib.com
+
+ - repo: portfolio
+ owner: yourname
+ branch: main
+ domain: yourname.com
+ spa: true
+
+ - repo: other-docs
+ owner: yourname
+ branch: gh-pages
+ domain: other-docs.yourdomain.com
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..8b4d5a3
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,24 @@
+module quay
+
+go 1.25.0
+
+require (
+ github.com/andybalholm/brotli v1.2.0 // indirect
+ github.com/gofiber/fiber/v3 v3.1.0 // indirect
+ github.com/gofiber/schema v1.7.0 // indirect
+ github.com/gofiber/utils/v2 v2.0.2 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/joho/godotenv v1.5.1 // indirect
+ github.com/klauspost/compress v1.18.4 // indirect
+ github.com/mattn/go-colorable v0.1.14 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/philhofer/fwd v1.2.0 // indirect
+ github.com/tinylib/msgp v1.6.3 // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasthttp v1.69.0 // indirect
+ golang.org/x/crypto v0.48.0 // indirect
+ golang.org/x/net v0.50.0 // indirect
+ golang.org/x/sys v0.41.0 // indirect
+ golang.org/x/text v0.34.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..81320aa
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,38 @@
+github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
+github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
+github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY=
+github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU=
+github.com/gofiber/schema v1.7.0 h1:yNM+FNRZjyYEli9Ey0AXRBrAY9jTnb+kmGs3lJGPvKg=
+github.com/gofiber/schema v1.7.0/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk=
+github.com/gofiber/utils/v2 v2.0.2 h1:ShRRssz0F3AhTlAQcuEj54OEDtWF7+HJDwEi/aa6QLI=
+github.com/gofiber/utils/v2 v2.0.2/go.mod h1:+9Ub4NqQ+IaJoTliq5LfdmOJAA/Hzwf4pXOxOa3RrJ0=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
+github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
+github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
+github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
+github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
+github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
+github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
+golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
+golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
+golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
+golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
+golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
+golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/config/config.go b/internal/config/config.go
new file mode 100644
index 0000000..11ac9eb
--- /dev/null
+++ b/internal/config/config.go
@@ -0,0 +1,80 @@
+package config
+
+import (
+ "os"
+
+ "gopkg.in/yaml.v3"
+)
+
+type GlobalConfig struct {
+ Token string `yaml:"token"`
+ GithubPat string `yaml:"github_pat"`
+ StoragePath string `yaml:"storage_path"`
+}
+
+type SiteConfig struct {
+ Repo string `yaml:"repo"`
+ Owner string `yaml:"owner"`
+ Branch string `yaml:"branch"`
+ Domain string `yaml:"domain"`
+}
+
+type Config struct {
+ Global GlobalConfig `yaml:"global"`
+ Sites []SiteConfig `yaml:"sites"`
+}
+
+type Error struct {
+ Field string
+ Message string
+}
+
+func (c Error) Error() string {
+ return "Config error: " + c.Field + " - " + c.Message
+}
+
+func validateConfig(config *Config) error {
+ if config.Global.Token == "" {
+ return &Error{Field: "global.token", Message: "Token is required"}
+ }
+ if config.Global.GithubPat == "" {
+ return &Error{Field: "global.github_pat", Message: "GitHub PAT is required"}
+ }
+ if config.Global.StoragePath == "" {
+ return &Error{Field: "global.storage_path", Message: "Storage path is required"}
+ }
+ for i, site := range config.Sites {
+ if site.Repo == "" {
+ return &Error{Field: "sites[" + string(i) + "].repo", Message: "Repo is required"}
+ }
+ if site.Owner == "" {
+ return &Error{Field: "sites[" + string(i) + "].owner", Message: "Owner is required"}
+ }
+ if site.Branch == "" {
+ return &Error{Field: "sites[" + string(i) + "].branch", Message: "Branch is required"}
+ }
+ if site.Domain == "" {
+ return &Error{Field: "sites[" + string(i) + "].domain", Message: "Domain is required"}
+ }
+ }
+ return nil
+}
+
+func Load(path string) (*Config, error) {
+ f, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ expanded := os.Expand(string(f), os.Getenv)
+ var config Config
+ err = yaml.Unmarshal([]byte(expanded), &config)
+ if err != nil {
+ return nil, err
+ }
+
+ err = validateConfig(&config)
+ if err != nil {
+ return nil, err
+ }
+ return &config, nil
+}
diff --git a/internal/envconfig/envconfig.go b/internal/envconfig/envconfig.go
new file mode 100644
index 0000000..8dd700b
--- /dev/null
+++ b/internal/envconfig/envconfig.go
@@ -0,0 +1,18 @@
+package envconfig
+
+import "os"
+
+type EnvConfig struct {
+ Port string
+}
+
+func Load() EnvConfig {
+ port := os.Getenv("PORT")
+ if port == "" {
+ port = "4321"
+ }
+
+ return EnvConfig{
+ Port: port,
+ }
+}
diff --git a/internal/fiberconfig/fiberconfig.go b/internal/fiberconfig/fiberconfig.go
new file mode 100644
index 0000000..45370e1
--- /dev/null
+++ b/internal/fiberconfig/fiberconfig.go
@@ -0,0 +1,18 @@
+package fiberconfig
+
+import (
+ "github.com/gofiber/fiber/v3"
+ "github.com/gofiber/fiber/v3/middleware/logger"
+)
+
+func Setup(app *fiber.App) {
+ app.Use(func(c fiber.Ctx) error {
+ c.Set("Content-Type", "application/json")
+ return c.Next()
+ })
+
+ app.Use(logger.New(logger.Config{
+ Format: "[${time}] ${status} - ${method} ${path} (${latency}) - ${ip}\n",
+ TimeFormat: "2006-01-02 15:04:05",
+ }))
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..35caf96
--- /dev/null
+++ b/main.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+ "log"
+ "quay/app/routes"
+ "quay/internal/config"
+ "quay/internal/envconfig"
+ "quay/internal/fiberconfig"
+
+ "github.com/gofiber/fiber/v3"
+ "github.com/joho/godotenv"
+)
+
+func main() {
+ _ = godotenv.Load()
+
+ envCfg := envconfig.Load()
+
+ cfg, err := config.Load("config.yaml")
+ if err != nil {
+ panic("Failed to load config: " + err.Error())
+ }
+
+ app := fiber.New()
+
+ fiberconfig.Setup(app)
+ routes.Register(app, cfg)
+
+ log.Fatal(app.Listen(":" + envCfg.Port))
+}