1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-23 02:38:35 +00:00

Add user blocking (#29028)

Fixes #17453

This PR adds the abbility to block a user from a personal account or
organization to restrict how the blocked user can interact with the
blocker. The docs explain what's the consequence of blocking a user.

Screenshots:


![grafik](https://github.com/go-gitea/gitea/assets/1666336/4ed884f3-e06a-4862-afd3-3b8aa2488dc6)


![grafik](https://github.com/go-gitea/gitea/assets/1666336/ae6d4981-f252-4f50-a429-04f0f9f1cdf1)


![grafik](https://github.com/go-gitea/gitea/assets/1666336/ca153599-5b0f-4b4a-90fe-18bdfd6f0b6b)

---------

Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
KN4CK3R
2024-03-04 09:16:03 +01:00
committed by GitHub
parent 8e12ba34ba
commit c337ff0ec7
109 changed files with 2878 additions and 548 deletions

View File

@@ -1027,7 +1027,16 @@ func Routes() *web.Route {
m.Group("/avatar", func() {
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
m.Delete("", user.DeleteAvatar)
}, reqToken())
})
m.Group("/blocks", func() {
m.Get("", user.ListBlocks)
m.Group("/{username}", func() {
m.Get("", user.CheckUserBlock)
m.Put("", user.BlockUser)
m.Delete("", user.UnblockUser)
}, context.UserAssignmentAPI())
})
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
// Repositories (requires repo scope, org scope)
@@ -1477,6 +1486,15 @@ func Routes() *web.Route {
m.Delete("", org.DeleteAvatar)
}, reqToken(), reqOrgOwnership())
m.Get("/activities/feeds", org.ListOrgActivityFeeds)
m.Group("/blocks", func() {
m.Get("", org.ListBlocks)
m.Group("/{username}", func() {
m.Get("", org.CheckUserBlock)
m.Put("", org.BlockUser)
m.Delete("", org.UnblockUser)
})
}, reqToken(), reqOrgOwnership())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
m.Group("/teams/{teamid}", func() {
m.Combo("").Get(reqToken(), org.GetTeam).

116
routers/api/v1/org/block.go Normal file
View File

@@ -0,0 +1,116 @@
// Copyright 2024 The Gitea Authors.
// SPDX-License-Identifier: MIT
package org
import (
"code.gitea.io/gitea/routers/api/v1/shared"
"code.gitea.io/gitea/services/context"
)
func ListBlocks(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/blocks organization organizationListBlocks
// ---
// summary: List users blocked by the organization
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// produces:
// - application/json
// responses:
// "200":
// "$ref": "#/responses/UserList"
shared.ListBlocks(ctx, ctx.Org.Organization.AsUser())
}
func CheckUserBlock(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/blocks/{username} organization organizationCheckUserBlock
// ---
// summary: Check if a user is blocked by the organization
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: username
// in: path
// description: user to check
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
shared.CheckUserBlock(ctx, ctx.Org.Organization.AsUser())
}
func BlockUser(ctx *context.APIContext) {
// swagger:operation PUT /orgs/{org}/blocks/{username} organization organizationBlockUser
// ---
// summary: Block a user
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: username
// in: path
// description: user to block
// type: string
// required: true
// - name: note
// in: query
// description: optional note for the block
// type: string
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
shared.BlockUser(ctx, ctx.Org.Organization.AsUser())
}
func UnblockUser(ctx *context.APIContext) {
// swagger:operation DELETE /orgs/{org}/blocks/{username} organization organizationUnblockUser
// ---
// summary: Unblock a user
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: username
// in: path
// description: user to unblock
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
shared.UnblockUser(ctx, ctx.Doer, ctx.Org.Organization.AsUser())
}

View File

@@ -318,7 +318,7 @@ func DeleteMember(ctx *context.APIContext) {
if ctx.Written() {
return
}
if err := models.RemoveOrgUser(ctx, ctx.Org.Organization.ID, member.ID); err != nil {
if err := models.RemoveOrgUser(ctx, ctx.Org.Organization, member); err != nil {
ctx.Error(http.StatusInternalServerError, "RemoveOrgUser", err)
}
ctx.Status(http.StatusNoContent)

View File

@@ -15,6 +15,7 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
@@ -486,6 +487,8 @@ func AddTeamMember(ctx *context.APIContext) {
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
@@ -493,8 +496,12 @@ func AddTeamMember(ctx *context.APIContext) {
if ctx.Written() {
return
}
if err := models.AddTeamMember(ctx, ctx.Org.Team, u.ID); err != nil {
ctx.Error(http.StatusInternalServerError, "AddMember", err)
if err := models.AddTeamMember(ctx, ctx.Org.Team, u); err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "AddTeamMember", err)
} else {
ctx.Error(http.StatusInternalServerError, "AddTeamMember", err)
}
return
}
ctx.Status(http.StatusNoContent)
@@ -530,7 +537,7 @@ func RemoveTeamMember(ctx *context.APIContext) {
return
}
if err := models.RemoveTeamMember(ctx, ctx.Org.Team, u.ID); err != nil {
if err := models.RemoveTeamMember(ctx, ctx.Org.Team, u); err != nil {
ctx.Error(http.StatusInternalServerError, "RemoveTeamMember", err)
return
}

View File

@@ -8,7 +8,6 @@ import (
"errors"
"net/http"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
@@ -54,15 +53,10 @@ func ListCollaborators(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
count, err := db.Count[repo_model.Collaboration](ctx, repo_model.FindCollaborationOptions{
RepoID: ctx.Repo.Repository.ID,
collaborators, total, err := repo_model.GetCollaborators(ctx, &repo_model.FindCollaborationOptions{
ListOptions: utils.GetListOptions(ctx),
RepoID: ctx.Repo.Repository.ID,
})
if err != nil {
ctx.InternalServerError(err)
return
}
collaborators, err := repo_model.GetCollaborators(ctx, ctx.Repo.Repository.ID, utils.GetListOptions(ctx))
if err != nil {
ctx.Error(http.StatusInternalServerError, "ListCollaborators", err)
return
@@ -73,7 +67,7 @@ func ListCollaborators(ctx *context.APIContext) {
users[i] = convert.ToUser(ctx, collaborator.User, ctx.Doer)
}
ctx.SetTotalCountHeader(count)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, users)
}
@@ -159,6 +153,8 @@ func AddCollaborator(ctx *context.APIContext) {
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "422":
@@ -182,7 +178,11 @@ func AddCollaborator(ctx *context.APIContext) {
}
if err := repo_module.AddCollaborator(ctx, ctx.Repo.Repository, collaborator); err != nil {
ctx.Error(http.StatusInternalServerError, "AddCollaborator", err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "AddCollaborator", err)
} else {
ctx.Error(http.StatusInternalServerError, "AddCollaborator", err)
}
return
}
@@ -237,7 +237,7 @@ func DeleteCollaborator(ctx *context.APIContext) {
return
}
if err := repo_service.DeleteCollaboration(ctx, ctx.Repo.Repository, collaborator.ID); err != nil {
if err := repo_service.DeleteCollaboration(ctx, ctx.Repo.Repository, collaborator); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteCollaboration", err)
return
}

View File

@@ -149,6 +149,8 @@ func CreateFork(ctx *context.APIContext) {
if err != nil {
if errors.Is(err, util.ErrAlreadyExist) || repo_model.IsErrReachLimitOfRepo(err) {
ctx.Error(http.StatusConflict, "ForkRepository", err)
} else if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "ForkRepository", err)
} else {
ctx.Error(http.StatusInternalServerError, "ForkRepository", err)
}

View File

@@ -5,6 +5,7 @@
package repo
import (
"errors"
"fmt"
"net/http"
"strconv"
@@ -653,6 +654,7 @@ func CreateIssue(ctx *context.APIContext) {
// "$ref": "#/responses/validationError"
// "423":
// "$ref": "#/responses/repoArchivedError"
form := web.GetForm(ctx).(*api.CreateIssueOption)
var deadlineUnix timeutil.TimeStamp
if form.Deadline != nil && ctx.Repo.CanWrite(unit.TypeIssues) {
@@ -710,9 +712,11 @@ func CreateIssue(ctx *context.APIContext) {
if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs); err != nil {
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err)
return
} else if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "NewIssue", err)
} else {
ctx.Error(http.StatusInternalServerError, "NewIssue", err)
}
ctx.Error(http.StatusInternalServerError, "NewIssue", err)
return
}
@@ -848,7 +852,11 @@ func EditIssue(ctx *context.APIContext) {
err = issue_service.UpdateAssignees(ctx, issue, oneAssignee, form.Assignees, ctx.Doer)
if err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "UpdateAssignees", err)
} else {
ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err)
}
return
}
}

View File

@@ -382,6 +382,7 @@ func CreateIssueComment(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
// "423":
// "$ref": "#/responses/repoArchivedError"
form := web.GetForm(ctx).(*api.CreateIssueCommentOption)
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
@@ -401,7 +402,11 @@ func CreateIssueComment(ctx *context.APIContext) {
comment, err := issue_service.CreateIssueComment(ctx, ctx.Doer, ctx.Repo.Repository, issue, form.Body, nil)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CreateIssueComment", err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "CreateIssueComment", err)
} else {
ctx.Error(http.StatusInternalServerError, "CreateIssueComment", err)
}
return
}
@@ -522,6 +527,7 @@ func EditIssueComment(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
// "423":
// "$ref": "#/responses/repoArchivedError"
form := web.GetForm(ctx).(*api.EditIssueCommentOption)
editIssueComment(ctx, *form)
}
@@ -610,7 +616,11 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
oldContent := comment.Content
comment.Content = form.Body
if err := issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateComment", err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "UpdateComment", err)
} else {
ctx.Error(http.StatusInternalServerError, "UpdateComment", err)
}
return
}

View File

@@ -4,10 +4,12 @@
package repo
import (
"errors"
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@@ -154,6 +156,8 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
// "$ref": "#/responses/Attachment"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/error"
// "423":
@@ -199,7 +203,11 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
}
if err = issue_service.UpdateComment(ctx, comment, ctx.Doer, comment.Content); err != nil {
ctx.ServerError("UpdateComment", err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "UpdateComment", err)
} else {
ctx.ServerError("UpdateComment", err)
}
return
}

View File

@@ -8,11 +8,13 @@ import (
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
)
// GetIssueCommentReactions list reactions of a comment from an issue
@@ -218,9 +220,9 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
if isCreateType {
// PostIssueCommentReaction part
reaction, err := issues_model.CreateCommentReaction(ctx, ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Reaction)
reaction, err := issue_service.CreateCommentReaction(ctx, ctx.Doer, comment, form.Reaction)
if err != nil {
if issues_model.IsErrForbiddenIssueReaction(err) {
if issues_model.IsErrForbiddenIssueReaction(err) || errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, err.Error(), err)
} else if issues_model.IsErrReactionAlreadyExist(err) {
ctx.JSON(http.StatusOK, api.Reaction{
@@ -434,9 +436,9 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i
if isCreateType {
// PostIssueReaction part
reaction, err := issues_model.CreateIssueReaction(ctx, ctx.Doer.ID, issue.ID, form.Reaction)
reaction, err := issue_service.CreateIssueReaction(ctx, ctx.Doer, issue, form.Reaction)
if err != nil {
if issues_model.IsErrForbiddenIssueReaction(err) {
if issues_model.IsErrForbiddenIssueReaction(err) || errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, err.Error(), err)
} else if issues_model.IsErrReactionAlreadyExist(err) {
ctx.JSON(http.StatusOK, api.Reaction{
@@ -445,7 +447,7 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i
Created: reaction.CreatedUnix.AsTime(),
})
} else {
ctx.Error(http.StatusInternalServerError, "CreateCommentReaction", err)
ctx.Error(http.StatusInternalServerError, "CreateIssueReaction", err)
}
return
}

View File

@@ -362,6 +362,8 @@ func CreatePullRequest(ctx *context.APIContext) {
// responses:
// "201":
// "$ref": "#/responses/PullRequest"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "409":
@@ -510,9 +512,11 @@ func CreatePullRequest(ctx *context.APIContext) {
if err := pull_service.NewPullRequest(ctx, repo, prIssue, labelIDs, []string{}, pr, assigneeIDs); err != nil {
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err)
return
} else if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "BlockedUser", err)
} else {
ctx.Error(http.StatusInternalServerError, "NewPullRequest", err)
}
ctx.Error(http.StatusInternalServerError, "NewPullRequest", err)
return
}
@@ -630,6 +634,8 @@ func EditPullRequest(ctx *context.APIContext) {
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
} else if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "UpdateAssignees", err)
} else {
ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err)
}

View File

@@ -4,6 +4,7 @@
package repo
import (
"errors"
"fmt"
"net/http"
@@ -117,7 +118,11 @@ func Transfer(ctx *context.APIContext) {
return
}
ctx.InternalServerError(err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "BlockedUser", err)
} else {
ctx.InternalServerError(err)
}
return
}

