diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index 361a9a8..0000000 --- a/.golangci.yml +++ /dev/null @@ -1,5 +0,0 @@ -issues: - exclude-rules: - - path: (.+)_test.go - linters: - - errcheck diff --git a/go.mod b/go.mod index fbdcffd..f008e9c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module synyx.de/tales go 1.22 require ( - github.com/gorilla/mux v1.8.1 github.com/mozillazg/go-slugify v0.2.0 github.com/stretchr/testify v1.9.0 olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 diff --git a/go.sum b/go.sum index 351a6f8..eff4d93 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ 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/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/mozillazg/go-slugify v0.2.0 h1:SIhqDlnJWZH8OdiTmQgeXR28AOnypmAXPeOTcG7b9lk= github.com/mozillazg/go-slugify v0.2.0/go.mod h1:z7dPH74PZf2ZPFkyxx+zjPD8CNzRJNa1CGacv0gg8Ns= github.com/mozillazg/go-unidecode v0.2.0 h1:vFGEzAH9KSwyWmXCOblazEWDh7fOkpmy/Z4ArmamSUc= diff --git a/pkg/buildinfo/version.go b/pkg/buildinfo/version.go index ae4ad03..85181c9 100644 --- a/pkg/buildinfo/version.go +++ b/pkg/buildinfo/version.go @@ -16,5 +16,6 @@ func FormattedGitSHA() string { if GitTreeState != "clean" { return fmt.Sprintf("%s-%s", GitSHA, GitTreeState) } + return GitSHA } diff --git a/pkg/buildinfo/version_test.go b/pkg/buildinfo/version_test.go new file mode 100644 index 0000000..64b2d58 --- /dev/null +++ b/pkg/buildinfo/version_test.go @@ -0,0 +1,23 @@ +package buildinfo + +import "testing" + +func TestCleanState(t *testing.T) { + Version = "1.0" + GitSHA = "2547ce676b1f899d08c61c5367c635c76505b443" + GitTreeState = "clean" + + if sha := FormattedGitSHA(); sha != GitSHA { + t.Errorf("git sha should be %s, got %s", GitSHA, sha) + } +} + +func TestDirtyState(t *testing.T) { + Version = "1.0" + GitSHA = "2547ce676b1f899d08c61c5367c635c76505b443" + GitTreeState = "dirty" + + if sha := FormattedGitSHA(); sha != GitSHA+"-dirty" { + t.Errorf("git sha should be %s, got %s", GitSHA, sha) + } +} diff --git a/pkg/project/fs.go b/pkg/project/fs.go index 36825c5..f34a1d3 100644 --- a/pkg/project/fs.go +++ b/pkg/project/fs.go @@ -24,18 +24,31 @@ func (fr *FilesystemRepository) imageFile(slug, filename string) string { } // Exists implements the Repository.Exists method. -func (fr *FilesystemRepository) Exists(slug string) bool { +func (fr *FilesystemRepository) Exists(slug string) (bool, error) { + dir, err := os.Stat(fr.ProjectDir) + if err != nil { + return false, fmt.Errorf("project directory %s not accessible: %w", fr.ProjectDir, err) + } + if !dir.IsDir() { + return false, fmt.Errorf("project directory %s is not a directory", fr.ProjectDir) + } + filename := fr.configFile(slug) info, err := os.Stat(filename) if os.IsNotExist(err) { - return false + return false, nil + } + if info.IsDir() { + return false, fmt.Errorf("%s is a directory", slug) } - return !info.IsDir() + + return true, nil } // LoadProjects implements the Repository.LoadProjects method. func (fr *FilesystemRepository) LoadProjects() ([]Project, error) { projects := make([]Project, 0) + files, err := os.ReadDir(fr.ProjectDir) if err != nil { return projects, err @@ -45,6 +58,7 @@ func (fr *FilesystemRepository) LoadProjects() ([]Project, error) { if !f.IsDir() { continue } + slug := f.Name() project, err := fr.LoadProject(slug) if err != nil { @@ -59,9 +73,12 @@ func (fr *FilesystemRepository) LoadProjects() ([]Project, error) { // LoadProject implements the Repository.LoadProject method. func (fr *FilesystemRepository) LoadProject(slug string) (Project, error) { - if !fr.Exists(slug) { + if exists, err := fr.Exists(slug); err != nil { + return Project{}, err + } else if !exists { return Project{}, ErrNotExist } + jsonFile := fr.configFile(slug) data, err := os.ReadFile(jsonFile) if err != nil { @@ -69,8 +86,7 @@ func (fr *FilesystemRepository) LoadProject(slug string) (Project, error) { } var project Project - err = json.Unmarshal(data, &project) - if err != nil { + if err := json.Unmarshal(data, &project); err != nil { return Project{}, err } @@ -99,42 +115,52 @@ func (fr *FilesystemRepository) SaveProject(slug string, project Project) (Proje // DeleteProject implements the Repository.DeleteProject method. func (fr *FilesystemRepository) DeleteProject(slug string) error { - if !fr.Exists(slug) { + if exists, err := fr.Exists(slug); err != nil { + return err + } else if !exists { return ErrNotExist } + jsonFile := fr.configFile(slug) + return os.RemoveAll(filepath.Dir(jsonFile)) } // SaveImage implements the Repository.SaveImage method. func (fr *FilesystemRepository) SaveImage(slug, contentType string, data []byte) (Project, error) { - if !fr.Exists(slug) { + if exists, err := fr.Exists(slug); err != nil { + return Project{}, err + } else if !exists { return Project{}, ErrNotExist } + extension := imageType(contentType) - filename := slug + "." + extension if extension == "" { return Project{}, fmt.Errorf("unsupported content-type: %v", contentType) } + + filename := slug + "." + extension imageFile := fr.imageFile(slug, filename) - err := os.MkdirAll(filepath.Dir(imageFile), os.ModePerm) - if err != nil { + if err := os.MkdirAll(filepath.Dir(imageFile), os.ModePerm); err != nil { return Project{}, err } - err = os.WriteFile(imageFile, data, 0644) - if err != nil { + + if err := os.WriteFile(imageFile, data, 0644); err != nil { return Project{}, err } + project, err := fr.LoadProject(slug) if err != nil { return Project{}, err } + project.FilePath = filename project.FileType = contentType project, err = fr.SaveProject(slug, project) if err != nil { return Project{}, err } + return project, nil } diff --git a/pkg/project/fs_test.go b/pkg/project/fs_test.go index 5f14aec..2e1c5e0 100644 --- a/pkg/project/fs_test.go +++ b/pkg/project/fs_test.go @@ -14,9 +14,13 @@ func TestFilesystemRepository_Exists(t *testing.T) { defer os.RemoveAll(repo.ProjectDir) t.Run("checks existence", func(t *testing.T) { - assert.False(t, repo.Exists("project-1")) - repo.SaveProject("project-1", Project{Name: "Project 1"}) - assert.True(t, repo.Exists("project-1")) + exists, _ := repo.Exists("project-1") + assert.False(t, exists) + + _, _ = repo.SaveProject("project-1", Project{Name: "Project 1"}) + + exists, _ = repo.Exists("project-1") + assert.True(t, exists) }) } @@ -27,11 +31,11 @@ func TestFilesystemRepository_LoadProjects(t *testing.T) { defer os.RemoveAll(repo.ProjectDir) project1 := Project{Name: "Project 1"} - repo.SaveProject("project-1", project1) + _, _ = repo.SaveProject("project-1", project1) project2 := Project{Name: "Project 2"} - repo.SaveProject("project-2", project2) + _, _ = repo.SaveProject("project-2", project2) project3 := Project{Name: "Project 3"} - repo.SaveProject("project-3", project3) + _, _ = repo.SaveProject("project-3", project3) projects, err := repo.LoadProjects() assert.Nil(t, err) @@ -42,7 +46,9 @@ func TestFilesystemRepository_LoadProjects(t *testing.T) { }) t.Run("non-existing repo directory", func(t *testing.T) { repo := testRepo(t) - os.RemoveAll(repo.ProjectDir) + if err := os.RemoveAll(repo.ProjectDir); err != nil { + t.Fatal("could not delete project dir for testing", err) + } projects, err := repo.LoadProjects() assert.NotNil(t, err) @@ -114,7 +120,7 @@ func TestFilesystemRepository_LoadProject(t *testing.T) { }) t.Run("load existing project", func(t *testing.T) { project1 := Project{Name: "Project 1"} - repo.SaveProject("project-1", project1) + _, _ = repo.SaveProject("project-1", project1) project, err := repo.LoadProject("project-1") assert.Nil(t, err) assert.Equal(t, project1, project) @@ -149,7 +155,7 @@ func TestFilesystemRepository_DeleteProject(t *testing.T) { }) t.Run("delete valid project", func(t *testing.T) { project := Project{Name: "Foo"} - repo.SaveProject("foo", project) + _, _ = repo.SaveProject("foo", project) _, err := repo.LoadProject("foo") assert.Nil(t, err) err = repo.DeleteProject("foo") @@ -173,13 +179,13 @@ func TestFilesystemRepository_SaveImage(t *testing.T) { }) t.Run("save image with invalid content-type", func(t *testing.T) { project := Project{} - repo.SaveProject("project", project) + _, _ = repo.SaveProject("project", project) _, err := repo.SaveImage("project", "foo/bar", []byte{}) assert.EqualError(t, err, "unsupported content-type: foo/bar") }) t.Run("save image for valid project", func(t *testing.T) { project := Project{} - repo.SaveProject("project", project) + _, _ = repo.SaveProject("project", project) savedProject, err := repo.SaveImage("project", "image/jpeg", []byte{'f', 'o', 'o'}) assert.NoError(t, err) assert.Equal(t, "project.jpg", savedProject.FilePath) @@ -196,5 +202,6 @@ func TestFilesystemRepository_SaveImage(t *testing.T) { func testRepo(t *testing.T) FilesystemRepository { dir, err := os.MkdirTemp("", "tales-test") assert.Nil(t, err) + return FilesystemRepository{dir} } diff --git a/pkg/project/project.go b/pkg/project/project.go index 4a5558f..4b949e4 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -37,7 +37,7 @@ type Project struct { // A Repository manages projects. type Repository interface { - Exists(string) bool + Exists(string) (bool, error) LoadProjects() ([]Project, error) LoadProject(string) (Project, error) SaveProject(string, Project) (Project, error) diff --git a/pkg/web/api.go b/pkg/web/api.go index 0cb5065..10eefc1 100644 --- a/pkg/web/api.go +++ b/pkg/web/api.go @@ -5,142 +5,158 @@ import ( "io" "net/http" - "github.com/gorilla/mux" "github.com/mozillazg/go-slugify" "synyx.de/tales/pkg/project" ) -func listProjects(repository project.Repository) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - projects, err := repository.LoadProjects() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - jsonResponse(w, projects, http.StatusOK) +func (s *server) listProjects(w http.ResponseWriter, r *http.Request) { + projects, err := s.repository.LoadProjects() + if err != nil { + internalServerError(w, err) + return } + + jsonResponse(w, projects, http.StatusOK) } -func loadProject(repository project.Repository) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - slug := vars["slug"] - if !repository.Exists(slug) { - notFound(w) - return - } - proj, err := repository.LoadProject(slug) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - jsonResponse(w, proj, http.StatusOK) +func (s *server) loadProject(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("slug") + if exists, err := s.repository.Exists(slug); err != nil { + internalServerError(w, err) + return + } else if !exists { + notFound(w) + return } + + proj, err := s.repository.LoadProject(slug) + if err != nil { + internalServerError(w, err) + return + } + + jsonResponse(w, proj, http.StatusOK) } -func createProject(repository project.Repository) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - proj, err := extractProject(r) - if err != nil { - badRequest(w) - return - } - if proj.Slug == "" { - proj.Slug = slugify.Slugify(proj.Name) - } - if proj.Slug == "" { - badRequest(w) - return - } - if repository.Exists(proj.Slug) { - conflict(w) - return - } - proj, err = repository.SaveProject(proj.Slug, proj) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - jsonResponse(w, proj, http.StatusCreated) +func (s *server) createProject(w http.ResponseWriter, r *http.Request) { + proj, err := extractProject(r) + if err != nil { + badRequest(w) + return } + + if proj.Slug == "" { + proj.Slug = slugify.Slugify(proj.Name) + } + if proj.Slug == "" { + badRequest(w) + return + } + if exists, err := s.repository.Exists(proj.Slug); err != nil { + internalServerError(w, err) + return + } else if exists { + conflict(w) + return + } + + proj, err = s.repository.SaveProject(proj.Slug, proj) + if err != nil { + internalServerError(w, err) + return + } + + jsonResponse(w, proj, http.StatusCreated) } -func updateProject(repository project.Repository) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - slug := vars["slug"] - proj, err := extractProject(r) - if err != nil { - badRequest(w) - return - } - if !repository.Exists(slug) { - notFound(w) - return - } - proj, err = repository.SaveProject(slug, proj) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - jsonResponse(w, proj, http.StatusAccepted) +func (s *server) updateProject(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("slug") + proj, err := extractProject(r) + if err != nil { + badRequest(w) + return + } + if exists, err := s.repository.Exists(slug); err != nil { + internalServerError(w, err) + return + } else if !exists { + notFound(w) + return } + + proj, err = s.repository.SaveProject(slug, proj) + if err != nil { + internalServerError(w, err) + return + } + + jsonResponse(w, proj, http.StatusAccepted) } -func deleteProject(repository project.Repository) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - slug := vars["slug"] - if !repository.Exists(slug) { - notFound(w) - return - } - err := repository.DeleteProject(slug) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - emptyResponse(w, http.StatusAccepted) +func (s *server) deleteProject(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("slug") + if exists, err := s.repository.Exists(slug); err != nil { + internalServerError(w, err) + return + } else if !exists { + notFound(w) + return + } + + if err := s.repository.DeleteProject(slug); err != nil { + internalServerError(w, err) + return } + + emptyResponse(w, http.StatusAccepted) } -func saveProjectImage(repository project.Repository) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - slug := vars["slug"] - if !repository.Exists(slug) { - notFound(w) - return - } - data, err := io.ReadAll(r.Body) - if err != nil { - badRequest(w) - return - } - contentType := r.Header.Get("Content-Type") - proj, err := repository.SaveImage(slug, contentType, data) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - jsonResponse(w, proj, http.StatusAccepted) +func (s *server) saveProjectImage(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("slug") + if exists, err := s.repository.Exists(slug); err != nil { + internalServerError(w, err) + return + } else if !exists { + notFound(w) + return + } + + data, err := io.ReadAll(r.Body) + if err != nil { + badRequest(w) + return + } + + contentType := r.Header.Get("Content-Type") + proj, err := s.repository.SaveImage(slug, contentType, data) + if err != nil { + internalServerError(w, err) + return } + + jsonResponse(w, proj, http.StatusAccepted) } func extractProject(req *http.Request) (project.Project, error) { var proj project.Project + body, err := io.ReadAll(req.Body) if err != nil { return proj, err } - err = json.Unmarshal(body, &proj) - if err != nil { + + if err := json.Unmarshal(body, &proj); err != nil { return proj, err } + return proj, nil } +func internalServerError(w http.ResponseWriter, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) +} + func badRequest(w http.ResponseWriter) { http.Error(w, "invalid project", http.StatusBadRequest) } @@ -160,7 +176,7 @@ func emptyResponse(w http.ResponseWriter, code int) { func jsonResponse(w http.ResponseWriter, data interface{}, code int) { result, err := json.Marshal(data) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + internalServerError(w, err) return } diff --git a/pkg/web/api_test.go b/pkg/web/api_test.go index 578181f..8bc22c3 100644 --- a/pkg/web/api_test.go +++ b/pkg/web/api_test.go @@ -30,9 +30,9 @@ func TestAPI_listProjects(t *testing.T) { tc := NewTestClient(t) defer tc.Cleanup() - tc.repo.SaveProject("test-1", project.Project{}) - tc.repo.SaveProject("test-2", project.Project{}) - tc.repo.SaveProject("test-3", project.Project{}) + _, _ = tc.repo.SaveProject("test-1", project.Project{}) + _, _ = tc.repo.SaveProject("test-2", project.Project{}) + _, _ = tc.repo.SaveProject("test-3", project.Project{}) resp := tc.Request("GET", "/api/tales/", nil) @@ -77,7 +77,7 @@ func TestAPI_createProject(t *testing.T) { tc := NewTestClient(t) defer tc.Cleanup() - tc.repo.SaveProject("foo", project.Project{Slug: "foo", Name: "Bar"}) + _, _ = tc.repo.SaveProject("foo", project.Project{Slug: "foo", Name: "Bar"}) p := project.Project{Slug: "foo", Name: "Bar"} resp := tc.Request("POST", "/api/tales/", p) @@ -101,14 +101,12 @@ func TestAPI_createProject(t *testing.T) { AssertError(t, resp, "invalid project\n", 400) }) t.Run("internal error", func(t *testing.T) { - /* - tc := NewTestClient(t) - tc.Cleanup() + tc := NewTestClient(t) + tc.Cleanup() - resp := tc.Request("POST", "/api/tales/", project.Project{Slug: "foo"}) + resp := tc.Request("POST", "/api/tales/", project.Project{Slug: "foo"}) - AssertError(t, resp, "open "+tc.dir+": no such file or directory\n", 500) - */ + AssertError(t, resp, tc.dir+" not accessible", 500) }) } @@ -118,7 +116,7 @@ func TestAPI_loadProject(t *testing.T) { defer tc.Cleanup() p := project.Project{Slug: "foo"} - tc.repo.SaveProject("foo", p) + _, _ = tc.repo.SaveProject("foo", p) resp := tc.Request("GET", "/api/tales/foo", nil) @@ -133,14 +131,12 @@ func TestAPI_loadProject(t *testing.T) { AssertError(t, resp, "project not found\n", 404) }) t.Run("internal error", func(t *testing.T) { - /* - tc := NewTestClient(t) - tc.Cleanup() + tc := NewTestClient(t) + tc.Cleanup() - resp := tc.Request("GET", "/api/tales/foo", nil) + resp := tc.Request("GET", "/api/tales/foo", nil) - AssertError(t, resp, "open "+tc.dir+": no such file or directory\n", 500) - */ + AssertError(t, resp, tc.dir+" not accessible", 500) }) } @@ -149,7 +145,7 @@ func TestAPI_updateProject(t *testing.T) { tc := NewTestClient(t) defer tc.Cleanup() - tc.repo.SaveProject("foo", project.Project{Slug: "foo", Name: "Bar"}) + _, _ = tc.repo.SaveProject("foo", project.Project{Slug: "foo", Name: "Bar"}) p := project.Project{Slug: "foo", Name: "Baz"} resp := tc.Request("PUT", "/api/tales/foo", p) @@ -162,7 +158,7 @@ func TestAPI_updateProject(t *testing.T) { resp := tc.Request("PUT", "/api/tales/foo", nil) - AssertError(t, resp, "invalid project\n", 400) + AssertError(t, resp, "invalid project", 400) }) t.Run("unknown project", func(t *testing.T) { tc := NewTestClient(t) @@ -174,14 +170,12 @@ func TestAPI_updateProject(t *testing.T) { AssertError(t, resp, "project not found\n", 404) }) t.Run("internal error", func(t *testing.T) { - /* - tc := NewTestClient(t) - tc.Cleanup() + tc := NewTestClient(t) + tc.Cleanup() - resp := tc.Request("PUT", "/api/tales/", project.Project{Slug: "foo"}) + resp := tc.Request("PUT", "/api/tales/foo", project.Project{Slug: "foo"}) - AssertError(t, resp, "open "+tc.dir+": no such file or directory\n", 500) - */ + AssertError(t, resp, tc.dir+" not accessible", 500) }) } @@ -190,7 +184,7 @@ func TestAPI_deleteProject(t *testing.T) { tc := NewTestClient(t) defer tc.Cleanup() - tc.repo.SaveProject("foo", project.Project{Slug: "foo"}) + _, _ = tc.repo.SaveProject("foo", project.Project{Slug: "foo"}) resp := tc.Request("DELETE", "/api/tales/foo", nil) @@ -205,17 +199,15 @@ func TestAPI_deleteProject(t *testing.T) { resp := tc.Request("DELETE", "/api/tales/foo", nil) - AssertError(t, resp, "project not found\n", 404) + AssertError(t, resp, "project not found", 404) }) t.Run("internal error", func(t *testing.T) { - /* - tc := NewTestClient(t) - tc.Cleanup() + tc := NewTestClient(t) + tc.Cleanup() - resp := tc.Request("DELETE", "/api/tales/foo", nil) + resp := tc.Request("DELETE", "/api/tales/foo", nil) - AssertError(t, resp, "open "+tc.dir+": no such file or directory\n", 500) - */ + AssertError(t, resp, tc.dir+" not accessible", 500) }) } @@ -224,7 +216,7 @@ func TestAPI_updateProjectImage(t *testing.T) { tc := NewTestClient(t) defer tc.Cleanup() - tc.repo.SaveProject("foo", project.Project{Slug: "foo", Name: "Bar"}) + _, _ = tc.repo.SaveProject("foo", project.Project{Slug: "foo", Name: "Bar"}) data := []byte{'f', 'o', 'o'} header := http.Header(map[string][]string{}) @@ -238,11 +230,11 @@ func TestAPI_updateProjectImage(t *testing.T) { tc := NewTestClient(t) defer tc.Cleanup() - tc.repo.SaveProject("foo", project.Project{Slug: "foo", Name: "Bar"}) + _, _ = tc.repo.SaveProject("foo", project.Project{Slug: "foo", Name: "Bar"}) resp := tc.Request("PUT", "/api/tales/foo/image", nil) - AssertError(t, resp, "unsupported content-type: \n", 500) + AssertError(t, resp, "unsupported content-type", 500) }) t.Run("unknown project", func(t *testing.T) { tc := NewTestClient(t) @@ -250,7 +242,7 @@ func TestAPI_updateProjectImage(t *testing.T) { resp := tc.Request("PUT", "/api/tales/foo/image", nil) - AssertError(t, resp, "project not found\n", 404) + AssertError(t, resp, "project not found", 404) }) } @@ -267,7 +259,7 @@ func AssertProjectResponse(t *testing.T, resp *http.Response, expected project.P func AssertError(t *testing.T, resp *http.Response, errorMsg string, statusCode int) { AssertHTTPError(t, resp, statusCode) body, _ := io.ReadAll(resp.Body) - assert.Equal(t, errorMsg, string(body)) + assert.Contains(t, string(body), errorMsg) } func AssertHTTPError(t *testing.T, resp *http.Response, statusCode int) { diff --git a/pkg/web/server.go b/pkg/web/server.go index 5ad8cbc..88b15f9 100644 --- a/pkg/web/server.go +++ b/pkg/web/server.go @@ -3,13 +3,12 @@ package web import ( "net/http" - "github.com/gorilla/mux" - "synyx.de/tales/pkg/project" ) type server struct { - router http.Handler + router http.Handler + repository *project.FilesystemRepository } // Implements the http.Handler interface @@ -17,30 +16,29 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.router.ServeHTTP(w, r) } -// NewServer creates an http.Handler ready to handle tales requests. +// NewServer creates a http.Handler ready to handle tales requests. func NewServer(projectsDir, resourcesDir string) http.Handler { + r := http.NewServeMux() repository := &project.FilesystemRepository{ ProjectDir: projectsDir, } - - r := mux.NewRouter() - r.StrictSlash(true) + s := &server{ + router: r, + repository: repository, + } fs := http.FileServer(http.Dir(projectsDir)) - r.PathPrefix("/editor").Handler(http.StripPrefix("/editor", fs)) - r.PathPrefix("/presenter").Handler(http.StripPrefix("/presenter", fs)) + r.Handle("GET /editor/", http.StripPrefix("/editor", fs)) + r.Handle("GET /presenter/", http.StripPrefix("/presenter", fs)) - api := r.PathPrefix("/api/tales").Subrouter() - api.HandleFunc("/", listProjects(repository)).Methods("GET") - api.HandleFunc("/", createProject(repository)).Methods("POST") - api.HandleFunc("/{slug}", loadProject(repository)).Methods("GET") - api.HandleFunc("/{slug}", updateProject(repository)).Methods("PUT") - api.HandleFunc("/{slug}", deleteProject(repository)).Methods("DELETE") - api.HandleFunc("/{slug}/image", saveProjectImage(repository)).Methods("PUT") + r.HandleFunc("GET /api/tales/", s.listProjects) + r.HandleFunc("POST /api/tales/", s.createProject) + r.HandleFunc("GET /api/tales/{slug}", s.loadProject) + r.HandleFunc("PUT /api/tales/{slug}", s.updateProject) + r.HandleFunc("DELETE /api/tales/{slug}", s.deleteProject) + r.HandleFunc("PUT /api/tales/{slug}/image", s.saveProjectImage) - r.PathPrefix("/").Handler(http.FileServer(http.Dir(resourcesDir))) + r.Handle("GET /", http.FileServer(http.Dir(resourcesDir))) - return &server{ - router: r, - } + return s }