summaryrefslogtreecommitdiffstats
path: root/routers/api
diff options
context:
space:
mode:
authorKN4CK3R <admin@oldschoolhack.me>2024-03-04 09:16:03 +0100
committerGitHub <noreply@github.com>2024-03-04 08:16:03 +0000
commitc337ff0ec70618ef2ead7850f90ab2a8458db192 (patch)
treecf4618cf7dc258018d5f9ec827b0fda4a9ebd196 /routers/api
parent8e12ba34bab7e728ac93ccfaecbe91e053ef1c89 (diff)
downloadgitea-c337ff0ec70618ef2ead7850f90ab2a8458db192.tar.gz
gitea-c337ff0ec70618ef2ead7850f90ab2a8458db192.zip
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>
Diffstat (limited to 'routers/api')
-rw-r--r--routers/api/v1/api.go20
-rw-r--r--routers/api/v1/org/block.go116
-rw-r--r--routers/api/v1/org/member.go2
-rw-r--r--routers/api/v1/org/team.go13
-rw-r--r--routers/api/v1/repo/collaborators.go24
-rw-r--r--routers/api/v1/repo/fork.go2
-rw-r--r--routers/api/v1/repo/issue.go14
-rw-r--r--routers/api/v1/repo/issue_comment.go14
-rw-r--r--routers/api/v1/repo/issue_comment_attachment.go10
-rw-r--r--routers/api/v1/repo/issue_reaction.go12
-rw-r--r--routers/api/v1/repo/pull.go10
-rw-r--r--routers/api/v1/repo/transfer.go7
-rw-r--r--routers/api/v1/shared/block.go98
-rw-r--r--routers/api/v1/user/block.go96
-rw-r--r--routers/api/v1/user/follower.go11
-rw-r--r--routers/api/v1/user/star.go27
-rw-r--r--routers/api/v1/user/watch.go27
17 files changed, 452 insertions, 51 deletions
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 1587d413f5..c65650c388 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -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).
diff --git a/routers/api/v1/org/block.go b/routers/api/v1/org/block.go
new file mode 100644
index 0000000000..69a5222a20
--- /dev/null
+++ b/routers/api/v1/org/block.go
@@ -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())
+}
diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go
index fb66d4c3f5..9db9ad964b 100644
--- a/routers/api/v1/org/member.go
+++ b/routers/api/v1/org/member.go
@@ -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)
diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go
index b62a386fd7..015af774e3 100644
--- a/routers/api/v1/org/team.go
+++ b/routers/api/v1/org/team.go
@@ -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
}
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index 7d48d71516..4ce14f7d01 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -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,16 +53,11 @@ 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
}
diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go
index 212cc7a93b..a1e3c9804b 100644
--- a/routers/api/v1/repo/fork.go
+++ b/routers/api/v1/repo/fork.go
@@ -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)
}
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index 1b2ecd474b..d43711e362 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -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
}
}
diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go
index 6209e960af..21aabadf3d 100644
--- a/routers/api/v1/repo/issue_comment.go
+++ b/routers/api/v1/repo/issue_comment.go
@@ -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
}
diff --git a/routers/api/v1/repo/issue_comment_attachment.go b/routers/api/v1/repo/issue_comment_attachment.go
index e7436db798..4096cbf07b 100644
--- a/routers/api/v1/repo/issue_comment_attachment.go
+++ b/routers/api/v1/repo/issue_comment_attachment.go
@@ -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
}
diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go
index 799c687812..3ff3d19f13 100644
--- a/routers/api/v1/repo/issue_reaction.go
+++ b/routers/api/v1/repo/issue_reaction.go
@@ -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
}
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index 8f9848f71d..4cb94b11a2 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -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)
}
diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go
index 4f05c0df51..776b336761 100644
--- a/routers/api/v1/repo/transfer.go
+++ b/routers/api/v1/repo/transfer.go
@@ -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
}
diff --git a/routers/api/v1/shared/block.go b/routers/api/v1/shared/block.go
new file mode 100644
index 0000000000..a1e65625ed
--- /dev/null
+++ b/routers/api/v1/shared/block.go
@@ -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)
+}
diff --git a/routers/api/v1/user/block.go b/routers/api/v1/user/block.go
new file mode 100644
index 0000000000..7231e9add7
--- /dev/null
+++ b/routers/api/v1/user/block.go
@@ -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)
+}
diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go
index 398c6b2567..6abb70de19 100644
--- a/routers/api/v1/user/follower.go
+++ b/routers/api/v1/user/follower.go
@@ -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)
diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go
index e624884db3..ad9ed9548d 100644
--- a/routers/api/v1/user/star.go
+++ b/routers/api/v1/user/star.go
@@ -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
diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go
index 706f4cc66b..2cc23ae476 100644
--- a/routers/api/v1/user/watch.go
+++ b/routers/api/v1/user/watch.go
@@ -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