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.
|
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package models
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
Loading…
Reference in New Issue