mirror of https://github.com/go-gitea/gitea
Move some functions to service layer (#26969)
parent
b8ad558c93
commit
e3ed67859a
@ -0,0 +1,315 @@ |
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package issues |
||||
|
||||
import ( |
||||
"context" |
||||
"strings" |
||||
|
||||
"code.gitea.io/gitea/models/db" |
||||
"code.gitea.io/gitea/modules/setting" |
||||
api "code.gitea.io/gitea/modules/structs" |
||||
|
||||
"xorm.io/builder" |
||||
) |
||||
|
||||
// MilestoneList is a list of milestones offering additional functionality
|
||||
type MilestoneList []*Milestone |
||||
|
||||
func (milestones MilestoneList) getMilestoneIDs() []int64 { |
||||
ids := make([]int64, 0, len(milestones)) |
||||
for _, ms := range milestones { |
||||
ids = append(ids, ms.ID) |
||||
} |
||||
return ids |
||||
} |
||||
|
||||
// GetMilestonesOption contain options to get milestones
|
||||
type GetMilestonesOption struct { |
||||
db.ListOptions |
||||
RepoID int64 |
||||
State api.StateType |
||||
Name string |
||||
SortType string |
||||
} |
||||
|
||||
func (opts GetMilestonesOption) toCond() builder.Cond { |
||||
cond := builder.NewCond() |
||||
if opts.RepoID != 0 { |
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) |
||||
} |
||||
|
||||
switch opts.State { |
||||
case api.StateClosed: |
||||
cond = cond.And(builder.Eq{"is_closed": true}) |
||||
case api.StateAll: |
||||
break |
||||
// api.StateOpen:
|
||||
default: |
||||
cond = cond.And(builder.Eq{"is_closed": false}) |
||||
} |
||||
|
||||
if len(opts.Name) != 0 { |
||||
cond = cond.And(db.BuildCaseInsensitiveLike("name", opts.Name)) |
||||
} |
||||
|
||||
return cond |
||||
} |
||||
|
||||
// GetMilestones returns milestones filtered by GetMilestonesOption's
|
||||
func GetMilestones(opts GetMilestonesOption) (MilestoneList, int64, error) { |
||||
sess := db.GetEngine(db.DefaultContext).Where(opts.toCond()) |
||||
|
||||
if opts.Page != 0 { |
||||
sess = db.SetSessionPagination(sess, &opts) |
||||
} |
||||
|
||||
switch opts.SortType { |
||||
case "furthestduedate": |
||||
sess.Desc("deadline_unix") |
||||
case "leastcomplete": |
||||
sess.Asc("completeness") |
||||
case "mostcomplete": |
||||
sess.Desc("completeness") |
||||
case "leastissues": |
||||
sess.Asc("num_issues") |
||||
case "mostissues": |
||||
sess.Desc("num_issues") |
||||
case "id": |
||||
sess.Asc("id") |
||||
default: |
||||
sess.Asc("deadline_unix").Asc("id") |
||||
} |
||||
|
||||
miles := make([]*Milestone, 0, opts.PageSize) |
||||
total, err := sess.FindAndCount(&miles) |
||||
return miles, total, err |
||||
} |
||||
|
||||
// GetMilestoneIDsByNames returns a list of milestone ids by given names.
|
||||
// It doesn't filter them by repo, so it could return milestones belonging to different repos.
|
||||
// It's used for filtering issues via indexer, otherwise it would be useless.
|
||||
// Since it could return milestones with the same name, so the length of returned ids could be more than the length of names.
|
||||
func GetMilestoneIDsByNames(ctx context.Context, names []string) ([]int64, error) { |
||||
var ids []int64 |
||||
return ids, db.GetEngine(ctx).Table("milestone"). |
||||
Where(db.BuildCaseInsensitiveIn("name", names)). |
||||
Cols("id"). |
||||
Find(&ids) |
||||
} |
||||
|
||||
// SearchMilestones search milestones
|
||||
func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType, keyword string) (MilestoneList, error) { |
||||
miles := make([]*Milestone, 0, setting.UI.IssuePagingNum) |
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed) |
||||
if len(keyword) > 0 { |
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) |
||||
} |
||||
if repoCond.IsValid() { |
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) |
||||
} |
||||
if page > 0 { |
||||
sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum) |
||||
} |
||||
|
||||
switch sortType { |
||||
case "furthestduedate": |
||||
sess.Desc("deadline_unix") |
||||
case "leastcomplete": |
||||
sess.Asc("completeness") |
||||
case "mostcomplete": |
||||
sess.Desc("completeness") |
||||
case "leastissues": |
||||
sess.Asc("num_issues") |
||||
case "mostissues": |
||||
sess.Desc("num_issues") |
||||
default: |
||||
sess.Asc("deadline_unix") |
||||
} |
||||
return miles, sess.Find(&miles) |
||||
} |
||||
|
||||
// GetMilestonesByRepoIDs returns a list of milestones of given repositories and status.
|
||||
func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType string) (MilestoneList, error) { |
||||
return SearchMilestones( |
||||
builder.In("repo_id", repoIDs), |
||||
page, |
||||
isClosed, |
||||
sortType, |
||||
"", |
||||
) |
||||
} |
||||
|
||||
func (milestones MilestoneList) loadTotalTrackedTimes(ctx context.Context) error { |
||||
type totalTimesByMilestone struct { |
||||
MilestoneID int64 |
||||
Time int64 |
||||
} |
||||
if len(milestones) == 0 { |
||||
return nil |
||||
} |
||||
trackedTimes := make(map[int64]int64, len(milestones)) |
||||
|
||||
// Get total tracked time by milestone_id
|
||||
rows, err := db.GetEngine(ctx).Table("issue"). |
||||
Join("INNER", "milestone", "issue.milestone_id = milestone.id"). |
||||
Join("LEFT", "tracked_time", "tracked_time.issue_id = issue.id"). |
||||
Where("tracked_time.deleted = ?", false). |
||||
Select("milestone_id, sum(time) as time"). |
||||
In("milestone_id", milestones.getMilestoneIDs()). |
||||
GroupBy("milestone_id"). |
||||
Rows(new(totalTimesByMilestone)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
defer rows.Close() |
||||
|
||||
for rows.Next() { |
||||
var totalTime totalTimesByMilestone |
||||
err = rows.Scan(&totalTime) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
trackedTimes[totalTime.MilestoneID] = totalTime.Time |
||||
} |
||||
|
||||
for _, milestone := range milestones { |
||||
milestone.TotalTrackedTime = trackedTimes[milestone.ID] |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request
|
||||
func (milestones MilestoneList) LoadTotalTrackedTimes() error { |
||||
return milestones.loadTotalTrackedTimes(db.DefaultContext) |
||||
} |
||||
|
||||
// CountMilestones returns number of milestones in given repository with other options
|
||||
func CountMilestones(ctx context.Context, opts GetMilestonesOption) (int64, error) { |
||||
return db.GetEngine(ctx). |
||||
Where(opts.toCond()). |
||||
Count(new(Milestone)) |
||||
} |
||||
|
||||
// CountMilestonesByRepoCond map from repo conditions to number of milestones matching the options`
|
||||
func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64]int64, error) { |
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed) |
||||
if repoCond.IsValid() { |
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) |
||||
} |
||||
|
||||
countsSlice := make([]*struct { |
||||
RepoID int64 |
||||
Count int64 |
||||
}, 0, 10) |
||||
if err := sess.GroupBy("repo_id"). |
||||
Select("repo_id AS repo_id, COUNT(*) AS count"). |
||||
Table("milestone"). |
||||
Find(&countsSlice); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
countMap := make(map[int64]int64, len(countsSlice)) |
||||
for _, c := range countsSlice { |
||||
countMap[c.RepoID] = c.Count |
||||
} |
||||
return countMap, nil |
||||
} |
||||
|
||||
// CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options`
|
||||
func CountMilestonesByRepoCondAndKw(repoCond builder.Cond, keyword string, isClosed bool) (map[int64]int64, error) { |
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed) |
||||
if len(keyword) > 0 { |
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) |
||||
} |
||||
if repoCond.IsValid() { |
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) |
||||
} |
||||
|
||||
countsSlice := make([]*struct { |
||||
RepoID int64 |
||||
Count int64 |
||||
}, 0, 10) |
||||
if err := sess.GroupBy("repo_id"). |
||||
Select("repo_id AS repo_id, COUNT(*) AS count"). |
||||
Table("milestone"). |
||||
Find(&countsSlice); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
countMap := make(map[int64]int64, len(countsSlice)) |
||||
for _, c := range countsSlice { |
||||
countMap[c.RepoID] = c.Count |
||||
} |
||||
return countMap, nil |
||||
} |
||||
|
||||
// MilestonesStats represents milestone statistic information.
|
||||
type MilestonesStats struct { |
||||
OpenCount, ClosedCount int64 |
||||
} |
||||
|
||||
// Total returns the total counts of milestones
|
||||
func (m MilestonesStats) Total() int64 { |
||||
return m.OpenCount + m.ClosedCount |
||||
} |
||||
|
||||
// GetMilestonesStatsByRepoCond returns milestone statistic information for dashboard by given conditions.
|
||||
func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, error) { |
||||
var err error |
||||
stats := &MilestonesStats{} |
||||
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false) |
||||
if repoCond.IsValid() { |
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) |
||||
} |
||||
stats.OpenCount, err = sess.Count(new(Milestone)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true) |
||||
if repoCond.IsValid() { |
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) |
||||
} |
||||
stats.ClosedCount, err = sess.Count(new(Milestone)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return stats, nil |
||||
} |
||||
|
||||
// GetMilestonesStatsByRepoCondAndKw returns milestone statistic information for dashboard by given repo conditions and name keyword.
|
||||
func GetMilestonesStatsByRepoCondAndKw(repoCond builder.Cond, keyword string) (*MilestonesStats, error) { |
||||
var err error |
||||
stats := &MilestonesStats{} |
||||
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false) |
||||
if len(keyword) > 0 { |
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) |
||||
} |
||||
if repoCond.IsValid() { |
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) |
||||
} |
||||
stats.OpenCount, err = sess.Count(new(Milestone)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true) |
||||
if len(keyword) > 0 { |
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) |
||||
} |
||||
if repoCond.IsValid() { |
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) |
||||
} |
||||
stats.ClosedCount, err = sess.Count(new(Milestone)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return stats, nil |
||||
} |
@ -1,196 +0,0 @@ |
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"code.gitea.io/gitea/models/db" |
||||
issues_model "code.gitea.io/gitea/models/issues" |
||||
repo_model "code.gitea.io/gitea/models/repo" |
||||
"code.gitea.io/gitea/modules/container" |
||||
"code.gitea.io/gitea/modules/structs" |
||||
) |
||||
|
||||
// InsertMilestones creates milestones of repository.
|
||||
func InsertMilestones(ms ...*issues_model.Milestone) (err error) { |
||||
if len(ms) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer committer.Close() |
||||
sess := db.GetEngine(ctx) |
||||
|
||||
// to return the id, so we should not use batch insert
|
||||
for _, m := range ms { |
||||
if _, err = sess.NoAutoTime().Insert(m); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + ? WHERE id = ?", len(ms), ms[0].RepoID); err != nil { |
||||
return err |
||||
} |
||||
return committer.Commit() |
||||
} |
||||
|
||||
// InsertIssues insert issues to database
|
||||
func InsertIssues(issues ...*issues_model.Issue) error { |
||||
ctx, committer, err := db.TxContext(db.DefaultContext) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer committer.Close() |
||||
|
||||
for _, issue := range issues { |
||||
if err := insertIssue(ctx, issue); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return committer.Commit() |
||||
} |
||||
|
||||
func insertIssue(ctx context.Context, issue *issues_model.Issue) error { |
||||
sess := db.GetEngine(ctx) |
||||
if _, err := sess.NoAutoTime().Insert(issue); err != nil { |
||||
return err |
||||
} |
||||
issueLabels := make([]issues_model.IssueLabel, 0, len(issue.Labels)) |
||||
for _, label := range issue.Labels { |
||||
issueLabels = append(issueLabels, issues_model.IssueLabel{ |
||||
IssueID: issue.ID, |
||||
LabelID: label.ID, |
||||
}) |
||||
} |
||||
if len(issueLabels) > 0 { |
||||
if _, err := sess.Insert(issueLabels); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
for _, reaction := range issue.Reactions { |
||||
reaction.IssueID = issue.ID |
||||
} |
||||
|
||||
if len(issue.Reactions) > 0 { |
||||
if _, err := sess.Insert(issue.Reactions); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// InsertIssueComments inserts many comments of issues.
|
||||
func InsertIssueComments(comments []*issues_model.Comment) error { |
||||
if len(comments) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
issueIDs := make(container.Set[int64]) |
||||
for _, comment := range comments { |
||||
issueIDs.Add(comment.IssueID) |
||||
} |
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer committer.Close() |
||||
for _, comment := range comments { |
||||
if _, err := db.GetEngine(ctx).NoAutoTime().Insert(comment); err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, reaction := range comment.Reactions { |
||||
reaction.IssueID = comment.IssueID |
||||
reaction.CommentID = comment.ID |
||||
} |
||||
if len(comment.Reactions) > 0 { |
||||
if err := db.Insert(ctx, comment.Reactions); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
for issueID := range issueIDs { |
||||
if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?", |
||||
issueID, issues_model.CommentTypeComment, issueID); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return committer.Commit() |
||||
} |
||||
|
||||
// InsertPullRequests inserted pull requests
|
||||
func InsertPullRequests(ctx context.Context, prs ...*issues_model.PullRequest) error { |
||||
ctx, committer, err := db.TxContext(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer committer.Close() |
||||
sess := db.GetEngine(ctx) |
||||
for _, pr := range prs { |
||||
if err := insertIssue(ctx, pr.Issue); err != nil { |
||||
return err |
||||
} |
||||
pr.IssueID = pr.Issue.ID |
||||
if _, err := sess.NoAutoTime().Insert(pr); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return committer.Commit() |
||||
} |
||||
|
||||
// InsertReleases migrates release
|
||||
func InsertReleases(rels ...*repo_model.Release) error { |
||||
ctx, committer, err := db.TxContext(db.DefaultContext) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer committer.Close() |
||||
sess := db.GetEngine(ctx) |
||||
|
||||
for _, rel := range rels { |
||||
if _, err := sess.NoAutoTime().Insert(rel); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if len(rel.Attachments) > 0 { |
||||
for i := range rel.Attachments { |
||||
rel.Attachments[i].ReleaseID = rel.ID |
||||
} |
||||
|
||||
if _, err := sess.NoAutoTime().Insert(rel.Attachments); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
return committer.Commit() |
||||
} |
||||
|
||||
// UpdateMigrationsByType updates all migrated repositories' posterid from gitServiceType to replace originalAuthorID to posterID
|
||||
func UpdateMigrationsByType(tp structs.GitServiceType, externalUserID string, userID int64) error { |
||||
if err := issues_model.UpdateIssuesMigrationsByType(tp, externalUserID, userID); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err := issues_model.UpdateCommentsMigrationsByType(tp, externalUserID, userID); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err := repo_model.UpdateReleasesMigrationsByType(tp, externalUserID, userID); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err := issues_model.UpdateReactionsMigrationsByType(tp, externalUserID, userID); err != nil { |
||||
return err |
||||
} |
||||
return issues_model.UpdateReviewsMigrationsByType(tp, externalUserID, userID) |
||||
} |
@ -1,145 +0,0 @@ |
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"code.gitea.io/gitea/models/db" |
||||
issues_model "code.gitea.io/gitea/models/issues" |
||||
repo_model "code.gitea.io/gitea/models/repo" |
||||
"code.gitea.io/gitea/models/unittest" |
||||
user_model "code.gitea.io/gitea/models/user" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestMigrate_InsertMilestones(t *testing.T) { |
||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
||||
reponame := "repo1" |
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}) |
||||
name := "milestonetest1" |
||||
ms := &issues_model.Milestone{ |
||||
RepoID: repo.ID, |
||||
Name: name, |
||||
} |
||||
err := InsertMilestones(ms) |
||||
assert.NoError(t, err) |
||||
unittest.AssertExistsAndLoadBean(t, ms) |
||||
repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) |
||||
assert.EqualValues(t, repo.NumMilestones+1, repoModified.NumMilestones) |
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Milestone{}) |
||||
} |
||||
|
||||
func assertCreateIssues(t *testing.T, isPull bool) { |
||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
||||
reponame := "repo1" |
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}) |
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) |
||||
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) |
||||
milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) |
||||
assert.EqualValues(t, milestone.ID, 1) |
||||
reaction := &issues_model.Reaction{ |
||||
Type: "heart", |
||||
UserID: owner.ID, |
||||
} |
||||
|
||||
title := "issuetitle1" |
||||
is := &issues_model.Issue{ |
||||
RepoID: repo.ID, |
||||
MilestoneID: milestone.ID, |
||||
Repo: repo, |
||||
Title: title, |
||||
Content: "issuecontent1", |
||||
IsPull: isPull, |
||||
PosterID: owner.ID, |
||||
Poster: owner, |
||||
IsClosed: true, |
||||
Labels: []*issues_model.Label{label}, |
||||
Reactions: []*issues_model.Reaction{reaction}, |
||||
} |
||||
err := InsertIssues(is) |
||||
assert.NoError(t, err) |
||||
|
||||
i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: title}) |
||||
unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID}) |
||||
} |
||||
|
||||
func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) { |
||||
assertCreateIssues(t, false) |
||||
} |
||||
|
||||
func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) { |
||||
assertCreateIssues(t, true) |
||||
} |
||||
|
||||
func TestMigrate_InsertIssueComments(t *testing.T) { |
||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) |
||||
_ = issue.LoadRepo(db.DefaultContext) |
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) |
||||
reaction := &issues_model.Reaction{ |
||||
Type: "heart", |
||||
UserID: owner.ID, |
||||
} |
||||
|
||||
comment := &issues_model.Comment{ |
||||
PosterID: owner.ID, |
||||
Poster: owner, |
||||
IssueID: issue.ID, |
||||
Issue: issue, |
||||
Reactions: []*issues_model.Reaction{reaction}, |
||||
} |
||||
|
||||
err := InsertIssueComments([]*issues_model.Comment{comment}) |
||||
assert.NoError(t, err) |
||||
|
||||
issueModified := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) |
||||
assert.EqualValues(t, issue.NumComments+1, issueModified.NumComments) |
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Issue{}) |
||||
} |
||||
|
||||
func TestMigrate_InsertPullRequests(t *testing.T) { |
||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
||||
reponame := "repo1" |
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}) |
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) |
||||
|
||||
i := &issues_model.Issue{ |
||||
RepoID: repo.ID, |
||||
Repo: repo, |
||||
Title: "title1", |
||||
Content: "issuecontent1", |
||||
IsPull: true, |
||||
PosterID: owner.ID, |
||||
Poster: owner, |
||||
} |
||||
|
||||
p := &issues_model.PullRequest{ |
||||
Issue: i, |
||||
} |
||||
|
||||
err := InsertPullRequests(db.DefaultContext, p) |
||||
assert.NoError(t, err) |
||||
|
||||
_ = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{IssueID: i.ID}) |
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Issue{}, &issues_model.PullRequest{}) |
||||
} |
||||
|
||||
func TestMigrate_InsertReleases(t *testing.T) { |
||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
||||
|
||||
a := &repo_model.Attachment{ |
||||
UUID: "a0eebc91-9c0c-4ef7-bb6e-6bb9bd380a12", |
||||
} |
||||
r := &repo_model.Release{ |
||||
Attachments: []*repo_model.Attachment{a}, |
||||
} |
||||
|
||||
err := InsertReleases(r) |
||||
assert.NoError(t, err) |
||||
} |
@ -0,0 +1,26 @@ |
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"code.gitea.io/gitea/models/unittest" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestMigrate_InsertReleases(t *testing.T) { |
||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
||||
|
||||
a := &Attachment{ |
||||
UUID: "a0eebc91-9c0c-4ef7-bb6e-6bb9bd380a12", |
||||
} |
||||
r := &Release{ |
||||
Attachments: []*Attachment{a}, |
||||
} |
||||
|
||||
err := InsertReleases(r) |
||||
assert.NoError(t, err) |
||||
} |
@ -1,83 +0,0 @@ |
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
|
||||
"code.gitea.io/gitea/models/db" |
||||
issues_model "code.gitea.io/gitea/models/issues" |
||||
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" |
||||
|
||||
"xorm.io/builder" |
||||
) |
||||
|
||||
// DeleteCollaboration removes collaboration relation between the user and repository.
|
||||
func DeleteCollaboration(repo *repo_model.Repository, uid int64) (err error) { |
||||
collaboration := &repo_model.Collaboration{ |
||||
RepoID: repo.ID, |
||||
UserID: uid, |
||||
} |
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer committer.Close() |
||||
|
||||
if has, err := db.GetEngine(ctx).Delete(collaboration); err != nil || has == 0 { |
||||
return err |
||||
} else if err = access_model.RecalculateAccesses(ctx, repo); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err = repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err = reconsiderWatches(ctx, repo, uid); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Unassign a user from any issue (s)he has been assigned to in the repository
|
||||
if err := reconsiderRepoIssuesAssignee(ctx, repo, uid); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return committer.Commit() |
||||
} |
||||
|
||||
func reconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, uid int64) error { |
||||
user, err := user_model.GetUserByID(ctx, uid) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo, true); err != nil || canAssigned { |
||||
return err |
||||
} |
||||
|
||||
if _, err := db.GetEngine(ctx).Where(builder.Eq{"assignee_id": uid}). |
||||
In("issue_id", builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repo.ID})). |
||||
Delete(&issues_model.IssueAssignees{}); err != nil { |
||||
return fmt.Errorf("Could not delete assignee[%d] %w", uid, err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func reconsiderWatches(ctx context.Context, repo *repo_model.Repository, uid int64) error { |
||||
if has, err := access_model.HasAccess(ctx, uid, repo); err != nil || has { |
||||
return err |
||||
} |
||||
if err := repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Remove all IssueWatches a user has subscribed to in the repository
|
||||
return issues_model.RemoveIssueWatchersByRepoID(ctx, uid, repo.ID) |
||||
} |
@ -0,0 +1,47 @@ |
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repository |
||||
|
||||
import ( |
||||
"code.gitea.io/gitea/models" |
||||
"code.gitea.io/gitea/models/db" |
||||
access_model "code.gitea.io/gitea/models/perm/access" |
||||
repo_model "code.gitea.io/gitea/models/repo" |
||||
) |
||||
|
||||
// DeleteCollaboration removes collaboration relation between the user and repository.
|
||||
func DeleteCollaboration(repo *repo_model.Repository, uid int64) (err error) { |
||||
collaboration := &repo_model.Collaboration{ |
||||
RepoID: repo.ID, |
||||
UserID: uid, |
||||
} |
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer committer.Close() |
||||
|
||||
if has, err := db.GetEngine(ctx).Delete(collaboration); err != nil || has == 0 { |
||||
return err |
||||
} else if err = access_model.RecalculateAccesses(ctx, repo); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err = repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err = models.ReconsiderWatches(ctx, repo, uid); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Unassign a user from any issue (s)he has been assigned to in the repository
|
||||
if err := models.ReconsiderRepoIssuesAssignee(ctx, repo, uid); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return committer.Commit() |
||||
} |
@ -1,7 +1,7 @@ |
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models |
||||
package repository |
||||
|
||||
import ( |
||||
"testing" |
Loading…
Reference in new issue