From 534fabcb548081f38656e93dac32b3f5013285b9 Mon Sep 17 00:00:00 2001 From: Simon Martens Date: Thu, 13 Mar 2025 22:11:55 +0100 Subject: [PATCH] Server --- git/git.go | 255 +++++++++++++++++++------------------------ go.mod | 15 +++ go.sum | 33 ++++++ lenz.go | 8 +- server/functions.go | 7 ++ server/server.go | 71 +++++++++++- templating/engine.go | 20 ++-- watcher.go | 85 +++++++++++++++ 8 files changed, 332 insertions(+), 162 deletions(-) create mode 100644 server/functions.go create mode 100644 watcher.go diff --git a/git/git.go b/git/git.go index c7f64bd..cb4fb56 100644 --- a/git/git.go +++ b/git/git.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "os" - "sync" "time" "github.com/go-git/go-git/v5" @@ -13,232 +12,202 @@ import ( var InvalidBranchError = errors.New("The currently checked out branch does not match the requested branch. Please checkout the correct branch first.") var InvalidStateError = errors.New("The GitProvider is not in a valid state. Fix the issues or continue without Git data.") -var NoURLProvidedError = errors.New("No URL provided for GitProvider.") -var NoPathProvidedError = errors.New("No path or branch provided for GitProvider.") +var NoURLProvidedError = errors.New("Missing URL.") +var NoPathProvidedError = errors.New("Missing path.") +var NoBranchProvidedError = errors.New("Missing branch name.") -// NOTE: GitProvider does not open any worktree files, it only -// - reads in information from the repo, given a path -// - clones a repo, given an URL & a path -// - pulls a repo, given a path -// In case of success it updates it's state: the commit hash and date. Then it closes the repo. -type GitProvider struct { - mu sync.Mutex - - URL string +type Commit struct { Path string + URL string Branch string - Commit string + Hash string Date time.Time } -func NewGitProvider(url string, path string, branch string) (*GitProvider, error) { - // TODO: check if directory is empty - // TODO: force clone - if _, err := os.Stat(path); err == nil { - return GitProviderFromPath(path, branch) - } - - return GitProviderFromURL(url, path, branch) +func IsValidRepository(path, url, branch string) *Commit { + commit, _ := Read(path, branch, url) + return commit } -func GitProviderFromPath(path string, branch string) (*GitProvider, error) { - if branch == "" || path == "" { - return nil, NoPathProvidedError +func OpenOrClone(path, url, branch string) (*Commit, error) { + commit := IsValidRepository(path, url, branch) + if commit == nil { + if _, err := os.Stat(path); err == nil { + err := os.RemoveAll(path) + if err != nil { + return nil, err + } + } + + c, err := Clone(path, url, branch) + if err != nil { + return nil, err + } + return c, nil } - gp := GitProvider{Path: path, Branch: branch} - if err := gp.Read(); err != nil { - return nil, err - } - - return &gp, nil + return commit, nil } -func GitProviderFromURL(url string, path string, branch string) (*GitProvider, error) { +func Pull(path, url, branch string) (*Commit, error) { if url == "" { return nil, NoURLProvidedError } - if branch == "" || path == "" { + if branch == "" { return nil, NoPathProvidedError } - gp := GitProvider{URL: url, Path: path, Branch: branch} - if err := gp.Clone(); err != nil { - return nil, err + if path == "" { + return nil, NoPathProvidedError } - return &gp, nil -} - -// Returs true if the repo was updated remotely, false otherwise -func (g *GitProvider) Pull() (error, bool) { - g.mu.Lock() - defer g.mu.Unlock() - - branch := plumbing.NewBranchReferenceName(g.Branch) - repo, err := git.PlainOpen(g.Path) + br := plumbing.NewBranchReferenceName(branch) + repo, err := git.PlainOpen(path) if err != nil { - return err, false + return nil, err } wt, err := repo.Worktree() if err != nil { - return err, false + return nil, err } if err := wt.Pull(&git.PullOptions{ RemoteName: "origin", - ReferenceName: branch, + ReferenceName: br, Progress: os.Stdout, - }); err != nil { - if err == git.NoErrAlreadyUpToDate { - return nil, false - } - return err, false + }); err != nil && err != git.NoErrAlreadyUpToDate { + return nil, err } defer wt.Clean(&git.CleanOptions{Dir: true}) - oldCommit := g.Commit - if err := g.setValues(repo); err != nil { - return err, false - } - - if oldCommit == g.Commit { - return nil, false - } - - return nil, true + return latestCommit(repo, path, branch, url) } -func (g *GitProvider) Clone() error { - if g.URL == "" { - return NoURLProvidedError +func Read(path, branch, url string) (*Commit, error) { + if branch == "" { + return nil, NoBranchProvidedError } - g.mu.Lock() - defer g.mu.Unlock() + if path == "" { + return nil, NoPathProvidedError + } - branch := plumbing.NewBranchReferenceName(g.Branch) + if url == "" { + return nil, NoURLProvidedError + } - repo, err := git.PlainClone(g.Path, false, &git.CloneOptions{ - URL: g.URL, + repo, err := git.PlainOpen(path) + if err != nil { + return nil, err + } + + if err := ValidateBranch(repo, branch); err != nil { + br := plumbing.NewBranchReferenceName(branch) + wt, err := repo.Worktree() + if err != nil { + return nil, err + } + defer wt.Clean(&git.CleanOptions{Dir: true}) + + if err := wt.Checkout(&git.CheckoutOptions{ + Branch: br, + Force: true, + }); err != nil { + return nil, err + } + + if err := ValidateBranch(repo, branch); err != nil { + return nil, err + } + } + + return latestCommit(repo, path, branch, url) +} + +func Clone(path, url, branch string) (*Commit, error) { + if url == "" { + return nil, NoURLProvidedError + } + + if branch == "" { + return nil, NoBranchProvidedError + } + + if path == "" { + return nil, NoPathProvidedError + } + + br := plumbing.NewBranchReferenceName(branch) + + repo, err := git.PlainClone(path, false, &git.CloneOptions{ + URL: url, Progress: os.Stdout, }) if err != nil { - return err + return nil, err } wt, err := repo.Worktree() if err != nil { - return err + return nil, err } defer wt.Clean(&git.CleanOptions{Dir: true}) if err := wt.Checkout(&git.CheckoutOptions{ - Branch: branch, + Branch: br, Force: true, }); err != nil { - return err + return nil, err } - return g.setValues(repo) + return latestCommit(repo, path, branch, url) } -// Implement String Interface -func (g *GitProvider) String() string { - return fmt.Sprintf("GitProvider\nURL: %s\nPath: %s\nBranch: %s\nCommit: %s\nDate: %s\n", g.URL, g.Path, g.Branch, g.Commit, g.Date) +func (g Commit) String() string { + return fmt.Sprintf("Path: %s\nURL: %s\nBranch: %s\nHash: %s\nDate: %s", g.Path, g.URL, g.Branch, g.Hash, g.Date) } -func (g *GitProvider) setValues(repo *git.Repository) error { +func (g Commit) Pull() (*Commit, error) { + return Pull(g.Path, g.URL, g.Branch) +} + +func latestCommit(repo *git.Repository, path, branch, url string) (*Commit, error) { log, err := repo.Log(&git.LogOptions{}) if err != nil { - return err + return nil, err } defer log.Close() commit, err := log.Next() if err != nil { - return err + return nil, err } - g.Commit = commit.Hash.String() - g.Date = commit.Author.When + c := commit.Hash.String() + d := commit.Author.When - return nil + return &Commit{Path: path, URL: url, Branch: branch, Hash: c, Date: d}, nil } -func (g *GitProvider) Read() error { - g.mu.Lock() - defer g.mu.Unlock() - - repo, err := git.PlainOpen(g.Path) - if err != nil { - return err - } - - if err := g.ValidateBranch(repo); err != nil { - branch := plumbing.NewBranchReferenceName(g.Branch) - wt, err := repo.Worktree() - if err != nil { - return err - } - defer wt.Clean(&git.CleanOptions{Dir: true}) - - if err := wt.Checkout(&git.CheckoutOptions{ - Branch: branch, - Force: true, - }); err != nil { - return err - } - - if err := g.ValidateBranch(repo); err != nil { - return err - } - } - - return g.setValues(repo) -} - -func (g *GitProvider) Validate() error { - repo, err := git.PlainOpen(g.Path) - if err != nil { - return err - } - - if err := g.ValidateBranch(repo); err != nil { - return err - } - - if err := g.ValidateCommit(); err != nil { - return err - } - - return nil -} - -func (g *GitProvider) ValidateBranch(repo *git.Repository) error { +func ValidateBranch(repo *git.Repository, branch string) error { head, err := repo.Head() if err != nil { return err } cbranch := head.Name().Short() - if cbranch != g.Branch { + if cbranch != branch { return InvalidBranchError } return nil } -func (g *GitProvider) Wait() { - g.mu.Lock() - defer g.mu.Unlock() -} - -func (g *GitProvider) ValidateCommit() error { - if g.Commit == "" || g.Date.IsZero() { +func (g Commit) ValidateCommit() error { + if g.Hash == "" || g.Date.IsZero() { return InvalidStateError } return nil diff --git a/go.mod b/go.mod index 37fc93b..98d5c85 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,10 @@ module github.com/Theodor-Springmann-Stiftung/lenz-web go 1.24.0 require ( + github.com/fsnotify/fsnotify v1.8.0 github.com/go-git/go-git/v5 v5.14.0 + github.com/gofiber/fiber/v2 v2.52.6 + github.com/gofiber/storage/memory/v2 v2.0.1 github.com/kelseyhightower/envconfig v1.4.0 github.com/yalue/merged_fs v1.3.0 golang.org/x/net v0.36.0 @@ -14,17 +17,29 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.5 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect github.com/cloudflare/circl v1.6.0 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect + github.com/tinylib/msgp v1.2.5 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect golang.org/x/crypto v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index d9f226c..44db972 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -20,6 +22,8 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -30,16 +34,24 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= +github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= +github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/gofiber/storage/memory/v2 v2.0.1 h1:tAETnom9uvEB9B3I2LkgewiuqYDAH0ItrIsmT8MUEwk= +github.com/gofiber/storage/memory/v2 v2.0.1/go.mod h1:RRo3RfX6nTD/UhERyE/u5LcSfqtMo9dA4ltmieSe+QM= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -47,14 +59,25 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= @@ -67,6 +90,14 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= +github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= +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.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY= @@ -85,6 +116,8 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/lenz.go b/lenz.go index ec713b9..3d419c6 100644 --- a/lenz.go +++ b/lenz.go @@ -6,8 +6,8 @@ import ( "path/filepath" "github.com/Theodor-Springmann-Stiftung/lenz-web/config" - gitprovider "github.com/Theodor-Springmann-Stiftung/lenz-web/git" - xmlparsing "github.com/Theodor-Springmann-Stiftung/lenz-web/xml" + "github.com/Theodor-Springmann-Stiftung/lenz-web/git" + "github.com/Theodor-Springmann-Stiftung/lenz-web/xml" "github.com/Theodor-Springmann-Stiftung/lenz-web/xmlmodels" ) @@ -23,14 +23,14 @@ func main() { dir := filepath.Join(cfg.BaseDIR, cfg.GITPath) - gp, err := gitprovider.NewGitProvider(cfg.GitURL, dir, cfg.GitBranch) + gp, err := gitprovider.OpenOrClone(dir, cfg.GitURL, cfg.GitBranch) if err != nil { panic(err) } lib := xmlmodels.NewLibrary() - lib.Parse(xmlparsing.Commit, dir, gp.Commit) + lib.Parse(xmlparsing.Commit, dir, gp.Hash) fmt.Println("Library: ", lib) } diff --git a/server/functions.go b/server/functions.go new file mode 100644 index 0000000..7f1bc56 --- /dev/null +++ b/server/functions.go @@ -0,0 +1,7 @@ +package server + +import "github.com/gofiber/fiber/v2" + +func CacheFunc(c *fiber.Ctx) bool { + return c.Query("noCache") == "true" || c.Response().StatusCode() != fiber.StatusOK +} diff --git a/server/server.go b/server/server.go index a3b94c8..52db24c 100644 --- a/server/server.go +++ b/server/server.go @@ -1,11 +1,21 @@ package server -import "time" +import ( + "time" + + "github.com/Theodor-Springmann-Stiftung/lenz-web/templating" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cache" + "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/storage/memory/v2" +) const ( - // INFO: This timeout is stupid. Uploads can take a long time, other routes might not. It's messy. - REQUEST_TIMEOUT = 16 * time.Second - SERVER_TIMEOUT = 16 * time.Second + // INFO: This timeout is stupid. + // Uploads can take a long time, other routes might not. It's messy. + REQUEST_TIMEOUT = 120 * time.Second + SERVER_TIMEOUT = 120 * time.Second // INFO: Maybe this is too long/short? CACHE_TIME = 24 * time.Hour @@ -17,3 +27,56 @@ const ( ROUTES_FILEPATH = "./views/routes" LAYOUT_FILEPATH = "./views/layouts" ) + +type Server struct { + Engine *templating.Engine + Server *fiber.App + Cache *memory.Storage +} + +func New(engine *templating.Engine, debug bool) Server { + c := memory.New(memory.Config{ + GCInterval: CACHE_GC_INTERVAL, + }) + + server := fiber.New(fiber.Config{ + AppName: "Lenz", + CaseSensitive: false, + ErrorHandler: fiber.DefaultErrorHandler, + WriteTimeout: REQUEST_TIMEOUT, + ReadTimeout: REQUEST_TIMEOUT, + PassLocalsToViews: true, + Views: engine, + EnablePrintRoutes: debug, + ViewsLayout: templating.DEFAULT_LAYOUT_NAME, + UnescapePath: true, + }) + + if debug { + server.Use(logger.New()) + } + + server.Use(recover.New()) + + if debug { + server.Use(cache.New(cache.Config{ + Next: CacheFunc, + Expiration: CACHE_TIME, + CacheControl: false, + Storage: c, + })) + } else { + server.Use(cache.New(cache.Config{ + Next: CacheFunc, + Expiration: CACHE_TIME, + CacheControl: true, + Storage: c, + })) + } + + return Server{ + Engine: engine, + Server: server, + Cache: c, + } +} diff --git a/templating/engine.go b/templating/engine.go index fdfc8e6..9f8f6c6 100644 --- a/templating/engine.go +++ b/templating/engine.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/Theodor-Springmann-Stiftung/lenz-web/helpers/functions" + "github.com/gofiber/fiber/v2" "golang.org/x/net/websocket" ) @@ -151,7 +152,7 @@ func (e *Engine) Globals(data map[string]interface{}) { } } -func (e *Engine) Load() { +func (e *Engine) Load() error { wg := sync.WaitGroup{} wg.Add(2) @@ -166,6 +167,8 @@ func (e *Engine) Load() { }() wg.Wait() + + return nil } func (e *Engine) Reload() { @@ -197,17 +200,12 @@ func (e *Engine) AddFuncs(funcs map[string]interface{}) { } } -func (e *Engine) Render(out io.Writer, path string, ld map[string]interface{}, layout ...string) error { +func (e *Engine) Render(out io.Writer, path string, data interface{}, layout ...string) error { + ld := data.(fiber.Map) gd := e.GlobalData - if ld == nil { - ld = make(map[string]interface{}) - } - - // INFO: don't pollute the global data space - for k, v := range gd { - _, ok := ld[k] - if !ok { - ld[k] = v + if e.GlobalData != nil { + for k, v := range ld { + gd[k] = v } } diff --git a/watcher.go b/watcher.go new file mode 100644 index 0000000..ce8d256 --- /dev/null +++ b/watcher.go @@ -0,0 +1,85 @@ +package main + +import ( + "io/fs" + "log" + "os" + "path/filepath" + "time" + + "github.com/fsnotify/fsnotify" +) + +const ( + WATCHER_DEBOUNCE = 300 * time.Millisecond +) + +// INFO: this is hot reload for poor people +type Watcher struct { + *fsnotify.Watcher +} + +func RefreshWatcher(fn func()) (*Watcher, error) { + watcher := Watcher{} + w, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + watcher.Watcher = w + + done := make(chan bool) + + go func() { + var reloadTimer *time.Timer + + for { + select { + case event := <-watcher.Events: + if event.Op&(fsnotify.Create|fsnotify.Write|fsnotify.Remove|fsnotify.Rename) != 0 { + if reloadTimer != nil { + reloadTimer.Stop() + } + reloadTimer = time.AfterFunc(WATCHER_DEBOUNCE, func() { + log.Println("Changes detected, reloading templates...") + fn() + }) + } + + if event.Op&fsnotify.Create == fsnotify.Create { + fi, statErr := os.Stat(event.Name) + if statErr == nil && fi.IsDir() { + _ = watcher.Add(event.Name) + log.Printf("Now watching new directory: %s", event.Name) + } + } + + case err := <-watcher.Errors: + if err != nil { + log.Printf("fsnotify error: %v\n", err) + } + + case <-done: + watcher.Close() + return + } + } + }() + + return &watcher, nil +} + +func (w *Watcher) AddRecursive(root string) error { + return filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + werr := w.Add(path) + if werr != nil { + return werr + } + log.Printf("Now watching directory: %s", path) + } + return nil + }) +}