From e3fe53f2fc140e508cf5c49ccb963d5cca4ce885 Mon Sep 17 00:00:00 2001 From: Gitea Date: Sat, 28 May 2022 16:07:37 +0100 Subject: [PATCH 01/36] Add checks for pinned repositories --- models/repo_list.go | 33 +++++++++++++++++++++++++++++++++ routers/web/org/home.go | 12 ++++++++++++ routers/web/user/profile.go | 1 + 3 files changed, 46 insertions(+) diff --git a/models/repo_list.go b/models/repo_list.go index 906b7548d4a64..39a290a8c9e5c 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -6,6 +6,7 @@ package models import ( "context" + "encoding/json" "fmt" "strings" @@ -16,6 +17,7 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -146,6 +148,10 @@ type SearchRepoOptions struct { // True -> include just archived // False -> include just non-archived Archived util.OptionalBool + // None -> include pinned AND non-pinned + // True -> include just pinned + // False -> include just non-pinned + Pinned util.OptionalBool // only search topic name TopicOnly bool // only search repositories with specified primary language @@ -381,6 +387,33 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.IsTrue()}) } + if opts.Pinned != util.OptionalBoolNone { + pinnedstring, err := user_model.GetUserSetting(opts.OwnerID, "pinned") + + var pinnedIds []int + validIds := true + if err == nil { + jsonerr := json.Unmarshal([]byte(pinnedstring), &pinnedIds) + if jsonerr != nil { + log.Error("Parsing stored pinned Repo IDs: %v", jsonerr) + validIds = false + } + } + pinargs := make([]interface{}, len(pinnedIds)) + for i := range pinnedIds { + pinargs[i] = pinnedIds[i] + } + + if validIds { + if opts.Pinned == util.OptionalBoolTrue { + cond = cond.And(builder.In("id", pinargs...)) + } else { + cond = cond.And(builder.NotIn("id", pinargs...)) + } + } + + } + if opts.Template != util.OptionalBoolNone { cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue}) } diff --git a/routers/web/org/home.go b/routers/web/org/home.go index 24a0f13b542e1..9732b7abef335 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -14,9 +14,11 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) const ( @@ -117,6 +119,7 @@ func Home(ctx *context.Context) { Actor: ctx.Doer, Language: language, IncludeDescription: setting.UI.SearchRepoDescription, + Pinned: util.OptionalBoolFalse, }) if err != nil { ctx.ServerError("SearchRepository", err) @@ -166,3 +169,12 @@ func Home(ctx *context.Context) { ctx.HTML(http.StatusOK, tplOrgHome) } + +func present(data map[string]interface{}, ix int) { + log.Info("ix %v", ix) + if _, ok := data["CanCreateOrgRepo"]; ok { + log.Error("It's here!!!!") + } else { + log.Info("It's not here :(") + } +} diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 8bce5460ccec1..1f9722c8939bd 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -263,6 +263,7 @@ func Profile(ctx *context.Context) { TopicOnly: topicOnly, Language: language, IncludeDescription: setting.UI.SearchRepoDescription, + Pinned: util.OptionalBoolFalse, }) if err != nil { ctx.ServerError("SearchRepository", err) From fa46bfdf0e2903ce044205d040f264781927b714 Mon Sep 17 00:00:00 2001 From: Gitea Date: Sat, 28 May 2022 19:11:27 +0100 Subject: [PATCH 02/36] Working pin and unpin in repository. --- models/repo/pin.go | 44 ++++++++++++++++++++++++++++++++ models/repo/repo.go | 23 +++++++++++++++++ routers/web/org/home.go | 40 ++++++++++++++++++++++++----- routers/web/repo/repo.go | 23 +++++++++++++++++ templates/explore/repo_list.tmpl | 3 +++ templates/repo/header.tmpl | 10 ++++++++ 6 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 models/repo/pin.go diff --git a/models/repo/pin.go b/models/repo/pin.go new file mode 100644 index 0000000000000..a0c58ea84fdcd --- /dev/null +++ b/models/repo/pin.go @@ -0,0 +1,44 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "encoding/json" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" +) + +//Pin Repo or unpin repo +func PinRepo(ownerID, repoID int64, pin bool) error { + + log.Info("pinning %v %v %v", ownerID, repoID, pin) + var newpinned []int64 + if pin { + newpinned = append(newpinned, repoID) + } + pinnedstring, err := user_model.GetUserSetting(ownerID, "pinned") + if err == nil { + var pinned []int64 + err = json.Unmarshal([]byte(pinnedstring), &pinned) + if err != nil { + log.Info("E0 ", err) + return err + } + for _, v := range pinned { + if v != repoID { + newpinned = append(newpinned, v) + } + } + } + stringed, jsonerr := json.Marshal(newpinned) + if jsonerr != nil { + log.Info("E1 ", err) + return jsonerr + } + err = user_model.SetUserSetting(ownerID, "pinned", string(stringed)) + log.Info("E2 %v", err) + return err +} diff --git a/models/repo/repo.go b/models/repo/repo.go index 3fd6b94eb1265..95872c52cf3a4 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -6,6 +6,7 @@ package repo import ( "context" + "encoding/json" "fmt" "html/template" "net" @@ -201,6 +202,28 @@ func (repo *Repository) IsBroken() bool { return repo.Status == RepositoryBroken } +// IsPinned indicates that repository is pinned +func (repo *Repository) IsPinned() bool { + pinstring, err := user_model.GetUserSetting(repo.OwnerID, "pinned") + if err != nil { + return false + } + + var pinitems []int64 + err = json.Unmarshal([]byte(pinstring), &pinitems) + + if err != nil { + log.Warn("Couldn't deserialise pinned repos: %v", pinstring) + return false + } + for _, pinned := range pinitems { + if pinned == repo.ID { + return true + } + } + return false +} + // AfterLoad is invoked from XORM after setting the values of all fields of this object. func (repo *Repository) AfterLoad() { // FIXME: use models migration to solve all at once. diff --git a/routers/web/org/home.go b/routers/web/org/home.go index 9732b7abef335..c4c8ef96455ee 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -103,11 +103,11 @@ func Home(ctx *context.Context) { } var ( - repos []*repo_model.Repository - count int64 - err error + unpinnedrepos []*repo_model.Repository + unpinnedcount int64 + err error ) - repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ + unpinnedrepos, unpinnedcount, err = models.SearchRepository(&models.SearchRepoOptions{ ListOptions: db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, @@ -125,6 +125,32 @@ func Home(ctx *context.Context) { ctx.ServerError("SearchRepository", err) return } + var ( + pinnedrepos []*repo_model.Repository + pinnedcount int64 + pinnederr error + ) + pinnedrepos, pinnedcount, pinnederr = models.SearchRepository(&models.SearchRepoOptions{ + ListOptions: db.ListOptions{ + PageSize: setting.UI.User.RepoPagingNum, + Page: page, + }, + Keyword: keyword, + OwnerID: org.ID, + OrderBy: orderBy, + Private: ctx.IsSigned, + Actor: ctx.Doer, + Language: language, + IncludeDescription: setting.UI.SearchRepoDescription, + Pinned: util.OptionalBoolTrue, + }) + if pinnederr != nil { + ctx.ServerError("SearchRepository", pinnederr) + return + } + + allrepos := append(pinnedrepos, unpinnedrepos...) + allcount := unpinnedcount + pinnedcount opts := &organization.FindOrgMembersOpts{ OrgID: org.ID, @@ -154,15 +180,15 @@ func Home(ctx *context.Context) { } ctx.Data["Owner"] = org - ctx.Data["Repos"] = repos - ctx.Data["Total"] = count + ctx.Data["Repos"] = allrepos + ctx.Data["Total"] = allcount ctx.Data["MembersTotal"] = membersCount ctx.Data["Members"] = members ctx.Data["Teams"] = ctx.Org.Teams ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull ctx.Data["PageIsViewRepositories"] = true - pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5) + pager := context.NewPagination(int(unpinnedcount), setting.UI.User.RepoPagingNum, page, 5) pager.SetDefaultParams(ctx) pager.AddParam(ctx, "language", "Language") ctx.Data["Page"] = pager diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 30cb888dce989..55de23dbc949b 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" + access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -280,6 +281,16 @@ func CreatePost(ctx *context.Context) { handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) } +func canPin(ctx *context.Context) bool { + perm, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, ctx.Doer) + + if err == nil && perm.IsAdmin() { + return true + } else { + return false + } +} + // Action response for actions to a repository func Action(ctx *context.Context) { var err error @@ -292,6 +303,18 @@ func Action(ctx *context.Context) { err = repo_model.StarRepo(ctx.Doer.ID, ctx.Repo.Repository.ID, true) case "unstar": err = repo_model.StarRepo(ctx.Doer.ID, ctx.Repo.Repository.ID, false) + case "pin": + if canPin(ctx) { + err = repo_model.PinRepo(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID, true) + } else { + err = errors.New("User does not have permission to pin") + } + case "unpin": + if canPin(ctx) { + err = repo_model.PinRepo(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID, false) + } else { + err = errors.New("User does not have permission to pin") + } case "accept_transfer": err = acceptOrRejectRepoTransfer(ctx, true) case "reject_transfer": diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl index 03f1928e2bd72..2031d30a40d04 100644 --- a/templates/explore/repo_list.tmpl +++ b/templates/explore/repo_list.tmpl @@ -7,6 +7,9 @@ {{if $avatar}} {{$avatar}} {{end}} + {{if .IsPinned}} + {{svg "octicon-pin" 16 "mr-3"}} + {{end}} {{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}} diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 2d963d67c8974..6cba50b76a142 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -61,6 +61,16 @@ {{end}} + {{if $.Permission.IsAdmin}} +
+ {{$.CsrfTokenHtml}} +
+ +
+
+ {{end}}
{{$.CsrfTokenHtml}}
From 9f87fd65581ef82b510bca5d621380efb2f2b1d8 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Sun, 29 May 2022 06:37:39 +0100 Subject: [PATCH 03/36] Fixes unpin typo Co-authored-by: delvh --- routers/web/repo/repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 55de23dbc949b..f8834d5c376a0 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -313,7 +313,7 @@ func Action(ctx *context.Context) { if canPin(ctx) { err = repo_model.PinRepo(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID, false) } else { - err = errors.New("User does not have permission to pin") + err = errors.New("User does not have permission to unpin") } case "accept_transfer": err = acceptOrRejectRepoTransfer(ctx, true) From 5c224732e2f6917bfa5111514fd4d1df226a0206 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Sun, 29 May 2022 06:39:41 +0100 Subject: [PATCH 04/36] Simplify canPin check Co-authored-by: delvh --- routers/web/repo/repo.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index f8834d5c376a0..b52973075dc6d 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -283,12 +283,7 @@ func CreatePost(ctx *context.Context) { func canPin(ctx *context.Context) bool { perm, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, ctx.Doer) - - if err == nil && perm.IsAdmin() { - return true - } else { - return false - } + return err == nil && perm.IsAdmin() } // Action response for actions to a repository From 8464a09e534482245d2256fa69a5a6c350c6c83c Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Sun, 29 May 2022 06:46:36 +0100 Subject: [PATCH 05/36] Remove disabled pin button for anons Co-authored-by: delvh --- templates/repo/header.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 6cba50b76a142..0538133b987a6 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -65,7 +65,7 @@ {{$.CsrfTokenHtml}}
-
From 0140a492bd49290f30b9d93fd5ec1e44000f9d46 Mon Sep 17 00:00:00 2001 From: Gitea Date: Sun, 29 May 2022 12:19:50 +0100 Subject: [PATCH 06/36] Refactor based on advice in PR and implement cards --- models/db/engine.go | 0 models/repo/pin.go | 44 ------------ models/repo/repo.go | 15 +--- models/user/pin.go | 80 +++++++++++++++++++++ models/user/setting_keys.go | 1 + modules/metrics/collector.go | 0 routers/web/org/home.go | 73 ++++++++----------- routers/web/repo/repo.go | 4 +- templates/explore/repo_list.tmpl | 3 - templates/org/home.tmpl | 116 ++++++++++++++++++------------- templates/repo/pinned_repos.tmpl | 40 +++++++++++ 11 files changed, 223 insertions(+), 153 deletions(-) mode change 100755 => 100644 models/db/engine.go delete mode 100644 models/repo/pin.go create mode 100644 models/user/pin.go mode change 100755 => 100644 modules/metrics/collector.go create mode 100644 templates/repo/pinned_repos.tmpl diff --git a/models/db/engine.go b/models/db/engine.go old mode 100755 new mode 100644 diff --git a/models/repo/pin.go b/models/repo/pin.go deleted file mode 100644 index a0c58ea84fdcd..0000000000000 --- a/models/repo/pin.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2016 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "encoding/json" - - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/log" -) - -//Pin Repo or unpin repo -func PinRepo(ownerID, repoID int64, pin bool) error { - - log.Info("pinning %v %v %v", ownerID, repoID, pin) - var newpinned []int64 - if pin { - newpinned = append(newpinned, repoID) - } - pinnedstring, err := user_model.GetUserSetting(ownerID, "pinned") - if err == nil { - var pinned []int64 - err = json.Unmarshal([]byte(pinnedstring), &pinned) - if err != nil { - log.Info("E0 ", err) - return err - } - for _, v := range pinned { - if v != repoID { - newpinned = append(newpinned, v) - } - } - } - stringed, jsonerr := json.Marshal(newpinned) - if jsonerr != nil { - log.Info("E1 ", err) - return jsonerr - } - err = user_model.SetUserSetting(ownerID, "pinned", string(stringed)) - log.Info("E2 %v", err) - return err -} diff --git a/models/repo/repo.go b/models/repo/repo.go index 95872c52cf3a4..00faf6b77063b 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -6,7 +6,6 @@ package repo import ( "context" - "encoding/json" "fmt" "html/template" "net" @@ -204,20 +203,12 @@ func (repo *Repository) IsBroken() bool { // IsPinned indicates that repository is pinned func (repo *Repository) IsPinned() bool { - pinstring, err := user_model.GetUserSetting(repo.OwnerID, "pinned") + pinned, err := user_model.GetPinnedRepositoryIDs(repo.OwnerID) if err != nil { return false } - - var pinitems []int64 - err = json.Unmarshal([]byte(pinstring), &pinitems) - - if err != nil { - log.Warn("Couldn't deserialise pinned repos: %v", pinstring) - return false - } - for _, pinned := range pinitems { - if pinned == repo.ID { + for _, r := range pinned { + if r == repo.ID { return true } } diff --git a/models/user/pin.go b/models/user/pin.go new file mode 100644 index 0000000000000..98a542fcdfbfa --- /dev/null +++ b/models/user/pin.go @@ -0,0 +1,80 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "encoding/json" +) + +func GetPinnedRepositoryIDs(userID int64) ([]int64, error) { + pinnedstring, err := GetUserSetting(userID, PinnedRepositories) + + if err != nil { + return nil, err + } + + if len(pinnedstring) == 0 { + return nil, nil + } + var parsedValues []int64 + + err = json.Unmarshal([]byte(pinnedstring), &parsedValues) + + if err != nil { + return nil, err + } + + return parsedValues, nil +} + +func setPinnedRepositories(userID int64, repos []int64) error { + stringed, err := json.Marshal(repos) + + if err != nil { + return err + } + + return SetUserSetting(userID, PinnedRepositories, string(stringed)) + +} + +func PinRepo(ownerID, repoID int64) error { + + repos, err := GetPinnedRepositoryIDs(ownerID) + + if err != nil { + return err + } + alreadyPresent := false + for _, r := range repos { + if r == repoID { + alreadyPresent = true + break + } + } + + if !alreadyPresent { + repos = append(repos, repoID) + } + + return setPinnedRepositories(ownerID, repos) +} + +func UnpinRepo(ownerID, repoID int64) error { + + prevRepos, err := GetPinnedRepositoryIDs(ownerID) + if err != nil { + return err + } + var nextRepos []int64 + + for _, r := range prevRepos { + if r != repoID { + nextRepos = append(nextRepos, r) + } + } + + return setPinnedRepositories(ownerID, nextRepos) +} diff --git a/models/user/setting_keys.go b/models/user/setting_keys.go index 109b5dd916365..f759082909d6a 100644 --- a/models/user/setting_keys.go +++ b/models/user/setting_keys.go @@ -9,4 +9,5 @@ const ( SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types" // SettingsKeyDiffWhitespaceBehavior is the setting key for whitespace behavior of diff SettingsKeyDiffWhitespaceBehavior = "diff.whitespace_behaviour" + PinnedRepositories = "pinned_repos" ) diff --git a/modules/metrics/collector.go b/modules/metrics/collector.go old mode 100755 new mode 100644 diff --git a/routers/web/org/home.go b/routers/web/org/home.go index c4c8ef96455ee..b9bd7e7f5422b 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -12,13 +12,13 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" ) const ( @@ -103,11 +103,11 @@ func Home(ctx *context.Context) { } var ( - unpinnedrepos []*repo_model.Repository - unpinnedcount int64 - err error + repos []*repo_model.Repository + count int64 + err error ) - unpinnedrepos, unpinnedcount, err = models.SearchRepository(&models.SearchRepoOptions{ + repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ ListOptions: db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, @@ -119,38 +119,11 @@ func Home(ctx *context.Context) { Actor: ctx.Doer, Language: language, IncludeDescription: setting.UI.SearchRepoDescription, - Pinned: util.OptionalBoolFalse, }) if err != nil { ctx.ServerError("SearchRepository", err) return } - var ( - pinnedrepos []*repo_model.Repository - pinnedcount int64 - pinnederr error - ) - pinnedrepos, pinnedcount, pinnederr = models.SearchRepository(&models.SearchRepoOptions{ - ListOptions: db.ListOptions{ - PageSize: setting.UI.User.RepoPagingNum, - Page: page, - }, - Keyword: keyword, - OwnerID: org.ID, - OrderBy: orderBy, - Private: ctx.IsSigned, - Actor: ctx.Doer, - Language: language, - IncludeDescription: setting.UI.SearchRepoDescription, - Pinned: util.OptionalBoolTrue, - }) - if pinnederr != nil { - ctx.ServerError("SearchRepository", pinnederr) - return - } - - allrepos := append(pinnedrepos, unpinnedrepos...) - allcount := unpinnedcount + pinnedcount opts := &organization.FindOrgMembersOpts{ OrgID: org.ID, @@ -179,28 +152,40 @@ func Home(ctx *context.Context) { return } + pinnedRepoIDs, err := user_model.GetPinnedRepositoryIDs(org.ID) + if err != nil { + ctx.ServerError("GetPinnedRepositoryIDs", err) + return + } + var pinnedRepos []*repo_model.Repository + for _, id := range pinnedRepoIDs { + repo, err := repo_model.GetRepositoryByID(id) + + if err != nil { + ctx.ServerError("GetRepositoryByID", err) + return + } + if repo.OwnerID != org.ID { + log.Warn("Ignoring pinned repo ID %v because it's not owned by %v", repo.ID, org.Name) + } else { + pinnedRepos = append(pinnedRepos, repo) + } + } + ctx.Data["Owner"] = org - ctx.Data["Repos"] = allrepos - ctx.Data["Total"] = allcount + ctx.Data["Repos"] = repos + ctx.Data["PinnedRepos"] = pinnedRepos + ctx.Data["Total"] = count ctx.Data["MembersTotal"] = membersCount ctx.Data["Members"] = members ctx.Data["Teams"] = ctx.Org.Teams ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull ctx.Data["PageIsViewRepositories"] = true - pager := context.NewPagination(int(unpinnedcount), setting.UI.User.RepoPagingNum, page, 5) + pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5) pager.SetDefaultParams(ctx) pager.AddParam(ctx, "language", "Language") ctx.Data["Page"] = pager ctx.HTML(http.StatusOK, tplOrgHome) } - -func present(data map[string]interface{}, ix int) { - log.Info("ix %v", ix) - if _, ok := data["CanCreateOrgRepo"]; ok { - log.Error("It's here!!!!") - } else { - log.Info("It's not here :(") - } -} diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index b52973075dc6d..70fff099eea59 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -300,13 +300,13 @@ func Action(ctx *context.Context) { err = repo_model.StarRepo(ctx.Doer.ID, ctx.Repo.Repository.ID, false) case "pin": if canPin(ctx) { - err = repo_model.PinRepo(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID, true) + err = user_model.PinRepo(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID) } else { err = errors.New("User does not have permission to pin") } case "unpin": if canPin(ctx) { - err = repo_model.PinRepo(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID, false) + err = user_model.UnpinRepo(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID) } else { err = errors.New("User does not have permission to unpin") } diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl index 2031d30a40d04..03f1928e2bd72 100644 --- a/templates/explore/repo_list.tmpl +++ b/templates/explore/repo_list.tmpl @@ -7,9 +7,6 @@ {{if $avatar}} {{$avatar}} {{end}} - {{if .IsPinned}} - {{svg "octicon-pin" 16 "mr-3"}} - {{end}} {{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}} diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl index a49029d4d5082..120d540174e50 100644 --- a/templates/org/home.tmpl +++ b/templates/org/home.tmpl @@ -1,20 +1,32 @@ {{template "base/head" .}}
- {{avatar .Org 140 "org-avatar"}} -
-
- {{.Org.DisplayName}} - {{svg "octicon-rss" 36}} - - {{if .Org.Visibility.IsLimited}}
{{.i18n.Tr "org.settings.visibility.limited_shortname"}}
{{end}} - {{if .Org.Visibility.IsPrivate}}
{{.i18n.Tr "org.settings.visibility.private_shortname"}}
{{end}} -
+
+
+ {{avatar .Org 140 "org-avatar"}}
- {{if $.RenderedDescription}}

{{$.RenderedDescription|Str2html}}

{{end}} -
- {{if .Org.Location}}
{{svg "octicon-location"}} {{.Org.Location}}
{{end}} - {{if .Org.Website}}
{{svg "octicon-link"}} {{.Org.Website}}
{{end}} +
+
+ {{.Org.DisplayName}} + {{svg "octicon-rss" 36}} + + {{if .Org.Visibility.IsLimited}}
{{.i18n.Tr + "org.settings.visibility.limited_shortname"}}
{{end}} + {{if .Org.Visibility.IsPrivate}}
{{.i18n.Tr + "org.settings.visibility.private_shortname"}}
{{end}} +
+
+ {{if $.RenderedDescription}}

{{$.RenderedDescription|Str2html}}

+ {{end}} +
+ {{if .Org.Location}}
{{svg "octicon-location"}} {{.Org.Location}} +
{{end}} + {{if .Org.Website}}
{{svg "octicon-link"}} {{.Org.Website}}
{{end}} +
+
+
+ {{template "repo/pinned_repos" .}}
@@ -31,59 +43,67 @@
{{if .CanCreateOrgRepo}} -
- {{.i18n.Tr "new_repo"}} - {{if not .DisableNewPullMirrors}} - {{.i18n.Tr "new_migrate"}} - {{end}} -
-
+
+ {{.i18n.Tr + "new_repo"}} + {{if not .DisableNewPullMirrors}} + {{.i18n.Tr + "new_migrate"}} + {{end}} +
+
{{end}}

{{.i18n.Tr "org.people"}} {{if .IsOrganizationMember}} - + {{end}}

{{$isMember := .IsOrganizationMember}} {{range .Members}} - {{if or $isMember (call $.IsPublicMember .ID)}} - - {{avatar .}} - - {{end}} + {{if or $isMember (call $.IsPublicMember .ID)}} + + {{avatar .}} + + {{end}} {{end}}
{{if .IsOrganizationMember}} -
- {{.i18n.Tr "org.teams"}} - +
-{{template "base/footer" .}} +{{template "base/footer" .}} \ No newline at end of file diff --git a/templates/repo/pinned_repos.tmpl b/templates/repo/pinned_repos.tmpl new file mode 100644 index 0000000000000..4df7bffcab8c8 --- /dev/null +++ b/templates/repo/pinned_repos.tmpl @@ -0,0 +1,40 @@ +
+{{range .PinnedRepos}} +
+
+
+ {{.Name}} +
+
+ {{if .PrimaryLanguage }} + + {{ .PrimaryLanguage.Language }} + + {{end}} + {{if not $.DisableStars}} + {{svg "octicon-star" 16 "mr-3"}}{{.NumStars}} + {{end}} + {{svg "octicon-git-branch" 16 "mr-3"}}{{.NumForks}} +
+
+ {{ $description := .DescriptionHTML $.Context}} + {{if $description}}

{{$description}}

{{end}} + {{if .Topics }} +
+ {{range .Topics}} + {{if ne . "" }} +
{{.}}
+
{{end}} + {{end}} +
+ {{end}} +
+
+

{{$.i18n.Tr "org.repo_updated"}} {{TimeSinceUnix .UpdatedUnix $.i18n.Lang}}

+
+
+
+{{end}} +
\ No newline at end of file From 30a2076915e14ff4b03c4ac0a51f4412c03db392 Mon Sep 17 00:00:00 2001 From: Gitea Date: Sun, 29 May 2022 20:19:31 +0100 Subject: [PATCH 07/36] Working and styled carded pinned repos --- routers/web/org/home.go | 18 ++++++- templates/org/home.tmpl | 49 +++++++++---------- templates/repo/pinned_repos.tmpl | 82 +++++++++++++++++++++++--------- 3 files changed, 98 insertions(+), 51 deletions(-) diff --git a/routers/web/org/home.go b/routers/web/org/home.go index b9bd7e7f5422b..ea41079b4c75c 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" + access "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -165,10 +166,23 @@ func Home(ctx *context.Context) { ctx.ServerError("GetRepositoryByID", err) return } + if err = repo.LoadAttributes(ctx); err != nil { + ctx.ServerError("LoadAttributes", err) + return + } if repo.OwnerID != org.ID { - log.Warn("Ignoring pinned repo ID %v because it's not owned by %v", repo.ID, org.Name) + log.Warn("Ignoring pinned repo %v because it's not owned by %v", repo.FullName(), org.Name) } else { - pinnedRepos = append(pinnedRepos, repo) + access, err := access.GetUserRepoPermission(ctx, repo, ctx.Doer) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return + } + if !access.HasAccess() { + log.Info("Ignoring pinned repo %v because it's not owned by %v", repo.FullName(), org.Name) + } else { + pinnedRepos = append(pinnedRepos, repo) + } } } diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl index 120d540174e50..ec36b8e054da7 100644 --- a/templates/org/home.tmpl +++ b/templates/org/home.tmpl @@ -1,36 +1,33 @@ {{template "base/head" .}}
-
-
- {{avatar .Org 140 "org-avatar"}} + {{avatar .Org 140 "org-avatar"}} +
+
+ {{.Org.DisplayName}} + {{svg "octicon-rss" 36}} + + {{if .Org.Visibility.IsLimited}}
{{.i18n.Tr + "org.settings.visibility.limited_shortname"}}
{{end}} + {{if .Org.Visibility.IsPrivate}}
{{.i18n.Tr + "org.settings.visibility.private_shortname"}}
{{end}} +
-
-
- {{.Org.DisplayName}} - {{svg "octicon-rss" 36}} - - {{if .Org.Visibility.IsLimited}}
{{.i18n.Tr - "org.settings.visibility.limited_shortname"}}
{{end}} - {{if .Org.Visibility.IsPrivate}}
{{.i18n.Tr - "org.settings.visibility.private_shortname"}}
{{end}} -
-
- {{if $.RenderedDescription}}

{{$.RenderedDescription|Str2html}}

- {{end}} -
- {{if .Org.Location}}
{{svg "octicon-location"}} {{.Org.Location}} -
{{end}} - {{if .Org.Website}}
{{svg "octicon-link"}} {{.Org.Website}}
{{end}} -
-
-
- {{template "repo/pinned_repos" .}} + {{if $.RenderedDescription}}

{{$.RenderedDescription|Str2html}}

+ {{end}} +
+ {{if .Org.Location}}
{{svg "octicon-location"}} {{.Org.Location}} +
{{end}} + {{if .Org.Website}}
{{svg "octicon-link"}} {{.Org.Website}}
{{end}}
- +
+
+ {{template "repo/pinned_repos" .}} +
+
{{template "org/menu" .}}
diff --git a/templates/repo/pinned_repos.tmpl b/templates/repo/pinned_repos.tmpl index 4df7bffcab8c8..f472540b0f89b 100644 --- a/templates/repo/pinned_repos.tmpl +++ b/templates/repo/pinned_repos.tmpl @@ -1,40 +1,76 @@ -
+{{if .PinnedRepos}} +
+
{{range .PinnedRepos}} -
-
+
+
- {{.Name}} -
-
- {{if .PrimaryLanguage }} - - {{ .PrimaryLanguage.Language }} - - {{end}} - {{if not $.DisableStars}} - {{svg "octicon-star" 16 "mr-3"}}{{.NumStars}} - {{end}} - {{svg "octicon-git-branch" 16 "mr-3"}}{{.NumForks}} +
+ {{$avatar := (repoAvatar . 32 "mr-3")}} + {{if $avatar}} + {{$avatar}} + {{end}} + + {{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}} + +
+ {{if .IsArchived}} + {{$.i18n.Tr "repo.desc.archived"}} + {{end}} + {{if .IsTemplate}} + {{if .IsPrivate}} + {{$.i18n.Tr "repo.desc.private_template"}} + {{else}} + {{if .Owner.Visibility.IsPrivate}} + {{$.i18n.Tr "repo.desc.internal_template"}} + {{end}} + {{end}} + {{else}} + {{if .IsPrivate}} + {{$.i18n.Tr "repo.desc.private"}} + {{else}} + {{if .Owner.Visibility.IsPrivate}} + {{$.i18n.Tr "repo.desc.internal"}} + {{end}} + {{end}} + {{end}} + {{if .IsFork}} + {{svg "octicon-repo-forked"}} + {{else if .IsMirror}} + {{svg "octicon-mirror"}} + {{end}} +
+
+
+ {{if .PrimaryLanguage }} + {{ .PrimaryLanguage.Language }} + {{end}} + {{if not $.DisableStars}} + {{svg "octicon-star" 16 "mr-3"}}{{.NumStars}} + {{end}} + {{svg "octicon-git-branch" 16 "mr-3"}}{{.NumForks}} +
{{ $description := .DescriptionHTML $.Context}} {{if $description}}

{{$description}}

{{end}} {{if .Topics }}
{{range .Topics}} - {{if ne . "" }} + {{if ne . "" }} +
{{.}}
-
{{end}} + + {{end}} {{end}}
{{end}}
-
-

{{$.i18n.Tr "org.repo_updated"}} {{TimeSinceUnix .UpdatedUnix $.i18n.Lang}}

-
+
+
+

{{$.i18n.Tr "org.repo_updated"}} {{TimeSinceUnix .UpdatedUnix $.i18n.Lang}}

{{end}} -
\ No newline at end of file +
+{{end}} \ No newline at end of file From cf5ef19387efa72a2f760f241f5c2718c37035b1 Mon Sep 17 00:00:00 2001 From: Gitea Date: Sun, 29 May 2022 20:30:38 +0100 Subject: [PATCH 08/36] Revert weird permissions changes --- models/db/engine.go | 0 modules/metrics/collector.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 models/db/engine.go mode change 100644 => 100755 modules/metrics/collector.go diff --git a/models/db/engine.go b/models/db/engine.go old mode 100644 new mode 100755 diff --git a/modules/metrics/collector.go b/modules/metrics/collector.go old mode 100644 new mode 100755 From 33c5db98cd61b0426ec9acd0963c46578ddea951 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Sun, 29 May 2022 20:35:38 +0100 Subject: [PATCH 09/36] Set correct initial pinnedRepo capacity Co-authored-by: delvh --- routers/web/org/home.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/web/org/home.go b/routers/web/org/home.go index ea41079b4c75c..1a957c43ea9ab 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -158,7 +158,7 @@ func Home(ctx *context.Context) { ctx.ServerError("GetPinnedRepositoryIDs", err) return } - var pinnedRepos []*repo_model.Repository + pinnedRepos := make([]*repo_model.Repository, 0, len(pinnedRepoIDs)) for _, id := range pinnedRepoIDs { repo, err := repo_model.GetRepositoryByID(id) From a0f31484e6d820b9a962fad763e3a0e528af6b49 Mon Sep 17 00:00:00 2001 From: Gitea Date: Sun, 29 May 2022 21:21:43 +0100 Subject: [PATCH 10/36] Move CanPin to services --- routers/web/repo/repo.go | 11 +++-------- services/user/user.go | 6 ++++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 70fff099eea59..e84b93cdd4e2c 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -15,7 +15,6 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" - access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -33,6 +32,7 @@ import ( "code.gitea.io/gitea/services/forms" repo_service "code.gitea.io/gitea/services/repository" archiver_service "code.gitea.io/gitea/services/repository/archiver" + user_service "code.gitea.io/gitea/services/user" ) const ( @@ -281,11 +281,6 @@ func CreatePost(ctx *context.Context) { handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) } -func canPin(ctx *context.Context) bool { - perm, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, ctx.Doer) - return err == nil && perm.IsAdmin() -} - // Action response for actions to a repository func Action(ctx *context.Context) { var err error @@ -299,13 +294,13 @@ func Action(ctx *context.Context) { case "unstar": err = repo_model.StarRepo(ctx.Doer.ID, ctx.Repo.Repository.ID, false) case "pin": - if canPin(ctx) { + if user_service.CanPin(ctx, ctx.Doer, ctx.Repo.Repository) { err = user_model.PinRepo(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID) } else { err = errors.New("User does not have permission to pin") } case "unpin": - if canPin(ctx) { + if user_service.CanPin(ctx, ctx.Doer, ctx.Repo.Repository) { err = user_model.UnpinRepo(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID) } else { err = errors.New("User does not have permission to unpin") diff --git a/services/user/user.go b/services/user/user.go index 4db4d7ca17f12..e016fc5d0133b 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" + access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/avatar" @@ -181,3 +182,8 @@ func DeleteAvatar(u *user_model.User) error { } return nil } + +func CanPin(ctx context.Context, u *user_model.User, r *repo_model.Repository) bool { + perm, err := access_model.GetUserRepoPermission(ctx, r, u) + return err == nil && perm.IsAdmin() +} From b6d440367bdddf57e64c1a65c1123e9cb466d578 Mon Sep 17 00:00:00 2001 From: Gitea Date: Sun, 29 May 2022 21:48:37 +0100 Subject: [PATCH 11/36] Pin/Unpin takes batch args. Max limit imposed. --- models/user/pin.go | 43 ++++++++++++++++++++++++++++------------ routers/web/repo/repo.go | 8 ++++---- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/models/user/pin.go b/models/user/pin.go index 98a542fcdfbfa..c59086d1b27ff 100644 --- a/models/user/pin.go +++ b/models/user/pin.go @@ -6,8 +6,11 @@ package user import ( "encoding/json" + "fmt" ) +const maxPinnedRepos = 3 + func GetPinnedRepositoryIDs(userID int64) ([]int64, error) { pinnedstring, err := GetUserSetting(userID, PinnedRepositories) @@ -40,29 +43,36 @@ func setPinnedRepositories(userID int64, repos []int64) error { } -func PinRepo(ownerID, repoID int64) error { +func PinRepos(ownerID int64, repoIDs ...int64) error { repos, err := GetPinnedRepositoryIDs(ownerID) if err != nil { return err } - alreadyPresent := false - for _, r := range repos { - if r == repoID { - alreadyPresent = true - break + newrepos := make([]int64, 0, len(repoIDs)+len(repos)) + + allrepos := append(repos, repoIDs...) + + for _, toadd := range allrepos { + alreadypresent := false + for _, present := range newrepos { + if toadd == present { + alreadypresent = true + break + } + } + if !alreadypresent { + newrepos = append(newrepos, toadd) } } - - if !alreadyPresent { - repos = append(repos, repoID) + if len(newrepos) > maxPinnedRepos { + return fmt.Errorf("can pin at most %d repositories, %d pinned repositories is too much", maxPinnedRepos, len(newrepos)) } - - return setPinnedRepositories(ownerID, repos) + return setPinnedRepositories(ownerID, newrepos) } -func UnpinRepo(ownerID, repoID int64) error { +func UnpinRepos(ownerID int64, repoIDs ...int64) error { prevRepos, err := GetPinnedRepositoryIDs(ownerID) if err != nil { @@ -71,7 +81,14 @@ func UnpinRepo(ownerID, repoID int64) error { var nextRepos []int64 for _, r := range prevRepos { - if r != repoID { + keep := true + for _, unp := range repoIDs { + if r == unp { + keep = false + break + } + } + if keep { nextRepos = append(nextRepos, r) } } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index e84b93cdd4e2c..e194e31f111af 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -295,15 +295,15 @@ func Action(ctx *context.Context) { err = repo_model.StarRepo(ctx.Doer.ID, ctx.Repo.Repository.ID, false) case "pin": if user_service.CanPin(ctx, ctx.Doer, ctx.Repo.Repository) { - err = user_model.PinRepo(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID) + err = user_model.PinRepos(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID) } else { - err = errors.New("User does not have permission to pin") + err = errors.New("user does not have permission to pin") } case "unpin": if user_service.CanPin(ctx, ctx.Doer, ctx.Repo.Repository) { - err = user_model.UnpinRepo(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID) + err = user_model.UnpinRepos(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID) } else { - err = errors.New("User does not have permission to unpin") + err = errors.New("user does not have permission to unpin") } case "accept_transfer": err = acceptOrRejectRepoTransfer(ctx, true) From 0cfd4681ddc45bb9f9608e046befa347e423edcc Mon Sep 17 00:00:00 2001 From: Gitea Date: Sun, 29 May 2022 22:14:19 +0100 Subject: [PATCH 12/36] Adds better error reporting when too many repos are pinned. --- models/user/pin.go | 10 +++++++++- routers/web/repo/repo.go | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/models/user/pin.go b/models/user/pin.go index c59086d1b27ff..d09f94bd150a6 100644 --- a/models/user/pin.go +++ b/models/user/pin.go @@ -43,6 +43,14 @@ func setPinnedRepositories(userID int64, repos []int64) error { } +type TooManyPinnedReposError struct { + count int +} + +func (e *TooManyPinnedReposError) Error() string { + return fmt.Sprintf("can pin at most %d repositories, %d pinned repositories is too much", maxPinnedRepos, e.count) +} + func PinRepos(ownerID int64, repoIDs ...int64) error { repos, err := GetPinnedRepositoryIDs(ownerID) @@ -67,7 +75,7 @@ func PinRepos(ownerID int64, repoIDs ...int64) error { } } if len(newrepos) > maxPinnedRepos { - return fmt.Errorf("can pin at most %d repositories, %d pinned repositories is too much", maxPinnedRepos, len(newrepos)) + return &TooManyPinnedReposError{count: len(newrepos)} } return setPinnedRepositories(ownerID, newrepos) } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index e194e31f111af..3ad04c179d10e 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -296,6 +296,10 @@ func Action(ctx *context.Context) { case "pin": if user_service.CanPin(ctx, ctx.Doer, ctx.Repo.Repository) { err = user_model.PinRepos(ctx.Repo.Owner.ID, ctx.Repo.Repository.ID) + if _, ok := err.(*user_model.TooManyPinnedReposError); ok { + ctx.Error(http.StatusBadRequest, err.Error()) + return + } } else { err = errors.New("user does not have permission to pin") } From ef80b118165c496f82ff95149e1a580bd043c0db Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Mon, 30 May 2022 08:59:32 +0100 Subject: [PATCH 13/36] Add signin check to pin button Co-authored-by: delvh --- templates/repo/header.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 0538133b987a6..68a4b33c3c1c9 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -61,7 +61,7 @@
{{end}} - {{if $.Permission.IsAdmin}} + {{if and $.IsSigned $.Permission.IsAdmin}}
{{$.CsrfTokenHtml}}
From ca30e0ca069fbe94b3323a00c020d724039db61b Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Mon, 30 May 2022 09:00:50 +0100 Subject: [PATCH 14/36] Tidier empty array code Co-authored-by: delvh --- models/user/pin.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/user/pin.go b/models/user/pin.go index d09f94bd150a6..0e708dde2fe5f 100644 --- a/models/user/pin.go +++ b/models/user/pin.go @@ -18,10 +18,10 @@ func GetPinnedRepositoryIDs(userID int64) ([]int64, error) { return nil, err } - if len(pinnedstring) == 0 { - return nil, nil - } var parsedValues []int64 + if pinnedstring == "" { + return parsedValues, nil + } err = json.Unmarshal([]byte(pinnedstring), &parsedValues) From d7cc870f82c0ad2a3c9df3df0c5647c3f2905036 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Mon, 30 May 2022 10:26:04 +0100 Subject: [PATCH 15/36] Linter pleasing Co-authored-by: delvh --- models/user/pin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/user/pin.go b/models/user/pin.go index 0e708dde2fe5f..5c273989fe356 100644 --- a/models/user/pin.go +++ b/models/user/pin.go @@ -60,9 +60,9 @@ func PinRepos(ownerID int64, repoIDs ...int64) error { } newrepos := make([]int64, 0, len(repoIDs)+len(repos)) - allrepos := append(repos, repoIDs...) + repos := append(repos, repoIDs...) - for _, toadd := range allrepos { + for _, toadd := range repos { alreadypresent := false for _, present := range newrepos { if toadd == present { From 2e94d4eb8d9759aeed775bbc491a9a629ba06759 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Mon, 30 May 2022 10:26:35 +0100 Subject: [PATCH 16/36] Linter pleasing Co-authored-by: delvh --- models/user/pin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user/pin.go b/models/user/pin.go index 5c273989fe356..91acf468868da 100644 --- a/models/user/pin.go +++ b/models/user/pin.go @@ -5,7 +5,7 @@ package user import ( - "encoding/json" + "code.gitea.io/gitea/modules/json" "fmt" ) From 65c1c26e6100192d89e0241f2882aa8d39ad4eda Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Mon, 30 May 2022 10:26:50 +0100 Subject: [PATCH 17/36] Copyright typo Co-authored-by: delvh --- models/user/pin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user/pin.go b/models/user/pin.go index 91acf468868da..fb29096ce51ae 100644 --- a/models/user/pin.go +++ b/models/user/pin.go @@ -1,4 +1,4 @@ -// Copyright 2016 The Gitea Authors. All rights reserved. +// Copyright 2022 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. From 3bcfaa2ef0dc51b5f869edbc58ebaf1c3f63d3eb Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Mon, 30 May 2022 10:27:51 +0100 Subject: [PATCH 18/36] Better logs for pinning repos without permission Co-authored-by: delvh --- routers/web/org/home.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/routers/web/org/home.go b/routers/web/org/home.go index 1a957c43ea9ab..91115cbbd93dc 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -173,13 +173,13 @@ func Home(ctx *context.Context) { if repo.OwnerID != org.ID { log.Warn("Ignoring pinned repo %v because it's not owned by %v", repo.FullName(), org.Name) } else { - access, err := access.GetUserRepoPermission(ctx, repo, ctx.Doer) + perm, err := access.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return } - if !access.HasAccess() { - log.Info("Ignoring pinned repo %v because it's not owned by %v", repo.FullName(), org.Name) + if !perm.HasAccess() { + log.Info("Ignoring pinned repo %v because user %v has no access to it.", repo.FullName(), ctx.Doer) } else { pinnedRepos = append(pinnedRepos, repo) } From f0e8a25af8ca34333ce6d6a3222e12cdd66a2ad1 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Mon, 30 May 2022 09:34:24 +0100 Subject: [PATCH 19/36] Topics and metas at bottom of pin card --- templates/repo/pinned_repos.tmpl | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/templates/repo/pinned_repos.tmpl b/templates/repo/pinned_repos.tmpl index f472540b0f89b..ff97c5c05974d 100644 --- a/templates/repo/pinned_repos.tmpl +++ b/templates/repo/pinned_repos.tmpl @@ -43,15 +43,6 @@
-
- {{if .PrimaryLanguage }} - {{ .PrimaryLanguage.Language }} - {{end}} - {{if not $.DisableStars}} - {{svg "octicon-star" 16 "mr-3"}}{{.NumStars}} - {{end}} - {{svg "octicon-git-branch" 16 "mr-3"}}{{.NumForks}} -
{{ $description := .DescriptionHTML $.Context}} {{if $description}}

{{$description}}

{{end}} {{if .Topics }} @@ -68,7 +59,15 @@
-

{{$.i18n.Tr "org.repo_updated"}} {{TimeSinceUnix .UpdatedUnix $.i18n.Lang}}

+
+ {{if .PrimaryLanguage }} + {{ .PrimaryLanguage.Language }} + {{end}} + {{if not $.DisableStars}} + {{svg "octicon-star" 16 "mr-3"}}{{.NumStars}} + {{end}} + {{svg "octicon-git-branch" 16 "mr-3"}}{{.NumForks}} +
{{end}} From 8be963a887e84a02650f16156a599c847f8a8eef Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Mon, 30 May 2022 10:41:41 +0100 Subject: [PATCH 20/36] Move pinned_repos.tmpl to shared. Reverts whitespace issues in home.tmpl --- models/user/pin.go | 5 +- templates/org/home.tmpl | 95 +++++++++----------- templates/{repo => shared}/pinned_repos.tmpl | 0 3 files changed, 44 insertions(+), 56 deletions(-) rename templates/{repo => shared}/pinned_repos.tmpl (100%) diff --git a/models/user/pin.go b/models/user/pin.go index fb29096ce51ae..6f79481c5c9a4 100644 --- a/models/user/pin.go +++ b/models/user/pin.go @@ -5,8 +5,9 @@ package user import ( - "code.gitea.io/gitea/modules/json" "fmt" + + "code.gitea.io/gitea/modules/json" ) const maxPinnedRepos = 3 @@ -60,7 +61,7 @@ func PinRepos(ownerID int64, repoIDs ...int64) error { } newrepos := make([]int64, 0, len(repoIDs)+len(repos)) - repos := append(repos, repoIDs...) + repos = append(repos, repoIDs...) for _, toadd := range repos { alreadypresent := false diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl index ec36b8e054da7..05d3dd0bacaea 100644 --- a/templates/org/home.tmpl +++ b/templates/org/home.tmpl @@ -7,25 +7,20 @@ {{.Org.DisplayName}} {{svg "octicon-rss" 36}} - {{if .Org.Visibility.IsLimited}}
{{.i18n.Tr - "org.settings.visibility.limited_shortname"}}
{{end}} - {{if .Org.Visibility.IsPrivate}}
{{.i18n.Tr - "org.settings.visibility.private_shortname"}}
{{end}} + {{if .Org.Visibility.IsLimited}}
{{.i18n.Tr "org.settings.visibility.limited_shortname"}}
{{end}} + {{if .Org.Visibility.IsPrivate}}
{{.i18n.Tr "org.settings.visibility.private_shortname"}}
{{end}}
- {{if $.RenderedDescription}}

{{$.RenderedDescription|Str2html}}

- {{end}} + {{if $.RenderedDescription}}

{{$.RenderedDescription|Str2html}}

{{end}}
- {{if .Org.Location}}
{{svg "octicon-location"}} {{.Org.Location}} -
{{end}} - {{if .Org.Website}}
{{svg "octicon-link"}} {{.Org.Website}}
{{end}} + {{if .Org.Location}}
{{svg "octicon-location"}} {{.Org.Location}}
{{end}} + {{if .Org.Website}}
{{svg "octicon-link"}} {{.Org.Website}}
{{end}}
- {{template "repo/pinned_repos" .}} + {{template "shared/pinned_repos" .}}
{{template "org/menu" .}} @@ -40,67 +35,59 @@
{{if .CanCreateOrgRepo}} -
- {{.i18n.Tr - "new_repo"}} - {{if not .DisableNewPullMirrors}} - {{.i18n.Tr - "new_migrate"}} - {{end}} -
-
+
+ {{.i18n.Tr "new_repo"}} + {{if not .DisableNewPullMirrors}} + {{.i18n.Tr "new_migrate"}} + {{end}} +
+
{{end}}

{{.i18n.Tr "org.people"}} {{if .IsOrganizationMember}} - + {{end}}

{{$isMember := .IsOrganizationMember}} {{range .Members}} - {{if or $isMember (call $.IsPublicMember .ID)}} - - {{avatar .}} - - {{end}} + {{if or $isMember (call $.IsPublicMember .ID)}} + + {{avatar .}} + + {{end}} {{end}}
{{if .IsOrganizationMember}} -
- {{.i18n.Tr "org.teams"}} - -
-{{template "base/footer" .}} \ No newline at end of file +{{template "base/footer" .}} diff --git a/templates/repo/pinned_repos.tmpl b/templates/shared/pinned_repos.tmpl similarity index 100% rename from templates/repo/pinned_repos.tmpl rename to templates/shared/pinned_repos.tmpl From f489c685c0af1c7d6d4853185bc7672466ef899a Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Mon, 30 May 2022 10:52:42 +0100 Subject: [PATCH 21/36] Remove Pinned option in repo search and associated IsPinned check --- models/repo/repo.go | 14 -------------- models/repo_list.go | 33 --------------------------------- routers/web/user/profile.go | 1 - 3 files changed, 48 deletions(-) diff --git a/models/repo/repo.go b/models/repo/repo.go index 00faf6b77063b..3fd6b94eb1265 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -201,20 +201,6 @@ func (repo *Repository) IsBroken() bool { return repo.Status == RepositoryBroken } -// IsPinned indicates that repository is pinned -func (repo *Repository) IsPinned() bool { - pinned, err := user_model.GetPinnedRepositoryIDs(repo.OwnerID) - if err != nil { - return false - } - for _, r := range pinned { - if r == repo.ID { - return true - } - } - return false -} - // AfterLoad is invoked from XORM after setting the values of all fields of this object. func (repo *Repository) AfterLoad() { // FIXME: use models migration to solve all at once. diff --git a/models/repo_list.go b/models/repo_list.go index 39a290a8c9e5c..906b7548d4a64 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -6,7 +6,6 @@ package models import ( "context" - "encoding/json" "fmt" "strings" @@ -17,7 +16,6 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -148,10 +146,6 @@ type SearchRepoOptions struct { // True -> include just archived // False -> include just non-archived Archived util.OptionalBool - // None -> include pinned AND non-pinned - // True -> include just pinned - // False -> include just non-pinned - Pinned util.OptionalBool // only search topic name TopicOnly bool // only search repositories with specified primary language @@ -387,33 +381,6 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.IsTrue()}) } - if opts.Pinned != util.OptionalBoolNone { - pinnedstring, err := user_model.GetUserSetting(opts.OwnerID, "pinned") - - var pinnedIds []int - validIds := true - if err == nil { - jsonerr := json.Unmarshal([]byte(pinnedstring), &pinnedIds) - if jsonerr != nil { - log.Error("Parsing stored pinned Repo IDs: %v", jsonerr) - validIds = false - } - } - pinargs := make([]interface{}, len(pinnedIds)) - for i := range pinnedIds { - pinargs[i] = pinnedIds[i] - } - - if validIds { - if opts.Pinned == util.OptionalBoolTrue { - cond = cond.And(builder.In("id", pinargs...)) - } else { - cond = cond.And(builder.NotIn("id", pinargs...)) - } - } - - } - if opts.Template != util.OptionalBoolNone { cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue}) } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 1f9722c8939bd..8bce5460ccec1 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -263,7 +263,6 @@ func Profile(ctx *context.Context) { TopicOnly: topicOnly, Language: language, IncludeDescription: setting.UI.SearchRepoDescription, - Pinned: util.OptionalBoolFalse, }) if err != nil { ctx.ServerError("SearchRepository", err) From 6cbdb44c3e3e97a3c6f5432145467276d54261a9 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Mon, 30 May 2022 13:22:35 +0100 Subject: [PATCH 22/36] Pinned repos on user profiles --- models/repo/repo.go | 14 +++++++ routers/web/user/profile.go | 38 ++++++++++++++++++- templates/org/home.tmpl | 5 ++- ...ned_repos.tmpl => pinned_repos_cards.tmpl} | 5 --- templates/shared/pinned_repos_onewide.tmpl | 3 ++ templates/shared/pinned_repos_threewide.tmpl | 3 ++ templates/user/profile.tmpl | 7 +++- web_src/less/_user.less | 2 +- 8 files changed, 68 insertions(+), 9 deletions(-) rename templates/shared/{pinned_repos.tmpl => pinned_repos_cards.tmpl} (96%) create mode 100644 templates/shared/pinned_repos_onewide.tmpl create mode 100644 templates/shared/pinned_repos_threewide.tmpl diff --git a/models/repo/repo.go b/models/repo/repo.go index 3fd6b94eb1265..00faf6b77063b 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -201,6 +201,20 @@ func (repo *Repository) IsBroken() bool { return repo.Status == RepositoryBroken } +// IsPinned indicates that repository is pinned +func (repo *Repository) IsPinned() bool { + pinned, err := user_model.GetPinnedRepositoryIDs(repo.OwnerID) + if err != nil { + return false + } + for _, r := range pinned { + if r == repo.ID { + return true + } + } + return false +} + // AfterLoad is invoked from XORM after setting the values of all fields of this object. func (repo *Repository) AfterLoad() { // FIXME: use models migration to solve all at once. diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 8bce5460ccec1..0d971f0232066 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -13,10 +13,12 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" + access "code.gitea.io/gitea/models/perm/access" project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" @@ -271,9 +273,43 @@ func Profile(ctx *context.Context) { total = int(count) } + + pinnedRepoIDs, err := user_model.GetPinnedRepositoryIDs(ctx.ContextUser.ID) + if err != nil { + ctx.ServerError("GetPinnedRepositoryIDs", err) + return + } + pinnedRepos := make([]*repo_model.Repository, 0, len(pinnedRepoIDs)) + for _, id := range pinnedRepoIDs { + repo, err := repo_model.GetRepositoryByID(id) + + if err != nil { + ctx.ServerError("GetRepositoryByID", err) + return + } + if err = repo.LoadAttributes(ctx); err != nil { + ctx.ServerError("LoadAttributes", err) + return + } + if repo.OwnerID != ctx.ContextUser.ID { + log.Warn("Ignoring pinned repo %v because it's not owned by %v", repo.FullName(), ctx.ContextUser.Name) + } else { + perm, err := access.GetUserRepoPermission(ctx, repo, ctx.Doer) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return + } + if !perm.HasAccess() { + log.Info("Ignoring pinned repo %v because user %v has no access to it.", repo.FullName(), ctx.Doer) + } else { + pinnedRepos = append(pinnedRepos, repo) + } + } + } + ctx.Data["Repos"] = repos ctx.Data["Total"] = total - + ctx.Data["PinnedRepos"] = pinnedRepos pager := context.NewPagination(total, setting.UI.User.RepoPagingNum, page, 5) pager.SetDefaultParams(ctx) if tab != "followers" && tab != "following" && tab != "activity" && tab != "projects" { diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl index 05d3dd0bacaea..1cf7b3f46434c 100644 --- a/templates/org/home.tmpl +++ b/templates/org/home.tmpl @@ -18,11 +18,14 @@
+ {{if .PinnedRepos}}
+
- {{template "shared/pinned_repos" .}} + {{template "shared/pinned_repos_threewide" .}}
+ {{end}} {{template "org/menu" .}}
diff --git a/templates/shared/pinned_repos.tmpl b/templates/shared/pinned_repos_cards.tmpl similarity index 96% rename from templates/shared/pinned_repos.tmpl rename to templates/shared/pinned_repos_cards.tmpl index ff97c5c05974d..00d478ec46eb5 100644 --- a/templates/shared/pinned_repos.tmpl +++ b/templates/shared/pinned_repos_cards.tmpl @@ -1,6 +1,3 @@ -{{if .PinnedRepos}} -
-
{{range .PinnedRepos}}
@@ -70,6 +67,4 @@
-{{end}} -
{{end}} \ No newline at end of file diff --git a/templates/shared/pinned_repos_onewide.tmpl b/templates/shared/pinned_repos_onewide.tmpl new file mode 100644 index 0000000000000..1d82d15b135ba --- /dev/null +++ b/templates/shared/pinned_repos_onewide.tmpl @@ -0,0 +1,3 @@ +
+{{template "shared/pinned_repos_cards" .}} +
\ No newline at end of file diff --git a/templates/shared/pinned_repos_threewide.tmpl b/templates/shared/pinned_repos_threewide.tmpl new file mode 100644 index 0000000000000..5353921bd7aa7 --- /dev/null +++ b/templates/shared/pinned_repos_threewide.tmpl @@ -0,0 +1,3 @@ +
+{{template "shared/pinned_repos_cards" .}} +
\ No newline at end of file diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index abcc227bec359..c530ede15a698 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -3,7 +3,7 @@
-
+
{{if eq .SignedUserName .Owner.Name}} {{avatar .Owner 290}} @@ -87,6 +87,11 @@
+ {{if .PinnedRepos}} +
+ {{template "shared/pinned_repos_onewide" .}} +
+ {{end}}
diff --git a/templates/shared/pinned_repos_threewide.tmpl b/templates/shared/pinned_repos_threewide.tmpl index 5353921bd7aa7..71ae0103a30ff 100644 --- a/templates/shared/pinned_repos_threewide.tmpl +++ b/templates/shared/pinned_repos_threewide.tmpl @@ -1,3 +1,3 @@
{{template "shared/pinned_repos_cards" .}} -
\ No newline at end of file +
From 22f29562780136436ad59ac8ec0c852388d8ba1a Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Tue, 31 May 2022 14:27:11 +0100 Subject: [PATCH 26/36] Document GetPinnedRepositoryIDs --- models/user/pin.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/models/user/pin.go b/models/user/pin.go index 6bf2ed5a45664..1a7bb1f5a0af3 100644 --- a/models/user/pin.go +++ b/models/user/pin.go @@ -12,6 +12,8 @@ import ( const maxPinnedRepos = 3 +// Get all the repositories pinned by a user. If they've never +// set pinned repositories, an empty array is returned. func GetPinnedRepositoryIDs(userID int64) ([]int64, error) { pinnedstring, err := GetUserSetting(userID, PinnedRepositories) if err != nil { From 9eeb53b888ce4343648624034470b68b7f3134dc Mon Sep 17 00:00:00 2001 From: Gitea Date: Wed, 1 Jun 2022 19:39:24 +0100 Subject: [PATCH 27/36] Add tests for pinning --- models/user/pin_test.go | 37 +++++++++++ routers/web/repo/pin_test.go | 122 +++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 models/user/pin_test.go create mode 100644 routers/web/repo/pin_test.go diff --git a/models/user/pin_test.go b/models/user/pin_test.go new file mode 100644 index 0000000000000..8cd98dc843705 --- /dev/null +++ b/models/user/pin_test.go @@ -0,0 +1,37 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "testing" + + "code.gitea.io/gitea/models/unittest" + "github.com/stretchr/testify/assert" +) + +func TestUserPinUnpinRepos(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + // User:2 pins repositories 1 and 2 + { + assert.NoError(t, PinRepos(2, 1, 2)) + pinned, err := GetPinnedRepositoryIDs(2) + + if assert.NoError(t, err) { + expected := []int64{1, 2} + assert.Equal(t, pinned, expected) + } + } + // User:2 unpins repository 2, leaving just 1 + { + assert.NoError(t, UnpinRepos(2, 1)) + + pinned, err := GetPinnedRepositoryIDs(2) + + if assert.NoError(t, err) { + expected := []int64{2} + assert.Equal(t, pinned, expected) + } + } +} diff --git a/routers/web/repo/pin_test.go b/routers/web/repo/pin_test.go new file mode 100644 index 0000000000000..4e7d98e022a26 --- /dev/null +++ b/routers/web/repo/pin_test.go @@ -0,0 +1,122 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "testing" + + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" +) + +const ( + pin = true + unpin = false +) + +func TestUserPinUnpin(t *testing.T) { + unittest.PrepareTestEnv(t) + // These test cases run sequentially since they modify state + testcases := []struct { + uid int64 + rid int64 + action bool + endstate bool + failmesssage string + }{ + + { + uid: 2, + rid: 2, + action: pin, + endstate: pin, + failmesssage: "user cannot pin repos they own", + }, + { + uid: 2, + rid: 2, + action: unpin, + endstate: unpin, + failmesssage: "user cannot unpin repos they own", + }, + + { + uid: 2, + rid: 5, + action: pin, + endstate: pin, + failmesssage: "user cannot pin repos they have admin access to", + }, + { + uid: 2, + rid: 5, + action: unpin, + endstate: unpin, + failmesssage: "user cannot unpin repos they have admin access to", + }, + + { + uid: 2, + rid: 4, + action: pin, + endstate: unpin, + failmesssage: "user can pin repos they don't have access to", + }, + + { + uid: 5, + rid: 4, + action: pin, + endstate: pin, + failmesssage: "user cannot pin repos they own (this should never fail)", + }, + { + uid: 2, + rid: 4, + action: unpin, + endstate: pin, + failmesssage: "user can unpin repos they don't have access to", + }, + { + uid: 1, + rid: 4, + action: unpin, + endstate: unpin, + failmesssage: "admin can't unpin repos they don't have access to", + }, + { + uid: 1, + rid: 4, + action: pin, + endstate: pin, + failmesssage: "admin can't pin repos they don't have access to", + }, + } + + for _, c := range testcases { + ctx := test.MockContext(t, "") + test.LoadUser(t, ctx, c.uid) + test.LoadRepo(t, ctx, c.rid) + + switch c.action { + case pin: + ctx.SetParams(":action", "pin") + case unpin: + ctx.SetParams(":action", "unpin") + } + + Action(ctx) + ispinned := getRepository(ctx, c.rid).IsPinned() + + assert.Equal(t, ispinned, c.endstate, c.failmesssage) + + if c.endstate != ispinned { + // We have to stop at first failure, state won't be coherent afterwards. + return + } + } +} From 16a99069605795ea3d5c2ed7d6059daa4bda50a4 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Wed, 1 Jun 2022 19:47:56 +0100 Subject: [PATCH 28/36] Make fmt --- models/user/pin_test.go | 1 + routers/web/repo/pin_test.go | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user/pin_test.go b/models/user/pin_test.go index 8cd98dc843705..e687120149465 100644 --- a/models/user/pin_test.go +++ b/models/user/pin_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + "github.com/stretchr/testify/assert" ) diff --git a/routers/web/repo/pin_test.go b/routers/web/repo/pin_test.go index 4e7d98e022a26..27fa64ea6ac74 100644 --- a/routers/web/repo/pin_test.go +++ b/routers/web/repo/pin_test.go @@ -28,7 +28,6 @@ func TestUserPinUnpin(t *testing.T) { endstate bool failmesssage string }{ - { uid: 2, rid: 2, From 56555539ef5d94388f5ff42eb40915db39a8f0ba Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:31:14 +0100 Subject: [PATCH 29/36] Pin button correctly rounded and unpin svg --- public/img/svg/octicon-custom-pin-off.svg | 17 +++++++++++++++++ templates/repo/header.tmpl | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 public/img/svg/octicon-custom-pin-off.svg diff --git a/public/img/svg/octicon-custom-pin-off.svg b/public/img/svg/octicon-custom-pin-off.svg new file mode 100644 index 0000000000000..18a2f6ba554d3 --- /dev/null +++ b/public/img/svg/octicon-custom-pin-off.svg @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 68a4b33c3c1c9..7c30efafdd9e8 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -64,9 +64,9 @@ {{if and $.IsSigned $.Permission.IsAdmin}} {{$.CsrfTokenHtml}} -
+
From d8d7a4010b5e9eed3de551e504c42d7fc913ab62 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:36:17 +0100 Subject: [PATCH 30/36] Fix svg formatting --- public/img/svg/octicon-custom-pin-off.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/img/svg/octicon-custom-pin-off.svg b/public/img/svg/octicon-custom-pin-off.svg index 18a2f6ba554d3..61d211d313c4d 100644 --- a/public/img/svg/octicon-custom-pin-off.svg +++ b/public/img/svg/octicon-custom-pin-off.svg @@ -14,4 +14,4 @@ c-0.683-0.684-1.791-0.684-2.475,0L0.734,4.456C0.588,4.602,0.469,4.772,0.383,4.959C-0.022,5.837,0.36,6.877,1.238,7.282 l1.327,0.613c0.428,0.197,0.59,0.272,0.882,0.626c0.135,0.136,0.321,0.22,0.527,0.22c0.41,0,0.742-0.332,0.742-0.742 c0-0.154-0.058-0.29-0.139-0.409"/> - \ No newline at end of file + From 70274d53cd220e4744def5bafcef6d2fdb0745fa Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:40:12 +0100 Subject: [PATCH 31/36] Fix svg issues with frontend check --- public/img/svg/octicon-custom-pin-off.svg | 18 +----------------- web_src/svg/octicon-custom-pin-off.svg | 1 + 2 files changed, 2 insertions(+), 17 deletions(-) create mode 100644 web_src/svg/octicon-custom-pin-off.svg diff --git a/public/img/svg/octicon-custom-pin-off.svg b/public/img/svg/octicon-custom-pin-off.svg index 61d211d313c4d..a6e96e7bd8510 100644 --- a/public/img/svg/octicon-custom-pin-off.svg +++ b/public/img/svg/octicon-custom-pin-off.svg @@ -1,17 +1 @@ - + \ No newline at end of file diff --git a/web_src/svg/octicon-custom-pin-off.svg b/web_src/svg/octicon-custom-pin-off.svg new file mode 100644 index 0000000000000..a5e6fda34193a --- /dev/null +++ b/web_src/svg/octicon-custom-pin-off.svg @@ -0,0 +1 @@ + \ No newline at end of file From 3e566e08d8b8487a15c4d3345ccadc4425a3b417 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Fri, 3 Jun 2022 12:36:29 +0100 Subject: [PATCH 32/36] Change pin access to org owners, not repo admins --- routers/web/repo/view.go | 2 ++ services/user/user.go | 20 +++++++++++++++++--- templates/repo/header.tmpl | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 95ca81c442d01..e5acd6ecd32e4 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -39,6 +39,7 @@ import ( "code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/web/feed" + user_service "code.gitea.io/gitea/services/user" ) const ( @@ -720,6 +721,7 @@ func Home(ctx *context.Context) { } ctx.Data["FeedURL"] = ctx.Repo.Repository.HTMLURL() + ctx.Data["CanPin"] = ctx.IsSigned && user_service.CanPin(ctx, ctx.Doer, ctx.Repo.Repository) checkHomeCodeViewable(ctx) if ctx.Written() { diff --git a/services/user/user.go b/services/user/user.go index 351687e0bcd4a..4d1ff6fe26915 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -18,7 +18,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" - access_model "code.gitea.io/gitea/models/perm/access" + "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/avatar" @@ -185,6 +185,20 @@ func DeleteAvatar(u *user_model.User) error { // Verify that a user has permission to pin/unpin a repository func CanPin(ctx context.Context, u *user_model.User, r *repo_model.Repository) bool { - perm, err := access_model.GetUserRepoPermission(ctx, r, u) - return err == nil && perm.IsAdmin() + if r.Owner.IsOrganization() { + org := organization.OrgFromUser(r.Owner) + teams, err := org.GetUserTeams(u.ID) + if err != nil { + return false + } else { + for _, team := range teams { + if team.AccessMode >= perm.AccessModeAdmin { + return true + } + } + return false + } + } else { + return u.ID == r.OwnerID + } } diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 7c30efafdd9e8..c8e484beaed98 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -61,7 +61,7 @@
{{end}} - {{if and $.IsSigned $.Permission.IsAdmin}} + {{if $.CanPin}}
{{$.CsrfTokenHtml}}
From 0e50a7c7c30545baf9bd6decda5f0afdeea0d92e Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Fri, 3 Jun 2022 13:30:09 +0100 Subject: [PATCH 33/36] Fix backend linting --- services/user/user.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/services/user/user.go b/services/user/user.go index 4d1ff6fe26915..c3275e176b79a 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -190,15 +190,13 @@ func CanPin(ctx context.Context, u *user_model.User, r *repo_model.Repository) b teams, err := org.GetUserTeams(u.ID) if err != nil { return false - } else { - for _, team := range teams { - if team.AccessMode >= perm.AccessModeAdmin { - return true - } + } + for _, team := range teams { + if team.AccessMode >= perm.AccessModeAdmin { + return true } - return false } - } else { - return u.ID == r.OwnerID + return false } + return u.ID == r.OwnerID } From 58f5754c0a3eeb7aac82f5d53d4b7c6e3c8f8c49 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Fri, 3 Jun 2022 14:22:58 +0100 Subject: [PATCH 34/36] Fix admin pin permission --- services/user/user.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/user/user.go b/services/user/user.go index c3275e176b79a..4ea61e91507cc 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/perm" + access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/avatar" @@ -185,6 +186,9 @@ func DeleteAvatar(u *user_model.User) error { // Verify that a user has permission to pin/unpin a repository func CanPin(ctx context.Context, u *user_model.User, r *repo_model.Repository) bool { + if u.IsAdmin { + return true + } if r.Owner.IsOrganization() { org := organization.OrgFromUser(r.Owner) teams, err := org.GetUserTeams(u.ID) @@ -198,5 +202,6 @@ func CanPin(ctx context.Context, u *user_model.User, r *repo_model.Repository) b } return false } - return u.ID == r.OwnerID + perm, err := access_model.GetUserRepoPermission(ctx, r, u) + return err != nil && perm.IsAdmin() } From cb1052966b8f6e421f439901bc25b9ed94ea4656 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Fri, 3 Jun 2022 14:59:50 +0100 Subject: [PATCH 35/36] Typo in canPin --- services/user/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/user/user.go b/services/user/user.go index 4ea61e91507cc..963c802825c69 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -203,5 +203,5 @@ func CanPin(ctx context.Context, u *user_model.User, r *repo_model.Repository) b return false } perm, err := access_model.GetUserRepoPermission(ctx, r, u) - return err != nil && perm.IsAdmin() + return err == nil && perm.IsAdmin() } From b2a72701a0b66b6fc1ef20e261f3b90217399fdd Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Sun, 9 Oct 2022 18:58:20 +0100 Subject: [PATCH 36/36] Apply suggestions from code review - Delvh Co-authored-by: delvh --- models/user/pin.go | 19 +++++++++---------- models/user/pin_test.go | 31 +++++++++++++++---------------- routers/web/repo/pin_test.go | 4 ++-- routers/web/repo/view.go | 2 +- 4 files changed, 27 insertions(+), 29 deletions(-) diff --git a/models/user/pin.go b/models/user/pin.go index 1a7bb1f5a0af3..778b4787733a0 100644 --- a/models/user/pin.go +++ b/models/user/pin.go @@ -12,10 +12,10 @@ import ( const maxPinnedRepos = 3 -// Get all the repositories pinned by a user. If they've never -// set pinned repositories, an empty array is returned. -func GetPinnedRepositoryIDs(userID int64) ([]int64, error) { - pinnedstring, err := GetUserSetting(userID, PinnedRepositories) +// GetPinnedRepositoryIDs returns all the repository IDs pinned by the given user or org. +// If they've never pinned a repository, an empty array is returned. +func GetPinnedRepositoryIDs(ownerID int64) ([]int64, error) { + pinnedstring, err := GetUserSetting(ownerID, PinnedRepositories) if err != nil { return nil, err } @@ -34,13 +34,13 @@ func GetPinnedRepositoryIDs(userID int64) ([]int64, error) { return parsedValues, nil } -func setPinnedRepositories(userID int64, repos []int64) error { +func setPinnedRepositories(ownerID int64, repos []int64) error { stringed, err := json.Marshal(repos) if err != nil { return err } - return SetUserSetting(userID, PinnedRepositories, string(stringed)) + return SetUserSetting(ownerID, PinnedRepositories, string(stringed)) } type TooManyPinnedReposError struct { @@ -51,9 +51,8 @@ func (e *TooManyPinnedReposError) Error() string { return fmt.Sprintf("can pin at most %d repositories, %d pinned repositories is too much", maxPinnedRepos, e.count) } -// Add some repos to a user's pinned repositories. -// The caller must ensure all repos belong to the -// owner. +// PinRepos pin the specified repos for the given user or organization. +// The caller must ensure all repos belong to the owner. func PinRepos(ownerID int64, repoIDs ...int64) error { repos, err := GetPinnedRepositoryIDs(ownerID) if err != nil { @@ -81,7 +80,7 @@ func PinRepos(ownerID int64, repoIDs ...int64) error { return setPinnedRepositories(ownerID, newrepos) } -// Remove some repos from a user's pinned repositories. +// UnpinRepos unpin the given repositories for the given user or organization func UnpinRepos(ownerID int64, repoIDs ...int64) error { prevRepos, err := GetPinnedRepositoryIDs(ownerID) if err != nil { diff --git a/models/user/pin_test.go b/models/user/pin_test.go index e687120149465..331f4138faa18 100644 --- a/models/user/pin_test.go +++ b/models/user/pin_test.go @@ -12,27 +12,26 @@ import ( "github.com/stretchr/testify/assert" ) -func TestUserPinUnpinRepos(t *testing.T) { +func TestPinAndUnpinRepos(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) // User:2 pins repositories 1 and 2 + userID, repoID1, repoID2 := 2, 1, 2 { - assert.NoError(t, PinRepos(2, 1, 2)) - pinned, err := GetPinnedRepositoryIDs(2) + assert.NoError(t, PinRepos(userID, repoID1, repoID2)) + pinned, err := GetPinnedRepositoryIDs(userID) - if assert.NoError(t, err) { - expected := []int64{1, 2} - assert.Equal(t, pinned, expected) - } + assert.NoError(t, err) + expected := []int64{repoID1, repoID2} + assert.Equal(t, pinned, expected) } - // User:2 unpins repository 2, leaving just 1 + // User 2 unpins repository 2, leaving just 1 { - assert.NoError(t, UnpinRepos(2, 1)) - - pinned, err := GetPinnedRepositoryIDs(2) - - if assert.NoError(t, err) { - expected := []int64{2} - assert.Equal(t, pinned, expected) - } + assert.NoError(t, UnpinRepos(userID, repoID2)) + + pinned, err := GetPinnedRepositoryIDs(userID) + + assert.NoError(t, err) + expected := []int64{repoID1} + assert.Equal(t, pinned, expected) } } diff --git a/routers/web/repo/pin_test.go b/routers/web/repo/pin_test.go index 27fa64ea6ac74..57c774f0e82ed 100644 --- a/routers/web/repo/pin_test.go +++ b/routers/web/repo/pin_test.go @@ -85,14 +85,14 @@ func TestUserPinUnpin(t *testing.T) { rid: 4, action: unpin, endstate: unpin, - failmesssage: "admin can't unpin repos they don't have access to", + failmesssage: "admin can't unpin repos", }, { uid: 1, rid: 4, action: pin, endstate: pin, - failmesssage: "admin can't pin repos they don't have access to", + failmesssage: "admin can't pin repos", }, } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index bc6bf5657784c..d195d7295288a 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -734,7 +734,7 @@ func Home(ctx *context.Context) { } ctx.Data["FeedURL"] = ctx.Repo.Repository.HTMLURL() - ctx.Data["CanPin"] = ctx.IsSigned && user_service.CanPin(ctx, ctx.Doer, ctx.Repo.Repository) + ctx.Data["CanPinRepos"] = ctx.IsSigned && user_service.CanPin(ctx, ctx.Doer, ctx.Repo.Repository) checkHomeCodeViewable(ctx) if ctx.Written() {