diff --git a/Makefile b/Makefile index 547f06e..8e56de7 100644 --- a/Makefile +++ b/Makefile @@ -16,41 +16,30 @@ build: build_linux_amd64: cd src \ - && GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/collerd-${APPVERSION}-linux-amd64 cmd/collerd/main.go \ - && GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/coller-${APPVERSION}-linux-amd64 cmd/coller/main.go \ - && GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/copier-${APPVERSION}-linux-amd64 cmd/copier/main.go - -archive_linux_amd64: - mkdir -p releases/coller-${APPVERSION}-linux-amd64 \ - && cp bin/collerd-${APPVERSION}-linux-amd64 releases/coller-${APPVERSION}-linux-amd64/collerd \ - && cp bin/coller-${APPVERSION}-linux-amd64 releases/coller-${APPVERSION}-linux-amd64/coller \ - && cp bin/copier-${APPVERSION}-linux-amd64 releases/coller-${APPVERSION}-linux-amd64/copier \ - && cd releases/ \ - && tar cvpzf coller-${APPVERSION}-linux-amd64.tar.gz coller-${APPVERSION}-linux-amd64 + && GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/collerd-linux-amd64 cmd/collerd/main.go \ + && GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/coller-linux-amd64 cmd/coller/main.go \ + && GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/copier-linux-amd64 cmd/copier/main.go build_darwin_arm64: cd src \ - && GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/collerd-${APPVERSION}-darwin-arm64 cmd/collerd/main.go \ - && GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/coller-${APPVERSION}-darwin-arm64 cmd/coller/main.go \ - && GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/copier-${APPVERSION}-darwin-arm64 cmd/copier/main.go + && GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/collerd-darwin-arm64 cmd/collerd/main.go \ + && GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/coller-darwin-arm64 cmd/coller/main.go \ + && GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/copier-darwin-arm64 cmd/copier/main.go -archive_darwin_arm64: - mkdir -p releases/coller-${APPVERSION}-darwin-arm64 \ - && cp bin/collerd-${APPVERSION}-darwin-arm64 releases/coller-${APPVERSION}-darwin-arm64/collerd \ - && cp bin/coller-${APPVERSION}-darwin-arm64 releases/coller-${APPVERSION}-darwin-arm64/coller \ - && cp bin/copier-${APPVERSION}-darwin-arm64 releases/coller-${APPVERSION}-darwin-arm64/copier \ - && cd releases/ \ - && tar cvpzf coller-${APPVERSION}-darwin-arm64.tar.gz coller-${APPVERSION}-darwin-arm64 +create_release: + mkdir -p releases/${APPVERSION} \ + && cp -p bin/collerd-linux-amd64 releases/${APPVERSION}/collerd-linux-amd64 \ + && cp -p bin/coller-linux-amd64 releases/${APPVERSION}/coller-linux-amd64 \ + && cp -p bin/copier-linux-amd64 releases/${APPVERSION}/copier-linux-amd64 \ + && cp -p bin/collerd-darwin-arm64 releases/${APPVERSION}/collerd-darwin-arm64 \ + && cp -p bin/coller-darwin-arm64 releases/${APPVERSION}/coller-darwin-arm64 \ + && cp -p bin/copier-darwin-arm64 releases/${APPVERSION}/copier-darwin-arm64 checksum: - cd releases \ - && sha256sum *.tar.gz > checksums.txt + cd releases/${APPVERSION} \ + && sha256sum collerd-* coller-* copier-* > checksums.txt -clean_for_releases: - rm -rf releases/coller-${APPVERSION}-linux-amd64 \ - && rm -rf releases/coller-${APPVERSION}-darwin-arm64 - -releases: build_linux_amd64 build_darwin_arm64 archive_linux_amd64 archive_darwin_arm64 checksum clean_for_releases +releases: build_linux_amd64 build_darwin_arm64 create_release checksum releases_with_docker: docker run -it -v $(shell pwd):/mnt -w /mnt -e "UID=$(shell id -u)" -e "GID=$(shell id -g)" ${DOCKER_IMAGE} ./docker/build.sh diff --git a/docker/build.sh b/docker/build.sh index 783ba44..47c817d 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -17,4 +17,4 @@ apt-get install -y libx11-dev make releases -chown ${UID}:${GID} -R releases +chown ${UID}:${GID} -R bin releases diff --git a/src/internal/utils.go b/src/internal/utils.go index c98c2e3..9db5ece 100644 --- a/src/internal/utils.go +++ b/src/internal/utils.go @@ -117,3 +117,12 @@ func ToLowerStringSlice(src []string) (dst []string) { } return } + +func InSlice(s []string, elem string) bool { + for _, v := range s { + if v == elem { + return true + } + } + return false +} diff --git a/src/internal/utils_test.go b/src/internal/utils_test.go index cc251f0..eef7c03 100644 --- a/src/internal/utils_test.go +++ b/src/internal/utils_test.go @@ -85,3 +85,25 @@ func TestToLowerStringsSlice(t *testing.T) { t.Logf("got '%s', want '%s'", got, expected) } } + +func TestInSlice(t *testing.T) { + tests := []struct { + elem string + s []string + expected bool + }{ + {"linux", []string{"linux", "darwin"}, true}, + {"windows", []string{"linux", "darwin"}, false}, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("TestInSlice#%s", tc.elem), func(t *testing.T) { + got := InSlice(tc.s, tc.elem) + if got != tc.expected { + t.Errorf("got '%t', want '%t'", got, tc.expected) + } else { + t.Logf("got '%t', want '%t'", got, tc.expected) + } + }) + } +} diff --git a/src/server/handlers_api.go b/src/server/handlers_api.go new file mode 100644 index 0000000..f260328 --- /dev/null +++ b/src/server/handlers_api.go @@ -0,0 +1,149 @@ +package server + +import ( + "encoding/json" + "fmt" + "log/slog" + "net/http" + + "github.com/gorilla/mux" + + "git.riou.xyz/jriou/coller/internal" +) + +func HealthHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "OK") +} + +type CreateNoteHandler struct { + logger *slog.Logger + db *Database + maxUploadSize int64 +} + +type CreateNotePayload struct { + Content string `json:"content"` + Password string `json:"password"` + Encrypted bool `json:"encrypted"` + Expiration int `json:"expiration"` + DeleteAfterRead bool `json:"delete_after_read"` + Language string `json:"language"` +} + +type CreateNoteResponse struct { + ID string `json:"id"` +} + +func (h *CreateNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + bodyReader := http.MaxBytesReader(w, r.Body, h.maxUploadSize) + defer r.Body.Close() + + var body CreateNotePayload + err := json.NewDecoder(bodyReader).Decode(&body) + if err != nil { + WriteError(w, "could not decode payload to create note", err) + return + } + + content, err := internal.Decode(body.Content) + + if err != nil { + WriteError(w, "could not decode content", err) + return + } + + note, err := h.db.Create(content, body.Password, body.Encrypted, body.Expiration, body.DeleteAfterRead, body.Language) + if err != nil { + WriteError(w, "could not create note", err) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(CreateNoteResponse{ID: note.ID}) +} + +type GetNoteHandler struct { + logger *slog.Logger + db *Database +} + +func (h *GetNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + + id := mux.Vars(r)["id"] + + note, err := h.db.Get(id) + + if err != nil { + WriteError(w, "could not get note", err) + } else if note == nil { + w.WriteHeader(http.StatusNotFound) + } else { + if note.Encrypted { + w.Header().Set("Content-Type", "application/octet-stream") + } + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, string(note.Content)) + } +} + +type GetProtectedNoteHandler struct { + logger *slog.Logger + db *Database +} + +func (h *GetProtectedNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + + vars := mux.Vars(r) + id := vars["id"] + password := vars["password"] + + note, err := h.db.Get(id) + + if err != nil { + WriteError(w, "could not get note", err) + return + } else if note == nil { + w.WriteHeader(http.StatusNotFound) + return + } + + if password != "" && note.Encrypted { + note.Content, err = internal.Decrypt(note.Content, password) + if err != nil { + WriteError(w, "could not decrypt note", err) + return + } + } + + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, string(note.Content)) +} + +type ClientHandler struct { + logger *slog.Logger + version string +} + +func (h *ClientHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.logger.Debug("rendering client redirection") + vars := mux.Vars(r) + os := vars["os"] + arch := vars["arch"] + clientName := vars["clientName"] + + if !internal.InSlice(supportedOSes, os) || !internal.InSlice(supportedArches, arch) || !internal.InSlice(supportedClients, clientName) { + w.WriteHeader(http.StatusNotFound) + return + } + + version := h.version + if version == "" { + version = "latest" + } + + http.Redirect(w, r, fmt.Sprintf("https://git.riou.xyz/jriou/%s/releases/download/%s/%s-%s-%s", clientName, version, clientName, os, arch), http.StatusMovedPermanently) +} diff --git a/src/server/handlers_web.go b/src/server/handlers_web.go new file mode 100644 index 0000000..d059d7a --- /dev/null +++ b/src/server/handlers_web.go @@ -0,0 +1,234 @@ +package server + +import ( + "bytes" + "errors" + "fmt" + "html/template" + "io" + "log/slog" + "net/http" + "strconv" + "strings" + + "github.com/gorilla/mux" + + "git.riou.xyz/jriou/coller/internal" +) + +type PageData struct { + Title string + Version string + Expirations []int + Expiration int + Languages []string + Err error + URL string + Note *Note + EnableUploadFileButton bool + BootstrapDirectory string +} + +type HomeHandler struct { + Templates *template.Template + PageData PageData +} + +func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.Templates.ExecuteTemplate(w, "index", h.PageData) +} + +type CreateNoteWithFormHandler struct { + Templates *template.Template + PageData PageData + logger *slog.Logger + db *Database + maxUploadSize int64 +} + +func (h *CreateNoteWithFormHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.PageData.Err = nil + templateName := "create" + + h.logger.Debug("parsing multipart form") + err := r.ParseMultipartForm(h.maxUploadSize) + if err != nil { + h.PageData.Err = err + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + h.logger.Debug("parsing content") + content := []byte(r.FormValue("content")) + + h.logger.Debug("parsing file") + file, handler, err := r.FormFile("file") + if err != nil && !errors.Is(err, http.ErrMissingFile) { + h.PageData.Err = err + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + if !errors.Is(err, http.ErrMissingFile) { + defer file.Close() + + h.logger.Debug("checking file size") + if handler.Size > h.maxUploadSize { + h.PageData.Err = fmt.Errorf("file too large (%d > %d)", handler.Size, h.maxUploadSize) + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + h.logger.Debug("checking file content type") + if !strings.HasPrefix(handler.Header.Get("Content-Type"), "text/") { + h.PageData.Err = fmt.Errorf("text file expected (got %s)", handler.Header.Get("Content-Type")) + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + h.logger.Debug("reading uploaded file") + var fileContent bytes.Buffer + n, err := io.Copy(&fileContent, file) + if err != nil { + h.PageData.Err = err + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + h.logger.Debug("file uploaded", slog.Any("bytes", n)) + if n != 0 { + content = fileContent.Bytes() + } + } + + h.logger.Debug("checking content") + if content == nil || len(content) == 0 { + h.PageData.Err = fmt.Errorf("empty note") + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + h.logger.Debug("checking inputs") + noPassword := r.FormValue("no-password") + password := r.FormValue("password") + expiration := r.FormValue("expiration") + deleteAfterRead := r.FormValue("delete-after-read") + language := r.FormValue("language") + + if password == "" && noPassword == "" { + h.logger.Debug("generating password") + password = internal.GenerateChars(passwordLength) + } + + h.logger.Debug("computing expiration") + var expirationInt int + if expiration == "Expiration" { + expirationInt = 0 + } else { + expirationInt, _ = strconv.Atoi(expiration) + } + + h.logger.Debug("saving note to the database") + note, err := h.db.Create(content, password, password != "", expirationInt, deleteAfterRead != "", language) + if err != nil { + h.PageData.Err = err + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + h.logger.Debug("building note url") + var scheme = "http://" + if r.TLS != nil { + scheme = "https://" + } + + h.PageData.URL = fmt.Sprintf("%s%s/%s", scheme, r.Host, note.ID) + if password != "" { + h.PageData.URL += "/" + password + } + + h.logger.Debug("rendering page") + h.Templates.ExecuteTemplate(w, "create", h.PageData) +} + +type GetWebNoteHandler struct { + Templates *template.Template + PageData PageData + logger *slog.Logger + db *Database +} + +func (h *GetWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.PageData.Err = nil + templateName := "note" + + vars := mux.Vars(r) + id := vars["id"] + + note, err := h.db.Get(id) + + if err != nil { + h.PageData.Err = fmt.Errorf("could not find note: %v", err) + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + if note == nil { + h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted") + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + if note.Encrypted { + h.PageData.Err = fmt.Errorf("note is encrypted") + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + h.PageData.Note = note + + h.logger.Debug("rendering note web page") + h.Templates.ExecuteTemplate(w, "note", h.PageData) +} + +func (h *GetProtectedWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.PageData.Err = nil + templateName := "note" + + vars := mux.Vars(r) + id := vars["id"] + password := vars["password"] + + note, err := h.db.Get(id) + + if err != nil { + h.PageData.Err = fmt.Errorf("could not find note: %v", err) + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + if note == nil { + h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted") + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + + if password != "" && note.Encrypted { + note.Content, err = internal.Decrypt(note.Content, password) + if err != nil { + h.PageData.Err = fmt.Errorf("could not decrypt note: %v", err) + h.Templates.ExecuteTemplate(w, templateName, h.PageData) + return + } + } + + h.PageData.Note = note + + h.logger.Debug("rendering protected note web page") + h.Templates.ExecuteTemplate(w, "note", h.PageData) +} + +func (h *ClientsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.logger.Debug("rendering clients web page") + h.Templates.ExecuteTemplate(w, "clients", h.PageData) +} diff --git a/src/server/server.go b/src/server/server.go index f8a2d3f..0c9bae5 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -1,16 +1,12 @@ package server import ( - "bytes" "embed" "encoding/json" - "errors" "fmt" "html/template" - "io" "log/slog" "net/http" - "strconv" "strings" "github.com/gorilla/mux" @@ -19,7 +15,12 @@ import ( "git.riou.xyz/jriou/coller/internal" ) -var passwordLength = internal.MIN_PASSWORD_LENGTH +var ( + passwordLength = internal.MIN_PASSWORD_LENGTH + supportedOSes = []string{"linux", "darwin"} + supportedArches = []string{"amd64", "arm64"} + supportedClients = []string{"coller", "copier"} +) type Server struct { logger *slog.Logger @@ -74,293 +75,6 @@ func WriteError(w http.ResponseWriter, message string, err error) { }.ToJSON()) } -func HealthHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "OK") -} - -type CreateNoteHandler struct { - logger *slog.Logger - db *Database - maxUploadSize int64 -} - -type CreateNotePayload struct { - Content string `json:"content"` - Password string `json:"password"` - Encrypted bool `json:"encrypted"` - Expiration int `json:"expiration"` - DeleteAfterRead bool `json:"delete_after_read"` - Language string `json:"language"` -} - -type CreateNoteResponse struct { - ID string `json:"id"` -} - -func (h *CreateNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - bodyReader := http.MaxBytesReader(w, r.Body, h.maxUploadSize) - defer r.Body.Close() - - var body CreateNotePayload - err := json.NewDecoder(bodyReader).Decode(&body) - if err != nil { - WriteError(w, "could not decode payload to create note", err) - return - } - - content, err := internal.Decode(body.Content) - - if err != nil { - WriteError(w, "could not decode content", err) - return - } - - note, err := h.db.Create(content, body.Password, body.Encrypted, body.Expiration, body.DeleteAfterRead, body.Language) - if err != nil { - WriteError(w, "could not create note", err) - return - } - - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(CreateNoteResponse{ID: note.ID}) -} - -type GetNoteHandler struct { - logger *slog.Logger - db *Database -} - -func (h *GetNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - - id := mux.Vars(r)["id"] - - note, err := h.db.Get(id) - - if err != nil { - WriteError(w, "could not get note", err) - } else if note == nil { - w.WriteHeader(http.StatusNotFound) - } else { - if note.Encrypted { - w.Header().Set("Content-Type", "application/octet-stream") - } - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, string(note.Content)) - } -} - -type GetProtectedNoteHandler struct { - logger *slog.Logger - db *Database -} - -func (h *GetProtectedNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - - vars := mux.Vars(r) - id := vars["id"] - password := vars["password"] - - note, err := h.db.Get(id) - - if err != nil { - WriteError(w, "could not get note", err) - return - } else if note == nil { - w.WriteHeader(http.StatusNotFound) - return - } - - if password != "" && note.Encrypted { - note.Content, err = internal.Decrypt(note.Content, password) - if err != nil { - WriteError(w, "could not decrypt note", err) - return - } - } - - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, string(note.Content)) -} - -type PageData struct { - Title string - Version string - Expirations []int - Expiration int - Languages []string - Err error - URL string - Note *Note - EnableUploadFileButton bool - BootstrapDirectory string -} - -type HomeHandler struct { - Templates *template.Template - PageData PageData -} - -func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - h.Templates.ExecuteTemplate(w, "index", h.PageData) -} - -type CreateNoteWithFormHandler struct { - Templates *template.Template - PageData PageData - logger *slog.Logger - db *Database - maxUploadSize int64 -} - -func (h *CreateNoteWithFormHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - h.PageData.Err = nil - templateName := "create" - - h.logger.Debug("parsing multipart form") - err := r.ParseMultipartForm(h.maxUploadSize) - if err != nil { - h.PageData.Err = err - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - h.logger.Debug("parsing content") - content := []byte(r.FormValue("content")) - - h.logger.Debug("parsing file") - file, handler, err := r.FormFile("file") - if err != nil && !errors.Is(err, http.ErrMissingFile) { - h.PageData.Err = err - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - if !errors.Is(err, http.ErrMissingFile) { - defer file.Close() - - h.logger.Debug("checking file size") - if handler.Size > h.maxUploadSize { - h.PageData.Err = fmt.Errorf("file too large (%d > %d)", handler.Size, h.maxUploadSize) - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - h.logger.Debug("checking file content type") - if !strings.HasPrefix(handler.Header.Get("Content-Type"), "text/") { - h.PageData.Err = fmt.Errorf("text file expected (got %s)", handler.Header.Get("Content-Type")) - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - h.logger.Debug("reading uploaded file") - var fileContent bytes.Buffer - n, err := io.Copy(&fileContent, file) - if err != nil { - h.PageData.Err = err - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - h.logger.Debug("file uploaded", slog.Any("bytes", n)) - if n != 0 { - content = fileContent.Bytes() - } - } - - h.logger.Debug("checking content") - if content == nil || len(content) == 0 { - h.PageData.Err = fmt.Errorf("empty note") - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - h.logger.Debug("checking inputs") - noPassword := r.FormValue("no-password") - password := r.FormValue("password") - expiration := r.FormValue("expiration") - deleteAfterRead := r.FormValue("delete-after-read") - language := r.FormValue("language") - - if password == "" && noPassword == "" { - h.logger.Debug("generating password") - password = internal.GenerateChars(passwordLength) - } - - h.logger.Debug("computing expiration") - var expirationInt int - if expiration == "Expiration" { - expirationInt = 0 - } else { - expirationInt, _ = strconv.Atoi(expiration) - } - - h.logger.Debug("saving note to the database") - note, err := h.db.Create(content, password, password != "", expirationInt, deleteAfterRead != "", language) - if err != nil { - h.PageData.Err = err - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - h.logger.Debug("building note url") - var scheme = "http://" - if r.TLS != nil { - scheme = "https://" - } - - h.PageData.URL = fmt.Sprintf("%s%s/%s", scheme, r.Host, note.ID) - if password != "" { - h.PageData.URL += "/" + password - } - - h.logger.Debug("rendering page") - h.Templates.ExecuteTemplate(w, "create", h.PageData) -} - -type GetWebNoteHandler struct { - Templates *template.Template - PageData PageData - logger *slog.Logger - db *Database -} - -func (h *GetWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - h.PageData.Err = nil - templateName := "note" - - vars := mux.Vars(r) - id := vars["id"] - - note, err := h.db.Get(id) - - if err != nil { - h.PageData.Err = fmt.Errorf("could not find note: %v", err) - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - if note == nil { - h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted") - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - if note.Encrypted { - h.PageData.Err = fmt.Errorf("note is encrypted") - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - h.PageData.Note = note - - h.logger.Debug("rendering note web page") - h.Templates.ExecuteTemplate(w, "note", h.PageData) -} - type GetProtectedWebNoteHandler struct { Templates *template.Template PageData PageData @@ -368,41 +82,10 @@ type GetProtectedWebNoteHandler struct { db *Database } -func (h *GetProtectedWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - h.PageData.Err = nil - templateName := "note" - - vars := mux.Vars(r) - id := vars["id"] - password := vars["password"] - - note, err := h.db.Get(id) - - if err != nil { - h.PageData.Err = fmt.Errorf("could not find note: %v", err) - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - if note == nil { - h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted") - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - - if password != "" && note.Encrypted { - note.Content, err = internal.Decrypt(note.Content, password) - if err != nil { - h.PageData.Err = fmt.Errorf("could not decrypt note: %v", err) - h.Templates.ExecuteTemplate(w, templateName, h.PageData) - return - } - } - - h.PageData.Note = note - - h.logger.Debug("rendering protected note web page") - h.Templates.ExecuteTemplate(w, "note", h.PageData) +type ClientsHandler struct { + Templates *template.Template + PageData PageData + logger *slog.Logger } //go:embed templates/* @@ -456,6 +139,14 @@ func (s *Server) Start() error { } r.Path("/create").Handler(createNoteWithFormHandler).Methods("POST") + clientsHandler := &ClientsHandler{ + Templates: templates, + PageData: p, + logger: s.logger, + } + r.Path("/clients.html").Handler(clientsHandler).Methods("GET") + r.Path("/clients/{os:[a-z]+}-{arch:[a-z0-9]+}/{clientName:[a-z]+}").Handler(&ClientHandler{logger: s.logger, version: p.Version}).Methods("GET") + protectedWebNoteHandler := &GetProtectedWebNoteHandler{ Templates: templates, PageData: p, diff --git a/src/server/templates/clients.html b/src/server/templates/clients.html new file mode 100644 index 0000000..6e57267 --- /dev/null +++ b/src/server/templates/clients.html @@ -0,0 +1,62 @@ +{{define "clients"}} + + + +{{block "head" .}}{{end}} + +
+ {{block "header" .}}{{end}} +Command-line clients
+| Name | +Docs | +OS | +Arch | +Download | +
|---|---|---|---|---|
| coller | +📄 + | +Linux | +x86-64 | +💾 | +
| macOS | +ARM64 | +💾 | +||
| copier | +📄 + | +Linux | +x86-64 | +💾 | +
| macOS | +ARM64 | +💾 | +