View File

@@ -0,0 +1,98 @@
// Copyright 2024 The Gitea Authors.
// SPDX-License-Identifier: MIT
package shared
import (
"errors"
"net/http"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
user_service "code.gitea.io/gitea/services/user"
)
func ListBlocks(ctx *context.APIContext, blocker *user_model.User) {
blocks, total, err := user_model.FindBlockings(ctx, &user_model.FindBlockingOptions{
ListOptions: utils.GetListOptions(ctx),
BlockerID: blocker.ID,
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindBlockings", err)
return
}
if err := user_model.BlockingList(blocks).LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return
}
users := make([]*api.User, 0, len(blocks))
for _, b := range blocks {
users = append(users, convert.ToUser(ctx, b.Blockee, blocker))
}
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &users)
}
func CheckUserBlock(ctx *context.APIContext, blocker *user_model.User) {
blockee, err := user_model.GetUserByName(ctx, ctx.Params("username"))
if err != nil {
ctx.NotFound("GetUserByName", err)
return
}
status := http.StatusNotFound
blocking, err := user_model.GetBlocking(ctx, blocker.ID, blockee.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBlocking", err)
return
}
if blocking != nil {
status = http.StatusNoContent
}
ctx.Status(status)
}
func BlockUser(ctx *context.APIContext, blocker *user_model.User) {
blockee, err := user_model.GetUserByName(ctx, ctx.Params("username"))
if err != nil {
ctx.NotFound("GetUserByName", err)
return
}
if err := user_service.BlockUser(ctx, ctx.Doer, blocker, blockee, ctx.FormString("note")); err != nil {
if errors.Is(err, user_model.ErrCanNotBlock) || errors.Is(err, user_model.ErrBlockOrganization) {
ctx.Error(http.StatusBadRequest, "BlockUser", err)
} else {
ctx.Error(http.StatusInternalServerError, "BlockUser", err)
}
return
}
ctx.Status(http.StatusNoContent)
}
func UnblockUser(ctx *context.APIContext, doer, blocker *user_model.User) {
blockee, err := user_model.GetUserByName(ctx, ctx.Params("username"))
if err != nil {
ctx.NotFound("GetUserByName", err)
return
}
if err := user_service.UnblockUser(ctx, doer, blocker, blockee); err != nil {
if errors.Is(err, user_model.ErrCanNotUnblock) || errors.Is(err, user_model.ErrBlockOrganization) {
ctx.Error(http.StatusBadRequest, "UnblockUser", err)
} else {
ctx.Error(http.StatusInternalServerError, "UnblockUser", err)
}
return
}
ctx.Status(http.StatusNoContent)
}

