Enable/disable owner and repo projects independently (#28805)
Part of #23318 Add menu in repo settings to allow for repo admin to decide not just if projects are enabled or disabled per repo, but also which kind of projects (repo-level/owner-level) are enabled. If repo projects disabled, don't show the projects tab. ![grafik](https://github.com/go-gitea/gitea/assets/47871822/b9b43fb4-824b-47f9-b8e2-12004313647c) --------- Co-authored-by: delvh <dev.lh@web.de>
This commit is contained in:
parent
8553b4600e
commit
fe6792dff3
@ -520,6 +520,7 @@
|
||||
id: 75
|
||||
repo_id: 1
|
||||
type: 8
|
||||
config: "{\"ProjectsMode\":\"all\"}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
@ -650,12 +651,6 @@
|
||||
type: 2
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 98
|
||||
repo_id: 1
|
||||
type: 8
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 99
|
||||
repo_id: 1
|
||||
|
@ -411,6 +411,11 @@ func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit
|
||||
Type: tp,
|
||||
Config: new(ActionsConfig),
|
||||
}
|
||||
} else if tp == unit.TypeProjects {
|
||||
return &RepoUnit{
|
||||
Type: tp,
|
||||
Config: new(ProjectsConfig),
|
||||
}
|
||||
}
|
||||
|
||||
return &RepoUnit{
|
||||
|
@ -202,6 +202,53 @@ func (cfg *ActionsConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
// ProjectsMode represents the projects enabled for a repository
|
||||
type ProjectsMode string
|
||||
|
||||
const (
|
||||
// ProjectsModeRepo allows only repo-level projects
|
||||
ProjectsModeRepo ProjectsMode = "repo"
|
||||
// ProjectsModeOwner allows only owner-level projects
|
||||
ProjectsModeOwner ProjectsMode = "owner"
|
||||
// ProjectsModeAll allows both kinds of projects
|
||||
ProjectsModeAll ProjectsMode = "all"
|
||||
// ProjectsModeNone doesn't allow projects
|
||||
ProjectsModeNone ProjectsMode = "none"
|
||||
)
|
||||
|
||||
// ProjectsConfig describes projects config
|
||||
type ProjectsConfig struct {
|
||||
ProjectsMode ProjectsMode
|
||||
}
|
||||
|
||||
// FromDB fills up a ProjectsConfig from serialized format.
|
||||
func (cfg *ProjectsConfig) FromDB(bs []byte) error {
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
}
|
||||
|
||||
// ToDB exports a ProjectsConfig to a serialized format.
|
||||
func (cfg *ProjectsConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
func (cfg *ProjectsConfig) GetProjectsMode() ProjectsMode {
|
||||
if cfg.ProjectsMode != "" {
|
||||
return cfg.ProjectsMode
|
||||
}
|
||||
|
||||
return ProjectsModeNone
|
||||
}
|
||||
|
||||
func (cfg *ProjectsConfig) IsProjectsAllowed(m ProjectsMode) bool {
|
||||
projectsMode := cfg.GetProjectsMode()
|
||||
|
||||
if m == ProjectsModeNone {
|
||||
return true
|
||||
}
|
||||
|
||||
return projectsMode == m || projectsMode == ProjectsModeAll
|
||||
}
|
||||
|
||||
// BeforeSet is invoked from XORM before setting the value of a field of this object.
|
||||
func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
|
||||
switch colName {
|
||||
@ -217,7 +264,9 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
|
||||
r.Config = new(IssuesConfig)
|
||||
case unit.TypeActions:
|
||||
r.Config = new(ActionsConfig)
|
||||
case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects, unit.TypePackages:
|
||||
case unit.TypeProjects:
|
||||
r.Config = new(ProjectsConfig)
|
||||
case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypePackages:
|
||||
fallthrough
|
||||
default:
|
||||
r.Config = new(UnitConfig)
|
||||
@ -265,6 +314,11 @@ func (r *RepoUnit) ActionsConfig() *ActionsConfig {
|
||||
return r.Config.(*ActionsConfig)
|
||||
}
|
||||
|
||||
// ProjectsConfig returns config for unit.ProjectsConfig
|
||||
func (r *RepoUnit) ProjectsConfig() *ProjectsConfig {
|
||||
return r.Config.(*ProjectsConfig)
|
||||
}
|
||||
|
||||
func getUnitsByRepoID(ctx context.Context, repoID int64) (units []*RepoUnit, err error) {
|
||||
var tmpUnits []*RepoUnit
|
||||
if err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Find(&tmpUnits); err != nil {
|
||||
|
@ -93,6 +93,12 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re
|
||||
AllowRebaseUpdate: true,
|
||||
},
|
||||
})
|
||||
} else if tp == unit.TypeProjects {
|
||||
units = append(units, repo_model.RepoUnit{
|
||||
RepoID: repo.ID,
|
||||
Type: tp,
|
||||
Config: &repo_model.ProjectsConfig{ProjectsMode: repo_model.ProjectsModeAll},
|
||||
})
|
||||
} else {
|
||||
units = append(units, repo_model.RepoUnit{
|
||||
RepoID: repo.ID,
|
||||
|
@ -90,6 +90,7 @@ type Repository struct {
|
||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||
HasPullRequests bool `json:"has_pull_requests"`
|
||||
HasProjects bool `json:"has_projects"`
|
||||
ProjectsMode string `json:"projects_mode"`
|
||||
HasReleases bool `json:"has_releases"`
|
||||
HasPackages bool `json:"has_packages"`
|
||||
HasActions bool `json:"has_actions"`
|
||||
@ -180,6 +181,8 @@ type EditRepoOption struct {
|
||||
HasPullRequests *bool `json:"has_pull_requests,omitempty"`
|
||||
// either `true` to enable project unit, or `false` to disable them.
|
||||
HasProjects *bool `json:"has_projects,omitempty"`
|
||||
// `repo` to only allow repo-level projects, `owner` to only allow owner projects, `all` to allow both.
|
||||
ProjectsMode *string `json:"projects_mode,omitempty" binding:"In(repo,owner,all)"`
|
||||
// either `true` to enable releases unit, or `false` to disable them.
|
||||
HasReleases *bool `json:"has_releases,omitempty"`
|
||||
// either `true` to enable packages unit, or `false` to disable them.
|
||||
|
@ -2090,7 +2090,11 @@ settings.pulls.default_delete_branch_after_merge = Delete pull request branch af
|
||||
settings.pulls.default_allow_edits_from_maintainers = Allow edits from maintainers by default
|
||||
settings.releases_desc = Enable Repository Releases
|
||||
settings.packages_desc = Enable Repository Packages Registry
|
||||
settings.projects_desc = Enable Repository Projects
|
||||
settings.projects_desc = Enable Projects
|
||||
settings.projects_mode_desc = Projects Mode (which kinds of projects to show)
|
||||
settings.projects_mode_repo = Repo projects only
|
||||
settings.projects_mode_owner = Only user or org projects
|
||||
settings.projects_mode_all = All projects
|
||||
settings.actions_desc = Enable Repository Actions
|
||||
settings.admin_settings = Administrator Settings
|
||||
settings.admin_enable_health_check = Enable Repository Health Checks (git fsck)
|
||||
|
@ -944,13 +944,33 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
|
||||
}
|
||||
}
|
||||
|
||||
if opts.HasProjects != nil && !unit_model.TypeProjects.UnitGlobalDisabled() {
|
||||
if *opts.HasProjects {
|
||||
currHasProjects := repo.UnitEnabled(ctx, unit_model.TypeProjects)
|
||||
newHasProjects := currHasProjects
|
||||
if opts.HasProjects != nil {
|
||||
newHasProjects = *opts.HasProjects
|
||||
}
|
||||
if currHasProjects || newHasProjects {
|
||||
if newHasProjects && !unit_model.TypeProjects.UnitGlobalDisabled() {
|
||||
unit, err := repo.GetUnit(ctx, unit_model.TypeProjects)
|
||||
var config *repo_model.ProjectsConfig
|
||||
if err != nil {
|
||||
config = &repo_model.ProjectsConfig{
|
||||
ProjectsMode: repo_model.ProjectsModeAll,
|
||||
}
|
||||
} else {
|
||||
config = unit.ProjectsConfig()
|
||||
}
|
||||
|
||||
if opts.ProjectsMode != nil {
|
||||
config.ProjectsMode = repo_model.ProjectsMode(*opts.ProjectsMode)
|
||||
}
|
||||
|
||||
units = append(units, repo_model.RepoUnit{
|
||||
RepoID: repo.ID,
|
||||
Type: unit_model.TypeProjects,
|
||||
Config: config,
|
||||
})
|
||||
} else {
|
||||
} else if !newHasProjects && !unit_model.TypeProjects.UnitGlobalDisabled() {
|
||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
|
||||
}
|
||||
}
|
||||
|
@ -587,8 +587,15 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
|
||||
if repo.Owner.IsOrganization() {
|
||||
repoOwnerType = project_model.TypeOrganization
|
||||
}
|
||||
|
||||
projectsUnit := repo.MustGetUnit(ctx, unit.TypeProjects)
|
||||
|
||||
var openProjects []*project_model.Project
|
||||
var closedProjects []*project_model.Project
|
||||
var err error
|
||||
projects, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
|
||||
|
||||
if projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeRepo) {
|
||||
openProjects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
|
||||
ListOptions: db.ListOptionsAll,
|
||||
RepoID: repo.ID,
|
||||
IsClosed: optional.Some(false),
|
||||
@ -598,20 +605,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
|
||||
ctx.ServerError("GetProjects", err)
|
||||
return
|
||||
}
|
||||
projects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
|
||||
ListOptions: db.ListOptionsAll,
|
||||
OwnerID: repo.OwnerID,
|
||||
IsClosed: optional.Some(false),
|
||||
Type: repoOwnerType,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetProjects", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["OpenProjects"] = append(projects, projects2...)
|
||||
|
||||
projects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
|
||||
closedProjects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
|
||||
ListOptions: db.ListOptionsAll,
|
||||
RepoID: repo.ID,
|
||||
IsClosed: optional.Some(true),
|
||||
@ -621,7 +615,21 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
|
||||
ctx.ServerError("GetProjects", err)
|
||||
return
|
||||
}
|
||||
projects2, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
|
||||
}
|
||||
|
||||
if projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeOwner) {
|
||||
openProjects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
|
||||
ListOptions: db.ListOptionsAll,
|
||||
OwnerID: repo.OwnerID,
|
||||
IsClosed: optional.Some(false),
|
||||
Type: repoOwnerType,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetProjects", err)
|
||||
return
|
||||
}
|
||||
openProjects = append(openProjects, openProjects2...)
|
||||
closedProjects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
|
||||
ListOptions: db.ListOptionsAll,
|
||||
OwnerID: repo.OwnerID,
|
||||
IsClosed: optional.Some(true),
|
||||
@ -631,8 +639,11 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
|
||||
ctx.ServerError("GetProjects", err)
|
||||
return
|
||||
}
|
||||
closedProjects = append(closedProjects, closedProjects2...)
|
||||
}
|
||||
|
||||
ctx.Data["ClosedProjects"] = append(projects, projects2...)
|
||||
ctx.Data["OpenProjects"] = openProjects
|
||||
ctx.Data["ClosedProjects"] = closedProjects
|
||||
}
|
||||
|
||||
// repoReviewerSelection items to bee shown
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
attachment_model "code.gitea.io/gitea/models/repo"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
@ -33,16 +33,17 @@ const (
|
||||
tplProjectsView base.TplName = "repo/projects/view"
|
||||
)
|
||||
|
||||
// MustEnableProjects check if projects are enabled in settings
|
||||
func MustEnableProjects(ctx *context.Context) {
|
||||
// MustEnableRepoProjects check if repo projects are enabled in settings
|
||||
func MustEnableRepoProjects(ctx *context.Context) {
|
||||
if unit.TypeProjects.UnitGlobalDisabled() {
|
||||
ctx.NotFound("EnableKanbanBoard", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Repo.Repository != nil {
|
||||
if !ctx.Repo.CanRead(unit.TypeProjects) {
|
||||
ctx.NotFound("MustEnableProjects", nil)
|
||||
projectsUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeProjects)
|
||||
if !ctx.Repo.CanRead(unit.TypeProjects) || !projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeRepo) {
|
||||
ctx.NotFound("MustEnableRepoProjects", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -325,10 +326,10 @@ func ViewProject(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if project.CardType != project_model.CardTypeTextOnly {
|
||||
issuesAttachmentMap := make(map[int64][]*attachment_model.Attachment)
|
||||
issuesAttachmentMap := make(map[int64][]*repo_model.Attachment)
|
||||
for _, issuesList := range issuesMap {
|
||||
for _, issue := range issuesList {
|
||||
if issueAttachment, err := attachment_model.GetAttachmentsByIssueIDImagesLatest(ctx, issue.ID); err == nil {
|
||||
if issueAttachment, err := repo_model.GetAttachmentsByIssueIDImagesLatest(ctx, issue.ID); err == nil {
|
||||
issuesAttachmentMap[issue.ID] = issueAttachment
|
||||
}
|
||||
}
|
||||
|
@ -533,6 +533,9 @@ func SettingsPost(ctx *context.Context) {
|
||||
units = append(units, repo_model.RepoUnit{
|
||||
RepoID: repo.ID,
|
||||
Type: unit_model.TypeProjects,
|
||||
Config: &repo_model.ProjectsConfig{
|
||||
ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode),
|
||||
},
|
||||
})
|
||||
} else if !unit_model.TypeProjects.UnitGlobalDisabled() {
|
||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
|
||||
|
@ -1344,7 +1344,7 @@ func registerRoutes(m *web.Route) {
|
||||
})
|
||||
})
|
||||
}, reqRepoProjectsWriter, context.RepoMustNotBeArchived())
|
||||
}, reqRepoProjectsReader, repo.MustEnableProjects)
|
||||
}, reqRepoProjectsReader, repo.MustEnableRepoProjects)
|
||||
|
||||
m.Group("/actions", func() {
|
||||
m.Get("", actions.List)
|
||||
|
@ -113,8 +113,11 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||
defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit
|
||||
}
|
||||
hasProjects := false
|
||||
if _, err := repo.GetUnit(ctx, unit_model.TypeProjects); err == nil {
|
||||
projectsMode := repo_model.ProjectsModeAll
|
||||
if unit, err := repo.GetUnit(ctx, unit_model.TypeProjects); err == nil {
|
||||
hasProjects = true
|
||||
config := unit.ProjectsConfig()
|
||||
projectsMode = config.ProjectsMode
|
||||
}
|
||||
|
||||
hasReleases := false
|
||||
@ -211,6 +214,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||
InternalTracker: internalTracker,
|
||||
HasWiki: hasWiki,
|
||||
HasProjects: hasProjects,
|
||||
ProjectsMode: string(projectsMode),
|
||||
HasReleases: hasReleases,
|
||||
HasPackages: hasPackages,
|
||||
HasActions: hasActions,
|
||||
|
@ -142,6 +142,7 @@ type RepoSettingForm struct {
|
||||
ExternalTrackerRegexpPattern string
|
||||
EnableCloseIssuesViaCommitInAnyBranch bool
|
||||
EnableProjects bool
|
||||
ProjectsMode string
|
||||
EnableReleases bool
|
||||
EnablePackages bool
|
||||
EnablePulls bool
|
||||
|
@ -174,7 +174,8 @@
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
{{if and (not .UnitProjectsGlobalDisabled) (.Permission.CanRead $.UnitTypeProjects)}}
|
||||
{{$projectsUnit := .Repository.MustGetUnit $.Context $.UnitTypeProjects}}
|
||||
{{if and (not .UnitProjectsGlobalDisabled) (.Permission.CanRead $.UnitTypeProjects) ($projectsUnit.ProjectsConfig.IsProjectsAllowed "repo")}}
|
||||
<a href="{{.RepoLink}}/projects" class="{{if .IsProjectsPage}}active {{end}}item">
|
||||
{{svg "octicon-project"}} {{ctx.Locale.Tr "repo.project_board"}}
|
||||
{{if .Repository.NumOpenProjects}}
|
||||
|
@ -446,13 +446,45 @@
|
||||
|
||||
{{$isProjectsEnabled := .Repository.UnitEnabled $.Context $.UnitTypeProjects}}
|
||||
{{$isProjectsGlobalDisabled := .UnitTypeProjects.UnitGlobalDisabled}}
|
||||
{{$projectsUnit := .Repository.MustGetUnit $.Context $.UnitTypeProjects}}
|
||||
<div class="inline field">
|
||||
<label>{{ctx.Locale.Tr "repo.project_board"}}</label>
|
||||
<div class="ui checkbox{{if $isProjectsGlobalDisabled}} disabled{{end}}"{{if $isProjectsGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
|
||||
<input class="enable-system" name="enable_projects" type="checkbox" {{if $isProjectsEnabled}}checked{{end}}>
|
||||
<input class="enable-system" name="enable_projects" type="checkbox" data-target="#projects_box" {{if $isProjectsEnabled}}checked{{end}}>
|
||||
<label>{{ctx.Locale.Tr "repo.settings.projects_desc"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field {{if not $isProjectsEnabled}} disabled{{end}} gt-pl-4" id="projects_box">
|
||||
<p>
|
||||
{{ctx.Locale.Tr "repo.settings.projects_mode_desc"}}
|
||||
</p>
|
||||
<div class="ui dropdown selection">
|
||||
<select name="projects_mode">
|
||||
<option value="repo" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "repo")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_repo"}}</option>
|
||||
<option value="owner" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "owner")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_owner"}}</option>
|
||||
<option value="all" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "all")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_all"}}</option>
|
||||
</select>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="default text">
|
||||
{{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "repo")}}
|
||||
{{ctx.Locale.Tr "repo.settings.projects_mode_repo"}}
|
||||
{{end}}
|
||||
{{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "owner")}}
|
||||
{{ctx.Locale.Tr "repo.settings.projects_mode_owner"}}
|
||||
{{end}}
|
||||
{{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "all")}}
|
||||
{{ctx.Locale.Tr "repo.settings.projects_mode_all"}}
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="menu">
|
||||
<div class="item" data-value="repo">{{ctx.Locale.Tr "repo.settings.projects_mode_repo"}}</div>
|
||||
<div class="item" data-value="owner">{{ctx.Locale.Tr "repo.settings.projects_mode_owner"}}</div>
|
||||
<div class="item" data-value="all">{{ctx.Locale.Tr "repo.settings.projects_mode_all"}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
{{$isReleasesEnabled := .Repository.UnitEnabled $.Context $.UnitTypeReleases}}
|
||||
{{$isReleasesGlobalDisabled := .UnitTypeReleases.UnitGlobalDisabled}}
|
||||
|
9
templates/swagger/v1_json.tmpl
generated
9
templates/swagger/v1_json.tmpl
generated
@ -19570,6 +19570,11 @@
|
||||
"type": "boolean",
|
||||
"x-go-name": "Private"
|
||||
},
|
||||
"projects_mode": {
|
||||
"description": "`repo` to only allow repo-level projects, `owner` to only allow owner projects, `all` to allow both.",
|
||||
"type": "string",
|
||||
"x-go-name": "ProjectsMode"
|
||||
},
|
||||
"template": {
|
||||
"description": "either `true` to make this repository a template or `false` to make it a normal repository",
|
||||
"type": "boolean",
|
||||
@ -22491,6 +22496,10 @@
|
||||
"type": "boolean",
|
||||
"x-go-name": "Private"
|
||||
},
|
||||
"projects_mode": {
|
||||
"type": "string",
|
||||
"x-go-name": "ProjectsMode"
|
||||
},
|
||||
"release_counter": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
|
Loading…
Reference in New Issue
Block a user