Add GET /api/v1/sites/:host

This commit is contained in:
Melon 2025-03-25 00:16:31 +00:00
parent 79912dc5c4
commit 1f2e9880bc
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
7 changed files with 128 additions and 3 deletions

View File

@ -16,6 +16,7 @@ import (
)
type apiDB interface {
GetBranchesByHost(ctx context.Context, arg database.GetBranchesByHostParams) ([]database.Branch, error)
AddSite(ctx context.Context, arg database.AddSiteParams) error
UpdateSiteToken(ctx context.Context, arg database.UpdateSiteTokenParams) error
SetBranchEnabled(ctx context.Context, arg database.SetBranchEnabledParams) error
@ -38,6 +39,33 @@ func New(upload *upload.Handler, keyStore *mjwt.KeyStore, db apiDB) *httprouter.
})
})
// Site lookup endpoint
router.GET("/api/v1/sites/:host", checkAuth(keyStore, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, b AuthClaims) {
host := params.ByName("host")
if !validation.IsValidHost(host) {
http.Error(rw, "Invalid host", http.StatusBadRequest)
return
}
if !validateDomainOwnershipClaims(host, b.Claims.Perms) {
http.Error(rw, "Forbidden", http.StatusForbidden)
return
}
branches, err := db.GetBranchesByHost(req.Context(), database.GetBranchesByHostParams{
Domain: host,
DomainWildcard: "%." + host,
})
if err != nil {
http.Error(rw, "Failed to fetch sites", http.StatusInternalServerError)
return
}
rw.WriteHeader(http.StatusOK)
_ = json.NewEncoder(rw).Encode(branches)
}))
// Site creation endpoint
router.PUT("/api/v1/sites/:host", checkAuth(keyStore, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, b AuthClaims) {
host := params.ByName("host")

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.25.0
// sqlc v1.28.0
package database

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.25.0
// sqlc v1.28.0
package database

View File

@ -4,6 +4,12 @@ FROM sites
WHERE domain = ?
LIMIT 1;
-- name: GetBranchesByHost :many
SELECT *
FROM branches
WHERE domain = ?
OR domain LIKE @domain_wildcard;
-- name: GetLastUpdatedByDomainBranch :one
SELECT last_update
FROM branches

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.25.0
// sqlc v1.28.0
// source: sites.sql
package database
@ -47,6 +47,46 @@ func (q *Queries) AddSite(ctx context.Context, arg AddSiteParams) error {
return err
}
const getBranchesByHost = `-- name: GetBranchesByHost :many
SELECT domain, branch, last_update, enable
FROM branches
WHERE domain = ?
OR domain LIKE ?
`
type GetBranchesByHostParams struct {
Domain string `json:"domain"`
DomainWildcard string `json:"domain_wildcard"`
}
func (q *Queries) GetBranchesByHost(ctx context.Context, arg GetBranchesByHostParams) ([]Branch, error) {
rows, err := q.db.QueryContext(ctx, getBranchesByHost, arg.Domain, arg.DomainWildcard)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Branch
for rows.Next() {
var i Branch
if err := rows.Scan(
&i.Domain,
&i.Branch,
&i.LastUpdate,
&i.Enable,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getLastUpdatedByDomainBranch = `-- name: GetLastUpdatedByDomainBranch :one
SELECT last_update
FROM branches

View File

@ -1,5 +1,27 @@
package validation
import "strings"
// IsValidHost ensures a host string is valid.
//
// - Each rune must match 0-9, a-z, "-" or ".".
// - Hosts are separated by "." into segments and each segment must not be empty.
// - Naturally this also ensures the host does not start or end with ".".
// - Host segments must not start or end with "-".
func IsValidHost(domain string) bool {
if !containsOnly(domain, isDomainRune) {
return false
}
segments := strings.Split(domain, ".")
for _, segment := range segments {
if segment == "" || strings.HasPrefix(segment, "-") || strings.HasSuffix(segment, "-") {
return false
}
}
return true
}
func IsValidSite(site string) bool {
if len(site) < 1 || site[0] == '-' {
return false

View File

@ -5,6 +5,35 @@ import (
"testing"
)
func TestIsValidHost(t *testing.T) {
for _, i := range []struct {
s string
valid bool
}{
{"example.com", true},
{"example.org", true},
{"foobar.example.com", true},
{"foobar.example.com_", false},
{"foobar.example.com[", false},
{"foobar.example.com]", false},
{"foobar.example.com<", false},
{"foobar.example.com/", false},
{"foobar.example.com?", false},
{"foobar.example.com@", false},
{"foobar.example.com!", false},
{"foobar.example..com", false},
{"foobar.example-.com", false},
{"foobar.-example.com", false},
{"-foobar.example.com", false},
{"foobar.example.com-", false},
{"foobar..example..com", false},
{".example.com", false},
{"example.com.", false},
} {
assert.Equal(t, i.valid, IsValidHost(i.s), "Test failed \"%s\" - %v", i.s, i.valid)
}
}
func TestIsValidSite(t *testing.T) {
for _, i := range []struct {
s string