Add frontend #1

Merged
KartoffelChipss merged 50 commits from feature/frontend into main 2026-05-06 20:16:59 +02:00
6 changed files with 311 additions and 0 deletions
Showing only changes of commit 76e3454c4c - Show all commits
+123
View File
@@ -0,0 +1,123 @@
package handlers
import (
"database/sql"
"errors"
"log"
"quay/app/models"
"quay/app/repository"
"github.com/gofiber/fiber/v3"
)
type GitServerHandler struct {
Repo repository.GitServerRepository
}
func NewGitServerHandler(repo repository.GitServerRepository) *GitServerHandler {
return &GitServerHandler{Repo: repo}
}
func (h *GitServerHandler) GetGitServers(c fiber.Ctx) error {
gs, err := h.Repo.ListGitServers()
if err != nil {
log.Println("Error listing gitservers: ", err)
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while listing git servers"})
}
if gs == nil {
gs = []models.GitServer{}
}
return c.JSON(gs)
}
func (h *GitServerHandler) GetGitServer(c fiber.Ctx) error {
id := c.Params("id")
g, err := h.Repo.GetGitServer(id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Git server not found"})
}
log.Println("Error getting gitserver: ", err)
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while getting git server"})
}
if g == nil {
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Git server not found"})
}
return c.JSON(g)
}
func validateIncomingGitServer(gs *models.GitServer) error {
return models.ValidateGitServer(gs)
}
func (h *GitServerHandler) PostGitServer(c fiber.Ctx) error {
var gs models.GitServer
if err := c.Bind().Body(&gs); err != nil {
log.Println("Error parsing body: ", err)
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body"})
}
if err := validateIncomingGitServer(&gs); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body: " + err.Error()})
}
if err := h.Repo.CreateGitServer(&gs); err != nil {
log.Println("Error creating gitserver: ", err)
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while creating git server"})
}
return c.JSON(gs)
}
func (h *GitServerHandler) PutGitServer(c fiber.Ctx) error {
id := c.Params("id")
if _, err := h.Repo.GetGitServer(id); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Git server not found"})
}
log.Println("Error checking gitserver before update: ", err)
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while updating git server"})
}
var gs models.GitServer
if err := c.Bind().Body(&gs); err != nil {
log.Println("Error parsing body: ", err)
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body"})
}
if err := validateIncomingGitServer(&gs); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body: " + err.Error()})
}
gs.ID = id
if err := h.Repo.UpdateGitServer(&gs); err != nil {
log.Println("Error updating gitserver: ", err)
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while updating git server"})
}
updated, err := h.Repo.GetGitServer(id)
if err != nil {
log.Println("Error getting updated gitserver: ", err)
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Git server was updated but could not be retrieved"})
}
return c.JSON(updated)
}
func (h *GitServerHandler) DeleteGitServer(c fiber.Ctx) error {
id := c.Params("id")
if _, err := h.Repo.GetGitServer(id); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Git server not found"})
}
log.Println("Error checking gitserver before delete: ", err)
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while deleting git server"})
}
if err := h.Repo.DeleteGitServer(id); err != nil {
log.Println("Error deleting gitserver: ", err)
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while deleting git server"})
}
return c.SendStatus(fiber.StatusNoContent)
}
+34
View File
@@ -0,0 +1,34 @@
package models
import "errors"
type GitServer struct {
ID string `json:"id"`
Name string `json:"name"`
Protocol string `json:"protocol"`
BaseUrl string `json:"baseUrl"`
Type string `json:"type"`
CreatedAt string `json:"created_at"`
}
func ValidateGitServer(gs *GitServer) error {
if gs.Name == "" {
return errors.New("name is required")
}
if gs.Protocol == "" {
return errors.New("protocol is required")
}
if gs.Protocol != "http" && gs.Protocol != "https" {
return errors.New("protocol must be either 'http' or 'https'")
}
if gs.BaseUrl == "" {
return errors.New("baseUrl is required")
}
if gs.Type == "" {
return errors.New("type is required")
}
if gs.Type != "github" && gs.Type != "gitlab" && gs.Type != "gitea" {
return errors.New("type must be either 'github', 'gitlab' or 'gitea'")
}
return nil
}
@@ -0,0 +1,16 @@
package repository
import (
"errors"
"quay/app/models"
)
var ErrGitServerAlreadyExists = errors.New("gitserver already exists")
type GitServerRepository interface {
ListGitServers() ([]models.GitServer, error)
GetGitServer(id string) (*models.GitServer, error)
CreateGitServer(gs *models.GitServer) error
UpdateGitServer(gs *models.GitServer) error
DeleteGitServer(id string) error
}
+24
View File
@@ -19,10 +19,27 @@ func Register(app *fiber.App, cfg *config.Config, envCfg *envconfig.EnvConfig, d
siteRepository := cachedrepo.NewCachedSiteRepository(database.NewSQLiteSiteRepository(db)) siteRepository := cachedrepo.NewCachedSiteRepository(database.NewSQLiteSiteRepository(db))
deploymentRepository := database.NewSQLiteDeploymentRepository(db) deploymentRepository := database.NewSQLiteDeploymentRepository(db)
userRepository := database.NewSQLiteUserRepository(db) userRepository := database.NewSQLiteUserRepository(db)
gitServerRepository := database.NewSQLiteGitServerRepository(db)
// Seed default git servers if none exist
if gsList, err := gitServerRepository.ListGitServers(); err != nil {
log.Printf("Warning checking gitservers: %v", err)
} else if len(gsList) == 0 {
defaults := []models.GitServer{
{Name: "GitHub", Protocol: "https", BaseUrl: "github.com", Type: "github"},
{Name: "GitLab", Protocol: "https", BaseUrl: "gitlab.com", Type: "gitlab"},
}
for _, d := range defaults {
if err := gitServerRepository.CreateGitServer(&d); err != nil {
log.Printf("Warning creating default gitserver %s: %v", d.Name, err)
}
}
}
siteHandler := handlers.NewSiteHandler(siteRepository) siteHandler := handlers.NewSiteHandler(siteRepository)
deploySiteHandler := handlers.NewDeploySiteHandler(envCfg, siteRepository, deploymentRepository) deploySiteHandler := handlers.NewDeploySiteHandler(envCfg, siteRepository, deploymentRepository)
userHandler := handlers.NewUserHandler(userRepository) userHandler := handlers.NewUserHandler(userRepository)
gitServerHandler := handlers.NewGitServerHandler(gitServerRepository)
deploymentsHandler := handlers.NewDeploymentHandler(deploymentRepository) deploymentsHandler := handlers.NewDeploymentHandler(deploymentRepository)
api := app.Group("/api/v1", middleware.APIHostGuard(envCfg.DashboardHost)) api := app.Group("/api/v1", middleware.APIHostGuard(envCfg.DashboardHost))
@@ -46,6 +63,13 @@ func Register(app *fiber.App, cfg *config.Config, envCfg *envconfig.EnvConfig, d
protected.Delete("/sites/:id", siteHandler.DeleteSite) protected.Delete("/sites/:id", siteHandler.DeleteSite)
protected.Patch("/sites/:id/enabled", siteHandler.ToggleEnabled) protected.Patch("/sites/:id/enabled", siteHandler.ToggleEnabled)
// Git servers
protected.Get("/gitservers", gitServerHandler.GetGitServers)
protected.Get("/gitservers/:id", gitServerHandler.GetGitServer)
protected.Post("/gitservers", gitServerHandler.PostGitServer)
protected.Put("/gitservers/:id", gitServerHandler.PutGitServer)
protected.Delete("/gitservers/:id", gitServerHandler.DeleteGitServer)
// Forward rules // Forward rules
protected.Get("/sites/:id/forward-rules", siteHandler.GetSiteForwardRules) protected.Get("/sites/:id/forward-rules", siteHandler.GetSiteForwardRules)
protected.Post("/sites/:id/forward-rules", siteHandler.PostForwardRule) protected.Post("/sites/:id/forward-rules", siteHandler.PostForwardRule)
@@ -0,0 +1,104 @@
package database
import (
"database/sql"
"fmt"
"quay/app/models"
"quay/app/repository"
"github.com/google/uuid"
)
type SQLiteGitServerRepository struct {
db *sql.DB
}
func NewSQLiteGitServerRepository(db *sql.DB) *SQLiteGitServerRepository {
return &SQLiteGitServerRepository{db: db}
}
var _ repository.GitServerRepository = (*SQLiteGitServerRepository)(nil)
func (r *SQLiteGitServerRepository) ListGitServers() ([]models.GitServer, error) {
rows, err := r.db.Query(`SELECT id, name, protocol, base_url, type, created_at FROM gitservers`)
if err != nil {
return nil, fmt.Errorf("list gitservers: %w", err)
}
var out []models.GitServer
for rows.Next() {
gs, err := scanGitServer(rows)
if err != nil {
rows.Close()
return nil, fmt.Errorf("list gitservers scan: %w", err)
}
out = append(out, *gs)
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
return out, nil
}
func (r *SQLiteGitServerRepository) GetGitServer(id string) (*models.GitServer, error) {
row := r.db.QueryRow(`SELECT id, name, protocol, base_url, type, created_at FROM gitservers WHERE id = ?`, id)
return scanGitServer(row)
}
func (r *SQLiteGitServerRepository) CreateGitServer(gs *models.GitServer) error {
tx, err := r.db.Begin()
if err != nil {
return fmt.Errorf("create gitserver begin tx: %w", err)
}
defer tx.Rollback()
gs.ID = uuid.NewString()
_, err = tx.Exec(`INSERT INTO gitservers (id, name, protocol, base_url, type) VALUES (?, ?, ?, ?, ?)`,
gs.ID, gs.Name, gs.Protocol, gs.BaseUrl, gs.Type)
if err != nil {
if isSQLiteUniqueConstraintError(err) {
return fmt.Errorf("create gitserver insert: %w", repository.ErrGitServerAlreadyExists)
}
return fmt.Errorf("create gitserver insert: %w", err)
}
return tx.Commit()
}
func (r *SQLiteGitServerRepository) UpdateGitServer(gs *models.GitServer) error {
tx, err := r.db.Begin()
if err != nil {
return fmt.Errorf("update gitserver begin tx: %w", err)
}
defer tx.Rollback()
_, err = tx.Exec(`UPDATE gitservers SET name = ?, protocol = ?, base_url = ?, type = ? WHERE id = ?`,
gs.Name, gs.Protocol, gs.BaseUrl, gs.Type, gs.ID)
if err != nil {
if isSQLiteUniqueConstraintError(err) {
return fmt.Errorf("update gitserver: %w", repository.ErrGitServerAlreadyExists)
}
return fmt.Errorf("update gitserver: %w", err)
}
return tx.Commit()
}
func (r *SQLiteGitServerRepository) DeleteGitServer(id string) error {
_, err := r.db.Exec(`DELETE FROM gitservers WHERE id = ?`, id)
if err != nil {
return fmt.Errorf("delete gitserver: %w", err)
}
return nil
}
func scanGitServer(s scanner) (*models.GitServer, error) {
g := new(models.GitServer)
err := s.Scan(&g.ID, &g.Name, &g.Protocol, &g.BaseUrl, &g.Type, &g.CreatedAt)
if err != nil {
return nil, err
}
return g, nil
}
+10
View File
@@ -68,6 +68,16 @@ CREATE TABLE IF NOT EXISTS users (
hashed_password TEXT NOT NULL, hashed_password TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) )
;
CREATE TABLE IF NOT EXISTS gitservers (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
protocol TEXT NOT NULL,
base_url TEXT NOT NULL,
type TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
`) `)
if err == nil { if err == nil {