初始提交: Gitea 项目代码
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/indexer"
|
||||
"gitea.dev/modules/optional"
|
||||
"gitea.dev/modules/timeutil"
|
||||
)
|
||||
|
||||
// IndexerData data stored in the issue indexer
|
||||
type IndexerData struct {
|
||||
ID int64 `json:"id"`
|
||||
RepoID int64 `json:"repo_id"`
|
||||
IsPublic bool `json:"is_public"` // If the repo is public
|
||||
|
||||
// Fields used for keyword searching
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Comments []string `json:"comments"`
|
||||
|
||||
// Fields used for filtering
|
||||
IsPull bool `json:"is_pull"`
|
||||
IsClosed bool `json:"is_closed"`
|
||||
IsArchived bool `json:"is_archived"`
|
||||
LabelIDs []int64 `json:"label_ids"`
|
||||
NoLabel bool `json:"no_label"` // True if LabelIDs is empty
|
||||
MilestoneID int64 `json:"milestone_id"`
|
||||
ProjectIDs []int64 `json:"project_ids"`
|
||||
NoProject bool `json:"no_project"` // True if ProjectIDs is empty
|
||||
ProjectColumnMap map[int64]int64 `json:"project_column_map,omitempty"` // Maps project ID to column ID for each project the issue is in
|
||||
PosterID int64 `json:"poster_id"`
|
||||
AssigneeID int64 `json:"assignee_id"`
|
||||
MentionIDs []int64 `json:"mention_ids"`
|
||||
ReviewedIDs []int64 `json:"reviewed_ids"`
|
||||
ReviewRequestedIDs []int64 `json:"review_requested_ids"`
|
||||
SubscriberIDs []int64 `json:"subscriber_ids"`
|
||||
UpdatedUnix timeutil.TimeStamp `json:"updated_unix"`
|
||||
|
||||
// Fields used for sorting
|
||||
// UpdatedUnix is both used for filtering and sorting.
|
||||
// ID is used for sorting too, to make the sorting stable.
|
||||
CreatedUnix timeutil.TimeStamp `json:"created_unix"`
|
||||
DeadlineUnix timeutil.TimeStamp `json:"deadline_unix"`
|
||||
CommentCount int64 `json:"comment_count"`
|
||||
}
|
||||
|
||||
// Match represents on search result
|
||||
type Match struct {
|
||||
ID int64 `json:"id"`
|
||||
Score float64 `json:"score"`
|
||||
}
|
||||
|
||||
// SearchResult represents search results
|
||||
type SearchResult struct {
|
||||
Total int64
|
||||
Hits []Match
|
||||
}
|
||||
|
||||
// SearchOptions represents search options.
|
||||
//
|
||||
// It has a slightly different design from database query options.
|
||||
// In database query options, a field is never a pointer, so it could be confusing when it's zero value:
|
||||
// Do you want to find data with a field value of 0, or do you not specify the field in the options?
|
||||
// To avoid this confusion, db introduced db.NoConditionID(-1).
|
||||
// So zero value means the field is not specified in the search options, and db.NoConditionID means "== 0" or "id NOT IN (SELECT id FROM ...)"
|
||||
// It's still not ideal, it trapped developers many times.
|
||||
// And sometimes -1 could be a valid value, like issue ID, negative numbers indicate exclusion.
|
||||
// Since db.NoConditionID is for "db" (the package name is db), it makes sense not to use it in the indexer:
|
||||
// Why do bleve/elasticsearch/meilisearch indexers need to know about db.NoConditionID?
|
||||
// So in SearchOptions, we use pointer for fields which could be not specified,
|
||||
// and always use the value to filter if it's not nil, even if it's zero or negative.
|
||||
// It can handle almost all cases, if there is an exception, we can add a new field, like NoLabelOnly.
|
||||
// Unfortunately, we still use db for the indexer and have to convert between db.NoConditionID and nil for legacy reasons.
|
||||
type SearchOptions struct {
|
||||
Keyword string // keyword to search
|
||||
|
||||
SearchMode indexer.SearchModeType
|
||||
|
||||
RepoIDs []int64 // repository IDs which the issues belong to
|
||||
AllPublic bool // if include all public repositories
|
||||
|
||||
IsPull optional.Option[bool] // if the issues is a pull request
|
||||
IsClosed optional.Option[bool] // if the issues is closed
|
||||
IsArchived optional.Option[bool] // if the repo is archived
|
||||
|
||||
IncludedLabelIDs []int64 // labels the issues have
|
||||
ExcludedLabelIDs []int64 // labels the issues don't have
|
||||
IncludedAnyLabelIDs []int64 // labels the issues have at least one. It will be ignored if IncludedLabelIDs is not empty. It's an uncommon filter, but it has been supported accidentally by issues.IssuesOptions.IncludedLabelNames.
|
||||
NoLabelOnly bool // if the issues have no label, if true, IncludedLabelIDs and ExcludedLabelIDs, IncludedAnyLabelIDs will be ignored
|
||||
|
||||
MilestoneIDs []int64 // milestones the issues have
|
||||
|
||||
ProjectIDs []int64 // project the issues belong to. FIXME: ISSUE-MULTIPLE-PROJECTS-FILTER: no multiple project filter support yet. Search logic is wrong.
|
||||
NoProjectOnly bool // if the issues have no project, if true, ProjectIDs will be ignored
|
||||
|
||||
PosterID string // poster of the issues, "(none)" or "(any)" or a user ID
|
||||
AssigneeID string // assignee of the issues, "(none)" or "(any)" or a user ID
|
||||
|
||||
MentionID optional.Option[int64] // mentioned user of the issues
|
||||
|
||||
ReviewedID optional.Option[int64] // reviewer of the issues
|
||||
ReviewRequestedID optional.Option[int64] // requested reviewer of the issues
|
||||
|
||||
SubscriberID optional.Option[int64] // subscriber of the issues
|
||||
|
||||
UpdatedAfterUnix optional.Option[int64]
|
||||
UpdatedBeforeUnix optional.Option[int64]
|
||||
|
||||
Paginator *db.ListOptions
|
||||
|
||||
SortBy SortBy // sort by field
|
||||
}
|
||||
|
||||
// Copy returns a copy of the options.
|
||||
// Be careful, it's not a deep copy, so `SearchOptions.RepoIDs = {...}` is OK while `SearchOptions.RepoIDs[0] = ...` is not.
|
||||
func (o *SearchOptions) Copy(edit ...func(options *SearchOptions)) *SearchOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
v := *o
|
||||
for _, e := range edit {
|
||||
e(&v)
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
||||
// used for optimized issue index based search
|
||||
func (o *SearchOptions) IsKeywordNumeric() bool {
|
||||
_, err := strconv.Atoi(o.Keyword)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type SortBy string
|
||||
|
||||
const (
|
||||
SortByCreatedDesc SortBy = "-created_unix"
|
||||
SortByUpdatedDesc SortBy = "-updated_unix"
|
||||
SortByCommentsDesc SortBy = "-comment_count"
|
||||
SortByDeadlineDesc SortBy = "-deadline_unix"
|
||||
SortByCreatedAsc SortBy = "created_unix"
|
||||
SortByUpdatedAsc SortBy = "updated_unix"
|
||||
SortByCommentsAsc SortBy = "comment_count"
|
||||
SortByDeadlineAsc SortBy = "deadline_unix"
|
||||
// Unsupported sort types which are supported by issues.IssuesOptions.SortType:
|
||||
//
|
||||
// - "priorityrepo":
|
||||
// It's impossible to support it in the indexer.
|
||||
// It is based on the specified repository in the request, so we cannot add static field to the indexer.
|
||||
// If we do something like that query the issues in the specified repository first then append other issues,
|
||||
// it will break the pagination.
|
||||
//
|
||||
// - "project-column-sorting":
|
||||
// Although it's possible to support it by adding project.ProjectIssue.Sorting to the indexer,
|
||||
// but what if the issue belongs to multiple projects?
|
||||
// Since it's unsupported to search issues with keyword in project page, we don't need to support it.
|
||||
)
|
||||
Reference in New Issue
Block a user