Basic REST app

This commit is contained in:
2026-03-30 18:26:45 +02:00
commit 42f9b974b2
17 changed files with 309 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
.env
config.yaml
+10
View File
@@ -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/
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Ask2AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoImports">
<option name="excludedPackages">
<array>
<option value="github.com/pkg/errors" />
<option value="golang.org/x/net/context" />
</array>
</option>
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/quay.iml" filepath="$PROJECT_DIR$/.idea/quay.iml" />
</modules>
</component>
</project>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+9
View File
@@ -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",
})
}
+5
View File
@@ -0,0 +1,5 @@
package models
type APIError struct {
Error string `json:"error"`
}
+14
View File
@@ -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)
}
+21
View File
@@ -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
+24
View File
@@ -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
)
+38
View File
@@ -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=
+80
View File
@@ -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
}
+18
View File
@@ -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,
}
}
+18
View File
@@ -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",
}))
}
+30
View File
@@ -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))
}