View File

@@ -0,0 +1,96 @@
// Copyright 2024 The Gitea Authors.
// SPDX-License-Identifier: MIT
package user
import (
"code.gitea.io/gitea/routers/api/v1/shared"
"code.gitea.io/gitea/services/context"
)
func ListBlocks(ctx *context.APIContext) {
// swagger:operation GET /user/blocks user userListBlocks
// ---
// summary: List users blocked by the authenticated user
// parameters:
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// produces:
// - application/json
// responses:
// "200":
// "$ref": "#/responses/UserList"
shared.ListBlocks(ctx, ctx.Doer)
}
func CheckUserBlock(ctx *context.APIContext) {
// swagger:operation GET /user/blocks/{username} user userCheckUserBlock
// ---
// summary: Check if a user is blocked by the authenticated user
// parameters:
// - name: username
// in: path
// description: user to check
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
shared.CheckUserBlock(ctx, ctx.Doer)
}
func BlockUser(ctx *context.APIContext) {
// swagger:operation PUT /user/blocks/{username} user userBlockUser
// ---
// summary: Block a user
// parameters:
// - name: username
// in: path
// description: user to block
// type: string
// required: true
// - name: note
// in: query
// description: optional note for the block
// type: string
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
shared.BlockUser(ctx, ctx.Doer)
}
func UnblockUser(ctx *context.APIContext) {
// swagger:operation DELETE /user/blocks/{username} user userUnblockUser
// ---
// summary: Unblock a user
// parameters:
// - name: username
// in: path
// description: user to unblock
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
shared.UnblockUser(ctx, ctx.Doer, ctx.Doer)
}

