first-commit
This commit is contained in:
76
routers/web/shared/user/block.go
Normal file
76
routers/web/shared/user/block.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
)
|
||||
|
||||
func BlockedUsers(ctx *context.Context, blocker *user_model.User) {
|
||||
blocks, _, err := user_model.FindBlockings(ctx, &user_model.FindBlockingOptions{
|
||||
BlockerID: blocker.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("FindBlockings", err)
|
||||
return
|
||||
}
|
||||
if err := user_model.BlockingList(blocks).LoadAttributes(ctx); err != nil {
|
||||
ctx.ServerError("LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["UserBlocks"] = blocks
|
||||
}
|
||||
|
||||
func BlockedUsersPost(ctx *context.Context, blocker *user_model.User) {
|
||||
form := web.GetForm(ctx).(*forms.BlockUserForm)
|
||||
if ctx.HasError() {
|
||||
ctx.ServerError("FormValidation", nil)
|
||||
return
|
||||
}
|
||||
|
||||
blockee, err := user_model.GetUserByName(ctx, form.Blockee)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserByName", nil)
|
||||
return
|
||||
}
|
||||
|
||||
switch form.Action {
|
||||
case "block":
|
||||
if err := user_service.BlockUser(ctx, ctx.Doer, blocker, blockee, form.Note); err != nil {
|
||||
if errors.Is(err, user_model.ErrCanNotBlock) || errors.Is(err, user_model.ErrBlockOrganization) {
|
||||
ctx.Flash.Error(ctx.Tr("user.block.block.failure", err.Error()))
|
||||
} else {
|
||||
ctx.ServerError("BlockUser", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
case "unblock":
|
||||
if err := user_service.UnblockUser(ctx, ctx.Doer, blocker, blockee); err != nil {
|
||||
if errors.Is(err, user_model.ErrCanNotUnblock) || errors.Is(err, user_model.ErrBlockOrganization) {
|
||||
ctx.Flash.Error(ctx.Tr("user.block.unblock.failure", err.Error()))
|
||||
} else {
|
||||
ctx.ServerError("UnblockUser", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
case "note":
|
||||
block, err := user_model.GetBlocking(ctx, blocker.ID, blockee.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBlocking", err)
|
||||
return
|
||||
}
|
||||
if block != nil {
|
||||
if err := user_model.UpdateBlockingNote(ctx, block.ID, form.Note); err != nil {
|
||||
ctx.ServerError("UpdateBlockingNote", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
196
routers/web/shared/user/header.go
Normal file
196
routers/web/shared/user/header.go
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
// prepareContextForProfileBigAvatar set the context for big avatar view on the profile page
|
||||
func prepareContextForProfileBigAvatar(ctx *context.Context) {
|
||||
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
|
||||
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate
|
||||
if setting.Service.UserLocationMapURL != "" {
|
||||
ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location)
|
||||
}
|
||||
// Show OpenID URIs
|
||||
openIDs, err := user_model.GetUserOpenIDs(ctx, ctx.ContextUser.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserOpenIDs", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["OpenIDs"] = openIDs
|
||||
if len(ctx.ContextUser.Description) != 0 {
|
||||
content, err := markdown.RenderString(markup.NewRenderContext(ctx).WithMetas(markup.ComposeSimpleDocumentMetas()), ctx.ContextUser.Description)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["RenderedDescription"] = content
|
||||
}
|
||||
|
||||
orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{
|
||||
UserID: ctx.ContextUser.ID,
|
||||
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser),
|
||||
ListOptions: db.ListOptions{
|
||||
Page: 1,
|
||||
// query one more result (without a separate counting) to see whether we need to add the "show more orgs" link
|
||||
PageSize: setting.UI.User.OrgPagingNum + 1,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("FindOrgs", err)
|
||||
return
|
||||
}
|
||||
if len(orgs) > setting.UI.User.OrgPagingNum {
|
||||
orgs = orgs[:setting.UI.User.OrgPagingNum]
|
||||
ctx.Data["ShowMoreOrgs"] = true
|
||||
}
|
||||
ctx.Data["Orgs"] = orgs
|
||||
ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(ctx, orgs, ctx.Doer)
|
||||
|
||||
badges, _, err := user_model.GetUserBadges(ctx, ctx.ContextUser)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserBadges", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Badges"] = badges
|
||||
|
||||
// in case the numbers are already provided by other functions, no need to query again (which is slow)
|
||||
if _, ok := ctx.Data["NumFollowers"]; !ok {
|
||||
_, ctx.Data["NumFollowers"], _ = user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{PageSize: 1, Page: 1})
|
||||
}
|
||||
if _, ok := ctx.Data["NumFollowing"]; !ok {
|
||||
_, ctx.Data["NumFollowing"], _ = user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{PageSize: 1, Page: 1})
|
||||
}
|
||||
|
||||
if ctx.Doer != nil {
|
||||
if block, err := user_model.GetBlocking(ctx, ctx.Doer.ID, ctx.ContextUser.ID); err != nil {
|
||||
ctx.ServerError("GetBlocking", err)
|
||||
} else {
|
||||
ctx.Data["UserBlocking"] = block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FindOwnerProfileReadme(ctx *context.Context, doer *user_model.User, optProfileRepoName ...string) (profileDbRepo *repo_model.Repository, profileReadmeBlob *git.Blob) {
|
||||
profileRepoName := util.OptionalArg(optProfileRepoName, RepoNameProfile)
|
||||
profileDbRepo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, profileRepoName)
|
||||
if err != nil {
|
||||
if !repo_model.IsErrRepoNotExist(err) {
|
||||
log.Error("FindOwnerProfileReadme failed to GetRepositoryByName: %v", err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, profileDbRepo, doer)
|
||||
if err != nil {
|
||||
log.Error("FindOwnerProfileReadme failed to GetRepositoryByName: %v", err)
|
||||
return nil, nil
|
||||
}
|
||||
if profileDbRepo.IsEmpty || !perm.CanRead(unit.TypeCode) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
profileGitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, profileDbRepo)
|
||||
if err != nil {
|
||||
log.Error("FindOwnerProfileReadme failed to OpenRepository: %v", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
commit, err := profileGitRepo.GetBranchCommit(profileDbRepo.DefaultBranch)
|
||||
if err != nil {
|
||||
log.Error("FindOwnerProfileReadme failed to GetBranchCommit: %v", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
profileReadmeBlob, _ = commit.GetBlobByPath("README.md") // no need to handle this error
|
||||
return profileDbRepo, profileReadmeBlob
|
||||
}
|
||||
|
||||
type PrepareOwnerHeaderResult struct {
|
||||
ProfilePublicRepo *repo_model.Repository
|
||||
ProfilePublicReadmeBlob *git.Blob
|
||||
ProfilePrivateRepo *repo_model.Repository
|
||||
ProfilePrivateReadmeBlob *git.Blob
|
||||
HasOrgProfileReadme bool
|
||||
}
|
||||
|
||||
const (
|
||||
RepoNameProfilePrivate = ".profile-private"
|
||||
RepoNameProfile = ".profile"
|
||||
)
|
||||
|
||||
func RenderUserOrgHeader(ctx *context.Context) (result *PrepareOwnerHeaderResult, err error) {
|
||||
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
|
||||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||
ctx.Data["EnableFeed"] = setting.Other.EnableFeed
|
||||
ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink()
|
||||
|
||||
if err := loadHeaderCount(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = &PrepareOwnerHeaderResult{}
|
||||
if ctx.ContextUser.IsOrganization() {
|
||||
result.ProfilePublicRepo, result.ProfilePublicReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer)
|
||||
result.ProfilePrivateRepo, result.ProfilePrivateReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer, RepoNameProfilePrivate)
|
||||
result.HasOrgProfileReadme = result.ProfilePublicReadmeBlob != nil || result.ProfilePrivateReadmeBlob != nil
|
||||
ctx.Data["HasOrgProfileReadme"] = result.HasOrgProfileReadme // many pages need it to show the "overview" tab
|
||||
} else {
|
||||
_, profileReadmeBlob := FindOwnerProfileReadme(ctx, ctx.Doer)
|
||||
ctx.Data["HasUserProfileReadme"] = profileReadmeBlob != nil
|
||||
prepareContextForProfileBigAvatar(ctx)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func loadHeaderCount(ctx *context.Context) error {
|
||||
repoCount, err := repo_model.CountRepository(ctx, repo_model.SearchRepoOptions{
|
||||
Actor: ctx.Doer,
|
||||
OwnerID: ctx.ContextUser.ID,
|
||||
Private: ctx.IsSigned,
|
||||
Collaborate: optional.Some(false),
|
||||
IncludeDescription: setting.UI.SearchRepoDescription,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Data["RepoCount"] = repoCount
|
||||
|
||||
var projectType project_model.Type
|
||||
if ctx.ContextUser.IsOrganization() {
|
||||
projectType = project_model.TypeOrganization
|
||||
} else {
|
||||
projectType = project_model.TypeIndividual
|
||||
}
|
||||
projectCount, err := db.Count[project_model.Project](ctx, project_model.SearchOptions{
|
||||
OwnerID: ctx.ContextUser.ID,
|
||||
IsClosed: optional.Some(false),
|
||||
Type: projectType,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Data["ProjectCount"] = projectCount
|
||||
|
||||
return nil
|
||||
}
|
51
routers/web/shared/user/helper.go
Normal file
51
routers/web/shared/user/helper.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/models/user"
|
||||
)
|
||||
|
||||
func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
|
||||
if doer != nil {
|
||||
idx := slices.IndexFunc(users, func(u *user.User) bool {
|
||||
return u.ID == doer.ID
|
||||
})
|
||||
if idx > 0 {
|
||||
newUsers := make([]*user.User, len(users))
|
||||
newUsers[0] = users[idx]
|
||||
copy(newUsers[1:], users[:idx])
|
||||
copy(newUsers[idx+1:], users[idx+1:])
|
||||
return newUsers
|
||||
}
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
// GetFilterUserIDByName tries to get the user ID from the given username.
|
||||
// Before, the "issue filter" passes user ID to query the list, but in many cases, it's impossible to pre-fetch the full user list.
|
||||
// So it's better to make it work like GitHub: users could input username directly.
|
||||
// Since it only converts the username to ID directly and is only used internally (to search issues), so no permission check is needed.
|
||||
// Return values:
|
||||
// * "": no filter
|
||||
// * "{the-id}": match the id
|
||||
// * "(none)": match no issue (due to the user doesn't exist)
|
||||
func GetFilterUserIDByName(ctx context.Context, name string) string {
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
u, err := user.GetUserByName(ctx, name)
|
||||
if err != nil {
|
||||
if id, err := strconv.ParseInt(name, 10, 64); err == nil {
|
||||
return strconv.FormatInt(id, 10)
|
||||
}
|
||||
// The "(none)" is for internal usage only: when doer tries to search non-existing user, use "(none)" to return empty result.
|
||||
return "(none)"
|
||||
}
|
||||
return strconv.FormatInt(u.ID, 10)
|
||||
}
|
26
routers/web/shared/user/helper_test.go
Normal file
26
routers/web/shared/user/helper_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMakeSelfOnTop(t *testing.T) {
|
||||
users := MakeSelfOnTop(nil, []*user.User{{ID: 2}, {ID: 1}})
|
||||
assert.Len(t, users, 2)
|
||||
assert.EqualValues(t, 2, users[0].ID)
|
||||
|
||||
users = MakeSelfOnTop(&user.User{ID: 1}, []*user.User{{ID: 2}, {ID: 1}})
|
||||
assert.Len(t, users, 2)
|
||||
assert.EqualValues(t, 1, users[0].ID)
|
||||
|
||||
users = MakeSelfOnTop(&user.User{ID: 2}, []*user.User{{ID: 2}, {ID: 1}})
|
||||
assert.Len(t, users, 2)
|
||||
assert.EqualValues(t, 2, users[0].ID)
|
||||
}
|
Reference in New Issue
Block a user