diff --git a/cmd/site-hosting/main.go b/cmd/site-hosting/main.go index 77e049c..7b7ccc9 100644 --- a/cmd/site-hosting/main.go +++ b/cmd/site-hosting/main.go @@ -3,8 +3,12 @@ package main import ( "flag" "fmt" + "github.com/1f349/site-hosting/config" + "github.com/1f349/site-hosting/serve" "github.com/1f349/site-hosting/upload" + "github.com/MrMelon54/exit-reload" "github.com/julienschmidt/httprouter" + "github.com/spf13/afero" "log" "net/http" "os" @@ -32,10 +36,15 @@ func main() { log.Fatal("[SiteHosting] Failed to stat storage path, does the directory exist? Error: ", err) } - uploadHandler := upload.New(storageFlag) + storageFs := afero.NewBasePathFs(afero.NewOsFs(), storageFlag) + liveConf := config.New(storageFs) + + uploadHandler := upload.New(storageFs, liveConf) + serveHandler := serve.New(storageFs, liveConf) router := httprouter.New() router.POST("/u/:site", uploadHandler.Handle) + router.GET("/", serveHandler.Handle) srv := &http.Server{ Addr: listenFlag, @@ -53,13 +62,29 @@ func main() { } }() + exit_reload.ExitReload("SiteHosting", func() { + + }, func() { + + }) + + exitSig := make(chan struct{}, 1) scReload := make(chan os.Signal, 1) signal.Notify(scReload, syscall.SIGHUP) + go func() { + for { + select { + case <-exitSig: + case <-scReload: + } + } + }() // Wait for exit signal sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) <-sc + close(exitSig) fmt.Println() // Stop server diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..8494094 --- /dev/null +++ b/config/config.go @@ -0,0 +1,92 @@ +package config + +import ( + "fmt" + "github.com/MrMelon54/trie" + "github.com/spf13/afero" + "gopkg.in/yaml.v3" + "sync" +) + +type Config struct { + fs afero.Fs + l *sync.RWMutex + m *trie.Trie[SiteConf] +} + +type SiteConf struct { + Domain string `json:"domain"` + Token string `json:"token"` +} + +func New(storageFs afero.Fs) *Config { + return &Config{ + fs: storageFs, + l: new(sync.RWMutex), + m: trie.BuildFromMap(map[string]SiteConf{}), + } +} + +func Testable(sites []SiteConf) *Config { + c := &Config{} + c.loadSlice(sites) + return c +} + +func (c *Config) Load() error { + open, err := c.fs.Open("sites.yml") + if err != nil { + return fmt.Errorf("failed to open sites.yml: %w", err) + } + var a []SiteConf + err = yaml.NewDecoder(open).Decode(&a) + if err != nil { + return fmt.Errorf("failed to parse yaml: %w", err) + } + + c.loadSlice(a) + return nil +} + +func (c *Config) loadSlice(sites []SiteConf) { + m := make(map[string]SiteConf, len(sites)) + + for _, i := range sites { + m[c.slugFromDomain(i.Domain)] = i + } + + t := trie.BuildFromMap(m) + + c.l.Lock() + c.m = t + c.l.Unlock() +} + +func (c *Config) slugFromDomain(domain string) string { + a := []byte(domain) + for i := range a { + switch { + case a[i] == '-': + // skip + case a[i] >= 'A' && a[i] <= 'Z': + a[i] += 32 + case a[i] >= 'a' && a[i] <= 'z': + // skip + case a[i] >= '0' && a[i] <= '9': + // skip + default: + a[i] = '-' + } + } + return string(a) +} + +func (c *Config) Get(key string) (*SiteConf, int, bool) { + return c.getInternal(c.slugFromDomain(key)) +} + +func (c *Config) getInternal(key string) (*SiteConf, int, bool) { + c.l.RLock() + defer c.l.RUnlock() + return c.m.SearchPrefixInString(key) +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..042f2d0 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,70 @@ +package config + +import ( + _ "embed" + "github.com/MrMelon54/trie" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "sync" + "testing" +) + +//go:embed test-sites.yml +var testSitesYml []byte + +func TestConfig_Load(t *testing.T) { + f := afero.NewMemMapFs() + create, err := f.Create("sites.yml") + assert.NoError(t, err) + _, err = create.Write(testSitesYml) + assert.NoError(t, err) + assert.NoError(t, create.Close()) + + c := New(f) + assert.NoError(t, c.Load()) + val, ok := c.m.GetByString("example-com") + assert.True(t, ok) + assert.Equal(t, SiteConf{Domain: "example.com", Token: "abcd1234"}, *val) +} + +func TestConfig_loadSlice(t *testing.T) { + c := &Config{l: new(sync.RWMutex)} + c.loadSlice([]SiteConf{ + {Domain: "example.com", Token: "abcd1234"}, + }) + a, ok := c.m.GetByString("example-com") + assert.True(t, ok) + assert.Equal(t, SiteConf{Domain: "example.com", Token: "abcd1234"}, *a) +} + +func TestConfig_slugFromDomain(t *testing.T) { + c := &Config{} + assert.Equal(t, "---------------", c.slugFromDomain("!\"#$%&'()*+,-./")) + assert.Equal(t, "0123456789", c.slugFromDomain("0123456789")) + assert.Equal(t, "-------", c.slugFromDomain(":;<=>?@")) + assert.Equal(t, "abcdefghijklmnopqrstuvwxyz", c.slugFromDomain("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) + assert.Equal(t, "------", c.slugFromDomain("[\\]^_`")) + assert.Equal(t, "abcdefghijklmnopqrstuvwxyz", c.slugFromDomain("abcdefghijklmnopqrstuvwxyz")) + assert.Equal(t, "----", c.slugFromDomain("{|}~")) +} + +func FuzzConfig_slugFromDomain(f *testing.F) { + c := &Config{} + f.Fuzz(func(t *testing.T, a string) { + b := c.slugFromDomain(a) + if len(a) != len(b) { + t.Fatalf("value '%s' (%d) did not match lengths with the output '%s' (%d)", a, len(a), b, len(b)) + } + }) +} + +func TestConfig_Get(t *testing.T) { + c := &Config{l: new(sync.RWMutex), m: &trie.Trie[SiteConf]{}} + c.loadSlice([]SiteConf{ + {Domain: "example.com", Token: "abcd1234"}, + }) + val, n, ok := c.Get("example.com") + assert.True(t, ok) + assert.Equal(t, 11, n) + assert.Equal(t, SiteConf{Domain: "example.com", Token: "abcd1234"}, *val) +} diff --git a/config/test-sites.yml b/config/test-sites.yml new file mode 100644 index 0000000..496b918 --- /dev/null +++ b/config/test-sites.yml @@ -0,0 +1,2 @@ +- domain: example.com + token: abcd1234 diff --git a/go.mod b/go.mod index ac490d8..b09cac5 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,16 @@ module github.com/1f349/site-hosting go 1.20 require ( + github.com/MrMelon54/exit-reload v0.0.1 + github.com/MrMelon54/trie v0.0.2 github.com/julienschmidt/httprouter v1.3.0 github.com/spf13/afero v1.9.5 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.8.4 + gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/text v0.3.7 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + golang.org/x/text v0.3.8 // indirect ) diff --git a/go.sum b/go.sum index 3ed378f..e0bf548 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,10 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MrMelon54/exit-reload v0.0.1 h1:sxHa59tNEQMcikwuX2+93lw6Vi1+R7oCRF8a0C3alXc= +github.com/MrMelon54/exit-reload v0.0.1/go.mod h1:PLiSfmUzwdpTTQP3BBfUPhkqPwaIZjx0DuXBnM76Bug= +github.com/MrMelon54/trie v0.0.2 h1:ZXWcX5ij62O9K4I/anuHmVg8L3tF0UGdlPceAASwKEY= +github.com/MrMelon54/trie v0.0.2/go.mod h1:sGCGOcqb+DxSxvHgSOpbpkmA7mFZR47YDExy9OCbVZI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -46,8 +50,9 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -137,8 +142,9 @@ github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -283,8 +289,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -432,8 +439,9 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/serve/serve.go b/serve/serve.go new file mode 100644 index 0000000..196574c --- /dev/null +++ b/serve/serve.go @@ -0,0 +1,112 @@ +package serve + +import ( + "github.com/1f349/site-hosting/config" + "github.com/julienschmidt/httprouter" + "github.com/spf13/afero" + "io" + "net/http" + "os" + "path" + "path/filepath" + "strings" +) + +var ( + indexBranches = []string{ + "main", + "master", + } + indexFiles = []func(p string) string{ + func(p string) string { return path.Join(p, "index.html") }, + func(p string) string { return p + ".html" }, + func(p string) string { return p }, + } +) + +func New(storage afero.Fs, conf *config.Config) *Handler { + return &Handler{storage, conf} +} + +type Handler struct { + storageFs afero.Fs + conf *config.Config +} + +func (h *Handler) Handle(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { + site, branch, subdomain, ok := h.findSiteBranchSubdomain(req.Host) + if !ok { + http.Error(rw, "Bad Gateway", http.StatusBadGateway) + return + } + if branch == "" { + for _, i := range indexBranches { + if h.tryServePath(rw, site, i, subdomain, req.URL.Path) { + return + } + } + } else if h.tryServePath(rw, site, branch, subdomain, req.URL.Path) { + return + } + http.Error(rw, "404 Not Found", http.StatusNotFound) +} + +func (h *Handler) findSiteBranchSubdomain(host string) (site, branch, subdomain string, ok bool) { + var siteN int + siteN, site = h.findSite(host) + if site == "" { + return + } + + if host[siteN] != '-' { + return + } + host = host[siteN+1:] + + strings.LastIndexByte(host, '-') + return +} + +func (h *Handler) findSite(host string) (int, string) { + siteVal, siteN, siteOk := h.conf.Get(host) + if !siteOk || siteVal == nil { + return -1, "" + } + + // so I used less than or equal here that's to prevent a bug where the prefix + // found is longer than the string obviously that sounds impossible, and it is, + // but I would rather the program not crash if some other bug allows this weird + // event to happen + if siteN <= len(host) { + return -1, "" + } + return siteN, siteVal.Domain +} + +func (h *Handler) tryServePath(rw http.ResponseWriter, site, branch, subdomain, p string) bool { + for _, i := range indexFiles { + if h.tryServeFile(rw, site, branch, subdomain, i(p)) { + return true + } + } + return false +} + +func (h *Handler) tryServeFile(rw http.ResponseWriter, site, branch, subdomain, p string) bool { + // if there is a subdomain then load files from inside the subdomain folder + if subdomain != "" { + p = filepath.Join("_subdomain", subdomain, p) + } + open, err := h.storageFs.Open(filepath.Join(site, branch, p)) + switch { + case err == nil: + rw.WriteHeader(http.StatusOK) + _, _ = io.Copy(rw, open) + case os.IsNotExist(err): + // check next path + return false + default: + http.Error(rw, "500 Internal Server Error", http.StatusInternalServerError) + } + return true +} diff --git a/serve/serve_test.go b/serve/serve_test.go new file mode 100644 index 0000000..d9791ac --- /dev/null +++ b/serve/serve_test.go @@ -0,0 +1,33 @@ +package serve + +import ( + "github.com/1f349/site-hosting/config" + "github.com/spf13/afero" + "testing" +) + +func makeConfig(f afero.Fs) (*config.Config, error) { + c := config.New(f) + return c, c.Load() +} + +func TestName(t *testing.T) { + f := afero.NewMemMapFs() + h := &Handler{ + storageFs: f, + conf: config.Testable([]config.SiteConf{ + {Domain: "example.com", Token: "abcd1234"}, + }), + } + h.findSiteBranchSubdomain("example-com-test") + site, branch := h.findSiteBranch("example_com_test") +} + +func TestHandler_Handle(t *testing.T) { + f := afero.NewMemMapFs() + h := &Handler{ + storageFs: f, + conf: &config.Config{}, + } + h.Handle() +} diff --git a/upload/test-sites.yml b/upload/test-sites.yml new file mode 100644 index 0000000..496b918 --- /dev/null +++ b/upload/test-sites.yml @@ -0,0 +1,2 @@ +- domain: example.com + token: abcd1234 diff --git a/upload/upload.go b/upload/upload.go index 21e3fde..1aae6a1 100644 --- a/upload/upload.go +++ b/upload/upload.go @@ -4,26 +4,39 @@ import ( "archive/tar" "compress/gzip" "fmt" + "github.com/1f349/site-hosting/config" "github.com/julienschmidt/httprouter" "github.com/spf13/afero" "io" + "io/fs" "net/http" "os" "path/filepath" ) -func New(storagePath string) *Handler { - fs := afero.NewBasePathFs(afero.NewOsFs(), storagePath) - return &Handler{fs} +func New(storage afero.Fs, conf *config.Config) *Handler { + return &Handler{storage, conf} } type Handler struct { storageFs afero.Fs + conf *config.Config } func (h *Handler) Handle(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { - site := params.ByName("site") - branch := req.URL.Query().Get("branch") + q := req.URL.Query() + site := q.Get("site") + branch := q.Get("branch") + + siteConf, siteN, siteOk := h.conf.Get(site) + if !siteOk || siteN != len(site) || siteConf == nil { + http.Error(rw, "400 Bad Request", http.StatusBadRequest) + return + } + if "Bearer "+siteConf.Token != req.Header.Get("Authorization") { + http.Error(rw, "403 Forbidden", http.StatusForbidden) + return + } fileData, fileHeader, err := req.FormFile("upload") if err != nil { @@ -47,7 +60,17 @@ func (h *Handler) Handle(rw http.ResponseWriter, req *http.Request, params httpr } func (h *Handler) extractTarGzUpload(fileData io.Reader, site, branch string) error { - storeFs := afero.NewBasePathFs(h.storageFs, filepath.Join(site, branch)) + siteBranchPath := filepath.Join(site, branch) + err := h.storageFs.Rename(siteBranchPath, siteBranchPath+".old") + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to save an old copy of the site: %w", err) + } + + err = h.storageFs.MkdirAll(siteBranchPath, fs.ModePerm) + if err != nil { + return fmt.Errorf("failed to make site directory: %w", err) + } + branchFs := afero.NewBasePathFs(h.storageFs, siteBranchPath) // decompress gzip wrapper gzipReader, err := gzip.NewReader(fileData) @@ -60,18 +83,19 @@ func (h *Handler) extractTarGzUpload(fileData io.Reader, site, branch string) er for { next, err := tarReader.Next() if err == io.EOF { + // finished reading tar, exit now break } if err != nil { return fmt.Errorf("invalid tar archive: %w", err) } - err = storeFs.MkdirAll(filepath.Dir(next.Name), os.ModePerm) + err = branchFs.MkdirAll(filepath.Dir(next.Name), fs.ModePerm) if err != nil { return fmt.Errorf("failed to make directory tree: %w", err) } - create, err := storeFs.Create(next.Name) + create, err := branchFs.Create(next.Name) if err != nil { return fmt.Errorf("failed to create output file: '%s': %w", next.Name, err) } diff --git a/upload/upload_test.go b/upload/upload_test.go index a0308be..c2dc78d 100644 --- a/upload/upload_test.go +++ b/upload/upload_test.go @@ -3,6 +3,7 @@ package upload import ( "bytes" _ "embed" + "github.com/1f349/site-hosting/config" "github.com/julienschmidt/httprouter" "github.com/spf13/afero" "github.com/stretchr/testify/assert" @@ -13,8 +14,12 @@ import ( "testing" ) -//go:embed test-archive.tar.gz -var testArchiveTarGz []byte +var ( + //go:embed test-archive.tar.gz + testArchiveTarGz []byte + //go:embed test-sites.yml + testSitesYml []byte +) func assertUploadedFile(t *testing.T, fs afero.Fs) { // check uploaded file exists @@ -32,8 +37,16 @@ func assertUploadedFile(t *testing.T, fs afero.Fs) { } func TestHandler_Handle(t *testing.T) { - fs := afero.NewMemMapFs() - h := &Handler{fs} + f := afero.NewMemMapFs() + conf := config.New(f) + h := &Handler{f, conf} + create, err := f.Create("sites.yml") + assert.NoError(t, err) + _, err = create.Write(testSitesYml) + assert.NoError(t, err) + assert.NoError(t, create.Close()) + assert.NoError(t, conf.Load()) + mpBuf := new(bytes.Buffer) mp := multipart.NewWriter(mpBuf) file, err := mp.CreateFormFile("upload", "test-archive.tar.gz") @@ -41,11 +54,12 @@ func TestHandler_Handle(t *testing.T) { _, err = file.Write(testArchiveTarGz) assert.NoError(t, err) assert.NoError(t, mp.Close()) - req, err := http.NewRequest(http.MethodPost, "https://example.com/u/example.com?branch=main", mpBuf) + req, err := http.NewRequest(http.MethodPost, "https://example.com/u?site=example.com&branch=main", mpBuf) assert.NoError(t, err) + req.Header.Set("Authorization", "Bearer abcd1234") req.Header.Set("Content-Type", mp.FormDataContentType()) rec := httptest.NewRecorder() - h.Handle(rec, req, httprouter.Params{{Key: "site", Value: "example.com"}}) + h.Handle(rec, req, httprouter.Params{}) res := rec.Result() assert.Equal(t, http.StatusAccepted, res.StatusCode) assert.NotNil(t, res.Body) @@ -53,12 +67,13 @@ func TestHandler_Handle(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "", string(all)) - assertUploadedFile(t, fs) + assertUploadedFile(t, f) } func TestHandler_extractTarGzUpload(t *testing.T) { fs := afero.NewMemMapFs() - h := &Handler{fs} + conf := config.New(fs) + h := &Handler{fs, conf} buffer := bytes.NewBuffer(testArchiveTarGz) assert.NoError(t, h.extractTarGzUpload(buffer, "example.com", "main"))