View File

@@ -5,6 +5,7 @@
package user
import (
"errors"
"net/http"
user_model "code.gitea.io/gitea/models/user"
@@ -221,11 +222,17 @@ func Follow(ctx *context.APIContext) {
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
if err := user_model.FollowUser(ctx, ctx.Doer.ID, ctx.ContextUser.ID); err != nil {
ctx.Error(http.StatusInternalServerError, "FollowUser", err)
if err := user_model.FollowUser(ctx, ctx.Doer, ctx.ContextUser); err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "FollowUser", err)
} else {
ctx.Error(http.StatusInternalServerError, "FollowUser", err)
}
return
}
ctx.Status(http.StatusNoContent)

View File

@@ -5,10 +5,9 @@
package user
import (
std_context "context"
"errors"
"net/http"
"code.gitea.io/gitea/models/db"
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"
@@ -20,8 +19,12 @@ import (
// getStarredRepos returns the repos that the user with the specified userID has
// starred
func getStarredRepos(ctx std_context.Context, user *user_model.User, private bool, listOptions db.ListOptions) ([]*api.Repository, error) {
starredRepos, err := repo_model.GetStarredRepos(ctx, user.ID, private, listOptions)
func getStarredRepos(ctx *context.APIContext, user *user_model.User, private bool) ([]*api.Repository, error) {
starredRepos, err := repo_model.GetStarredRepos(ctx, &repo_model.StarredReposOptions{
ListOptions: utils.GetListOptions(ctx),
StarrerID: user.ID,
IncludePrivate: private,
})
if err != nil {
return nil, err
}
@@ -65,7 +68,7 @@ func GetStarredRepos(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
private := ctx.ContextUser.ID == ctx.Doer.ID
repos, err := getStarredRepos(ctx, ctx.ContextUser, private, utils.GetListOptions(ctx))
repos, err := getStarredRepos(ctx, ctx.ContextUser, private)
if err != nil {
ctx.Error(http.StatusInternalServerError, "getStarredRepos", err)
return
@@ -95,7 +98,7 @@ func GetMyStarredRepos(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/RepositoryList"
repos, err := getStarredRepos(ctx, ctx.Doer, true, utils.GetListOptions(ctx))
repos, err := getStarredRepos(ctx, ctx.Doer, true)
if err != nil {
ctx.Error(http.StatusInternalServerError, "getStarredRepos", err)
}
@@ -152,12 +155,18 @@ func Star(ctx *context.APIContext) {
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
err := repo_model.StarRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, true)
err := repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, true)
if err != nil {
ctx.Error(http.StatusInternalServerError, "StarRepo", err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "BlockedUser", err)
} else {
ctx.Error(http.StatusInternalServerError, "StarRepo", err)
}
return
}
ctx.Status(http.StatusNoContent)
@@ -185,7 +194,7 @@ func Unstar(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
err := repo_model.StarRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, false)
err := repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, false)
if err != nil {
ctx.Error(http.StatusInternalServerError, "StarRepo", err)
return

View File

@@ -4,10 +4,9 @@
package user
import (
std_context "context"
"errors"
"net/http"
"code.gitea.io/gitea/models/db"
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"
@@ -18,8 +17,12 @@ import (
)
// getWatchedRepos returns the repos that the user with the specified userID is watching
func getWatchedRepos(ctx std_context.Context, user *user_model.User, private bool, listOptions db.ListOptions) ([]*api.Repository, int64, error) {
watchedRepos, total, err := repo_model.GetWatchedRepos(ctx, user.ID, private, listOptions)
func getWatchedRepos(ctx *context.APIContext, user *user_model.User, private bool) ([]*api.Repository, int64, error) {
watchedRepos, total, err := repo_model.GetWatchedRepos(ctx, &repo_model.WatchedReposOptions{
ListOptions: utils.GetListOptions(ctx),
WatcherID: user.ID,
IncludePrivate: private,
})
if err != nil {
return nil, 0, err
}
@@ -63,7 +66,7 @@ func GetWatchedRepos(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
private := ctx.ContextUser.ID == ctx.Doer.ID
repos, total, err := getWatchedRepos(ctx, ctx.ContextUser, private, utils.GetListOptions(ctx))
repos, total, err := getWatchedRepos(ctx, ctx.ContextUser, private)
if err != nil {
ctx.Error(http.StatusInternalServerError, "getWatchedRepos", err)
}
@@ -92,7 +95,7 @@ func GetMyWatchedRepos(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/RepositoryList"
repos, total, err := getWatchedRepos(ctx, ctx.Doer, true, utils.GetListOptions(ctx))
repos, total, err := getWatchedRepos(ctx, ctx.Doer, true)
if err != nil {
ctx.Error(http.StatusInternalServerError, "getWatchedRepos", err)
}
@@ -157,12 +160,18 @@ func Watch(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/WatchInfo"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
err := repo_model.WatchRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, true)
err := repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, true)
if err != nil {
ctx.Error(http.StatusInternalServerError, "WatchRepo", err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "BlockedUser", err)
} else {
ctx.Error(http.StatusInternalServerError, "WatchRepo", err)
}
return
}
ctx.JSON(http.StatusOK, api.WatchInfo{
@@ -197,7 +206,7 @@ func Unwatch(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
err := repo_model.WatchRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, false)
err := repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, false)
if err != nil {
ctx.Error(http.StatusInternalServerError, "UnwatchRepo", err)
return

38
routers/web/org/block.go Normal file
View File

@@ -0,0 +1,38 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package org
import (
"net/http"
"code.gitea.io/gitea/modules/base"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
)
const (
tplSettingsBlockedUsers base.TplName = "org/settings/blocked_users"
)
func BlockedUsers(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("user.block.list")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsBlockedUsers"] = true
shared_user.BlockedUsers(ctx, ctx.ContextUser)
if ctx.Written() {
return
}
ctx.HTML(http.StatusOK, tplSettingsBlockedUsers)
}
func BlockedUsersPost(ctx *context.Context) {
shared_user.BlockedUsersPost(ctx, ctx.ContextUser)
if ctx.Written() {
return
}
ctx.Redirect(ctx.ContextUser.OrganisationLink() + "/settings/blocked_users")
}

View File

@@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -78,40 +79,43 @@ func Members(ctx *context.Context) {
// MembersAction response for operation to a member of organization
func MembersAction(ctx *context.Context) {
uid := ctx.FormInt64("uid")
if uid == 0 {
member, err := user_model.GetUserByID(ctx, ctx.FormInt64("uid"))
if err != nil {
log.Error("GetUserByID: %v", err)
}
if member == nil {
ctx.Redirect(ctx.Org.OrgLink + "/members")
return
}
org := ctx.Org.Organization
var err error
switch ctx.Params(":action") {
case "private":
if ctx.Doer.ID != uid && !ctx.Org.IsOwner {
if ctx.Doer.ID != member.ID && !ctx.Org.IsOwner {
ctx.Error(http.StatusNotFound)
return
}
err = organization.ChangeOrgUserStatus(ctx, org.ID, uid, false)
err = organization.ChangeOrgUserStatus(ctx, org.ID, member.ID, false)
case "public":
if ctx.Doer.ID != uid && !ctx.Org.IsOwner {
if ctx.Doer.ID != member.ID && !ctx.Org.IsOwner {
ctx.Error(http.StatusNotFound)
return
}
err = organization.ChangeOrgUserStatus(ctx, org.ID, uid, true)
err = organization.ChangeOrgUserStatus(ctx, org.ID, member.ID, true)
case "remove":
if !ctx.Org.IsOwner {
ctx.Error(http.StatusNotFound)
return
}
err = models.RemoveOrgUser(ctx, org.ID, uid)
err = models.RemoveOrgUser(ctx, org, member)
if organization.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
ctx.JSONRedirect(ctx.Org.OrgLink + "/members")
return
}
case "leave":
err = models.RemoveOrgUser(ctx, org.ID, ctx.Doer.ID)
err = models.RemoveOrgUser(ctx, org, ctx.Doer)
if err == nil {
ctx.Flash.Success(ctx.Tr("form.organization_leave_success", org.DisplayName()))
ctx.JSON(http.StatusOK, map[string]any{

View File

@@ -5,6 +5,7 @@
package org
import (
"errors"
"fmt"
"net/http"
"net/url"
@@ -77,9 +78,9 @@ func TeamsAction(ctx *context.Context) {
ctx.Error(http.StatusNotFound)
return
}
err = models.AddTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID)
err = models.AddTeamMember(ctx, ctx.Org.Team, ctx.Doer)
case "leave":
err = models.RemoveTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID)
err = models.RemoveTeamMember(ctx, ctx.Org.Team, ctx.Doer)
if err != nil {
if org_model.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
@@ -100,13 +101,13 @@ func TeamsAction(ctx *context.Context) {
return
}
uid := ctx.FormInt64("uid")
if uid == 0 {
user, _ := user_model.GetUserByID(ctx, ctx.FormInt64("uid"))
if user == nil {
ctx.Redirect(ctx.Org.OrgLink + "/teams")
return
}
err = models.RemoveTeamMember(ctx, ctx.Org.Team, uid)
err = models.RemoveTeamMember(ctx, ctx.Org.Team, user)
if err != nil {
if org_model.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
@@ -161,7 +162,7 @@ func TeamsAction(ctx *context.Context) {
if ctx.Org.Team.IsMember(ctx, u.ID) {
ctx.Flash.Error(ctx.Tr("org.teams.add_duplicate_users"))
} else {
err = models.AddTeamMember(ctx, ctx.Org.Team, u.ID)
err = models.AddTeamMember(ctx, ctx.Org.Team, u)
}
page = "team"
@@ -189,6 +190,8 @@ func TeamsAction(ctx *context.Context) {
if err != nil {
if org_model.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
} else if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Flash.Error(ctx.Tr("org.teams.members.blocked_user"))
} else {
log.Error("Action(%s): %v", ctx.Params(":action"), err)
ctx.JSON(http.StatusOK, map[string]any{
@@ -590,7 +593,7 @@ func TeamInvitePost(ctx *context.Context) {
return
}
if err := models.AddTeamMember(ctx, team, ctx.Doer.ID); err != nil {
if err := models.AddTeamMember(ctx, team, ctx.Doer); err != nil {
ctx.ServerError("AddTeamMember", err)
return
}

View File

@@ -57,6 +57,7 @@ import (
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user"
)
const (
@@ -1258,9 +1259,11 @@ func NewIssuePost(ctx *context.Context) {
if err := issue_service.NewIssue(ctx, repo, issue, labelIDs, attachments, assigneeIDs); err != nil {
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
return
} else if errors.Is(err, user_model.ErrBlockedUser) {
ctx.JSONError(ctx.Tr("repo.issues.new.blocked_user"))
} else {
ctx.ServerError("NewIssue", err)
}
ctx.ServerError("NewIssue", err)
return
}
@@ -2047,6 +2050,10 @@ func ViewIssue(ctx *context.Context) {
}
ctx.Data["Tags"] = tags
ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool {
return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee)
}
ctx.HTML(http.StatusOK, tplIssueView)
}
@@ -2250,7 +2257,11 @@ func UpdateIssueContent(ctx *context.Context) {
}
if err := issue_service.ChangeContent(ctx, issue, ctx.Doer, ctx.Req.FormValue("content")); err != nil {
ctx.ServerError("ChangeContent", err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.JSONError(ctx.Tr("repo.issues.edit.blocked_user"))
} else {
ctx.ServerError("ChangeContent", err)
}
return
}
@@ -3108,7 +3119,11 @@ func NewComment(ctx *context.Context) {
comment, err := issue_service.CreateIssueComment(ctx, ctx.Doer, ctx.Repo.Repository, issue, form.Content, attachments)
if err != nil {
ctx.ServerError("CreateIssueComment", err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
} else {
ctx.ServerError("CreateIssueComment", err)
}
return
}
@@ -3152,7 +3167,11 @@ func UpdateCommentContent(ctx *context.Context) {
return
}
if err = issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
ctx.ServerError("UpdateComment", err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
} else {
ctx.ServerError("UpdateComment", err)
}
return
}
@@ -3260,9 +3279,9 @@ func ChangeIssueReaction(ctx *context.Context) {
switch ctx.Params(":action") {
case "react":
reaction, err := issues_model.CreateIssueReaction(ctx, ctx.Doer.ID, issue.ID, form.Content)
reaction, err := issue_service.CreateIssueReaction(ctx, ctx.Doer, issue, form.Content)
if err != nil {
if issues_model.IsErrForbiddenIssueReaction(err) {
if issues_model.IsErrForbiddenIssueReaction(err) || errors.Is(err, user_model.ErrBlockedUser) {
ctx.ServerError("ChangeIssueReaction", err)
return
}
@@ -3367,9 +3386,9 @@ func ChangeCommentReaction(ctx *context.Context) {
switch ctx.Params(":action") {
case "react":
reaction, err := issues_model.CreateCommentReaction(ctx, ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Content)
reaction, err := issue_service.CreateCommentReaction(ctx, ctx.Doer, comment, form.Content)
if err != nil {
if issues_model.IsErrForbiddenIssueReaction(err) {
if issues_model.IsErrForbiddenIssueReaction(err) || errors.Is(err, user_model.ErrBlockedUser) {
ctx.ServerError("ChangeIssueReaction", err)
return
}

View File

@@ -47,6 +47,7 @@ import (
notify_service "code.gitea.io/gitea/services/notify"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user"
"github.com/gobwas/glob"
)
@@ -308,6 +309,8 @@ func ForkPost(ctx *context.Context) {
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplFork, &form)
case db.IsErrNamePatternNotAllowed(err):
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplFork, &form)
case errors.Is(err, user_model.ErrBlockedUser):
ctx.RenderWithErr(ctx.Tr("repo.fork.blocked_user"), tplFork, form)
default:
ctx.ServerError("ForkPost", err)
}
@@ -1065,6 +1068,10 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
}
upload.AddUploadContext(ctx, "comment")
ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool {
return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee)
}
ctx.HTML(http.StatusOK, tplPullFiles)
}
@@ -1483,7 +1490,6 @@ func CompareAndPullRequestPost(ctx *context.Context) {
if err := pull_service.NewPullRequest(ctx, repo, pullIssue, labelIDs, attachments, pullRequest, assigneeIDs); err != nil {
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
return
} else if git.IsErrPushRejected(err) {
pushrejErr := err.(*git.ErrPushRejected)
message := pushrejErr.Message
@@ -1501,9 +1507,17 @@ func CompareAndPullRequestPost(ctx *context.Context) {
return
}
ctx.JSONError(flashError)
return
} else if errors.Is(err, user_model.ErrBlockedUser) {
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.push_rejected"),
"Summary": ctx.Tr("repo.pulls.new.blocked_user"),
})
if err != nil {
ctx.ServerError("CompareAndPullRequest.HTMLString", err)
return
}
ctx.JSONError(flashError)
}
ctx.ServerError("NewPullRequest", err)
return
}

View File

@@ -313,13 +313,13 @@ func Action(ctx *context.Context) {
var err error
switch ctx.Params(":action") {
case "watch":
err = repo_model.WatchRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, true)
err = repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, true)
case "unwatch":
err = repo_model.WatchRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, false)
err = repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, false)
case "star":
err = repo_model.StarRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, true)
err = repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, true)
case "unstar":
err = repo_model.StarRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, false)
err = repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, false)
case "accept_transfer":
err = acceptOrRejectRepoTransfer(ctx, true)
case "reject_transfer":
@@ -336,8 +336,12 @@ func Action(ctx *context.Context) {
}
if err != nil {
ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err)
return
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Flash.Error(ctx.Tr("repo.action.blocked_user"))
} else {
ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err)
return
}
}
switch ctx.Params(":action") {

View File

@@ -4,10 +4,10 @@
package setting
import (
"errors"
"net/http"
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
@@ -27,7 +27,7 @@ func Collaboration(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.collaboration")
ctx.Data["PageIsSettingsCollaboration"] = true
users, err := repo_model.GetCollaborators(ctx, ctx.Repo.Repository.ID, db.ListOptions{})
users, _, err := repo_model.GetCollaborators(ctx, &repo_model.FindCollaborationOptions{RepoID: ctx.Repo.Repository.ID})
if err != nil {
ctx.ServerError("GetCollaborators", err)
return
@@ -101,7 +101,12 @@ func CollaborationPost(ctx *context.Context) {
}
if err = repo_module.AddCollaborator(ctx, ctx.Repo.Repository, u); err != nil {
ctx.ServerError("AddCollaborator", err)
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator.blocked_user"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
} else {
ctx.ServerError("AddCollaborator", err)
}
return
}
@@ -126,10 +131,19 @@ func ChangeCollaborationAccessMode(ctx *context.Context) {
// DeleteCollaboration delete a collaboration for a repository
func DeleteCollaboration(ctx *context.Context) {
if err := repo_service.DeleteCollaboration(ctx, ctx.Repo.Repository, ctx.FormInt64("id")); err != nil {
ctx.Flash.Error("DeleteCollaboration: " + err.Error())
if collaborator, err := user_model.GetUserByID(ctx, ctx.FormInt64("id")); err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
} else {
ctx.ServerError("GetUserByName", err)
return
}
} else {
ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success"))
if err := repo_service.DeleteCollaboration(ctx, ctx.Repo.Repository, collaborator); err != nil {
ctx.Flash.Error("DeleteCollaboration: " + err.Error())
} else {
ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success"))
}
}
ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/collaboration")

View File

@@ -5,6 +5,7 @@
package setting
import (
"errors"
"fmt"
"net/http"
"strconv"
@@ -782,6 +783,8 @@ func SettingsPost(ctx *context.Context) {
ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
} else if models.IsErrRepoTransferInProgress(err) {
ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil)
} else if errors.Is(err, user_model.ErrBlockedUser) {
ctx.RenderWithErr(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil)
} else {
ctx.ServerError("TransferOwnership", err)
}

View 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
}
}
}
}

View File

@@ -72,6 +72,14 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
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 FindUserProfileReadme(ctx *context.Context, doer *user_model.User) (profileDbRepo *repo_model.Repository, profileGitRepo *git.Repository, profileReadmeBlob *git.Blob, profileClose func()) {

View File

@@ -339,7 +339,7 @@ func Action(ctx *context.Context) {
var err error
switch ctx.FormString("action") {
case "follow":
err = user_model.FollowUser(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
err = user_model.FollowUser(ctx, ctx.Doer, ctx.ContextUser)
case "unfollow":
err = user_model.UnfollowUser(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
}

View File

@@ -0,0 +1,38 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"net/http"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
)
const (
tplSettingsBlockedUsers base.TplName = "user/settings/blocked_users"
)
func BlockedUsers(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("user.block.list")
ctx.Data["PageIsSettingsBlockedUsers"] = true
shared_user.BlockedUsers(ctx, ctx.Doer)
if ctx.Written() {
return
}
ctx.HTML(http.StatusOK, tplSettingsBlockedUsers)
}
func BlockedUsersPost(ctx *context.Context) {
shared_user.BlockedUsersPost(ctx, ctx.Doer)
if ctx.Written() {
return
}
ctx.Redirect(setting.AppSubURL + "/user/settings/blocked_users")
}

View File

@@ -647,6 +647,11 @@ func registerRoutes(m *web.Route) {
})
addWebhookEditRoutes()
}, webhooksEnabled)
m.Group("/blocked_users", func() {
m.Get("", user_setting.BlockedUsers)
m.Post("", web.Bind(forms.BlockUserForm{}), user_setting.BlockedUsersPost)
})
}, reqSignIn, ctxDataSet("PageIsUserSettings", true, "AllThemes", setting.UI.Themes, "EnablePackages", setting.Packages.Enabled))
m.Group("/user", func() {
@@ -945,6 +950,11 @@ func registerRoutes(m *web.Route) {
m.Post("/rebuild", org.RebuildCargoIndex)
})
}, packagesEnabled)
m.Group("/blocked_users", func() {
m.Get("", org.BlockedUsers)
m.Post("", web.Bind(forms.BlockUserForm{}), org.BlockedUsersPost)
})
}, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled, "PageIsOrgSettings", true))
}, context.OrgAssignment(true, true))
}, reqSignIn)