feat: default visibility (#155)

Signed-off-by: jolheiser <john.olheiser@gmail.com>
This commit is contained in:
John Olheiser 2023-12-14 19:14:59 -06:00 committed by Thomas Miceli
parent 943212e492
commit 246f12c8cb
9 changed files with 187 additions and 145 deletions

View file

@ -1,14 +1,48 @@
package db package db
import ( import (
"github.com/labstack/echo/v4" "fmt"
"github.com/thomiceli/opengist/internal/git"
"gorm.io/gorm"
"os/exec" "os/exec"
"strings" "strings"
"time" "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 { type Gist struct {
ID uint `gorm:"primaryKey"` ID uint `gorm:"primaryKey"`
Uuid string Uuid string
@ -16,7 +50,7 @@ type Gist struct {
Preview string Preview string
PreviewFilename string PreviewFilename string
Description string Description string
Private int // 0: public, 1: unlisted, 2: private Private Visibility // 0: public, 1: unlisted, 2: private
UserID uint UserID uint
User User User User
NbFiles int NbFiles int
@ -388,7 +422,7 @@ func (gist *Gist) UpdatePreviewAndCount() error {
type GistDTO struct { type GistDTO struct {
Title string `validate:"max=250" form:"title"` Title string `validate:"max=250" form:"title"`
Description string `validate:"max=1000" form:"description"` Description string `validate:"max=1000" form:"description"`
Private int `validate:"number,min=0,max=2" form:"private"` Private Visibility `validate:"number,min=0,max=2" form:"private"`
Files []FileDTO `validate:"min=1,dive"` Files []FileDTO `validate:"min=1,dive"`
Name []string `form:"name"` Name []string `form:"name"`
Content []string `form:"content"` Content []string `form:"content"`

View file

@ -100,7 +100,6 @@ func GetUsersFromEmails(emailsSet map[string]struct{}) (map[string]*User, error)
err := db. err := db.
Where("email IN ?", emails). Where("email IN ?", emails).
Find(&users).Error Find(&users).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -4,16 +4,17 @@ import (
"archive/zip" "archive/zip"
"bytes" "bytes"
"errors" "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" "html/template"
"net/url" "net/url"
"regexp" "regexp"
"strconv" "strconv"
"strings" "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 { func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
@ -30,7 +31,7 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
return notFound("Gist not found") return notFound("Gist not found")
} }
if gist.Private == 2 { if gist.Private == db.PrivateVisibility {
if currUser == nil || currUser.ID != gist.UserID { if currUser == nil || currUser.ID != gist.UserID {
return notFound("Gist not found") return notFound("Gist not found")
} }
@ -433,7 +434,7 @@ func processCreate(ctx echo.Context) error {
} }
func toggleVisibility(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 gist.Private = (gist.Private + 1) % 3
if err := gist.Update(); err != nil { if err := gist.Update(); err != nil {
@ -445,7 +446,7 @@ func toggleVisibility(ctx echo.Context) error {
} }
func deleteGist(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 { if err := gist.Delete(); err != nil {
return errorRes(500, "Error deleting this gist", err) return errorRes(500, "Error deleting this gist", err)
@ -456,7 +457,7 @@ func deleteGist(ctx echo.Context) error {
} }
func like(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) currentUser := getUserLogged(ctx)
hasLiked, err := currentUser.HasLiked(gist) hasLiked, err := currentUser.HasLiked(gist)
@ -482,7 +483,7 @@ func like(ctx echo.Context) error {
} }
func fork(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) currentUser := getUserLogged(ctx)
alreadyForked, err := gist.GetForkParent(currentUser) alreadyForked, err := gist.GetForkParent(currentUser)
@ -535,7 +536,6 @@ func fork(ctx echo.Context) error {
func rawFile(ctx echo.Context) error { func rawFile(ctx echo.Context) error {
gist := getData(ctx, "gist").(*db.Gist) gist := getData(ctx, "gist").(*db.Gist)
file, err := gist.File(ctx.Param("revision"), ctx.Param("file"), false) file, err := gist.File(ctx.Param("revision"), ctx.Param("file"), false)
if err != nil { if err != nil {
return errorRes(500, "Error getting file content", err) return errorRes(500, "Error getting file content", err)
} }
@ -550,7 +550,6 @@ func rawFile(ctx echo.Context) error {
func downloadFile(ctx echo.Context) error { func downloadFile(ctx echo.Context) error {
gist := getData(ctx, "gist").(*db.Gist) gist := getData(ctx, "gist").(*db.Gist)
file, err := gist.File(ctx.Param("revision"), ctx.Param("file"), false) file, err := gist.File(ctx.Param("revision"), ctx.Param("file"), false)
if err != nil { if err != nil {
return errorRes(500, "Error getting file content", err) return errorRes(500, "Error getting file content", err)
} }
@ -572,7 +571,7 @@ func downloadFile(ctx echo.Context) error {
} }
func edit(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") files, err := gist.Files("HEAD")
if err != nil { if err != nil {
@ -586,8 +585,8 @@ func edit(ctx echo.Context) error {
} }
func downloadZip(ctx echo.Context) error { func downloadZip(ctx echo.Context) error {
var gist = getData(ctx, "gist").(*db.Gist) gist := getData(ctx, "gist").(*db.Gist)
var revision = ctx.Param("revision") revision := ctx.Param("revision")
files, err := gist.Files(revision) files, err := gist.Files(revision)
if err != nil { if err != nil {
@ -631,7 +630,7 @@ func downloadZip(ctx echo.Context) error {
} }
func likes(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) pageInt := getPage(ctx)
@ -650,7 +649,7 @@ func likes(ctx echo.Context) error {
} }
func forks(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) pageInt := getPage(ctx)
currentUser := getUserLogged(ctx) currentUser := getUserLogged(ctx)

View file

@ -6,13 +6,6 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "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" "net/http"
"os" "os"
"os/exec" "os/exec"
@ -21,6 +14,14 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "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 { var routes = []struct {
@ -73,7 +74,7 @@ func gitHttp(ctx echo.Context) error {
// - user wants to clone/pull a private gist // - user wants to clone/pull a private gist
// - gist is not found (obfuscation) // - gist is not found (obfuscation)
// - admin setting to require login is set to true // - 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) return route.handler(ctx)
} }

View file

@ -4,6 +4,16 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
htmlpkg "html"
"html/template"
"io"
"net/http"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
@ -16,21 +26,13 @@ import (
"github.com/thomiceli/opengist/public" "github.com/thomiceli/opengist/public"
"github.com/thomiceli/opengist/templates" "github.com/thomiceli/opengist/templates"
"golang.org/x/text/language" "golang.org/x/text/language"
htmlpkg "html"
"html/template"
"io"
"net/http"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
) )
var dev bool var (
var store *sessions.CookieStore dev bool
var re = regexp.MustCompile("[^a-z0-9]+") store *sessions.CookieStore
var fm = template.FuncMap{ re = regexp.MustCompile("[^a-z0-9]+")
fm = template.FuncMap{
"split": strings.Split, "split": strings.Split,
"indexByte": strings.IndexByte, "indexByte": strings.IndexByte,
"toInt": func(i string) int { "toInt": func(i string) int {
@ -94,7 +96,7 @@ var fm = template.FuncMap{
return dev return dev
}, },
"defaultAvatar": defaultAvatar, "defaultAvatar": defaultAvatar,
"visibilityStr": func(visibility int, lowercase bool) string { "visibilityStr": func(visibility db.Visibility, lowercase bool) string {
s := "Public" s := "Public"
switch visibility { switch visibility {
case 1: case 1:
@ -115,7 +117,8 @@ var fm = template.FuncMap{
"toStr": func(i interface{}) string { "toStr": func(i interface{}) string {
return fmt.Sprint(i) return fmt.Sprint(i)
}, },
} }
)
type Template struct { type Template struct {
templates *template.Template templates *template.Template
@ -159,7 +162,7 @@ func NewServer(isDev bool) *Server {
return nil return nil
}, },
})) }))
//e.Use(middleware.Recover()) // e.Use(middleware.Recover())
e.Use(middleware.Secure()) e.Use(middleware.Secure())
e.Renderer = &Template{ e.Renderer = &Template{
@ -316,7 +319,6 @@ func dataInit(next echo.HandlerFunc) echo.HandlerFunc {
func locale(next echo.HandlerFunc) echo.HandlerFunc { func locale(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error { return func(ctx echo.Context) error {
// Check URL arguments // Check URL arguments
lang := ctx.Request().URL.Query().Get("lang") lang := ctx.Request().URL.Query().Get("lang")
changeLang := lang != "" changeLang := lang != ""
@ -335,7 +337,7 @@ func locale(next echo.HandlerFunc) echo.HandlerFunc {
changeLang = false changeLang = false
} }
//3.Then check from 'Accept-Language' header. // 3.Then check from 'Accept-Language' header.
if len(lang) == 0 { if len(lang) == 0 {
tags, _, _ := language.ParseAcceptLanguage(ctx.Request().Header.Get("Accept-Language")) tags, _, _ := language.ParseAcceptLanguage(ctx.Request().Header.Get("Accept-Language"))
lang = i18n.Locales.MatchTag(tags) lang = i18n.Locales.MatchTag(tags)

View file

@ -3,12 +3,13 @@ package web
import ( import (
"crypto/md5" "crypto/md5"
"fmt" "fmt"
"github.com/labstack/echo/v4"
"github.com/thomiceli/opengist/internal/db"
"golang.org/x/crypto/ssh"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/labstack/echo/v4"
"github.com/thomiceli/opengist/internal/db"
"golang.org/x/crypto/ssh"
) )
func userSettings(ctx echo.Context) error { func userSettings(ctx echo.Context) error {
@ -62,7 +63,7 @@ func accountDeleteProcess(ctx echo.Context) error {
func sshKeysProcess(ctx echo.Context) error { func sshKeysProcess(ctx echo.Context) error {
user := getUserLogged(ctx) user := getUserLogged(ctx)
var dto = new(db.SSHKeyDTO) dto := new(db.SSHKeyDTO)
if err := ctx.Bind(dto); err != nil { if err := ctx.Bind(dto); err != nil {
return errorRes(400, "Cannot bind data", err) return errorRes(400, "Cannot bind data", err)
} }
@ -93,7 +94,6 @@ func sshKeysProcess(ctx echo.Context) error {
func sshKeysDelete(ctx echo.Context) error { func sshKeysDelete(ctx echo.Context) error {
user := getUserLogged(ctx) user := getUserLogged(ctx)
keyId, err := strconv.Atoi(ctx.Param("id")) keyId, err := strconv.Atoi(ctx.Param("id"))
if err != nil { if err != nil {
return redirect(ctx, "/settings") return redirect(ctx, "/settings")
} }

View file

@ -1,10 +1,11 @@
package test package test
import ( import (
"testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/thomiceli/opengist/internal/db" "github.com/thomiceli/opengist/internal/db"
"github.com/thomiceli/opengist/internal/git" "github.com/thomiceli/opengist/internal/git"
"testing"
) )
func TestGists(t *testing.T) { func TestGists(t *testing.T) {
@ -110,7 +111,7 @@ func TestVisibility(t *testing.T) {
gist1 := db.GistDTO{ gist1 := db.GistDTO{
Title: "gist1", Title: "gist1",
Description: "my first gist", Description: "my first gist",
Private: 1, Private: db.UnlistedVisibility,
Name: []string{""}, Name: []string{""},
Content: []string{"yeah"}, Content: []string{"yeah"},
} }
@ -119,25 +120,25 @@ func TestVisibility(t *testing.T) {
gist1db, err := db.GetGistByID("1") gist1db, err := db.GetGistByID("1")
require.NoError(t, err) 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) err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", nil, 302)
require.NoError(t, err) require.NoError(t, err)
gist1db, err = db.GetGistByID("1") gist1db, err = db.GetGistByID("1")
require.NoError(t, err) 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) err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", nil, 302)
require.NoError(t, err) require.NoError(t, err)
gist1db, err = db.GetGistByID("1") gist1db, err = db.GetGistByID("1")
require.NoError(t, err) 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) err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", nil, 302)
require.NoError(t, err) require.NoError(t, err)
gist1db, err = db.GetGistByID("1") gist1db, err = db.GetGistByID("1")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, gist1db.Private) require.Equal(t, db.UnlistedVisibility, gist1db.Private)
} }
func TestLikeFork(t *testing.T) { func TestLikeFork(t *testing.T) {

View file

@ -154,12 +154,18 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById('gist-visibility-menu-button')!.onclick = () => { document.getElementById('gist-visibility-menu-button')!.onclick = () => {
gistmenuvisibility!.classList.toggle('hidden'); gistmenuvisibility!.classList.toggle('hidden');
} }
const lastVisibility = localStorage.getItem('visibility');
Array.from(document.querySelectorAll('.gist-visibility-option')).forEach((el) => { Array.from(document.querySelectorAll('.gist-visibility-option')).forEach((el) => {
const visibility = (el as HTMLElement).dataset.visibility || '0';
(el as HTMLElement).onclick = () => { (el as HTMLElement).onclick = () => {
submitgistbutton.textContent = (el as HTMLElement).dataset.btntext; 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'); gistmenuvisibility!.classList.add('hidden');
} }
if (lastVisibility === visibility) {
(el as HTMLElement).click();
}
}); });
} }
}); });

View file

@ -21,7 +21,7 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" /> <path stroke-linecap="round" stroke-linejoin="round" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg> </svg>
{{ end }} {{ end }}
{{ .locale.Tr "gist.edit.change-visibility" }} {{ visibilityStr (inc .gist.Private) true }} {{ .locale.Tr "gist.edit.change-visibility" }} {{ visibilityStr .gist.Private.Next true }}
</button> </button>
</form> </form>
<form id="delete" onsubmit="return confirm('Are you sure you want to delete this gist ?')" class="ml-2 flex items-center" method="post" action="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/delete"> <form id="delete" onsubmit="return confirm('Are you sure you want to delete this gist ?')" class="ml-2 flex items-center" method="post" action="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/delete">