From 246f12c8cbd020a3b54f027795a5a9475d69d674 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Thu, 14 Dec 2023 19:14:59 -0600 Subject: [PATCH] feat: default visibility (#155) Signed-off-by: jolheiser --- internal/db/gist.go | 54 +++++++-- internal/db/user.go | 1 - internal/web/gist.go | 33 +++--- internal/web/git_http.go | 17 +-- internal/web/server.go | 194 +++++++++++++++++---------------- internal/web/settings.go | 10 +- internal/web/test/gist_test.go | 13 ++- public/main.ts | 8 +- templates/pages/edit.html | 2 +- 9 files changed, 187 insertions(+), 145 deletions(-) diff --git a/internal/db/gist.go b/internal/db/gist.go index d54007fb..d11fe5e3 100644 --- a/internal/db/gist.go +++ b/internal/db/gist.go @@ -1,14 +1,48 @@ package db import ( - "github.com/labstack/echo/v4" - "github.com/thomiceli/opengist/internal/git" - "gorm.io/gorm" + "fmt" "os/exec" "strings" "time" + + "github.com/labstack/echo/v4" + "github.com/thomiceli/opengist/internal/git" + "gorm.io/gorm" +) + +type Visibility int + +const ( + PublicVisibility Visibility = iota + UnlistedVisibility + PrivateVisibility ) +func (v Visibility) Next() Visibility { + switch v { + case PublicVisibility: + return UnlistedVisibility + case UnlistedVisibility: + return PrivateVisibility + default: + return PublicVisibility + } +} + +func ParseVisibility[T string | int](v T) (Visibility, error) { + switch s := fmt.Sprint(v); s { + case "0": + return PublicVisibility, nil + case "1": + return UnlistedVisibility, nil + case "2": + return PrivateVisibility, nil + default: + return -1, fmt.Errorf("unknown visibility %q", s) + } +} + type Gist struct { ID uint `gorm:"primaryKey"` Uuid string @@ -16,7 +50,7 @@ type Gist struct { Preview string PreviewFilename string Description string - Private int // 0: public, 1: unlisted, 2: private + Private Visibility // 0: public, 1: unlisted, 2: private UserID uint User User NbFiles int @@ -386,12 +420,12 @@ func (gist *Gist) UpdatePreviewAndCount() error { // -- DTO -- // type GistDTO struct { - Title string `validate:"max=250" form:"title"` - Description string `validate:"max=1000" form:"description"` - Private int `validate:"number,min=0,max=2" form:"private"` - Files []FileDTO `validate:"min=1,dive"` - Name []string `form:"name"` - Content []string `form:"content"` + Title string `validate:"max=250" form:"title"` + Description string `validate:"max=1000" form:"description"` + Private Visibility `validate:"number,min=0,max=2" form:"private"` + Files []FileDTO `validate:"min=1,dive"` + Name []string `form:"name"` + Content []string `form:"content"` } type FileDTO struct { diff --git a/internal/db/user.go b/internal/db/user.go index d5ec34a8..341b240b 100644 --- a/internal/db/user.go +++ b/internal/db/user.go @@ -100,7 +100,6 @@ func GetUsersFromEmails(emailsSet map[string]struct{}) (map[string]*User, error) err := db. Where("email IN ?", emails). Find(&users).Error - if err != nil { return nil, err } diff --git a/internal/web/gist.go b/internal/web/gist.go index 0f245b04..2a0367c3 100644 --- a/internal/web/gist.go +++ b/internal/web/gist.go @@ -4,16 +4,17 @@ import ( "archive/zip" "bytes" "errors" - "github.com/google/uuid" - "github.com/labstack/echo/v4" - "github.com/thomiceli/opengist/internal/config" - "github.com/thomiceli/opengist/internal/db" - "gorm.io/gorm" "html/template" "net/url" "regexp" "strconv" "strings" + + "github.com/google/uuid" + "github.com/labstack/echo/v4" + "github.com/thomiceli/opengist/internal/config" + "github.com/thomiceli/opengist/internal/db" + "gorm.io/gorm" ) func gistInit(next echo.HandlerFunc) echo.HandlerFunc { @@ -30,7 +31,7 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc { return notFound("Gist not found") } - if gist.Private == 2 { + if gist.Private == db.PrivateVisibility { if currUser == nil || currUser.ID != gist.UserID { return notFound("Gist not found") } @@ -433,7 +434,7 @@ func processCreate(ctx echo.Context) error { } func toggleVisibility(ctx echo.Context) error { - var gist = getData(ctx, "gist").(*db.Gist) + gist := getData(ctx, "gist").(*db.Gist) gist.Private = (gist.Private + 1) % 3 if err := gist.Update(); err != nil { @@ -445,7 +446,7 @@ func toggleVisibility(ctx echo.Context) error { } func deleteGist(ctx echo.Context) error { - var gist = getData(ctx, "gist").(*db.Gist) + gist := getData(ctx, "gist").(*db.Gist) if err := gist.Delete(); err != nil { return errorRes(500, "Error deleting this gist", err) @@ -456,7 +457,7 @@ func deleteGist(ctx echo.Context) error { } func like(ctx echo.Context) error { - var gist = getData(ctx, "gist").(*db.Gist) + gist := getData(ctx, "gist").(*db.Gist) currentUser := getUserLogged(ctx) hasLiked, err := currentUser.HasLiked(gist) @@ -482,7 +483,7 @@ func like(ctx echo.Context) error { } func fork(ctx echo.Context) error { - var gist = getData(ctx, "gist").(*db.Gist) + gist := getData(ctx, "gist").(*db.Gist) currentUser := getUserLogged(ctx) alreadyForked, err := gist.GetForkParent(currentUser) @@ -535,7 +536,6 @@ func fork(ctx echo.Context) error { func rawFile(ctx echo.Context) error { gist := getData(ctx, "gist").(*db.Gist) file, err := gist.File(ctx.Param("revision"), ctx.Param("file"), false) - if err != nil { return errorRes(500, "Error getting file content", err) } @@ -550,7 +550,6 @@ func rawFile(ctx echo.Context) error { func downloadFile(ctx echo.Context) error { gist := getData(ctx, "gist").(*db.Gist) file, err := gist.File(ctx.Param("revision"), ctx.Param("file"), false) - if err != nil { return errorRes(500, "Error getting file content", err) } @@ -572,7 +571,7 @@ func downloadFile(ctx echo.Context) error { } func edit(ctx echo.Context) error { - var gist = getData(ctx, "gist").(*db.Gist) + gist := getData(ctx, "gist").(*db.Gist) files, err := gist.Files("HEAD") if err != nil { @@ -586,8 +585,8 @@ func edit(ctx echo.Context) error { } func downloadZip(ctx echo.Context) error { - var gist = getData(ctx, "gist").(*db.Gist) - var revision = ctx.Param("revision") + gist := getData(ctx, "gist").(*db.Gist) + revision := ctx.Param("revision") files, err := gist.Files(revision) if err != nil { @@ -631,7 +630,7 @@ func downloadZip(ctx echo.Context) error { } func likes(ctx echo.Context) error { - var gist = getData(ctx, "gist").(*db.Gist) + gist := getData(ctx, "gist").(*db.Gist) pageInt := getPage(ctx) @@ -650,7 +649,7 @@ func likes(ctx echo.Context) error { } func forks(ctx echo.Context) error { - var gist = getData(ctx, "gist").(*db.Gist) + gist := getData(ctx, "gist").(*db.Gist) pageInt := getPage(ctx) currentUser := getUserLogged(ctx) diff --git a/internal/web/git_http.go b/internal/web/git_http.go index a514d8d4..4d69dcae 100644 --- a/internal/web/git_http.go +++ b/internal/web/git_http.go @@ -6,13 +6,6 @@ import ( "encoding/base64" "errors" "fmt" - "github.com/google/uuid" - "github.com/labstack/echo/v4" - "github.com/rs/zerolog/log" - "github.com/thomiceli/opengist/internal/db" - "github.com/thomiceli/opengist/internal/git" - "github.com/thomiceli/opengist/internal/memdb" - "gorm.io/gorm" "net/http" "os" "os/exec" @@ -21,6 +14,14 @@ import ( "strconv" "strings" "time" + + "github.com/google/uuid" + "github.com/labstack/echo/v4" + "github.com/rs/zerolog/log" + "github.com/thomiceli/opengist/internal/db" + "github.com/thomiceli/opengist/internal/git" + "github.com/thomiceli/opengist/internal/memdb" + "gorm.io/gorm" ) var routes = []struct { @@ -73,7 +74,7 @@ func gitHttp(ctx echo.Context) error { // - user wants to clone/pull a private gist // - gist is not found (obfuscation) // - admin setting to require login is set to true - if isPull && gist.Private != 2 && gist.ID != 0 && !getData(ctx, "RequireLogin").(bool) { + if isPull && gist.Private != db.PrivateVisibility && gist.ID != 0 && !getData(ctx, "RequireLogin").(bool) { return route.handler(ctx) } diff --git a/internal/web/server.go b/internal/web/server.go index 48f867b2..c5989c68 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -4,6 +4,16 @@ import ( "context" "encoding/json" "fmt" + htmlpkg "html" + "html/template" + "io" + "net/http" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + "github.com/gorilla/sessions" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -16,106 +26,99 @@ import ( "github.com/thomiceli/opengist/public" "github.com/thomiceli/opengist/templates" "golang.org/x/text/language" - htmlpkg "html" - "html/template" - "io" - "net/http" - "path/filepath" - "regexp" - "strconv" - "strings" - "time" ) -var dev bool -var store *sessions.CookieStore -var re = regexp.MustCompile("[^a-z0-9]+") -var fm = template.FuncMap{ - "split": strings.Split, - "indexByte": strings.IndexByte, - "toInt": func(i string) int { - val, _ := strconv.Atoi(i) - return val - }, - "inc": func(i int) int { - return i + 1 - }, - "splitGit": func(i string) []string { - return strings.FieldsFunc(i, func(r rune) bool { - return r == ',' || r == ' ' - }) - }, - "lines": func(i string) []string { - return strings.Split(i, "\n") - }, - "isMarkdown": func(i string) bool { - return strings.ToLower(filepath.Ext(i)) == ".md" - }, - "isCsv": func(i string) bool { - return strings.ToLower(filepath.Ext(i)) == ".csv" - }, - "csvFile": func(file *git.File) *git.CsvFile { - if strings.ToLower(filepath.Ext(file.Filename)) != ".csv" { - return nil - } +var ( + dev bool + store *sessions.CookieStore + re = regexp.MustCompile("[^a-z0-9]+") + fm = template.FuncMap{ + "split": strings.Split, + "indexByte": strings.IndexByte, + "toInt": func(i string) int { + val, _ := strconv.Atoi(i) + return val + }, + "inc": func(i int) int { + return i + 1 + }, + "splitGit": func(i string) []string { + return strings.FieldsFunc(i, func(r rune) bool { + return r == ',' || r == ' ' + }) + }, + "lines": func(i string) []string { + return strings.Split(i, "\n") + }, + "isMarkdown": func(i string) bool { + return strings.ToLower(filepath.Ext(i)) == ".md" + }, + "isCsv": func(i string) bool { + return strings.ToLower(filepath.Ext(i)) == ".csv" + }, + "csvFile": func(file *git.File) *git.CsvFile { + if strings.ToLower(filepath.Ext(file.Filename)) != ".csv" { + return nil + } - csvFile, err := git.ParseCsv(file) - if err != nil { - return nil - } + csvFile, err := git.ParseCsv(file) + if err != nil { + return nil + } - return csvFile - }, - "httpStatusText": http.StatusText, - "loadedTime": func(startTime time.Time) string { - return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" - }, - "slug": func(s string) string { - return strings.Trim(re.ReplaceAllString(strings.ToLower(s), "-"), "-") - }, - "avatarUrl": func(user *db.User, noGravatar bool) string { - if user.AvatarURL != "" { - return user.AvatarURL - } + return csvFile + }, + "httpStatusText": http.StatusText, + "loadedTime": func(startTime time.Time) string { + return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" + }, + "slug": func(s string) string { + return strings.Trim(re.ReplaceAllString(strings.ToLower(s), "-"), "-") + }, + "avatarUrl": func(user *db.User, noGravatar bool) string { + if user.AvatarURL != "" { + return user.AvatarURL + } - if user.MD5Hash != "" && !noGravatar { - return "https://www.gravatar.com/avatar/" + user.MD5Hash + "?d=identicon&s=200" - } + if user.MD5Hash != "" && !noGravatar { + return "https://www.gravatar.com/avatar/" + user.MD5Hash + "?d=identicon&s=200" + } - return defaultAvatar() - }, - "asset": func(file string) string { - if dev { - return "http://localhost:16157/" + file - } - return config.C.ExternalUrl + "/" + manifestEntries[file].File - }, - "dev": func() bool { - return dev - }, - "defaultAvatar": defaultAvatar, - "visibilityStr": func(visibility int, lowercase bool) string { - s := "Public" - switch visibility { - case 1: - s = "Unlisted" - case 2: - s = "Private" - } + return defaultAvatar() + }, + "asset": func(file string) string { + if dev { + return "http://localhost:16157/" + file + } + return config.C.ExternalUrl + "/" + manifestEntries[file].File + }, + "dev": func() bool { + return dev + }, + "defaultAvatar": defaultAvatar, + "visibilityStr": func(visibility db.Visibility, lowercase bool) string { + s := "Public" + switch visibility { + case 1: + s = "Unlisted" + case 2: + s = "Private" + } - if lowercase { - return strings.ToLower(s) - } - return s - }, - "unescape": htmlpkg.UnescapeString, - "join": func(s ...string) string { - return strings.Join(s, "") - }, - "toStr": func(i interface{}) string { - return fmt.Sprint(i) - }, -} + if lowercase { + return strings.ToLower(s) + } + return s + }, + "unescape": htmlpkg.UnescapeString, + "join": func(s ...string) string { + return strings.Join(s, "") + }, + "toStr": func(i interface{}) string { + return fmt.Sprint(i) + }, + } +) type Template struct { templates *template.Template @@ -159,7 +162,7 @@ func NewServer(isDev bool) *Server { return nil }, })) - //e.Use(middleware.Recover()) + // e.Use(middleware.Recover()) e.Use(middleware.Secure()) e.Renderer = &Template{ @@ -316,7 +319,6 @@ func dataInit(next echo.HandlerFunc) echo.HandlerFunc { func locale(next echo.HandlerFunc) echo.HandlerFunc { return func(ctx echo.Context) error { - // Check URL arguments lang := ctx.Request().URL.Query().Get("lang") changeLang := lang != "" @@ -335,7 +337,7 @@ func locale(next echo.HandlerFunc) echo.HandlerFunc { changeLang = false } - //3.Then check from 'Accept-Language' header. + // 3.Then check from 'Accept-Language' header. if len(lang) == 0 { tags, _, _ := language.ParseAcceptLanguage(ctx.Request().Header.Get("Accept-Language")) lang = i18n.Locales.MatchTag(tags) diff --git a/internal/web/settings.go b/internal/web/settings.go index 3df99fde..457324da 100644 --- a/internal/web/settings.go +++ b/internal/web/settings.go @@ -3,12 +3,13 @@ package web import ( "crypto/md5" "fmt" - "github.com/labstack/echo/v4" - "github.com/thomiceli/opengist/internal/db" - "golang.org/x/crypto/ssh" "strconv" "strings" "time" + + "github.com/labstack/echo/v4" + "github.com/thomiceli/opengist/internal/db" + "golang.org/x/crypto/ssh" ) func userSettings(ctx echo.Context) error { @@ -62,7 +63,7 @@ func accountDeleteProcess(ctx echo.Context) error { func sshKeysProcess(ctx echo.Context) error { user := getUserLogged(ctx) - var dto = new(db.SSHKeyDTO) + dto := new(db.SSHKeyDTO) if err := ctx.Bind(dto); err != nil { return errorRes(400, "Cannot bind data", err) } @@ -93,7 +94,6 @@ func sshKeysProcess(ctx echo.Context) error { func sshKeysDelete(ctx echo.Context) error { user := getUserLogged(ctx) keyId, err := strconv.Atoi(ctx.Param("id")) - if err != nil { return redirect(ctx, "/settings") } diff --git a/internal/web/test/gist_test.go b/internal/web/test/gist_test.go index b477e0e1..4dfc50ab 100644 --- a/internal/web/test/gist_test.go +++ b/internal/web/test/gist_test.go @@ -1,10 +1,11 @@ package test import ( + "testing" + "github.com/stretchr/testify/require" "github.com/thomiceli/opengist/internal/db" "github.com/thomiceli/opengist/internal/git" - "testing" ) func TestGists(t *testing.T) { @@ -110,7 +111,7 @@ func TestVisibility(t *testing.T) { gist1 := db.GistDTO{ Title: "gist1", Description: "my first gist", - Private: 1, + Private: db.UnlistedVisibility, Name: []string{""}, Content: []string{"yeah"}, } @@ -119,25 +120,25 @@ func TestVisibility(t *testing.T) { gist1db, err := db.GetGistByID("1") require.NoError(t, err) - require.Equal(t, 1, gist1db.Private) + require.Equal(t, db.UnlistedVisibility, gist1db.Private) err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", nil, 302) require.NoError(t, err) gist1db, err = db.GetGistByID("1") require.NoError(t, err) - require.Equal(t, 2, gist1db.Private) + require.Equal(t, db.PrivateVisibility, gist1db.Private) err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", nil, 302) require.NoError(t, err) gist1db, err = db.GetGistByID("1") require.NoError(t, err) - require.Equal(t, 0, gist1db.Private) + require.Equal(t, db.PublicVisibility, gist1db.Private) err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", nil, 302) require.NoError(t, err) gist1db, err = db.GetGistByID("1") require.NoError(t, err) - require.Equal(t, 1, gist1db.Private) + require.Equal(t, db.UnlistedVisibility, gist1db.Private) } func TestLikeFork(t *testing.T) { diff --git a/public/main.ts b/public/main.ts index 33606807..b759ed8a 100644 --- a/public/main.ts +++ b/public/main.ts @@ -154,12 +154,18 @@ document.addEventListener('DOMContentLoaded', () => { document.getElementById('gist-visibility-menu-button')!.onclick = () => { gistmenuvisibility!.classList.toggle('hidden'); } + const lastVisibility = localStorage.getItem('visibility'); Array.from(document.querySelectorAll('.gist-visibility-option')).forEach((el) => { + const visibility = (el as HTMLElement).dataset.visibility || '0'; (el as HTMLElement).onclick = () => { submitgistbutton.textContent = (el as HTMLElement).dataset.btntext; - submitgistbutton!.value = (el as HTMLElement).dataset.visibility || '0'; + submitgistbutton!.value = visibility; + localStorage.setItem('visibility', visibility); gistmenuvisibility!.classList.add('hidden'); } + if (lastVisibility === visibility) { + (el as HTMLElement).click(); + } }); } }); diff --git a/templates/pages/edit.html b/templates/pages/edit.html index a89067b1..f6c7ebc0 100644 --- a/templates/pages/edit.html +++ b/templates/pages/edit.html @@ -21,7 +21,7 @@

{{ end }} - {{ .locale.Tr "gist.edit.change-visibility" }} {{ visibilityStr (inc .gist.Private) true }} + {{ .locale.Tr "gist.edit.change-visibility" }} {{ visibilityStr .gist.Private.Next true }}