mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
303 lines
6.6 KiB
Go
303 lines
6.6 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package user
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/hanzoai/git/models/db"
|
|
issues_model "github.com/hanzoai/git/models/issues"
|
|
org_model "github.com/hanzoai/git/models/organization"
|
|
repo_model "github.com/hanzoai/git/models/repo"
|
|
user_model "github.com/hanzoai/git/models/user"
|
|
repo_service "github.com/hanzoai/git/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, doer, blocker, blockee); err != nil {
|
|
return err
|
|
}
|
|
if err := cancelRepositoryTransfers(ctx, doer, 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, doer, sender, recipient *user_model.User) error {
|
|
transfers, err := repo_model.GetPendingRepositoryTransfers(ctx, &repo_model.PendingRepositoryTransferOptions{
|
|
SenderID: sender.ID,
|
|
RecipientID: recipient.ID,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, transfer := range transfers {
|
|
if err := repo_service.CancelRepositoryTransfer(ctx, transfer, doer); 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
|
|
})
|
|
}
|