71d52e432e
Deploy MokoGitea / deploy (push) Successful in 5m8s
Add IsRequired field to IssueStatusDef. Open and Closed statuses are seeded as required and cannot be deleted. Delete attempts return an error flash in the web UI and ErrStatusRequired in the model layer. API response now includes is_required field.
341 lines
10 KiB
Go
341 lines
10 KiB
Go
// Copyright 2016 The Gogs Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package structs
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// StateType issue state type
|
|
//
|
|
// swagger:enum StateType
|
|
type StateType string
|
|
|
|
const (
|
|
// StateOpen pr is opened
|
|
StateOpen StateType = "open"
|
|
// StateClosed pr is closed
|
|
StateClosed StateType = "closed"
|
|
)
|
|
|
|
// StateAll is a query parameter filter value, not a valid object state.
|
|
const StateAll = "all"
|
|
|
|
// PullRequestMeta PR info if an issue is a PR
|
|
type PullRequestMeta struct {
|
|
HasMerged bool `json:"merged"`
|
|
Merged *time.Time `json:"merged_at"`
|
|
IsWorkInProgress bool `json:"draft"`
|
|
HTMLURL string `json:"html_url"`
|
|
}
|
|
|
|
// RepositoryMeta basic repository information
|
|
type RepositoryMeta struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Owner string `json:"owner"`
|
|
FullName string `json:"full_name"`
|
|
}
|
|
|
|
// Issue represents an issue in a repository
|
|
// swagger:model
|
|
type Issue struct {
|
|
ID int64 `json:"id"`
|
|
URL string `json:"url"`
|
|
HTMLURL string `json:"html_url"`
|
|
Index int64 `json:"number"`
|
|
Poster *User `json:"user"`
|
|
OriginalAuthor string `json:"original_author"`
|
|
OriginalAuthorID int64 `json:"original_author_id"`
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
Ref string `json:"ref"`
|
|
Attachments []*Attachment `json:"assets"`
|
|
Labels []*Label `json:"labels"`
|
|
Milestone *Milestone `json:"milestone"`
|
|
Projects []*Project `json:"projects"`
|
|
// deprecated
|
|
Assignee *User `json:"assignee"`
|
|
Assignees []*User `json:"assignees"`
|
|
State StateType `json:"state"`
|
|
IsLocked bool `json:"is_locked"`
|
|
Comments int `json:"comments"`
|
|
// swagger:strfmt date-time
|
|
Created time.Time `json:"created_at"`
|
|
// swagger:strfmt date-time
|
|
Updated time.Time `json:"updated_at"`
|
|
// swagger:strfmt date-time
|
|
Closed *time.Time `json:"closed_at"`
|
|
// swagger:strfmt date-time
|
|
Deadline *time.Time `json:"due_date"`
|
|
|
|
TimeEstimate int64 `json:"time_estimate"`
|
|
|
|
PullRequest *PullRequestMeta `json:"pull_request"`
|
|
Repo *RepositoryMeta `json:"repository"`
|
|
|
|
PinOrder int `json:"pin_order"`
|
|
// The version of the issue content for optimistic locking
|
|
ContentVersion int `json:"content_version"`
|
|
|
|
// Issue metadata (org-level definitions)
|
|
StatusID int64 `json:"status_id"`
|
|
StatusName string `json:"status_name"`
|
|
PriorityID int64 `json:"priority_id"`
|
|
PriorityName string `json:"priority_name"`
|
|
TypeID int64 `json:"type_id"`
|
|
TypeName string `json:"type_name"`
|
|
}
|
|
|
|
// CreateIssueOption options to create one issue
|
|
type CreateIssueOption struct {
|
|
// required:true
|
|
Title string `json:"title" binding:"Required"`
|
|
Body string `json:"body"`
|
|
Ref string `json:"ref"`
|
|
// deprecated
|
|
Assignee string `json:"assignee"`
|
|
Assignees []string `json:"assignees"`
|
|
// swagger:strfmt date-time
|
|
Deadline *time.Time `json:"due_date"`
|
|
// milestone id
|
|
Milestone int64 `json:"milestone"`
|
|
// list of label ids
|
|
Labels []int64 `json:"labels"`
|
|
// list of project ids
|
|
Projects []int64 `json:"projects"`
|
|
Closed bool `json:"closed"`
|
|
// custom field values keyed by field name
|
|
CustomFields map[string]string `json:"custom_fields,omitempty"`
|
|
// org-level issue metadata IDs (auto-assigned from org defaults when 0)
|
|
StatusID int64 `json:"status_id"`
|
|
PriorityID int64 `json:"priority_id"`
|
|
TypeID int64 `json:"type_id"`
|
|
}
|
|
|
|
// EditIssueOption options for editing an issue
|
|
type EditIssueOption struct {
|
|
Title string `json:"title"`
|
|
Body *string `json:"body"`
|
|
Ref *string `json:"ref"`
|
|
// deprecated
|
|
Assignee *string `json:"assignee"`
|
|
Assignees []string `json:"assignees"`
|
|
Milestone *int64 `json:"milestone"`
|
|
// list of project ids to set (replaces existing projects)
|
|
Projects *[]int64 `json:"projects"`
|
|
State *string `json:"state"`
|
|
// swagger:strfmt date-time
|
|
Deadline *time.Time `json:"due_date"`
|
|
RemoveDeadline *bool `json:"unset_due_date"`
|
|
// The current version of the issue content to detect conflicts during editing
|
|
ContentVersion *int `json:"content_version"`
|
|
// org-level issue metadata IDs
|
|
StatusID *int64 `json:"status_id,omitempty"`
|
|
PriorityID *int64 `json:"priority_id,omitempty"`
|
|
TypeID *int64 `json:"type_id,omitempty"`
|
|
}
|
|
|
|
// EditDeadlineOption options for creating a deadline
|
|
type EditDeadlineOption struct {
|
|
// required:true
|
|
// swagger:strfmt date-time
|
|
Deadline *time.Time `json:"due_date"`
|
|
}
|
|
|
|
// IssueDeadline represents an issue deadline
|
|
// swagger:model
|
|
type IssueDeadline struct {
|
|
// swagger:strfmt date-time
|
|
Deadline *time.Time `json:"due_date"`
|
|
}
|
|
|
|
// IssueStatusDef represents an org-level issue status definition
|
|
// swagger:model
|
|
type IssueStatusDef struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Color string `json:"color"`
|
|
Description string `json:"description"`
|
|
ClosesIssue bool `json:"closes_issue"`
|
|
IsRequired bool `json:"is_required"`
|
|
SortOrder int `json:"sort_order"`
|
|
}
|
|
|
|
// IssuePriorityDef represents an org-level issue priority definition
|
|
// swagger:model
|
|
type IssuePriorityDef struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Color string `json:"color"`
|
|
Description string `json:"description"`
|
|
SortOrder int `json:"sort_order"`
|
|
IsDefault bool `json:"is_default"`
|
|
}
|
|
|
|
// IssueTypeDef represents an org-level issue type definition
|
|
// swagger:model
|
|
type IssueTypeDef struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Color string `json:"color"`
|
|
Description string `json:"description"`
|
|
SortOrder int `json:"sort_order"`
|
|
IsDefault bool `json:"is_default"`
|
|
}
|
|
|
|
// IssueFormFieldType defines issue form field type, can be "markdown", "textarea", "input", "dropdown" or "checkboxes"
|
|
//
|
|
// swagger:enum IssueFormFieldType
|
|
type IssueFormFieldType string
|
|
|
|
const (
|
|
IssueFormFieldTypeMarkdown IssueFormFieldType = "markdown"
|
|
IssueFormFieldTypeTextarea IssueFormFieldType = "textarea"
|
|
IssueFormFieldTypeInput IssueFormFieldType = "input"
|
|
IssueFormFieldTypeDropdown IssueFormFieldType = "dropdown"
|
|
IssueFormFieldTypeCheckboxes IssueFormFieldType = "checkboxes"
|
|
)
|
|
|
|
// IssueFormField represents a form field
|
|
// swagger:model
|
|
type IssueFormField struct {
|
|
Type IssueFormFieldType `json:"type" yaml:"type"`
|
|
ID string `json:"id" yaml:"id"`
|
|
Attributes map[string]any `json:"attributes" yaml:"attributes"`
|
|
Validations map[string]any `json:"validations" yaml:"validations"`
|
|
Visible []IssueFormFieldVisible `json:"visible,omitempty"`
|
|
}
|
|
|
|
func (iff IssueFormField) VisibleOnForm() bool {
|
|
if len(iff.Visible) == 0 {
|
|
return true
|
|
}
|
|
return slices.Contains(iff.Visible, IssueFormFieldVisibleForm)
|
|
}
|
|
|
|
func (iff IssueFormField) VisibleInContent() bool {
|
|
if len(iff.Visible) == 0 {
|
|
// we have our markdown exception
|
|
return iff.Type != IssueFormFieldTypeMarkdown
|
|
}
|
|
return slices.Contains(iff.Visible, IssueFormFieldVisibleContent)
|
|
}
|
|
|
|
// IssueFormFieldVisible defines issue form field visible
|
|
//
|
|
// swagger:enum IssueFormFieldVisible
|
|
type IssueFormFieldVisible string
|
|
|
|
const (
|
|
IssueFormFieldVisibleForm IssueFormFieldVisible = "form"
|
|
IssueFormFieldVisibleContent IssueFormFieldVisible = "content"
|
|
)
|
|
|
|
// IssueTemplate represents an issue template for a repository
|
|
// swagger:model
|
|
type IssueTemplate struct {
|
|
Name string `json:"name" yaml:"name"`
|
|
Title string `json:"title" yaml:"title"`
|
|
About string `json:"about" yaml:"about"` // Using "description" in a template file is compatible
|
|
Labels IssueTemplateStringSlice `json:"labels" yaml:"labels"`
|
|
Assignees IssueTemplateStringSlice `json:"assignees" yaml:"assignees"`
|
|
Ref string `json:"ref" yaml:"ref"`
|
|
Content string `json:"content" yaml:"-"`
|
|
Fields []*IssueFormField `json:"body" yaml:"body"`
|
|
FileName string `json:"file_name" yaml:"-"`
|
|
CustomFields map[string]string `json:"custom_fields,omitempty" yaml:"custom_fields"`
|
|
}
|
|
|
|
type IssueTemplateStringSlice []string
|
|
|
|
func (l *IssueTemplateStringSlice) UnmarshalYAML(value *yaml.Node) error {
|
|
var labels []string
|
|
if value.IsZero() {
|
|
*l = labels
|
|
return nil
|
|
}
|
|
switch value.Kind {
|
|
case yaml.ScalarNode:
|
|
str := ""
|
|
err := value.Decode(&str)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for v := range strings.SplitSeq(str, ",") {
|
|
if v = strings.TrimSpace(v); v == "" {
|
|
continue
|
|
}
|
|
labels = append(labels, v)
|
|
}
|
|
*l = labels
|
|
return nil
|
|
case yaml.SequenceNode:
|
|
if err := value.Decode(&labels); err != nil {
|
|
return err
|
|
}
|
|
*l = labels
|
|
return nil
|
|
}
|
|
return fmt.Errorf("line %d: cannot unmarshal %s into IssueTemplateStringSlice", value.Line, value.ShortTag())
|
|
}
|
|
|
|
type IssueConfigContactLink struct {
|
|
Name string `json:"name" yaml:"name"`
|
|
URL string `json:"url" yaml:"url"`
|
|
About string `json:"about" yaml:"about"`
|
|
}
|
|
|
|
type IssueConfig struct {
|
|
BlankIssuesEnabled bool `json:"blank_issues_enabled" yaml:"blank_issues_enabled"`
|
|
ContactLinks []IssueConfigContactLink `json:"contact_links" yaml:"contact_links"`
|
|
}
|
|
|
|
type IssueConfigValidation struct {
|
|
Valid bool `json:"valid"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
// IssueTemplateType defines issue template type
|
|
type IssueTemplateType string
|
|
|
|
const (
|
|
IssueTemplateTypeMarkdown IssueTemplateType = "md"
|
|
IssueTemplateTypeYaml IssueTemplateType = "yaml"
|
|
)
|
|
|
|
// Type returns the type of IssueTemplate, can be "md", "yaml" or empty for known
|
|
func (it IssueTemplate) Type() IssueTemplateType {
|
|
if base := path.Base(it.FileName); base == "config.yaml" || base == "config.yml" {
|
|
// ignore config.yaml which is a special configuration file
|
|
return ""
|
|
}
|
|
if ext := path.Ext(it.FileName); ext == ".md" {
|
|
return IssueTemplateTypeMarkdown
|
|
} else if ext == ".yaml" || ext == ".yml" {
|
|
return IssueTemplateTypeYaml
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// IssueMeta basic issue information
|
|
// swagger:model
|
|
type IssueMeta struct {
|
|
Index int64 `json:"index"`
|
|
// owner of the issue's repo
|
|
Owner string `json:"owner"`
|
|
Name string `json:"repo"`
|
|
}
|
|
|
|
// LockIssueOption options to lock an issue
|
|
type LockIssueOption struct {
|
|
Reason string `json:"lock_reason"`
|
|
}
|