enhance(ui): use OpenAPI 3.0 spec in API viewer, add spec buttons (#38478)

Follow-ups from https://github.com/go-gitea/gitea/pull/37038:

- Render the OpenAPI 3.0 spec in the API viewer, it is richer than the
Swagger 2.0 rendering
- Replace the back link with gitea-styled buttons to view both specs and
return to Gitea, flowing above swagger-ui on viewports where they would
overlap the title
- Key `VisibilityModes` by the string enum type, now named
`VisibilityString`, removing the `string()` casts at API call sites
- Drop the raw visibility strings from the `Service` settings struct in
favor of the typed mode fields, which also removes the org default
visibility row from the admin config page
- Fix two pre-existing issues surfaced in review: an invalid
`DEFAULT_USER_VISIBILITY` was silently accepted as public, and the org
visibility error message showed the enum zero value instead of the
submitted input

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwind
2026-07-16 19:38:33 +02:00
committed by GitHub
parent 02f82a2054
commit f0d9af3a18
23 changed files with 163 additions and 131 deletions

View File

@@ -117,8 +117,8 @@ func (t *Team) CanNonMemberReadMeta(ctx context.Context, org, doer *user_model.U
}
}
func NormalizeTeamVisibility(s string) structs.VisibleType {
if vt, ok := structs.VisibilityModes[s]; ok {
func NormalizeTeamVisibility[T ~string](v T) structs.VisibleType {
if vt, ok := structs.VisibilityModes[structs.VisibilityString(v)]; ok {
return vt
}
return structs.VisibleTypePrivate

View File

@@ -25,12 +25,9 @@ const (
// Service settings
var Service = struct {
DefaultUserVisibility string
DefaultUserVisibilityMode structs.VisibleType
AllowedUserVisibilityModes []string
AllowedUserVisibilityModesSlice AllowedVisibility `ini:"-"`
DefaultOrgVisibility string
DefaultOrgVisibilityMode structs.VisibleType
DefaultUserVisibilityMode structs.VisibleType `ini:"-"`
AllowedUserVisibilityModesSlice AllowedVisibility `ini:"-"`
DefaultOrgVisibilityMode structs.VisibleType `ini:"-"`
ActiveCodeLives int
ResetPwdCodeLives int
RegisterEmailConfirm bool
@@ -217,36 +214,35 @@ func loadServiceFrom(rootCfg ConfigProvider) {
Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true)
Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true)
Service.AutoWatchOnChanges = sec.Key("AUTO_WATCH_ON_CHANGES").MustBool(false)
modes := sec.Key("ALLOWED_USER_VISIBILITY_MODES").Strings(",")
if len(modes) != 0 {
Service.AllowedUserVisibilityModes = []string{}
Service.AllowedUserVisibilityModesSlice = []bool{false, false, false}
for _, sMode := range modes {
if tp, ok := structs.VisibilityModes[sMode]; ok { // remove unsupported modes
Service.AllowedUserVisibilityModes = append(Service.AllowedUserVisibilityModes, sMode)
Service.AllowedUserVisibilityModesSlice[tp] = true
} else {
log.Warn("ALLOWED_USER_VISIBILITY_MODES %s is unsupported", sMode)
}
Service.AllowedUserVisibilityModesSlice = AllowedVisibility{false, false, false}
var allowedUserVisibilityModes []structs.VisibilityString
for _, allowedMode := range sec.Key("ALLOWED_USER_VISIBILITY_MODES").Strings(",") {
if tp, ok := structs.VisibilityModes[structs.VisibilityString(allowedMode)]; ok { // remove unsupported modes
allowedUserVisibilityModes = append(allowedUserVisibilityModes, structs.VisibilityString(allowedMode))
Service.AllowedUserVisibilityModesSlice[tp] = true
} else {
log.Warn("ALLOWED_USER_VISIBILITY_MODES %s is unsupported", allowedMode)
}
}
if len(Service.AllowedUserVisibilityModes) == 0 {
Service.AllowedUserVisibilityModes = []string{"public", "limited", "private"}
Service.AllowedUserVisibilityModesSlice = []bool{true, true, true}
if len(allowedUserVisibilityModes) == 0 {
allowedUserVisibilityModes = []structs.VisibilityString{structs.VisibilityStringPublic, structs.VisibilityStringLimited, structs.VisibilityStringPrivate}
Service.AllowedUserVisibilityModesSlice = AllowedVisibility{true, true, true}
}
Service.DefaultUserVisibility = sec.Key("DEFAULT_USER_VISIBILITY").String()
if Service.DefaultUserVisibility == "" {
Service.DefaultUserVisibility = Service.AllowedUserVisibilityModes[0]
} else if !Service.AllowedUserVisibilityModesSlice[structs.VisibilityModes[Service.DefaultUserVisibility]] {
log.Warn("DEFAULT_USER_VISIBILITY %s is wrong or not in ALLOWED_USER_VISIBILITY_MODES, using first allowed", Service.DefaultUserVisibility)
Service.DefaultUserVisibility = Service.AllowedUserVisibilityModes[0]
defaultUserVisibility := structs.VisibilityString(sec.Key("DEFAULT_USER_VISIBILITY").String())
if defaultUserVisibility == "" {
defaultUserVisibility = allowedUserVisibilityModes[0]
} else if vt, ok := structs.VisibilityModes[defaultUserVisibility]; !ok || !Service.AllowedUserVisibilityModesSlice[vt] {
log.Warn("DEFAULT_USER_VISIBILITY %s is wrong or not in ALLOWED_USER_VISIBILITY_MODES, using first allowed", defaultUserVisibility)
defaultUserVisibility = allowedUserVisibilityModes[0]
}
Service.DefaultUserVisibilityMode = structs.VisibilityModes[Service.DefaultUserVisibility]
Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes))
Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility]
Service.DefaultUserVisibilityMode = structs.VisibilityModes[defaultUserVisibility]
defaultOrgVisibility := structs.VisibilityString(sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.VisibilityModeKeys()))
Service.DefaultOrgVisibilityMode = structs.VisibilityModes[defaultOrgVisibility]
Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool()
Service.UserDeleteWithCommentsMaxTime = sec.Key("USER_DELETE_WITH_COMMENTS_MAX_TIME").MustDuration(0)
sec.Key("VALID_SITE_URL_SCHEMES").MustString("http,https")
Service.ValidSiteURLSchemes = sec.Key("VALID_SITE_URL_SCHEMES").Strings(",")

View File

@@ -48,80 +48,83 @@ EMAIL_DOMAIN_BLOCKLIST = d3, *.b
func TestLoadServiceVisibilityModes(t *testing.T) {
defer test.MockVariableValue(&Service)()
kases := map[string]func(){
visibleTypeSlice := func(s ...structs.VisibilityString) (ret []structs.VisibleType) {
for _, v := range s {
ret = append(ret, structs.VisibilityModes[v])
}
return ret
}
testCases := map[string]func(){
`
[service]
DEFAULT_USER_VISIBILITY = public
ALLOWED_USER_VISIBILITY_MODES = public,limited,private
`: func() {
assert.Equal(t, "public", Service.DefaultUserVisibility)
assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode)
assert.Equal(t, []string{"public", "limited", "private"}, Service.AllowedUserVisibilityModes)
assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
},
`
[service]
DEFAULT_USER_VISIBILITY = public
`: func() {
assert.Equal(t, "public", Service.DefaultUserVisibility)
assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode)
assert.Equal(t, []string{"public", "limited", "private"}, Service.AllowedUserVisibilityModes)
assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
},
`
[service]
DEFAULT_USER_VISIBILITY = limited
`: func() {
assert.Equal(t, "limited", Service.DefaultUserVisibility)
assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultUserVisibilityMode)
assert.Equal(t, []string{"public", "limited", "private"}, Service.AllowedUserVisibilityModes)
assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
},
`
[service]
ALLOWED_USER_VISIBILITY_MODES = public,limited,private
`: func() {
assert.Equal(t, "public", Service.DefaultUserVisibility)
assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode)
assert.Equal(t, []string{"public", "limited", "private"}, Service.AllowedUserVisibilityModes)
assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
},
`
[service]
DEFAULT_USER_VISIBILITY = public
ALLOWED_USER_VISIBILITY_MODES = limited,private
`: func() {
assert.Equal(t, "limited", Service.DefaultUserVisibility)
assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultUserVisibilityMode)
assert.Equal(t, []string{"limited", "private"}, Service.AllowedUserVisibilityModes)
assert.Equal(t, visibleTypeSlice("limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
},
`
[service]
DEFAULT_USER_VISIBILITY = my_type
ALLOWED_USER_VISIBILITY_MODES = limited,private
`: func() {
assert.Equal(t, "limited", Service.DefaultUserVisibility)
assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultUserVisibilityMode)
assert.Equal(t, []string{"limited", "private"}, Service.AllowedUserVisibilityModes)
assert.Equal(t, visibleTypeSlice("limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
},
`
[service]
DEFAULT_USER_VISIBILITY = my_type
`: func() {
assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode)
assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
},
`
[service]
DEFAULT_USER_VISIBILITY = public
ALLOWED_USER_VISIBILITY_MODES = public, limit, privated
`: func() {
assert.Equal(t, "public", Service.DefaultUserVisibility)
assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode)
assert.Equal(t, []string{"public"}, Service.AllowedUserVisibilityModes)
assert.Equal(t, visibleTypeSlice("public"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
},
}
for kase, fun := range kases {
t.Run(kase, func(t *testing.T) {
cfg, err := NewConfigProviderFromData(kase)
for tc, fn := range testCases {
t.Run(tc, func(t *testing.T) {
Service.AllowedUserVisibilityModesSlice = []bool{true, true, true}
Service.DefaultUserVisibilityMode = structs.VisibleTypePublic
cfg, err := NewConfigProviderFromData(tc)
assert.NoError(t, err)
loadServiceFrom(cfg)
fun()
// reset
Service.AllowedUserVisibilityModesSlice = []bool{true, true, true}
Service.AllowedUserVisibilityModes = []string{}
Service.DefaultUserVisibility = ""
Service.DefaultUserVisibilityMode = structs.VisibleTypePublic
fn()
})
}
}

View File

@@ -29,7 +29,7 @@ type CreateUserOption struct {
// Whether the user has restricted access privileges
Restricted *bool `json:"restricted"`
// User visibility level: public, limited, or private
Visibility UserVisibility `json:"visibility" binding:"In(,public,limited,private)"`
Visibility VisibilityString `json:"visibility" binding:"In(,public,limited,private)"`
// For explicitly setting the user creation timestamp. Useful when users are
// migrated from other systems. When omitted, the user's creation timestamp
@@ -78,5 +78,5 @@ type EditUserOption struct {
// Whether the user has restricted access privileges
Restricted *bool `json:"restricted"`
// User visibility level: public, limited, or private
Visibility UserVisibility `json:"visibility" binding:"In(,public,limited,private)"`
Visibility VisibilityString `json:"visibility" binding:"In(,public,limited,private)"`
}

View File

@@ -22,7 +22,7 @@ type Organization struct {
// The location of the organization
Location string `json:"location"`
// The visibility level of the organization (public, limited, private)
Visibility UserVisibility `json:"visibility"`
Visibility VisibilityString `json:"visibility"`
// Whether repository administrators can change team access
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
// username of the organization
@@ -60,7 +60,7 @@ type CreateOrgOption struct {
// The location of the organization
Location string `json:"location" binding:"MaxSize(50)"`
// possible values are `public` (default), `limited` or `private`
Visibility UserVisibility `json:"visibility" binding:"In(,public,limited,private)"`
Visibility VisibilityString `json:"visibility" binding:"In(,public,limited,private)"`
// Whether repository administrators can change team access
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
}
@@ -78,7 +78,7 @@ type EditOrgOption struct {
// The location of the organization
Location *string `json:"location" binding:"MaxSize(50)"`
// possible values are `public`, `limited` or `private`
Visibility *UserVisibility `json:"visibility" binding:"In(,public,limited,private)"`
Visibility *VisibilityString `json:"visibility" binding:"In(,public,limited,private)"`
// Whether repository administrators can change team access
RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"`
}

View File

@@ -51,7 +51,7 @@ type User struct {
// the user's description
Description string `json:"description"`
// User visibility level option: public, limited, private
Visibility UserVisibility `json:"visibility"`
Visibility VisibilityString `json:"visibility"`
// user counts
Followers int `json:"followers_count"`

View File

@@ -18,10 +18,10 @@ const (
)
// VisibilityModes is a map of Visibility types
var VisibilityModes = map[string]VisibleType{
"public": VisibleTypePublic,
"limited": VisibleTypeLimited,
"private": VisibleTypePrivate,
var VisibilityModes = map[VisibilityString]VisibleType{
VisibilityStringPublic: VisibleTypePublic,
VisibilityStringLimited: VisibleTypeLimited,
VisibilityStringPrivate: VisibleTypePrivate,
}
// IsPublic returns true if VisibleType is public
@@ -43,27 +43,27 @@ func (vt VisibleType) IsPrivate() bool {
func (vt VisibleType) String() string {
for k, v := range VisibilityModes {
if vt == v {
return k
return string(k)
}
}
return ""
}
// ExtractKeysFromMapString provides a slice of keys from map
func ExtractKeysFromMapString(in map[string]VisibleType) (keys []string) {
for k := range in {
keys = append(keys, k)
// VisibilityModeKeys returns the visibility mode names (public, limited, private)
func VisibilityModeKeys() (keys []string) {
for k := range VisibilityModes {
keys = append(keys, string(k))
}
return keys
}
// UserVisibility defines the visibility level of a user or organization as
// rendered in API payloads. The DB representation is VisibleType (int).
// swagger:enum UserVisibility
type UserVisibility string
// VisibilityString defines the visibility level of a user/organization/team as
// rendered in API and config payloads. The DB representation is VisibleType (int).
// swagger:enum VisibilityString
type VisibilityString string
const (
UserVisibilityPublic UserVisibility = "public"
UserVisibilityLimited UserVisibility = "limited"
UserVisibilityPrivate UserVisibility = "private"
VisibilityStringPublic VisibilityString = "public"
VisibilityStringLimited VisibilityString = "limited"
VisibilityStringPrivate VisibilityString = "private"
)

View File

@@ -3306,7 +3306,6 @@
"admin.config.default_enable_timetracking": "Enable Time Tracking by Default",
"admin.config.default_allow_only_contributors_to_track_time": "Let Only Contributors Track Time",
"admin.config.no_reply_address": "Hidden Email Domain",
"admin.config.default_visibility_organization": "Default visibility for new Organizations",
"admin.config.default_enable_dependencies": "Enable Issue Dependencies by Default",
"admin.config.webhook_config": "Webhook Configuration",
"admin.config.queue_length": "Queue Length",

View File

@@ -48,7 +48,7 @@ func CreateOrg(ctx *context.APIContext) {
visibility := api.VisibleTypePublic
if form.Visibility != "" {
visibility = api.VisibilityModes[string(form.Visibility)]
visibility = api.VisibilityModes[form.Visibility]
}
org := &organization.Organization{

View File

@@ -121,7 +121,7 @@ func CreateUser(ctx *context.APIContext) {
}
if form.Visibility != "" {
visibility := api.VisibilityModes[string(form.Visibility)]
visibility := api.VisibilityModes[form.Visibility]
overwriteDefault.Visibility = &visibility
}
@@ -237,7 +237,7 @@ func EditUser(ctx *context.APIContext) {
Description: optional.FromPtr(form.Description),
IsActive: optional.FromPtr(form.Active),
IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin),
Visibility: optional.FromMapLookup(api.VisibilityModes, string(form.Visibility)),
Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility),
AllowGitHook: optional.FromPtr(form.AllowGitHook),
AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation),
@@ -469,7 +469,7 @@ func SearchUsers(ctx *context.APIContext) {
var visible []api.VisibleType
visibilityParam := ctx.FormString("visibility")
if visibility, ok := api.VisibilityModes[visibilityParam]; ok {
if visibility, ok := api.VisibilityModes[api.VisibilityString(visibilityParam)]; ok {
visible = []api.VisibleType{visibility}
} else if visibilityParam != "" {
ctx.APIError(http.StatusUnprocessableEntity, "invalid visibility")

View File

@@ -269,7 +269,7 @@ func Create(ctx *context.APIContext) {
visibility := api.VisibleTypePublic
if form.Visibility != "" {
visibility = api.VisibilityModes[string(form.Visibility)]
visibility = api.VisibilityModes[form.Visibility]
}
org := &organization.Organization{
@@ -413,7 +413,7 @@ func Edit(ctx *context.APIContext) {
Description: optional.FromPtr(form.Description),
Website: optional.FromPtr(form.Website),
Location: optional.FromPtr(form.Location),
Visibility: optional.FromMapLookup(api.VisibilityModes, string(optional.FromPtr(form.Visibility).Value())),
Visibility: optional.FromMapLookup(api.VisibilityModes, optional.FromPtr(form.Visibility).Value()),
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
}
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {

View File

@@ -223,7 +223,7 @@ func CreateTeam(ctx *context.APIContext) {
IncludesAllRepositories: form.IncludesAllRepositories,
CanCreateOrgRepo: form.CanCreateOrgRepo,
AccessMode: teamPermission,
Visibility: organization.NormalizeTeamVisibility(string(form.Visibility)),
Visibility: organization.NormalizeTeamVisibility(form.Visibility),
}
if team.AccessMode < perm.AccessModeAdmin {
@@ -302,7 +302,7 @@ func EditTeam(ctx *context.APIContext) {
}
if form.Visibility != nil && !team.IsOwnerTeam() {
team.Visibility = organization.NormalizeTeamVisibility(string(*form.Visibility))
team.Visibility = organization.NormalizeTeamVisibility(*form.Visibility)
}
isAuthChanged := false

View File

@@ -235,9 +235,10 @@ func SettingsRenamePost(ctx *context.Context) {
// SettingsChangeVisibilityPost response for change organization visibility
func SettingsChangeVisibilityPost(ctx *context.Context) {
visibility, ok := structs.VisibilityModes[ctx.FormString("visibility")]
visibilityStr := ctx.FormString("visibility")
visibility, ok := structs.VisibilityModes[structs.VisibilityString(visibilityStr)]
if !ok {
ctx.Flash.Error(ctx.Tr("invalid_data", visibility))
ctx.Flash.Error(ctx.Tr("invalid_data", visibilityStr))
ctx.JSONRedirect(setting.AppSubURL + "/org/" + url.PathEscape(ctx.Org.Organization.Name) + "/settings")
return
}

View File

@@ -864,7 +864,7 @@ func ToOrganization(ctx context.Context, org *organization.Organization) *api.Or
Description: org.Description,
Website: org.Website,
Location: org.Location,
Visibility: api.UserVisibility(org.Visibility.String()),
Visibility: api.VisibilityString(org.Visibility.String()),
RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess,
}
}

View File

@@ -65,7 +65,7 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap
StarredRepos: user.NumStars,
}
result.Visibility = api.UserVisibility(user.Visibility.String())
result.Visibility = api.VisibilityString(user.Visibility.String())
// hide primary email if API caller is anonymous or user keep email private
if signed && (!user.KeepEmailPrivate || authed) {

View File

@@ -29,11 +29,11 @@ func TestUser_ToUser(t *testing.T) {
apiUser = toUser(t.Context(), user1, false, false)
assert.False(t, apiUser.IsAdmin)
assert.Equal(t, api.UserVisibilityPublic, apiUser.Visibility)
assert.Equal(t, api.VisibilityStringPublic, apiUser.Visibility)
user31 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31, IsAdmin: false, Visibility: api.VisibleTypePrivate})
apiUser = toUser(t.Context(), user31, true, true)
assert.False(t, apiUser.IsAdmin)
assert.Equal(t, api.UserVisibilityPrivate, apiUser.Visibility)
assert.Equal(t, api.VisibilityStringPrivate, apiUser.Visibility)
}

View File

@@ -155,8 +155,6 @@
<dt>{{ctx.Locale.Tr "admin.config.default_allow_only_contributors_to_track_time"}}</dt>
<dd>{{svg (Iif .Service.DefaultAllowOnlyContributorsToTrackTime "octicon-check" "octicon-x")}}</dd>
{{end}}
<dt>{{ctx.Locale.Tr "admin.config.default_visibility_organization"}}</dt>
<dd>{{.Service.DefaultOrgVisibility}}</dd>
<dt>{{ctx.Locale.Tr "admin.config.no_reply_address"}}</dt>
<dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd>

View File

@@ -4,13 +4,17 @@
{{ctx.HeadMetaContentSecurityPolicy}}
<title>Gitea API</title>
<link rel="stylesheet" href="{{ctx.CurrentWebTheme.PublicAssetURI}}">
{{/* HINT: SWAGGER-CSS-IMPORT: load swagger styles ahead to avoid flicker (e.g. the swagger-back-link) */}}
{{/* HINT: SWAGGER-CSS-IMPORT: load swagger styles ahead to avoid flicker (e.g. the swagger-page-links) */}}
{{AssetCSSLinks "web_src/js/swagger.ts" "web_src/css/swagger-standalone.css"}}
</head>
<body>
{{/* TODO: add Help & Glossary to help users understand the API, and explain some concepts like "Owner" */}}
<a class="swagger-back-link" href="{{AppSubUrl}}/">{{svg "octicon-reply"}}{{ctx.Locale.Tr "return_to_gitea"}}</a>
<div id="swagger-ui" data-source="{{AppSubUrl}}/swagger.v1.json"></div>
<div class="swagger-page-links">
<a href="{{AppSubUrl}}/openapi3.v1.json" target="_blank">{{svg "octicon-file-code"}}OpenAPI JSON</a>
<a href="{{AppSubUrl}}/swagger.v1.json" target="_blank">{{svg "octicon-file-code"}}Swagger JSON</a>
<a href="{{AppSubUrl}}/">{{svg "octicon-reply"}}{{ctx.Locale.Tr "return_to_gitea"}}</a>
</div>
<div id="swagger-ui" data-source="{{AppSubUrl}}/openapi3.v1.json"></div>
<footer class="page-footer"></footer>
{{ctx.ScriptImport "web_src/js/swagger.ts" "module"}}
</body>

View File

@@ -24561,7 +24561,7 @@
"limited",
"private"
],
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
"x-go-enum-desc": "public VisibilityStringPublic\nlimited VisibilityStringLimited\nprivate VisibilityStringPrivate",
"x-go-name": "Visibility"
},
"website": {
@@ -25131,7 +25131,7 @@
"limited",
"private"
],
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
"x-go-enum-desc": "public VisibilityStringPublic\nlimited VisibilityStringLimited\nprivate VisibilityStringPrivate",
"x-go-name": "Visibility"
}
},
@@ -25833,7 +25833,7 @@
"limited",
"private"
],
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
"x-go-enum-desc": "public VisibilityStringPublic\nlimited VisibilityStringLimited\nprivate VisibilityStringPrivate",
"x-go-name": "Visibility"
},
"website": {
@@ -26375,7 +26375,7 @@
"limited",
"private"
],
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
"x-go-enum-desc": "public VisibilityStringPublic\nlimited VisibilityStringLimited\nprivate VisibilityStringPrivate",
"x-go-name": "Visibility"
},
"website": {
@@ -28227,7 +28227,7 @@
"limited",
"private"
],
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
"x-go-enum-desc": "public VisibilityStringPublic\nlimited VisibilityStringLimited\nprivate VisibilityStringPrivate",
"x-go-name": "Visibility"
},
"website": {
@@ -30590,7 +30590,7 @@
"limited",
"private"
],
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
"x-go-enum-desc": "public VisibilityStringPublic\nlimited VisibilityStringLimited\nprivate VisibilityStringPrivate",
"x-go-name": "Visibility"
},
"website": {

View File

@@ -4304,7 +4304,7 @@
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/UserVisibility"
"$ref": "#/components/schemas/VisibilityString"
}
],
"description": "possible values are `public` (default), `limited` or `private`"
@@ -4845,7 +4845,7 @@
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/UserVisibility"
"$ref": "#/components/schemas/VisibilityString"
}
],
"description": "User visibility level: public, limited, or private"
@@ -5549,7 +5549,7 @@
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/UserVisibility"
"$ref": "#/components/schemas/VisibilityString"
}
],
"description": "possible values are `public`, `limited` or `private`"
@@ -6075,7 +6075,7 @@
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/UserVisibility"
"$ref": "#/components/schemas/VisibilityString"
}
],
"description": "User visibility level: public, limited, or private"
@@ -7949,7 +7949,7 @@
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/UserVisibility"
"$ref": "#/components/schemas/VisibilityString"
}
],
"description": "The visibility level of the organization (public, limited, private)"
@@ -10343,7 +10343,7 @@
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/UserVisibility"
"$ref": "#/components/schemas/VisibilityString"
}
],
"description": "User visibility level option: public, limited, private"
@@ -10496,7 +10496,7 @@
"type": "object",
"x-go-package": "gitea.dev/modules/structs"
},
"UserVisibility": {
"VisibilityString": {
"enum": [
"public",
"limited",

View File

@@ -128,7 +128,7 @@ func testAPIOrgGeneral(t *testing.T) {
apiOrgList := DecodeJSON(t, resp, []*api.Organization{})
assert.Len(t, apiOrgList, 13)
assert.Equal(t, "Limited Org 36", apiOrgList[1].FullName)
assert.Equal(t, api.UserVisibilityLimited, apiOrgList[1].Visibility)
assert.Equal(t, api.VisibilityStringLimited, apiOrgList[1].Visibility)
// accessing without a token will return only public orgs
req = NewRequest(t, "GET", "/api/v1/orgs")
@@ -137,7 +137,7 @@ func testAPIOrgGeneral(t *testing.T) {
apiOrgList = DecodeJSON(t, resp, []*api.Organization{})
assert.Len(t, apiOrgList, 9)
assert.Equal(t, "org 17", apiOrgList[0].FullName)
assert.Equal(t, api.UserVisibilityPublic, apiOrgList[0].Visibility)
assert.Equal(t, api.VisibilityStringPublic, apiOrgList[0].Visibility)
})
t.Run("OrgEdit", func(t *testing.T) {
@@ -149,7 +149,7 @@ func testAPIOrgGeneral(t *testing.T) {
Description: new("new description"),
Website: new("https://org3-new-website.example.com"),
Location: new("new location"),
Visibility: new(api.UserVisibilityLimited),
Visibility: new(api.VisibilityStringLimited),
Email: new("org3-new-email@example.com"),
}
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org3Edit).AddTokenAuth(user1Token)
@@ -179,7 +179,7 @@ func testAPIOrgGeneral(t *testing.T) {
t.Run("OrgEditInvalidVisibility", func(t *testing.T) {
org := api.EditOrgOption{
Visibility: new(api.UserVisibility("invalid-visibility")),
Visibility: new(api.VisibilityString("invalid-visibility")),
}
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token)
MakeRequest(t, req, http.StatusUnprocessableEntity)

View File

@@ -47,7 +47,7 @@ func TestAPIUserSearchLoggedIn(t *testing.T) {
for _, user := range results.Data {
assert.Contains(t, user.UserName, query)
assert.NotEmpty(t, user.Email)
assert.Equal(t, api.UserVisibilityPublic, user.Visibility)
assert.Equal(t, api.VisibilityStringPublic, user.Visibility)
}
}
@@ -103,7 +103,7 @@ func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) {
for _, user := range results.Data {
assert.Contains(t, user.UserName, query)
assert.NotEmpty(t, user.Email)
assert.Equal(t, api.UserVisibilityPrivate, user.Visibility)
assert.Equal(t, api.VisibilityStringPrivate, user.Visibility)
}
}

View File

@@ -4,26 +4,57 @@ body {
margin: 0;
}
.swagger-back-link {
color: var(--color-primary);
text-decoration: none;
.swagger-page-links {
/* overlay the top-right of the info block by replicating swagger-ui's ".wrapper" and ".info" metrics */
position: absolute;
top: 1rem;
right: 1.5rem;
top: 50px;
left: 0;
right: 0;
max-width: 1460px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box;
display: flex;
justify-content: flex-end;
gap: 8px;
font-family: sans-serif;
font-size: 14px;
pointer-events: none; /* only the links themselves are clickable */
}
/* below this width the links would overlap the title, flow above swagger-ui instead */
@media (max-width: 991.98px) {
.swagger-page-links {
position: static;
padding: 10px 20px 0;
flex-wrap: wrap;
}
}
/* mimic the regular gitea button, the main site CSS is not loaded on this page */
.swagger-page-links a {
pointer-events: auto;
display: inline-flex;
align-items: center;
gap: 6px;
height: 32px;
padding: 0 12px;
border-radius: 4px;
background: var(--color-button);
border: 1px solid var(--color-light-border);
color: var(--color-text);
text-decoration: none;
white-space: nowrap;
}
.swagger-back-link:hover {
text-decoration: underline;
.swagger-page-links a:hover {
background: var(--color-hover);
}
.swagger-back-link svg {
color: inherit;
.swagger-page-links a:active {
background: var(--color-active);
}
.swagger-page-links svg {
fill: currentcolor;
margin-right: 0.5rem;
}
.swagger-spec-content {
display: none;
}