From 7b5d035a323cf58b90c3bf922aec0ef2c8d837bf Mon Sep 17 00:00:00 2001 From: Thomas Miceli <27960254+thomiceli@users.noreply.github.com> Date: Wed, 21 Jun 2023 18:19:17 +0200 Subject: [PATCH] Search gists (#68) --- internal/models/db.go | 10 ++- internal/models/gist.go | 80 ++++++++++++++++++- internal/web/auth.go | 1 - internal/web/gist.go | 74 ++++++++++++++++-- internal/web/run.go | 3 + internal/web/util.go | 2 +- public/main.ts | 4 + templates/base/base_header.html | 98 +++++++++++++++++++---- templates/base/gist_header.html | 2 +- templates/pages/all.html | 134 ++++++++++++++++++++++---------- 10 files changed, 334 insertions(+), 74 deletions(-) diff --git a/internal/models/db.go b/internal/models/db.go index a3fc124..8605a3c 100644 --- a/internal/models/db.go +++ b/internal/models/db.go @@ -28,7 +28,15 @@ func Setup(dbPath string) error { return err } - if err = db.AutoMigrate(&User{}, &SSHKey{}, &Gist{}, &AdminSetting{}); err != nil { + if err = db.SetupJoinTable(&Gist{}, "Likes", &Like{}); err != nil { + return err + } + + if err = db.SetupJoinTable(&User{}, "Liked", &Like{}); err != nil { + return err + } + + if err = db.AutoMigrate(&User{}, &Gist{}, &SSHKey{}, &AdminSetting{}); err != nil { return err } diff --git a/internal/models/gist.go b/internal/models/gist.go index 938767c..bc5b5d2 100644 --- a/internal/models/gist.go +++ b/internal/models/gist.go @@ -29,6 +29,12 @@ type Gist struct { ForkedID uint } +type Like struct { + UserID uint `gorm:"primaryKey"` + GistID uint `gorm:"primaryKey"` + CreatedAt int64 +} + func (gist *Gist) BeforeDelete(tx *gorm.DB) error { // Decrement fork counter if the gist was forked err := tx.Model(&Gist{}). @@ -80,11 +86,11 @@ func GetAllGists(offset int) ([]*Gist, error) { return gists, err } -func GetAllGistsFromUser(fromUser string, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) { +func GetAllGistsFromSearch(currentUserId uint, query string, offset int, sort string, order string) ([]*Gist, error) { var gists []*Gist err := db.Preload("User").Preload("Forked.User"). - Where("users.username = ? and ((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", fromUser, currentUserId). - Joins("join users on gists.user_id = users.id"). + Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId). + Where("gists.title like ? or gists.description like ?", "%"+query+"%", "%"+query+"%"). Limit(11). Offset(offset * 10). Order("gists." + sort + "_at " + order). @@ -93,6 +99,74 @@ func GetAllGistsFromUser(fromUser string, currentUserId uint, offset int, sort s return gists, err } +func gistsFromUserStatement(fromUserId uint, currentUserId uint) *gorm.DB { + return db.Preload("User").Preload("Forked.User"). + Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId). + Where("users.id = ?", fromUserId). + Joins("join users on gists.user_id = users.id") +} + +func GetAllGistsFromUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) { + var gists []*Gist + err := gistsFromUserStatement(fromUserId, currentUserId).Limit(11). + Offset(offset * 10). + Order("gists." + sort + "_at " + order). + Find(&gists).Error + + return gists, err +} + +func CountAllGistsFromUser(fromUserId uint, currentUserId uint) (int64, error) { + var count int64 + err := gistsFromUserStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error + return count, err +} + +func likedStatement(fromUserId uint, currentUserId uint) *gorm.DB { + return db.Preload("User").Preload("Forked.User"). + Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId). + Where("likes.user_id = ?", fromUserId). + Joins("join likes on gists.id = likes.gist_id"). + Joins("join users on likes.user_id = users.id") +} + +func GetAllGistsLikedByUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) { + var gists []*Gist + err := likedStatement(fromUserId, currentUserId).Limit(11). + Offset(offset * 10). + Order("gists." + sort + "_at " + order). + Find(&gists).Error + return gists, err +} + +func CountAllGistsLikedByUser(fromUserId uint, currentUserId uint) (int64, error) { + var count int64 + err := likedStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error + return count, err +} + +func forkedStatement(fromUserId uint, currentUserId uint) *gorm.DB { + return db.Preload("User").Preload("Forked.User"). + Where("gists.forked_id is not null and ((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId). + Where("gists.user_id = ?", fromUserId). + Joins("join users on gists.user_id = users.id") +} + +func GetAllGistsForkedByUser(fromUserId uint, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) { + var gists []*Gist + err := forkedStatement(fromUserId, currentUserId).Limit(11). + Offset(offset * 10). + Order("gists." + sort + "_at " + order). + Find(&gists).Error + return gists, err +} + +func CountAllGistsForkedByUser(fromUserId uint, currentUserId uint) (int64, error) { + var count int64 + err := forkedStatement(fromUserId, currentUserId).Model(&Gist{}).Count(&count).Error + return count, err +} + func GetAllGistsRows() ([]*Gist, error) { var gists []*Gist err := db.Table("gists"). diff --git a/internal/web/auth.go b/internal/web/auth.go index 4d81c34..a5a4e9d 100644 --- a/internal/web/auth.go +++ b/internal/web/auth.go @@ -337,7 +337,6 @@ func urlJoin(base string, elem ...string) string { } func getAvatarUrlFromProvider(provider string, identifier string) string { - fmt.Println("getAvatarUrlFromProvider", provider, identifier) switch provider { case "github": return "https://avatars.githubusercontent.com/u/" + identifier + "?v=4" diff --git a/internal/web/gist.go b/internal/web/gist.go index de3e845..9c60430 100644 --- a/internal/web/gist.go +++ b/internal/web/gist.go @@ -11,6 +11,7 @@ import ( "gorm.io/gorm" "html/template" "net/url" + "regexp" "strconv" "strings" ) @@ -85,10 +86,10 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc { func allGists(ctx echo.Context) error { var err error + var urlPage string + fromUserStr := ctx.Param("user") - userLogged := getUserLogged(ctx) - pageInt := getPage(ctx) sort := "created" @@ -114,14 +115,37 @@ func allGists(ctx echo.Context) error { } else { currentUserId = 0 } + if fromUserStr == "" { - setData(ctx, "htmlTitle", "All gists") - fromUserStr = "all" - gists, err = models.GetAllGistsForCurrentUser(currentUserId, pageInt-1, sort, order) + urlctx := ctx.Request().URL.Path + if strings.HasSuffix(urlctx, "search") { + setData(ctx, "htmlTitle", "Search results") + setData(ctx, "mode", "search") + setData(ctx, "searchQuery", template.URL("&q="+ctx.QueryParam("q"))) + urlPage = "search" + gists, err = models.GetAllGistsFromSearch(currentUserId, ctx.QueryParam("q"), pageInt-1, sort, order) + } else if strings.HasSuffix(urlctx, "all") { + setData(ctx, "htmlTitle", "All gists") + setData(ctx, "mode", "all") + urlPage = "all" + gists, err = models.GetAllGistsForCurrentUser(currentUserId, pageInt-1, sort, order) + } } else { - setData(ctx, "htmlTitle", "All gists from "+fromUserStr) + liked := false + forked := false + + liked, err = regexp.MatchString(`/[^/]*/liked`, ctx.Request().URL.Path) + if err != nil { + return errorRes(500, "Error matching regexp", err) + } + + forked, err = regexp.MatchString(`/[^/]*/forked`, ctx.Request().URL.Path) + if err != nil { + return errorRes(500, "Error matching regexp", err) + } var fromUser *models.User + fromUser, err = models.GetUserByUsername(fromUserStr) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -131,7 +155,40 @@ func allGists(ctx echo.Context) error { } setData(ctx, "fromUser", fromUser) - gists, err = models.GetAllGistsFromUser(fromUserStr, currentUserId, pageInt-1, sort, order) + if countFromUser, err := models.CountAllGistsFromUser(fromUser.ID, currentUserId); err != nil { + return errorRes(500, "Error counting gists", err) + } else { + setData(ctx, "countFromUser", countFromUser) + } + + if countLiked, err := models.CountAllGistsLikedByUser(fromUser.ID, currentUserId); err != nil { + return errorRes(500, "Error counting liked gists", err) + } else { + setData(ctx, "countLiked", countLiked) + } + + if countForked, err := models.CountAllGistsForkedByUser(fromUser.ID, currentUserId); err != nil { + return errorRes(500, "Error counting forked gists", err) + } else { + setData(ctx, "countForked", countForked) + } + + if liked { + urlPage = fromUserStr + "/liked" + setData(ctx, "htmlTitle", "All gists liked by "+fromUserStr) + setData(ctx, "mode", "liked") + gists, err = models.GetAllGistsLikedByUser(fromUser.ID, currentUserId, pageInt-1, sort, order) + } else if forked { + urlPage = fromUserStr + "/forked" + setData(ctx, "htmlTitle", "All gists forked by "+fromUserStr) + setData(ctx, "mode", "forked") + gists, err = models.GetAllGistsForkedByUser(fromUser.ID, currentUserId, pageInt-1, sort, order) + } else { + urlPage = fromUserStr + setData(ctx, "htmlTitle", "All gists from "+fromUserStr) + setData(ctx, "mode", "fromUser") + gists, err = models.GetAllGistsFromUser(fromUser.ID, currentUserId, pageInt-1, sort, order) + } } if err != nil { @@ -142,6 +199,7 @@ func allGists(ctx echo.Context) error { return errorRes(404, "Page not found", nil) } + setData(ctx, "urlPage", urlPage) return html(ctx, "all.html") } @@ -527,7 +585,7 @@ func likes(ctx echo.Context) error { return errorRes(404, "Page not found", nil) } - setData(ctx, "htmlTitle", "Likes for "+gist.Title) + setData(ctx, "htmlTitle", "Like for "+gist.Title) setData(ctx, "revision", "HEAD") return html(ctx, "likes.html") } diff --git a/internal/web/run.go b/internal/web/run.go index a83f1dd..ec009c4 100644 --- a/internal/web/run.go +++ b/internal/web/run.go @@ -198,7 +198,10 @@ func Start() { } g1.GET("/all", allGists, checkRequireLogin) + g1.GET("/search", allGists, checkRequireLogin) g1.GET("/:user", allGists, checkRequireLogin) + g1.GET("/:user/liked", allGists, checkRequireLogin) + g1.GET("/:user/forked", allGists, checkRequireLogin) g3 := g1.Group("/:user/:gistname") { diff --git a/internal/web/util.go b/internal/web/util.go index fbf1beb..0facd5c 100644 --- a/internal/web/util.go +++ b/internal/web/util.go @@ -166,7 +166,7 @@ func validateReservedKeywords(fl validator.FieldLevel) bool { name := fl.Field().String() restrictedNames := map[string]struct{}{} - for _, restrictedName := range []string{"assets", "register", "login", "logout", "config", "admin-panel", "all"} { + for _, restrictedName := range []string{"assets", "register", "login", "logout", "settings", "admin-panel", "all", "search"} { restrictedNames[restrictedName] = struct{}{} } diff --git a/public/main.ts b/public/main.ts index d0e9e2b..8992039 100644 --- a/public/main.ts +++ b/public/main.ts @@ -35,6 +35,10 @@ document.addEventListener('DOMContentLoaded', () => { themeMenu.classList.toggle('hidden'); } + document.getElementById('user-btn')?.addEventListener("click" , (e) => { + document.getElementById('user-menu').classList.toggle('hidden'); + }) + document.querySelectorAll('.moment-timestamp').forEach((e: HTMLElement) => { e.title = moment.unix(parseInt(e.innerHTML)).format('LLLL'); e.innerHTML = moment.unix(parseInt(e.innerHTML)).fromNow(); diff --git a/templates/base/base_header.html b/templates/base/base_header.html index 39a62e3..a04cf42 100644 --- a/templates/base/base_header.html +++ b/templates/base/base_header.html @@ -63,27 +63,79 @@
All New - {{ if .userLogged }} - My gists - {{ end }} +
+
+ +
+
+ +
+
+ + +
+
+
+
{{ if .userLogged }} - {{ if .userLogged.IsAdmin }} - - {{ end }} - - - -

{{ .userLogged.Username }}

- - User - - +
{{ else }} {{ if not .DisableSignup }} @@ -106,7 +158,7 @@