[FEAT] API support for repository flags
Expose the repository flags feature over the API, so the flags can be managed by a site administrator without using the web API. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu> (cherry picked from commitbac9f0225d
) (cherry picked from commite7f5c1ba14
)
This commit is contained in:
parent
2f8b041489
commit
95d9fe19cf
6 changed files with 686 additions and 0 deletions
9
modules/structs/repo_flags.go
Normal file
9
modules/structs/repo_flags.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package structs
|
||||||
|
|
||||||
|
// ReplaceFlagsOption options when replacing the flags of a repository
|
||||||
|
type ReplaceFlagsOption struct {
|
||||||
|
Flags []string `json:"flags"`
|
||||||
|
}
|
|
@ -1096,6 +1096,18 @@ func Routes() *web.Route {
|
||||||
m.Get("/permission", repo.GetRepoPermissions)
|
m.Get("/permission", repo.GetRepoPermissions)
|
||||||
})
|
})
|
||||||
}, reqToken())
|
}, reqToken())
|
||||||
|
if setting.Repository.EnableFlags {
|
||||||
|
m.Group("/flags", func() {
|
||||||
|
m.Combo("").Get(repo.ListFlags).
|
||||||
|
Put(bind(api.ReplaceFlagsOption{}), repo.ReplaceAllFlags).
|
||||||
|
Delete(repo.DeleteAllFlags)
|
||||||
|
m.Group("/{flag}", func() {
|
||||||
|
m.Combo("").Get(repo.HasFlag).
|
||||||
|
Put(repo.AddFlag).
|
||||||
|
Delete(repo.DeleteFlag)
|
||||||
|
})
|
||||||
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin())
|
||||||
|
}
|
||||||
m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees)
|
m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees)
|
||||||
m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers)
|
m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers)
|
||||||
m.Group("/teams", func() {
|
m.Group("/teams", func() {
|
||||||
|
|
245
routers/api/v1/repo/flags.go
Normal file
245
routers/api/v1/repo/flags.go
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListFlags(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/flags repository repoListFlags
|
||||||
|
// ---
|
||||||
|
// summary: List a repository's flags
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/StringSlice"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repoFlags, err := ctx.Repo.Repository.ListFlags(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := make([]string, len(repoFlags))
|
||||||
|
for i := range repoFlags {
|
||||||
|
flags[i] = repoFlags[i].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(int64(len(repoFlags)))
|
||||||
|
ctx.JSON(http.StatusOK, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplaceAllFlags(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PUT /repos/{owner}/{repo}/flags repository repoReplaceAllFlags
|
||||||
|
// ---
|
||||||
|
// summary: Replace all flags of a repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/ReplaceFlagsOption"
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
flagsForm := web.GetForm(ctx).(*api.ReplaceFlagsOption)
|
||||||
|
|
||||||
|
if err := ctx.Repo.Repository.ReplaceAllFlags(ctx, flagsForm.Flags); err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteAllFlags(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /repos/{owner}/{repo}/flags repository repoDeleteAllFlags
|
||||||
|
// ---
|
||||||
|
// summary: Remove all flags from a repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
if err := ctx.Repo.Repository.ReplaceAllFlags(ctx, nil); err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasFlag(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/flags/{flag} repository repoCheckFlag
|
||||||
|
// ---
|
||||||
|
// summary: Check if a repository has a given flag
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: flag
|
||||||
|
// in: path
|
||||||
|
// description: name of the flag
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
hasFlag := ctx.Repo.Repository.HasFlag(ctx, ctx.Params(":flag"))
|
||||||
|
if hasFlag {
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
} else {
|
||||||
|
ctx.NotFound()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddFlag(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PUT /repos/{owner}/{repo}/flags/{flag} repository repoAddFlag
|
||||||
|
// ---
|
||||||
|
// summary: Add a flag to a repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: flag
|
||||||
|
// in: path
|
||||||
|
// description: name of the flag
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
flag := ctx.Params(":flag")
|
||||||
|
|
||||||
|
if ctx.Repo.Repository.HasFlag(ctx, flag) {
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.Repo.Repository.AddFlag(ctx, flag); err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteFlag(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /repos/{owner}/{repo}/flags/{flag} repository repoDeleteFlag
|
||||||
|
// ---
|
||||||
|
// summary: Remove a flag from a repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: flag
|
||||||
|
// in: path
|
||||||
|
// description: name of the flag
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
flag := ctx.Params(":flag")
|
||||||
|
|
||||||
|
if _, err := ctx.Repo.Repository.DeleteFlag(ctx, flag); err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
|
@ -17,6 +17,9 @@ type swaggerParameterBodies struct {
|
||||||
// in:body
|
// in:body
|
||||||
AddCollaboratorOption api.AddCollaboratorOption
|
AddCollaboratorOption api.AddCollaboratorOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
ReplaceFlagsOption api.ReplaceFlagsOption
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
CreateEmailOption api.CreateEmailOption
|
CreateEmailOption api.CreateEmailOption
|
||||||
// in:body
|
// in:body
|
||||||
|
|
268
templates/swagger/v1_json.tmpl
generated
268
templates/swagger/v1_json.tmpl
generated
|
@ -4992,6 +4992,260 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/flags": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "List a repository's flags",
|
||||||
|
"operationId": "repoListFlags",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/StringSlice"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Replace all flags of a repository",
|
||||||
|
"operationId": "repoReplaceAllFlags",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ReplaceFlagsOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Remove all flags from a repository",
|
||||||
|
"operationId": "repoDeleteAllFlags",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/repos/{owner}/{repo}/flags/{flag}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Check if a repository has a given flag",
|
||||||
|
"operationId": "repoCheckFlag",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the flag",
|
||||||
|
"name": "flag",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Add a flag to a repository",
|
||||||
|
"operationId": "repoAddFlag",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the flag",
|
||||||
|
"name": "flag",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Remove a flag from a repository",
|
||||||
|
"operationId": "repoDeleteFlag",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the flag",
|
||||||
|
"name": "flag",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/forks": {
|
"/repos/{owner}/{repo}/forks": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -22008,6 +22262,20 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"ReplaceFlagsOption": {
|
||||||
|
"description": "ReplaceFlagsOption options when replacing the flags of a repository",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"flags": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "Flags"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"RepoCollaboratorPermission": {
|
"RepoCollaboratorPermission": {
|
||||||
"description": "RepoCollaboratorPermission to get repository permission for a collaborator",
|
"description": "RepoCollaboratorPermission to get repository permission for a collaborator",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
@ -7,13 +7,16 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/routers"
|
"code.gitea.io/gitea/routers"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
@ -42,6 +45,152 @@ func TestRepositoryFlagsUIDisabled(t *testing.T) {
|
||||||
assert.Equal(t, 0, flagsLinkCount)
|
assert.Equal(t, 0, flagsLinkCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRepositoryFlagsAPI(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
defer test.MockVariableValue(&setting.Repository.EnableFlags, true)()
|
||||||
|
defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
|
||||||
|
|
||||||
|
// *************
|
||||||
|
// ** Helpers **
|
||||||
|
// *************
|
||||||
|
|
||||||
|
adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true}).Name
|
||||||
|
normalUserBean := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
assert.False(t, normalUserBean.IsAdmin)
|
||||||
|
normalUser := normalUserBean.Name
|
||||||
|
|
||||||
|
assertAccess := func(t *testing.T, user, method, uri string, expectedStatus int) {
|
||||||
|
session := loginUser(t, user)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadAdmin)
|
||||||
|
|
||||||
|
req := NewRequestf(t, method, "/api/v1/repos/user2/repo1/flags%s", uri).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, expectedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***********
|
||||||
|
// ** Tests **
|
||||||
|
// ***********
|
||||||
|
|
||||||
|
t.Run("API access", func(t *testing.T) {
|
||||||
|
t.Run("as admin", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
assertAccess(t, adminUser, "GET", "", http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("as normal user", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
assertAccess(t, normalUser, "GET", "", http.StatusForbidden)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("token scopes", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// Trying to access the API with a token that lacks permissions, will
|
||||||
|
// fail, even if the token owner is an instance admin.
|
||||||
|
session := loginUser(t, adminUser)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/flags").AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("setting.Repository.EnableFlags is respected", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
defer test.MockVariableValue(&setting.Repository.EnableFlags, false)()
|
||||||
|
defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
|
||||||
|
|
||||||
|
t.Run("as admin", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
assertAccess(t, adminUser, "GET", "", http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("as normal user", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
assertAccess(t, normalUser, "GET", "", http.StatusNotFound)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("API functionality", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||||
|
defer func() {
|
||||||
|
repo.ReplaceAllFlags(db.DefaultContext, []string{})
|
||||||
|
}()
|
||||||
|
|
||||||
|
baseURLFmtStr := "/api/v1/repos/user5/repo4/flags%s"
|
||||||
|
|
||||||
|
session := loginUser(t, adminUser)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteAdmin)
|
||||||
|
|
||||||
|
// Listing flags
|
||||||
|
req := NewRequestf(t, "GET", baseURLFmtStr, "").AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var flags []string
|
||||||
|
DecodeJSON(t, resp, &flags)
|
||||||
|
assert.Empty(t, flags)
|
||||||
|
|
||||||
|
// Replacing all tags works, twice in a row
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
req = NewRequestWithJSON(t, "PUT", fmt.Sprintf(baseURLFmtStr, ""), &api.ReplaceFlagsOption{
|
||||||
|
Flags: []string{"flag-1", "flag-2", "flag-3"},
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The list now includes all three flags
|
||||||
|
req = NewRequestf(t, "GET", baseURLFmtStr, "").AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &flags)
|
||||||
|
assert.Len(t, flags, 3)
|
||||||
|
for _, flag := range []string{"flag-1", "flag-2", "flag-3"} {
|
||||||
|
assert.True(t, slices.Contains(flags, flag))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check a flag that is on the repo
|
||||||
|
req = NewRequestf(t, "GET", baseURLFmtStr, "/flag-1").AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
// Check a flag that isn't on the repo
|
||||||
|
req = NewRequestf(t, "GET", baseURLFmtStr, "/no-such-flag").AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
|
||||||
|
// We can add the same flag twice
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
req = NewRequestf(t, "PUT", baseURLFmtStr, "/brand-new-flag").AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The new flag is there
|
||||||
|
req = NewRequestf(t, "GET", baseURLFmtStr, "/brand-new-flag").AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
// We can delete a flag, twice
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
req = NewRequestf(t, "DELETE", baseURLFmtStr, "/flag-3").AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can delete a flag that wasn't there
|
||||||
|
req = NewRequestf(t, "DELETE", baseURLFmtStr, "/no-such-flag").AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
// We can delete all of the flags in one go, too
|
||||||
|
req = NewRequestf(t, "DELETE", baseURLFmtStr, "").AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
// ..once all flags are deleted, none are listed, either
|
||||||
|
req = NewRequestf(t, "GET", baseURLFmtStr, "").AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &flags)
|
||||||
|
assert.Empty(t, flags)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestRepositoryFlagsUI(t *testing.T) {
|
func TestRepositoryFlagsUI(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
defer test.MockVariableValue(&setting.Repository.EnableFlags, true)()
|
defer test.MockVariableValue(&setting.Repository.EnableFlags, true)()
|
||||||
|
|
Loading…
Reference in a new issue