Added frontend
This commit is contained in:
@@ -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",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"log"
|
||||
"quay/app/models"
|
||||
"quay/app/repository"
|
||||
"quay/internal/security"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
type SiteHandler struct {
|
||||
Repo repository.SiteRepository
|
||||
}
|
||||
|
||||
func NewSiteHandler(repo repository.SiteRepository) *SiteHandler {
|
||||
return &SiteHandler{Repo: repo}
|
||||
}
|
||||
|
||||
// GetSites godoc
|
||||
// @Summary Get all sites
|
||||
// @Description Get a list of all sites
|
||||
// @Tags Sites
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.GetAllSitesResponse
|
||||
// @Failure 500 {object} models.APIError
|
||||
// @Router /sites [get]
|
||||
func (h *SiteHandler) GetSites(c fiber.Ctx) error {
|
||||
sites, err := h.Repo.ListSites()
|
||||
|
||||
if err != nil {
|
||||
log.Println("Error listing sites: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{
|
||||
Message: "Unexpected error while listing sites",
|
||||
})
|
||||
}
|
||||
|
||||
if sites == nil {
|
||||
sites = []models.Site{}
|
||||
}
|
||||
|
||||
return c.JSON(models.GetAllSitesResponse{
|
||||
Sites: sites,
|
||||
Total: len(sites),
|
||||
})
|
||||
}
|
||||
|
||||
// GetSite godoc
|
||||
// @Summary Get site by ID
|
||||
// @Description Get a single site by its ID
|
||||
// @Tags Sites
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Site ID"
|
||||
// @Success 200 {object} models.Site
|
||||
// @Failure 404 {object} models.APIError
|
||||
// @Failure 500 {object} models.APIError
|
||||
// @Router /sites/{id} [get]
|
||||
func (h *SiteHandler) GetSite(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
site, err := h.Repo.GetSite(id)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{
|
||||
Message: "Site not found",
|
||||
})
|
||||
}
|
||||
log.Println("Message getting site: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{
|
||||
Message: "Unexpected error while getting site",
|
||||
})
|
||||
}
|
||||
|
||||
if site == nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{
|
||||
Message: "Site not found",
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(site)
|
||||
}
|
||||
|
||||
func validateIncomingSite(site *models.Site) error {
|
||||
if site == nil {
|
||||
return errors.New("site is required")
|
||||
}
|
||||
if site.GitServer == "" {
|
||||
return errors.New("git server required")
|
||||
}
|
||||
if site.Owner == "" {
|
||||
return errors.New("owner required")
|
||||
}
|
||||
if site.Repository == "" {
|
||||
return errors.New("repository required")
|
||||
}
|
||||
if site.Branch == "" {
|
||||
return errors.New("branch required")
|
||||
}
|
||||
if site.Domain == "" {
|
||||
return errors.New("domain required")
|
||||
}
|
||||
if site.NotFoundFile == "" {
|
||||
site.NotFoundFile = "404.html"
|
||||
}
|
||||
if site.ForwardRules == nil {
|
||||
site.ForwardRules = []models.ForwardRule{}
|
||||
}
|
||||
if site.ForwardRules != nil {
|
||||
for _, r := range site.ForwardRules {
|
||||
if r.Source == "" {
|
||||
return errors.New("forward rule source is required")
|
||||
}
|
||||
if r.Destination == "" {
|
||||
return errors.New("forward rule destination is required")
|
||||
}
|
||||
if r.StatusCode < 300 || r.StatusCode > 399 {
|
||||
return errors.New("forward rule status code must be between 300 and 399")
|
||||
}
|
||||
}
|
||||
}
|
||||
if site.CustomHeaders == nil {
|
||||
site.CustomHeaders = []models.CustomHeaders{}
|
||||
}
|
||||
if site.CustomHeaders != nil {
|
||||
for _, h := range site.CustomHeaders {
|
||||
if h.Source == "" {
|
||||
return errors.New("custom header source required")
|
||||
}
|
||||
if h.Headers == nil {
|
||||
return errors.New("custom header is required")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostSite godoc
|
||||
// @Summary Create a new site
|
||||
// @Description Create a new site with the provided details
|
||||
// @Tags Sites
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param site body models.Site true "Site details"
|
||||
// @Success 200 {object} models.CreateSiteResponse
|
||||
// @Failure 400 {object} models.APIError
|
||||
// @Failure 500 {object} models.APIError
|
||||
// @Router /sites [post]
|
||||
func (h *SiteHandler) PostSite(c fiber.Ctx) error {
|
||||
var site models.Site
|
||||
|
||||
if err := c.Bind().Body(&site); err != nil {
|
||||
log.Println("Error parsing body: ", err)
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{
|
||||
Message: "Invalid request body",
|
||||
})
|
||||
}
|
||||
|
||||
if err := validateIncomingSite(&site); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{
|
||||
Message: "Invalid request body: " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
rawDeployToken, hashedDeployToken, err := security.CreateDeployToken()
|
||||
|
||||
if err != nil {
|
||||
log.Println("Error creating deploy token: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{
|
||||
Message: "Unexpected error while creating deploy token: " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
site.DeployToken = hashedDeployToken
|
||||
|
||||
if err := h.Repo.CreateSite(&site); err != nil {
|
||||
log.Println("Error creating site: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{
|
||||
Message: "Unexpected error while creating site",
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(&models.CreateSiteResponse{
|
||||
Site: site,
|
||||
RawDeployToken: rawDeployToken,
|
||||
})
|
||||
}
|
||||
|
||||
// PutSite godoc
|
||||
// @Summary Update an existing site
|
||||
// @Description Update an existing site by its ID
|
||||
// @Tags Sites
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Site ID"
|
||||
// @Param site body models.Site true "Updated site details"
|
||||
// @Success 200 {object} models.Site
|
||||
// @Failure 400 {object} models.APIError
|
||||
// @Failure 404 {object} models.APIError
|
||||
// @Failure 500 {object} models.APIError
|
||||
// @Router /sites/{id} [put]
|
||||
func (h *SiteHandler) PutSite(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
if _, err := h.Repo.GetSite(id); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{
|
||||
Message: "Site not found",
|
||||
})
|
||||
}
|
||||
log.Println("Error checking site before update: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{
|
||||
Message: "Unexpected error while updating site",
|
||||
})
|
||||
}
|
||||
|
||||
var site models.Site
|
||||
if err := c.Bind().Body(&site); err != nil {
|
||||
log.Println("Error parsing body: ", err)
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{
|
||||
Message: "Invalid request body",
|
||||
})
|
||||
}
|
||||
|
||||
if err := validateIncomingSite(&site); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{
|
||||
Message: "Invalid request body: " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
site.ID = id
|
||||
if err := h.Repo.UpdateSite(&site); err != nil {
|
||||
log.Println("Error updating site: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{
|
||||
Message: "Unexpected error while updating site",
|
||||
})
|
||||
}
|
||||
|
||||
updatedSite, err := h.Repo.GetSite(id)
|
||||
if err != nil {
|
||||
log.Println("Error getting updated site: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{
|
||||
Message: "Site was updated but could not be retrieved",
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(updatedSite)
|
||||
}
|
||||
|
||||
// DeleteSite godoc
|
||||
// @Summary Delete a site
|
||||
// @Description Delete a site by its ID
|
||||
// @Tags Sites
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Site ID"
|
||||
// @Success 204
|
||||
// @Failure 404 {object} models.APIError
|
||||
// @Failure 500 {object} models.APIError
|
||||
// @Router /sites/{id} [delete]
|
||||
func (h *SiteHandler) DeleteSite(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
if _, err := h.Repo.GetSite(id); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{
|
||||
Message: "Site not found",
|
||||
})
|
||||
}
|
||||
log.Println("Error checking site before delete: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{
|
||||
Message: "Unexpected error while deleting site",
|
||||
})
|
||||
}
|
||||
|
||||
if err := h.Repo.DeleteSite(id); err != nil {
|
||||
log.Println("Error deleting site: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{
|
||||
Message: "Unexpected error while deleting site",
|
||||
})
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"log"
|
||||
"quay/app/models"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
func validateForwardRule(rule *models.ForwardRule) error {
|
||||
if rule == nil {
|
||||
return errors.New("forward rule is required")
|
||||
}
|
||||
if rule.Source == "" {
|
||||
return errors.New("forward rule source is required")
|
||||
}
|
||||
if rule.Destination == "" {
|
||||
return errors.New("forward rule destination is required")
|
||||
}
|
||||
if rule.StatusCode < 300 || rule.StatusCode > 399 {
|
||||
return errors.New("forward rule status code must be between 300 and 399")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateHeader(header *models.Header) error {
|
||||
if header == nil {
|
||||
return errors.New("header is required")
|
||||
}
|
||||
if header.Key == "" {
|
||||
return errors.New("header key is required")
|
||||
}
|
||||
if header.Value == "" {
|
||||
return errors.New("header value is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCustomHeaders(customHeaders *models.CustomHeaders) error {
|
||||
if customHeaders == nil {
|
||||
return errors.New("custom headers are required")
|
||||
}
|
||||
if customHeaders.Source == "" {
|
||||
return errors.New("custom header source required")
|
||||
}
|
||||
if customHeaders.Headers == nil {
|
||||
customHeaders.Headers = []models.Header{}
|
||||
}
|
||||
for i := range customHeaders.Headers {
|
||||
if err := validateHeader(&customHeaders.Headers[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSiteForwardRules godoc
|
||||
//
|
||||
// @Summary List forward rules for a site
|
||||
// @Description Returns all forward rules associated with the given site
|
||||
// @Tags Forward-Rules
|
||||
// @Produce json
|
||||
// @Param id path string true "Site ID"
|
||||
// @Success 200 {array} models.ForwardRule "List of forward rules"
|
||||
// @Failure 404 {object} models.APIError "Site not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /sites/{id}/forward-rules [get]
|
||||
func (h *SiteHandler) GetSiteForwardRules(c fiber.Ctx) error {
|
||||
siteID := c.Params("id")
|
||||
|
||||
site, err := h.Repo.GetSite(siteID)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Site not found"})
|
||||
}
|
||||
log.Println("Error getting site forward rules: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while listing forward rules"})
|
||||
}
|
||||
|
||||
if site.ForwardRules == nil {
|
||||
site.ForwardRules = []models.ForwardRule{}
|
||||
}
|
||||
return c.JSON(site.ForwardRules)
|
||||
}
|
||||
|
||||
// GetForwardRule godoc
|
||||
//
|
||||
// @Summary Get a forward rule
|
||||
// @Description Returns a single forward rule by ID
|
||||
// @Tags Forward-Rules
|
||||
// @Produce json
|
||||
// @Param id path string true "Forward Rule ID"
|
||||
// @Success 200 {object} models.ForwardRule "Forward rule"
|
||||
// @Failure 404 {object} models.APIError "Forward rule not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /forward-rules/{id} [get]
|
||||
func (h *SiteHandler) GetForwardRule(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
rule, err := h.Repo.GetForwardRule(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Forward rule not found"})
|
||||
}
|
||||
log.Println("Error getting forward rule: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while getting forward rule"})
|
||||
}
|
||||
|
||||
return c.JSON(rule)
|
||||
}
|
||||
|
||||
// PostForwardRule godoc
|
||||
//
|
||||
// @Summary Create a forward rule
|
||||
// @Description Creates a new forward rule for the given site
|
||||
// @Tags Forward-Rules
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Site ID"
|
||||
// @Param rule body models.ForwardRule true "Forward rule to create"
|
||||
// @Success 200 {object} models.ForwardRule "Created forward rule"
|
||||
// @Failure 400 {object} models.APIError "Invalid request body"
|
||||
// @Failure 404 {object} models.APIError "Site not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /sites/{id}/forward-rules [post]
|
||||
func (h *SiteHandler) PostForwardRule(c fiber.Ctx) error {
|
||||
siteID := c.Params("id")
|
||||
|
||||
if _, err := h.Repo.GetSite(siteID); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Site not found"})
|
||||
}
|
||||
log.Println("Error checking site before creating forward rule: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while creating forward rule"})
|
||||
}
|
||||
|
||||
var rule models.ForwardRule
|
||||
if err := c.Bind().Body(&rule); err != nil {
|
||||
log.Println("Error parsing body: ", err)
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body"})
|
||||
}
|
||||
|
||||
if err := validateForwardRule(&rule); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body: " + err.Error()})
|
||||
}
|
||||
|
||||
if err := h.Repo.CreateForwardRule(siteID, &rule); err != nil {
|
||||
log.Println("Error creating forward rule: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while creating forward rule"})
|
||||
}
|
||||
|
||||
return c.JSON(rule)
|
||||
}
|
||||
|
||||
// PutForwardRule godoc
|
||||
//
|
||||
// @Summary Update a forward rule
|
||||
// @Description Replaces an existing forward rule by ID
|
||||
// @Tags Forward-Rules
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Forward Rule ID"
|
||||
// @Param rule body models.ForwardRule true "Updated forward rule"
|
||||
// @Success 200 {object} models.ForwardRule "Updated forward rule"
|
||||
// @Failure 400 {object} models.APIError "Invalid request body"
|
||||
// @Failure 404 {object} models.APIError "Forward rule not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /forward-rules/{id} [put]
|
||||
func (h *SiteHandler) PutForwardRule(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
if _, err := h.Repo.GetForwardRule(id); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Forward rule not found"})
|
||||
}
|
||||
log.Println("Error checking forward rule before update: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while updating forward rule"})
|
||||
}
|
||||
|
||||
var rule models.ForwardRule
|
||||
if err := c.Bind().Body(&rule); err != nil {
|
||||
log.Println("Error parsing body: ", err)
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body"})
|
||||
}
|
||||
|
||||
if err := validateForwardRule(&rule); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body: " + err.Error()})
|
||||
}
|
||||
|
||||
rule.ID = id
|
||||
if err := h.Repo.UpdateForwardRule(&rule); err != nil {
|
||||
log.Println("Error updating forward rule: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while updating forward rule"})
|
||||
}
|
||||
|
||||
updated, err := h.Repo.GetForwardRule(id)
|
||||
if err != nil {
|
||||
log.Println("Error getting updated forward rule: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Forward rule was updated but could not be retrieved"})
|
||||
}
|
||||
|
||||
return c.JSON(updated)
|
||||
}
|
||||
|
||||
// DeleteForwardRule godoc
|
||||
//
|
||||
// @Summary Delete a forward rule
|
||||
// @Description Deletes a forward rule by ID
|
||||
// @Tags Forward-Rules
|
||||
// @Produce json
|
||||
// @Param id path string true "Forward Rule ID"
|
||||
// @Success 204 "No content"
|
||||
// @Failure 404 {object} models.APIError "Forward rule not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /forward-rules/{id} [delete]
|
||||
func (h *SiteHandler) DeleteForwardRule(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
if _, err := h.Repo.GetForwardRule(id); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Forward rule not found"})
|
||||
}
|
||||
log.Println("Error checking forward rule before delete: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while deleting forward rule"})
|
||||
}
|
||||
|
||||
if err := h.Repo.DeleteForwardRule(id); err != nil {
|
||||
log.Println("Error deleting forward rule: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while deleting forward rule"})
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetSiteCustomHeaders godoc
|
||||
//
|
||||
// @Summary List custom header groups for a site
|
||||
// @Description Returns all custom header groups associated with the given site
|
||||
// @Tags Custom-Headers
|
||||
// @Produce json
|
||||
// @Param id path string true "Site ID"
|
||||
// @Success 200 {array} models.CustomHeaders "List of custom header groups"
|
||||
// @Failure 404 {object} models.APIError "Site not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /sites/{id}/custom-headers [get]
|
||||
func (h *SiteHandler) GetSiteCustomHeaders(c fiber.Ctx) error {
|
||||
siteID := c.Params("id")
|
||||
|
||||
site, err := h.Repo.GetSite(siteID)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Site not found"})
|
||||
}
|
||||
log.Println("Error getting site custom headers: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while listing custom headers"})
|
||||
}
|
||||
|
||||
if site.CustomHeaders == nil {
|
||||
site.CustomHeaders = []models.CustomHeaders{}
|
||||
}
|
||||
return c.JSON(site.CustomHeaders)
|
||||
}
|
||||
|
||||
// GetCustomHeaders godoc
|
||||
//
|
||||
// @Summary Get a custom header group
|
||||
// @Description Returns a single custom header group by ID
|
||||
// @Tags Custom-Headers
|
||||
// @Produce json
|
||||
// @Param id path string true "Custom Headers ID"
|
||||
// @Success 200 {object} models.CustomHeaders "Custom header group"
|
||||
// @Failure 404 {object} models.APIError "Custom headers not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /custom-headers/{id} [get]
|
||||
func (h *SiteHandler) GetCustomHeaders(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
customHeaders, err := h.Repo.GetCustomHeaders(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Custom headers not found"})
|
||||
}
|
||||
log.Println("Error getting custom headers: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while getting custom headers"})
|
||||
}
|
||||
|
||||
return c.JSON(customHeaders)
|
||||
}
|
||||
|
||||
// PostCustomHeaders godoc
|
||||
//
|
||||
// @Summary Create a custom header group
|
||||
// @Description Creates a new custom header group for the given site
|
||||
// @Tags Custom-Headers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Site ID"
|
||||
// @Param customHeaders body models.CustomHeaders true "Custom header group to create"
|
||||
// @Success 200 {object} models.CustomHeaders "Created custom header group"
|
||||
// @Failure 400 {object} models.APIError "Invalid request body"
|
||||
// @Failure 404 {object} models.APIError "Site not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /sites/{id}/custom-headers [post]
|
||||
func (h *SiteHandler) PostCustomHeaders(c fiber.Ctx) error {
|
||||
siteID := c.Params("id")
|
||||
|
||||
if _, err := h.Repo.GetSite(siteID); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Site not found"})
|
||||
}
|
||||
log.Println("Error checking site before creating custom headers: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while creating custom headers"})
|
||||
}
|
||||
|
||||
var customHeaders models.CustomHeaders
|
||||
if err := c.Bind().Body(&customHeaders); err != nil {
|
||||
log.Println("Error parsing body: ", err)
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body"})
|
||||
}
|
||||
|
||||
if err := validateCustomHeaders(&customHeaders); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body: " + err.Error()})
|
||||
}
|
||||
|
||||
if err := h.Repo.CreateCustomHeaders(siteID, &customHeaders); err != nil {
|
||||
log.Println("Error creating custom headers: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while creating custom headers"})
|
||||
}
|
||||
|
||||
return c.JSON(customHeaders)
|
||||
}
|
||||
|
||||
// PutCustomHeaders godoc
|
||||
//
|
||||
// @Summary Update a custom header group
|
||||
// @Description Replaces an existing custom header group by ID
|
||||
// @Tags Custom-Headers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Custom Headers ID"
|
||||
// @Param customHeaders body models.CustomHeaders true "Updated custom header group"
|
||||
// @Success 200 {object} models.CustomHeaders "Updated custom header group"
|
||||
// @Failure 400 {object} models.APIError "Invalid request body"
|
||||
// @Failure 404 {object} models.APIError "Custom headers not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /custom-headers/{id} [put]
|
||||
func (h *SiteHandler) PutCustomHeaders(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
if _, err := h.Repo.GetCustomHeaders(id); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Custom headers not found"})
|
||||
}
|
||||
log.Println("Error checking custom headers before update: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while updating custom headers"})
|
||||
}
|
||||
|
||||
var customHeaders models.CustomHeaders
|
||||
if err := c.Bind().Body(&customHeaders); err != nil {
|
||||
log.Println("Error parsing body: ", err)
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body"})
|
||||
}
|
||||
|
||||
if err := validateCustomHeaders(&customHeaders); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body: " + err.Error()})
|
||||
}
|
||||
|
||||
customHeaders.ID = id
|
||||
if err := h.Repo.UpdateCustomHeaders(&customHeaders); err != nil {
|
||||
log.Println("Error updating custom headers: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while updating custom headers"})
|
||||
}
|
||||
|
||||
updated, err := h.Repo.GetCustomHeaders(id)
|
||||
if err != nil {
|
||||
log.Println("Error getting updated custom headers: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Custom headers were updated but could not be retrieved"})
|
||||
}
|
||||
|
||||
return c.JSON(updated)
|
||||
}
|
||||
|
||||
// DeleteCustomHeaders godoc
|
||||
//
|
||||
// @Summary Delete a custom header group
|
||||
// @Description Deletes a custom header group by ID
|
||||
// @Tags Custom-Headers
|
||||
// @Produce json
|
||||
// @Param id path string true "Custom Headers ID"
|
||||
// @Success 204 "No content"
|
||||
// @Failure 404 {object} models.APIError "Custom headers not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /custom-headers/{id} [delete]
|
||||
func (h *SiteHandler) DeleteCustomHeaders(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
if _, err := h.Repo.GetCustomHeaders(id); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Custom headers not found"})
|
||||
}
|
||||
log.Println("Error checking custom headers before delete: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while deleting custom headers"})
|
||||
}
|
||||
|
||||
if err := h.Repo.DeleteCustomHeaders(id); err != nil {
|
||||
log.Println("Error deleting custom headers: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while deleting custom headers"})
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetCustomHeaderHeaders godoc
|
||||
//
|
||||
// @Summary List headers in a custom header group
|
||||
// @Description Returns all individual headers belonging to the given custom header group
|
||||
// @Tags Custom-Headers
|
||||
// @Produce json
|
||||
// @Param id path string true "Custom Headers ID"
|
||||
// @Success 200 {array} models.Header "List of headers"
|
||||
// @Failure 404 {object} models.APIError "Custom headers not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /custom-headers/{id}/headers [get]
|
||||
func (h *SiteHandler) GetCustomHeaderHeaders(c fiber.Ctx) error {
|
||||
customHeaderID := c.Params("id")
|
||||
|
||||
customHeaders, err := h.Repo.GetCustomHeaders(customHeaderID)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Custom headers not found"})
|
||||
}
|
||||
log.Println("Error listing headers: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while listing headers"})
|
||||
}
|
||||
|
||||
if customHeaders.Headers == nil {
|
||||
customHeaders.Headers = []models.Header{}
|
||||
}
|
||||
return c.JSON(customHeaders.Headers)
|
||||
}
|
||||
|
||||
// GetHeader godoc
|
||||
//
|
||||
// @Summary Get a header
|
||||
// @Description Returns a single header by ID
|
||||
// @Tags Headers
|
||||
// @Produce json
|
||||
// @Param id path string true "Header ID"
|
||||
// @Success 200 {object} models.Header "Header"
|
||||
// @Failure 404 {object} models.APIError "Header not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /headers/{id} [get]
|
||||
func (h *SiteHandler) GetHeader(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
header, err := h.Repo.GetHeader(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Header not found"})
|
||||
}
|
||||
log.Println("Error getting header: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while getting header"})
|
||||
}
|
||||
|
||||
return c.JSON(header)
|
||||
}
|
||||
|
||||
// PostHeader godoc
|
||||
//
|
||||
// @Summary Create a header
|
||||
// @Description Creates a new header within the given custom header group
|
||||
// @Tags Headers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Custom Headers ID"
|
||||
// @Param header body models.Header true "Header to create"
|
||||
// @Success 200 {object} models.Header "Created header"
|
||||
// @Failure 400 {object} models.APIError "Invalid request body"
|
||||
// @Failure 404 {object} models.APIError "Custom headers not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /custom-headers/{id}/headers [post]
|
||||
func (h *SiteHandler) PostHeader(c fiber.Ctx) error {
|
||||
customHeaderID := c.Params("id")
|
||||
|
||||
if _, err := h.Repo.GetCustomHeaders(customHeaderID); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Custom headers not found"})
|
||||
}
|
||||
log.Println("Error checking custom headers before creating header: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while creating header"})
|
||||
}
|
||||
|
||||
var header models.Header
|
||||
if err := c.Bind().Body(&header); err != nil {
|
||||
log.Println("Error parsing body: ", err)
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body"})
|
||||
}
|
||||
|
||||
if err := validateHeader(&header); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body: " + err.Error()})
|
||||
}
|
||||
|
||||
if err := h.Repo.CreateHeader(customHeaderID, &header); err != nil {
|
||||
log.Println("Error creating header: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while creating header"})
|
||||
}
|
||||
|
||||
return c.JSON(header)
|
||||
}
|
||||
|
||||
// PutHeader godoc
|
||||
//
|
||||
// @Summary Update a header
|
||||
// @Description Replaces an existing header by ID
|
||||
// @Tags Headers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Header ID"
|
||||
// @Param header body models.Header true "Updated header"
|
||||
// @Success 200 {object} models.Header "Updated header"
|
||||
// @Failure 400 {object} models.APIError "Invalid request body"
|
||||
// @Failure 404 {object} models.APIError "Header not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /headers/{id} [put]
|
||||
func (h *SiteHandler) PutHeader(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
if _, err := h.Repo.GetHeader(id); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Header not found"})
|
||||
}
|
||||
log.Println("Error checking header before update: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while updating header"})
|
||||
}
|
||||
|
||||
var header models.Header
|
||||
if err := c.Bind().Body(&header); err != nil {
|
||||
log.Println("Error parsing body: ", err)
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body"})
|
||||
}
|
||||
|
||||
if err := validateHeader(&header); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(&models.APIError{Message: "Invalid request body: " + err.Error()})
|
||||
}
|
||||
|
||||
header.ID = id
|
||||
if err := h.Repo.UpdateHeader(&header); err != nil {
|
||||
log.Println("Error updating header: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while updating header"})
|
||||
}
|
||||
|
||||
updated, err := h.Repo.GetHeader(id)
|
||||
if err != nil {
|
||||
log.Println("Error getting updated header: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Header was updated but could not be retrieved"})
|
||||
}
|
||||
|
||||
return c.JSON(updated)
|
||||
}
|
||||
|
||||
// DeleteHeader godoc
|
||||
//
|
||||
// @Summary Delete a header
|
||||
// @Description Deletes a header by ID
|
||||
// @Tags Headers
|
||||
// @Produce json
|
||||
// @Param id path string true "Header ID"
|
||||
// @Success 204 "No content"
|
||||
// @Failure 404 {object} models.APIError "Header not found"
|
||||
// @Failure 500 {object} models.APIError "Internal server error"
|
||||
// @Router /headers/{id} [delete]
|
||||
func (h *SiteHandler) DeleteHeader(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
if _, err := h.Repo.GetHeader(id); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{Message: "Header not found"})
|
||||
}
|
||||
log.Println("Error checking header before delete: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while deleting header"})
|
||||
}
|
||||
|
||||
if err := h.Repo.DeleteHeader(id); err != nil {
|
||||
log.Println("Error deleting header: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{Message: "Unexpected error while deleting header"})
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"quay/app/repository"
|
||||
"regexp"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
func NewStaticHandler(storagePath string, siteRepo repository.SiteRepository) fiber.Handler {
|
||||
return func(c fiber.Ctx) error {
|
||||
domain := c.Hostname()
|
||||
site, err := siteRepo.GetSiteByDomain(domain)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to resolve site")
|
||||
}
|
||||
|
||||
if site == nil {
|
||||
return c.Status(fiber.StatusNotFound).SendString("Site not found")
|
||||
}
|
||||
|
||||
if !site.Enabled {
|
||||
return c.Status(fiber.StatusServiceUnavailable).SendString("Site is currently unavailable")
|
||||
}
|
||||
|
||||
urlPath := filepath.Clean(c.Path())
|
||||
|
||||
for _, rule := range site.ForwardRules {
|
||||
if rule.Regex {
|
||||
re, err := regexp.Compile(rule.Source)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
match := re.FindStringSubmatchIndex(urlPath)
|
||||
if match != nil {
|
||||
dest := re.ExpandString([]byte{}, rule.Destination, urlPath, match)
|
||||
return c.Redirect().Status(rule.StatusCode).To(string(dest))
|
||||
}
|
||||
} else if rule.Source == urlPath {
|
||||
return c.Redirect().Status(rule.StatusCode).To(rule.Destination)
|
||||
}
|
||||
}
|
||||
|
||||
for _, customHeader := range site.CustomHeaders {
|
||||
var matched bool
|
||||
if customHeader.Regex {
|
||||
re, err := regexp.Compile(customHeader.Source)
|
||||
if err == nil {
|
||||
matched = re.MatchString(urlPath)
|
||||
}
|
||||
} else {
|
||||
matched, _ = path.Match(customHeader.Source, urlPath)
|
||||
}
|
||||
if matched {
|
||||
for _, header := range customHeader.Headers {
|
||||
c.Set(header.Key, header.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if urlPath == "/" || urlPath == "." {
|
||||
urlPath = "/index.html"
|
||||
}
|
||||
|
||||
basePath := filepath.Join(storagePath, site.ID)
|
||||
filePath := filepath.Join(basePath, urlPath)
|
||||
|
||||
info, err := os.Stat(filePath)
|
||||
if err == nil {
|
||||
if info.IsDir() {
|
||||
indexPath := filepath.Join(filePath, "index.html")
|
||||
if _, err := os.Stat(indexPath); err == nil {
|
||||
return c.SendFile(indexPath)
|
||||
}
|
||||
} else {
|
||||
return c.SendFile(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
if site.Spa {
|
||||
indexPath := filepath.Join(basePath, "index.html")
|
||||
if _, err := os.Stat(indexPath); err == nil {
|
||||
return c.SendFile(indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
if site.NotFoundFile != "" {
|
||||
notFoundPath := filepath.Join(basePath, site.NotFoundFile)
|
||||
if _, err := os.Stat(notFoundPath); err == nil {
|
||||
return c.SendFile(notFoundPath)
|
||||
}
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"quay/app/github"
|
||||
"quay/app/models"
|
||||
"quay/app/repository"
|
||||
"quay/internal/envconfig"
|
||||
"quay/internal/security"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
type UpdateSiteHandler struct {
|
||||
EnvCfg *envconfig.EnvConfig
|
||||
SiteRepo repository.SiteRepository
|
||||
}
|
||||
|
||||
func NewUpdateSiteHandler(envCfg *envconfig.EnvConfig, siteRepo repository.SiteRepository) *UpdateSiteHandler {
|
||||
return &UpdateSiteHandler{EnvCfg: envCfg, SiteRepo: siteRepo}
|
||||
}
|
||||
|
||||
func (h *UpdateSiteHandler) PostUpdate(c fiber.Ctx) error {
|
||||
siteId := c.Query("site")
|
||||
if siteId == "" {
|
||||
return c.Status(400).JSON(models.APIError{
|
||||
Message: "Missing 'site' query parameter",
|
||||
})
|
||||
}
|
||||
|
||||
site, err := h.SiteRepo.GetSite(siteId)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{
|
||||
Message: "Site not found",
|
||||
})
|
||||
}
|
||||
log.Println("Message getting site: ", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{
|
||||
Message: "Unexpected error while getting site",
|
||||
})
|
||||
}
|
||||
|
||||
if site == nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{
|
||||
Message: "Site not found",
|
||||
})
|
||||
}
|
||||
|
||||
deployToken := site.DeployToken
|
||||
if deployToken == "" {
|
||||
return c.Status(500).JSON(models.APIError{
|
||||
Message: "Deploy token not configured for this site",
|
||||
})
|
||||
}
|
||||
|
||||
providedToken := c.Get("Authorization")
|
||||
if strings.HasPrefix(providedToken, "Bearer ") {
|
||||
providedToken = strings.TrimPrefix(providedToken, "Bearer ")
|
||||
}
|
||||
if providedToken == "" {
|
||||
return c.Status(401).JSON(models.APIError{
|
||||
Message: "Missing Authorization header",
|
||||
})
|
||||
}
|
||||
|
||||
if security.CompareDeployTokens(providedToken, deployToken) == false {
|
||||
return c.Status(403).JSON(models.APIError{
|
||||
Message: "Invalid deploy token",
|
||||
})
|
||||
}
|
||||
|
||||
sitePath := filepath.Join(h.EnvCfg.StoragePath, site.ID)
|
||||
if _, err := filepath.Abs(sitePath); err != nil {
|
||||
return c.Status(500).JSON(models.APIError{
|
||||
Message: "Failed to resolve site path",
|
||||
})
|
||||
}
|
||||
|
||||
err = github.FetchAndDeployBranch(
|
||||
site.Owner,
|
||||
site.Repository,
|
||||
site.Branch,
|
||||
h.EnvCfg.GithubPat,
|
||||
sitePath,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(models.APIError{
|
||||
Message: "Failed to deploy site: " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.SendStatus(201)
|
||||
}
|
||||
Reference in New Issue
Block a user