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)) +}