// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package user import ( "context" "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" org_model "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" repo_service "code.gitea.io/gitea/services/repository" ) func CanBlockUser(ctx context.Context, doer, blocker, blockee *user_model.User) bool { if blocker.ID == blockee.ID { return false } if doer.ID == blockee.ID { return false } if blockee.IsOrganization() { return false } if user_model.IsUserBlockedBy(ctx, blockee, blocker.ID) { return false } if blocker.IsOrganization() { org := org_model.OrgFromUser(blocker) if isMember, _ := org.IsOrgMember(ctx, blockee.ID); isMember { return false } if isAdmin, _ := org.IsOwnedBy(ctx, doer.ID); !isAdmin && !doer.IsAdmin { return false } } else if !doer.IsAdmin && doer.ID != blocker.ID { return false } return true } func CanUnblockUser(ctx context.Context, doer, blocker, blockee *user_model.User) bool { if doer.ID == blockee.ID { return false } if !user_model.IsUserBlockedBy(ctx, blockee, blocker.ID) { return false } if blocker.IsOrganization() { org := org_model.OrgFromUser(blocker) if isAdmin, _ := org.IsOwnedBy(ctx, doer.ID); !isAdmin && !doer.IsAdmin { return false } } else if !doer.IsAdmin && doer.ID != blocker.ID { return false } return true } func BlockUser(ctx context.Context, doer, blocker, blockee *user_model.User, note string) error { if blockee.IsOrganization() { return user_model.ErrBlockOrganization } if !CanBlockUser(ctx, doer, blocker, blockee) { return user_model.ErrCanNotBlock } return db.WithTx(ctx, func(ctx context.Context) error { // unfollow each other if err := user_model.UnfollowUser(ctx, blocker.ID, blockee.ID); err != nil { return err } if err := user_model.UnfollowUser(ctx, blockee.ID, blocker.ID); err != nil { return err } // unstar each other if err := unstarRepos(ctx, blocker, blockee); err != nil { return err } if err := unstarRepos(ctx, blockee, blocker); err != nil { return err } // unwatch each others repositories if err := unwatchRepos(ctx, blocker, blockee); err != nil { return err } if err := unwatchRepos(ctx, blockee, blocker); err != nil { return err } // unassign each other from issues if err := unassignIssues(ctx, blocker, blockee); err != nil { return err } if err := unassignIssues(ctx, blockee, blocker); err != nil { return err } // remove each other from repository collaborations if err := removeCollaborations(ctx, blocker, blockee); err != nil { return err } if err := removeCollaborations(ctx, blockee, blocker); err != nil { return err } // cancel each other repository transfers if err := cancelRepositoryTransfers(ctx, blocker, blockee); err != nil { return err } if err := cancelRepositoryTransfers(ctx, blockee, blocker); err != nil { return err } return db.Insert(ctx, &user_model.Blocking{ BlockerID: blocker.ID, BlockeeID: blockee.ID, Note: note, }) }) } func unstarRepos(ctx context.Context, starrer, repoOwner *user_model.User) error { opts := &repo_model.StarredReposOptions{ ListOptions: db.ListOptions{ Page: 1, PageSize: 25, }, StarrerID: starrer.ID, RepoOwnerID: repoOwner.ID, } for { repos, err := repo_model.GetStarredRepos(ctx, opts) if err != nil { return err } if len(repos) == 0 { return nil } for _, repo := range repos { if err := repo_model.StarRepo(ctx, starrer, repo, false); err != nil { return err } } opts.Page++ } } func unwatchRepos(ctx context.Context, watcher, repoOwner *user_model.User) error { opts := &repo_model.WatchedReposOptions{ ListOptions: db.ListOptions{ Page: 1, PageSize: 25, }, WatcherID: watcher.ID, RepoOwnerID: repoOwner.ID, } for { repos, _, err := repo_model.GetWatchedRepos(ctx, opts) if err != nil { return err } if len(repos) == 0 { return nil } for _, repo := range repos { if err := repo_model.WatchRepo(ctx, watcher, repo, false); err != nil { return err } } opts.Page++ } } func cancelRepositoryTransfers(ctx context.Context, sender, recipient *user_model.User) error { transfers, err := models.GetPendingRepositoryTransfers(ctx, &models.PendingRepositoryTransferOptions{ SenderID: sender.ID, RecipientID: recipient.ID, }) if err != nil { return err } for _, transfer := range transfers { repo, err := repo_model.GetRepositoryByID(ctx, transfer.RepoID) if err != nil { return err } if err := repo_service.CancelRepositoryTransfer(ctx, repo); err != nil { return err } } return nil } func unassignIssues(ctx context.Context, assignee, repoOwner *user_model.User) error { opts := &issues_model.AssignedIssuesOptions{ ListOptions: db.ListOptions{ Page: 1, PageSize: 25, }, AssigneeID: assignee.ID, RepoOwnerID: repoOwner.ID, } for { issues, _, err := issues_model.GetAssignedIssues(ctx, opts) if err != nil { return err } if len(issues) == 0 { return nil } for _, issue := range issues { if err := issue.LoadAssignees(ctx); err != nil { return err } if _, _, err := issues_model.ToggleIssueAssignee(ctx, issue, assignee, assignee.ID); err != nil { return err } } opts.Page++ } } func removeCollaborations(ctx context.Context, repoOwner, collaborator *user_model.User) error { opts := &repo_model.FindCollaborationOptions{ ListOptions: db.ListOptions{ Page: 1, PageSize: 25, }, CollaboratorID: collaborator.ID, RepoOwnerID: repoOwner.ID, } for { collaborations, _, err := repo_model.GetCollaborators(ctx, opts) if err != nil { return err } if len(collaborations) == 0 { return nil } for _, collaboration := range collaborations { repo, err := repo_model.GetRepositoryByID(ctx, collaboration.Collaboration.RepoID) if err != nil { return err } if err := repo_service.DeleteCollaboration(ctx, repo, collaborator); err != nil { return err } } opts.Page++ } } func UnblockUser(ctx context.Context, doer, blocker, blockee *user_model.User) error { if blockee.IsOrganization() { return user_model.ErrBlockOrganization } if !CanUnblockUser(ctx, doer, blocker, blockee) { return user_model.ErrCanNotUnblock } return db.WithTx(ctx, func(ctx context.Context) error { block, err := user_model.GetBlocking(ctx, blocker.ID, blockee.ID) if err != nil { return err } if block != nil { _, err = db.DeleteByID[user_model.Blocking](ctx, block.ID) return err } return nil }) }