From 091b5493f6a7a9e7c7694766d1a529bcb009522a Mon Sep 17 00:00:00 2001 From: Dinesh Salunke Date: Sun, 3 Jul 2022 12:39:19 +0530 Subject: [PATCH 01/85] test(projects): start of the implementations --- integrations/api_repo_project_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 integrations/api_repo_project_test.go diff --git a/integrations/api_repo_project_test.go b/integrations/api_repo_project_test.go new file mode 100644 index 0000000000000..5d71f4ba1f5ed --- /dev/null +++ b/integrations/api_repo_project_test.go @@ -0,0 +1,13 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "testing" +) + +func TestAPIRepoProjects(t *testing.T) { + defer prepareTestEnv(t)() +} From ce3dbfd14dbaf1b372d9afc5a55c768300e65eb6 Mon Sep 17 00:00:00 2001 From: Dinesh Salunke Date: Sun, 3 Jul 2022 14:48:05 +0530 Subject: [PATCH 02/85] refactor(project): project and board swagger documentation --- modules/structs/project.go | 33 ++ routers/api/v1/repo/project.go | 350 +++++++++++++++++ routers/api/v1/swagger/options.go | 6 + routers/api/v1/swagger/repo.go | 24 ++ templates/swagger/v1_json.tmpl | 601 +++++++++++++++++++++++++++++- 5 files changed, 1013 insertions(+), 1 deletion(-) create mode 100644 modules/structs/project.go create mode 100644 routers/api/v1/repo/project.go diff --git a/modules/structs/project.go b/modules/structs/project.go new file mode 100644 index 0000000000000..a2c935e9d8663 --- /dev/null +++ b/modules/structs/project.go @@ -0,0 +1,33 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package structs + +// swagger:model +type UpsertProjectPayload struct { + Title string `json:"title" binding:"Required"` + Description string `json:"body"` + BoardType uint8 `json:"board_type"` +} + +type Project struct { + Title string `json:"title"` + Description string `json:"body"` + BoardType uint8 `json:"board_type"` +} + +type ProjectBoard struct { + Title string `json:"title"` + Default bool `json:"default"` + Color string `json:"color"` + Sorting int8 `json:"sorting"` +} + +// swagger:model +type UpsertProjectBoardPayload struct { + Title string `json:"title"` + Default bool `json:"default"` + Color string `json:"color"` + Sorting int8 `json:"sorting"` +} diff --git a/routers/api/v1/repo/project.go b/routers/api/v1/repo/project.go new file mode 100644 index 0000000000000..59a5b722beabc --- /dev/null +++ b/routers/api/v1/repo/project.go @@ -0,0 +1,350 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "code.gitea.io/gitea/modules/context" +) + +// CreateProject create a project for a repository +func CreateProject(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/projects repository repoCreateProject + // --- + // summary: Create a project for a repository + // consumes: + // - application/json + // 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/CreateProjectPayload" + // responses: + // "201": + // "$ref": "#/responses/Project" + // "403": + // "$ref": "#/responses/forbidden" + // "412": + // "$ref": "#/responses/error" + // "422": + // "$ref": "#/responses/validationError" +} + +// EditProject +func EditProject(ctx *context.APIContext) { + // swagger:operation PATCH /repos/{owner}/{repo}/projects/{id} repository repoEditProject + // --- + // summary: Edit a project + // produces: + // - application/json + // consumes: + // - 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: id + // in: path + // description: id of the project + // type: string + // required: true + // responses: + // "201": + // "$ref": "#/responses/Project" + // "403": + // "$ref": "#/responses/forbidden" + // "412": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + +} + +// GetProject a single project for repository +func GetProject(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/projects/{id} repository repoGetProject + // --- + // summary: List a single project + // 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: id + // in: path + // description: id of the project + // type: string + // required: true + // responses: + // "201": + // "$ref": "#/responses/Project" + // "404": + // "$ref": "#/responses/notFound" +} + +// ListProjects list all the projects for a particular repository +func ListProjects(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/projects repository repoListProjects + // --- + // summary: List a repository's projects + // 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: + // "201": + // "$ref": "#/responses/ProjectList" +} + +// DeleteProject delete a project from particular repository +func DeleteProject(ctx *context.APIContext) { + // swagger:operation DELETE /repos/{owner}/{repo}/projects/{id} repository repoDeleteProject + // --- + // summary: Delete a project + // 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: id + // in: path + // description: id of the project + // type: string + // required: true + // responses: + // "404": + // "$ref": "#/responses/notFound" +} + +// Project Boards + +func CreateBoard(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/projects/{projectId}/boards repository repoCreateProjectBoard + // --- + // summary: Create a board + // consumes: + // - application/json + // 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: projectId + // in: path + // description: project id + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpsertProjectBoardPayload" + // responses: + // "201": + // "$ref": "#/responses/ProjectBoard" + // "403": + // "$ref": "#/responses/forbidden" + // "412": + // "$ref": "#/responses/error" + // "422": + // "$ref": "#/responses/validationError" +} + +func EditProjectBoard(ctx *context.APIContext) { + // swagger:operation PATCH /repos/{owner}/{repo}/projects/{projectId}/boards/{id} repository repoEditProjectBoard + // --- + // summary: Edit Project Board + // consumes: + // - application/json + // 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: projectId + // in: path + // description: project id + // type: string + // required: true + // - name: id + // in: path + // description: board id + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpsertProjectBoardPayload" + // responses: + // "200": + // "$ref": "#/responses/ProjectBoard" + // "403": + // "$ref": "#/responses/forbidden" + // "412": + // "$ref": "#/responses/error" + // "422": + // "$ref": "#/responses/validationError" +} + +func GetProjectBoard(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/projects/{projectId}/boards/{id} repository repoGetProjectBoard + // --- + // summary: Create a board + // consumes: + // - application/json + // 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: projectId + // in: path + // description: project id + // type: string + // required: true + // - name: body + // - name: id + // in: path + // description: project id + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ProjectBoard" + // "403": + // "$ref": "#/responses/forbidden" +} + +func ListProjectBoards(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/projects/{projectId}/boards repository repoListProjectBoards + // --- + // summary: Get list of project boards + // consumes: + // - application/json + // 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: projectId + // in: path + // description: project id + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ProjectBoardList" + // "403": + // "$ref": "#/responses/forbidden" +} + +func DeleteProjectBoard(ctx *context.APIContext) { + // swagger:operation DELETE /repos/{owner}/{repo}/projects/{projectId}/boards/{id} repository repoDeleteProjectBoard + // --- + // summary: Delete project board + // consumes: + // - application/json + // 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: projectId + // in: path + // description: project id + // type: string + // required: true + // - name: id + // in: path + // description: board id + // type: string + // required: true + // responses: + // "403": + // "$ref": "#/responses/forbidden" +} diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index 2bd43c6180870..5271fe697343d 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -172,4 +172,10 @@ type swaggerParameterBodies struct { // in:body CreateWikiPageOptions api.CreateWikiPageOptions + + // in:body + UpsertProjectPayload api.UpsertProjectPayload + + // in:body + UpsertProjectBoardPayload api.UpsertProjectBoardPayload } diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index ab802db7812fe..9132a858b8352 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -351,3 +351,27 @@ type swaggerRepoCollaboratorPermission struct { // in:body Body api.RepoCollaboratorPermission `json:"body"` } + +// swagger:response Project +type swaggerProject struct { + // in:body + Body api.Project `json:"body"` +} + +// swagger:response ProjectList +type swaggerProjectList struct { + // in:body + Body []api.Project `json:"body"` +} + +// swagger:response ProjectBoard +type swaggerProjectBoard struct { + // in:body + Body api.ProjectBoard `json:"body"` +} + +// swagger:response ProjectBoardList +type swaggerProjectBoardList struct { + // in:body + Body []api.ProjectBoard `json:"body"` +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index f3f9a33672253..11296559433d6 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -7662,6 +7662,491 @@ } } }, + "/repos/{owner}/{repo}/projects": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "List a repository's projects", + "operationId": "repoListProjects", + "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": { + "201": { + "$ref": "#/responses/ProjectList" + } + } + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Create a project for a repository", + "operationId": "repoCreateProject", + "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/CreateProjectPayload" + } + } + ], + "responses": { + "201": { + "$ref": "#/responses/Project" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "412": { + "$ref": "#/responses/error" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, + "/repos/{owner}/{repo}/projects/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "List a single project", + "operationId": "repoGetProject", + "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": "id of the project", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "$ref": "#/responses/Project" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Delete a project", + "operationId": "repoDeleteProject", + "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": "id of the project", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Edit a project", + "operationId": "repoEditProject", + "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": "id of the project", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "$ref": "#/responses/Project" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "412": { + "$ref": "#/responses/error" + } + } + } + }, + "/repos/{owner}/{repo}/projects/{projectId}/boards": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Get list of project boards", + "operationId": "repoListProjectBoards", + "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": "project id", + "name": "projectId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ProjectBoardList" + }, + "403": { + "$ref": "#/responses/forbidden" + } + } + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Create a board", + "operationId": "repoCreateProjectBoard", + "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": "project id", + "name": "projectId", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/UpsertProjectBoardPayload" + } + } + ], + "responses": { + "201": { + "$ref": "#/responses/ProjectBoard" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "412": { + "$ref": "#/responses/error" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, + "/repos/{owner}/{repo}/projects/{projectId}/boards/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Create a board", + "operationId": "repoGetProjectBoard", + "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": "project id", + "name": "projectId", + "in": "path", + "required": true + }, + { + "name": "body" + }, + { + "type": "string", + "description": "project id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ProjectBoard" + }, + "403": { + "$ref": "#/responses/forbidden" + } + } + }, + "delete": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Delete project board", + "operationId": "repoDeleteProjectBoard", + "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": "project id", + "name": "projectId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "board id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "403": { + "$ref": "#/responses/forbidden" + } + } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Edit Project Board", + "operationId": "repoEditProjectBoard", + "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": "project id", + "name": "projectId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "board id", + "name": "id", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/UpsertProjectBoardPayload" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/ProjectBoard" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "412": { + "$ref": "#/responses/error" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, "/repos/{owner}/{repo}/pulls": { "get": { "produces": [ @@ -17263,6 +17748,48 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "Project": { + "type": "object", + "properties": { + "board_type": { + "type": "integer", + "format": "uint8", + "x-go-name": "BoardType" + }, + "body": { + "type": "string", + "x-go-name": "Description" + }, + "title": { + "type": "string", + "x-go-name": "Title" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, + "ProjectBoard": { + "type": "object", + "properties": { + "color": { + "type": "string", + "x-go-name": "Color" + }, + "default": { + "type": "boolean", + "x-go-name": "Default" + }, + "sorting": { + "type": "integer", + "format": "int8", + "x-go-name": "Sorting" + }, + "title": { + "type": "string", + "x-go-name": "Title" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "PublicKey": { "description": "PublicKey publickey is a user key to push code to repository", "type": "object", @@ -18499,6 +19026,48 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "UpsertProjectBoardPayload": { + "type": "object", + "properties": { + "color": { + "type": "string", + "x-go-name": "Color" + }, + "default": { + "type": "boolean", + "x-go-name": "Default" + }, + "sorting": { + "type": "integer", + "format": "int8", + "x-go-name": "Sorting" + }, + "title": { + "type": "string", + "x-go-name": "Title" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, + "UpsertProjectPayload": { + "type": "object", + "properties": { + "board_type": { + "type": "integer", + "format": "uint8", + "x-go-name": "BoardType" + }, + "body": { + "type": "string", + "x-go-name": "Description" + }, + "title": { + "type": "string", + "x-go-name": "Title" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "User": { "description": "User represents a user", "type": "object", @@ -19307,6 +19876,36 @@ } } }, + "Project": { + "description": "", + "schema": { + "$ref": "#/definitions/Project" + } + }, + "ProjectBoard": { + "description": "", + "schema": { + "$ref": "#/definitions/ProjectBoard" + } + }, + "ProjectBoardList": { + "description": "", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/ProjectBoard" + } + } + }, + "ProjectList": { + "description": "", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Project" + } + } + }, "PublicKey": { "description": "PublicKey", "schema": { @@ -19646,7 +20245,7 @@ "parameterBodies": { "description": "parameterBodies", "schema": { - "$ref": "#/definitions/CreateWikiPageOptions" + "$ref": "#/definitions/UpsertProjectBoardPayload" } }, "redirect": { From 781b1cc1c87cf01bbdcb6615e2421ec09f1f1b34 Mon Sep 17 00:00:00 2001 From: Dinesh Salunke Date: Sun, 3 Jul 2022 15:00:56 +0530 Subject: [PATCH 03/85] refactor(project): fix some swagger errors --- routers/api/v1/repo/project.go | 7 +------ templates/swagger/v1_json.tmpl | 11 +---------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/routers/api/v1/repo/project.go b/routers/api/v1/repo/project.go index 59a5b722beabc..780505f803690 100644 --- a/routers/api/v1/repo/project.go +++ b/routers/api/v1/repo/project.go @@ -32,7 +32,7 @@ func CreateProject(ctx *context.APIContext) { // - name: body // in: body // schema: - // "$ref": "#/definitions/CreateProjectPayload" + // "$ref": "#/definitions/UpsertProjectPayload" // responses: // "201": // "$ref": "#/responses/Project" @@ -251,8 +251,6 @@ func GetProjectBoard(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/projects/{projectId}/boards/{id} repository repoGetProjectBoard // --- // summary: Create a board - // consumes: - // - application/json // produces: // - application/json // parameters: @@ -271,7 +269,6 @@ func GetProjectBoard(ctx *context.APIContext) { // description: project id // type: string // required: true - // - name: body // - name: id // in: path // description: project id @@ -319,8 +316,6 @@ func DeleteProjectBoard(ctx *context.APIContext) { // swagger:operation DELETE /repos/{owner}/{repo}/projects/{projectId}/boards/{id} repository repoDeleteProjectBoard // --- // summary: Delete project board - // consumes: - // - application/json // produces: // - application/json // parameters: diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 11296559433d6..a7da889bf9ade 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -7725,7 +7725,7 @@ "name": "body", "in": "body", "schema": { - "$ref": "#/definitions/CreateProjectPayload" + "$ref": "#/definitions/UpsertProjectPayload" } } ], @@ -7981,9 +7981,6 @@ }, "/repos/{owner}/{repo}/projects/{projectId}/boards/{id}": { "get": { - "consumes": [ - "application/json" - ], "produces": [ "application/json" ], @@ -8014,9 +8011,6 @@ "in": "path", "required": true }, - { - "name": "body" - }, { "type": "string", "description": "project id", @@ -8035,9 +8029,6 @@ } }, "delete": { - "consumes": [ - "application/json" - ], "produces": [ "application/json" ], From 7103a2c65cb3d9f00b927594d10bdddb2b4670ba Mon Sep 17 00:00:00 2001 From: Dinesh Salunke Date: Sat, 9 Jul 2022 20:04:22 +0530 Subject: [PATCH 04/85] refactor(project): minor cleanup --- routers/api/v1/repo/project.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/routers/api/v1/repo/project.go b/routers/api/v1/repo/project.go index 780505f803690..0b5c93298434c 100644 --- a/routers/api/v1/repo/project.go +++ b/routers/api/v1/repo/project.go @@ -78,7 +78,6 @@ func EditProject(ctx *context.APIContext) { // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" - } // GetProject a single project for repository @@ -164,7 +163,7 @@ func DeleteProject(ctx *context.APIContext) { // Project Boards -func CreateBoard(ctx *context.APIContext) { +func CreateProjectBoard(ctx *context.APIContext) { // swagger:operation POST /repos/{owner}/{repo}/projects/{projectId}/boards repository repoCreateProjectBoard // --- // summary: Create a board From 45f44cb5830b1891a48aee5247541d3338fc37c5 Mon Sep 17 00:00:00 2001 From: Dinesh Salunke Date: Sat, 9 Jul 2022 23:08:25 +0530 Subject: [PATCH 05/85] refactor(api): try to match with github api --- routers/api/v1/repo/board.go | 119 ++++++ routers/api/v1/repo/project.go | 283 ++------------ templates/swagger/v1_json.tmpl | 686 +++++++++++++-------------------- 3 files changed, 420 insertions(+), 668 deletions(-) create mode 100644 routers/api/v1/repo/board.go diff --git a/routers/api/v1/repo/board.go b/routers/api/v1/repo/board.go new file mode 100644 index 0000000000000..fa323155776f9 --- /dev/null +++ b/routers/api/v1/repo/board.go @@ -0,0 +1,119 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "code.gitea.io/gitea/modules/context" +) + +func GetProjectBoard(ctx *context.APIContext) { + // swagger:operation GET /projects/boards/{id} board boardGetProjectBoard + // --- + // summary: Get project board + // produces: + // - application/json + // parameters: + // - name: id + // in: path + // description: id of the board + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ProjectBoard" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" +} + +func UpdateProjectBoard(ctx *context.APIContext) { + // swagger:operation PATCH /projects/boards/{id} board boardUpdateProjectBoard + // --- + // summary: Update project board + // produces: + // - application/json + // consumes: + // - application/json + // parameters: + // - name: id + // in: path + // description: id of the project board + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ProjectBoard" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" +} + +func DeleteProjectBoard(ctx *context.APIContext) { + // swagger:operation DELETE /projects/boards/{id} board boardDeleteProjectBoard + // --- + // summary: Delete project board + // produces: + // - application/json + // parameters: + // - name: id + // in: path + // description: id of the project board + // type: string + // required: true + // responses: + // "204": + // "description": "Project board deleted" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" +} + +func ListProjectBoards(ctx *context.APIContext) { + // swagger:operation GET /projects/{projectId}/boards board boardGetProjectBoards + // --- + // summary: Get project boards + // produces: + // - application/json + // parameters: + // - name: projectId + // in: path + // description: projectId of the project + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ProjectBoardList" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" +} + +func CreateProjectBoard(ctx *context.APIContext) { + // swagger:operation POST /projects/{projectId}/boards board boardCreateProjectBoard + // --- + // summary: Create project board + // produces: + // - application/json + // consumes: + // - application/json + // parameters: + // - name: id + // in: path + // description: id of the project + // type: string + // required: true + // responses: + // "201": + // "$ref": "#/responses/ProjectBoard" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" +} diff --git a/routers/api/v1/repo/project.go b/routers/api/v1/repo/project.go index 0b5c93298434c..2d82d330c4e11 100644 --- a/routers/api/v1/repo/project.go +++ b/routers/api/v1/repo/project.go @@ -9,336 +9,117 @@ import ( "code.gitea.io/gitea/modules/context" ) -// CreateProject create a project for a repository -func CreateProject(ctx *context.APIContext) { - // swagger:operation POST /repos/{owner}/{repo}/projects repository repoCreateProject +func GetProject(ctx *context.APIContext) { + // swagger:operation GET /projects/{id} project projectGetProject // --- - // summary: Create a project for a repository - // consumes: - // - application/json + // summary: Get project // 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/UpsertProjectPayload" - // responses: - // "201": - // "$ref": "#/responses/Project" - // "403": - // "$ref": "#/responses/forbidden" - // "412": - // "$ref": "#/responses/error" - // "422": - // "$ref": "#/responses/validationError" -} - -// EditProject -func EditProject(ctx *context.APIContext) { - // swagger:operation PATCH /repos/{owner}/{repo}/projects/{id} repository repoEditProject - // --- - // summary: Edit a project - // produces: - // - application/json - // consumes: - // - 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: id // in: path // description: id of the project // type: string // required: true // responses: - // "201": + // "200": // "$ref": "#/responses/Project" // "403": // "$ref": "#/responses/forbidden" - // "412": - // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" } -// GetProject a single project for repository -func GetProject(ctx *context.APIContext) { - // swagger:operation GET /repos/{owner}/{repo}/projects/{id} repository repoGetProject +func UpdateProject(ctx *context.APIContext) { + // swagger:operation PATCH /projects/{id} project projectUpdateProject // --- - // summary: List a single project + // summary: Update project // 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: id // in: path // description: id of the project // type: string // required: true // responses: - // "201": + // "200": // "$ref": "#/responses/Project" + // "403": + // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" } -// ListProjects list all the projects for a particular repository -func ListProjects(ctx *context.APIContext) { - // swagger:operation GET /repos/{owner}/{repo}/projects repository repoListProjects - // --- - // summary: List a repository's projects - // 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: - // "201": - // "$ref": "#/responses/ProjectList" -} - -// DeleteProject delete a project from particular repository func DeleteProject(ctx *context.APIContext) { - // swagger:operation DELETE /repos/{owner}/{repo}/projects/{id} repository repoDeleteProject + // swagger:operation DELETE /projects/{id} project projectDeleteProject // --- - // summary: Delete a project + // summary: Delete project // 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: id // in: path // description: id of the project // type: string // required: true // responses: - // "404": - // "$ref": "#/responses/notFound" -} - -// Project Boards - -func CreateProjectBoard(ctx *context.APIContext) { - // swagger:operation POST /repos/{owner}/{repo}/projects/{projectId}/boards repository repoCreateProjectBoard - // --- - // summary: Create a board - // consumes: - // - application/json - // 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: projectId - // in: path - // description: project id - // type: string - // required: true - // - name: body - // in: body - // schema: - // "$ref": "#/definitions/UpsertProjectBoardPayload" - // responses: - // "201": - // "$ref": "#/responses/ProjectBoard" - // "403": - // "$ref": "#/responses/forbidden" - // "412": - // "$ref": "#/responses/error" - // "422": - // "$ref": "#/responses/validationError" -} - -func EditProjectBoard(ctx *context.APIContext) { - // swagger:operation PATCH /repos/{owner}/{repo}/projects/{projectId}/boards/{id} repository repoEditProjectBoard - // --- - // summary: Edit Project Board - // consumes: - // - application/json - // 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: projectId - // in: path - // description: project id - // type: string - // required: true - // - name: id - // in: path - // description: board id - // type: string - // required: true - // - name: body - // in: body - // schema: - // "$ref": "#/definitions/UpsertProjectBoardPayload" - // responses: // "200": - // "$ref": "#/responses/ProjectBoard" + // "description": "Deleted the project" // "403": // "$ref": "#/responses/forbidden" - // "412": - // "$ref": "#/responses/error" - // "422": - // "$ref": "#/responses/validationError" + // "404": + // "$ref": "#/responses/notFound" } -func GetProjectBoard(ctx *context.APIContext) { - // swagger:operation GET /repos/{owner}/{repo}/projects/{projectId}/boards/{id} repository repoGetProjectBoard +func CreateRepositoryProject(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/projects project projectCreateRepositoryProject // --- - // summary: Create a board + // summary: Create a repository project // produces: // - application/json // parameters: // - name: owner // in: path - // description: owner of the repo + // description: owner of repo // type: string // required: true // - name: repo // in: path - // description: name of the repo - // type: string - // required: true - // - name: projectId - // in: path - // description: project id - // type: string - // required: true - // - name: id - // in: path - // description: project id + // description: repo // type: string // required: true // responses: - // "200": - // "$ref": "#/responses/ProjectBoard" + // "201": + // "$ref": "#/responses/Project" // "403": // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" } -func ListProjectBoards(ctx *context.APIContext) { - // swagger:operation GET /repos/{owner}/{repo}/projects/{projectId}/boards repository repoListProjectBoards +func ListRepositoryProjects(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/projects project projectListRepositoryProjects // --- - // summary: Get list of project boards - // consumes: - // - application/json + // summary: List repository projects // produces: // - application/json // parameters: // - name: owner // in: path - // description: owner of the repo + // description: owner of the repository // type: string // required: true // - name: repo // in: path - // description: name of the repo - // type: string - // required: true - // - name: projectId - // in: path - // description: project id + // description: repo // type: string // required: true // responses: // "200": - // "$ref": "#/responses/ProjectBoardList" - // "403": - // "$ref": "#/responses/forbidden" -} - -func DeleteProjectBoard(ctx *context.APIContext) { - // swagger:operation DELETE /repos/{owner}/{repo}/projects/{projectId}/boards/{id} repository repoDeleteProjectBoard - // --- - // summary: Delete project board - // 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: projectId - // in: path - // description: project id - // type: string - // required: true - // - name: id - // in: path - // description: board id - // type: string - // required: true - // responses: + // "$ref": "#/responses/ProjectList" // "403": // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index a7da889bf9ade..0c103333eb44a 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -2129,6 +2129,258 @@ } } }, + "/projects/boards/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "board" + ], + "summary": "Get project board", + "operationId": "boardGetProjectBoard", + "parameters": [ + { + "type": "string", + "description": "id of the board", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ProjectBoard" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "board" + ], + "summary": "Delete project board", + "operationId": "boardDeleteProjectBoard", + "parameters": [ + { + "type": "string", + "description": "id of the project board", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "Project board deleted" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "board" + ], + "summary": "Update project board", + "operationId": "boardUpdateProjectBoard", + "parameters": [ + { + "type": "string", + "description": "id of the project board", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ProjectBoard" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/projects/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "project" + ], + "summary": "Get project", + "operationId": "projectGetProject", + "parameters": [ + { + "type": "string", + "description": "id of the project", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/Project" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "project" + ], + "summary": "Delete project", + "operationId": "projectDeleteProject", + "parameters": [ + { + "type": "string", + "description": "id of the project", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Deleted the project" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "patch": { + "produces": [ + "application/json" + ], + "tags": [ + "project" + ], + "summary": "Update project", + "operationId": "projectUpdateProject", + "parameters": [ + { + "type": "string", + "description": "id of the project", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/Project" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/projects/{projectId}/boards": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "board" + ], + "summary": "Get project boards", + "operationId": "boardGetProjectBoards", + "parameters": [ + { + "type": "string", + "description": "projectId of the project", + "name": "projectId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ProjectBoardList" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "board" + ], + "summary": "Create project board", + "operationId": "boardCreateProjectBoard", + "parameters": [ + { + "type": "string", + "description": "id of the project", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "$ref": "#/responses/ProjectBoard" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/repos/issues/search": { "get": { "produces": [ @@ -7668,196 +7920,61 @@ "application/json" ], "tags": [ - "repository" + "project" ], - "summary": "List a repository's projects", - "operationId": "repoListProjects", + "summary": "List repository projects", + "operationId": "projectListRepositoryProjects", "parameters": [ { "type": "string", - "description": "owner of the repo", + "description": "owner of the repository", "name": "owner", "in": "path", "required": true }, { "type": "string", - "description": "name of the repo", + "description": "repo", "name": "repo", "in": "path", "required": true } ], "responses": { - "201": { + "200": { "$ref": "#/responses/ProjectList" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" } } }, "post": { - "consumes": [ - "application/json" - ], "produces": [ "application/json" ], "tags": [ - "repository" + "project" ], - "summary": "Create a project for a repository", - "operationId": "repoCreateProject", + "summary": "Create a repository project", + "operationId": "projectCreateRepositoryProject", "parameters": [ { "type": "string", - "description": "owner of the repo", + "description": "owner of repo", "name": "owner", "in": "path", "required": true }, { "type": "string", - "description": "name of the repo", + "description": "repo", "name": "repo", "in": "path", "required": true - }, - { - "name": "body", - "in": "body", - "schema": { - "$ref": "#/definitions/UpsertProjectPayload" - } - } - ], - "responses": { - "201": { - "$ref": "#/responses/Project" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "412": { - "$ref": "#/responses/error" - }, - "422": { - "$ref": "#/responses/validationError" - } - } - } - }, - "/repos/{owner}/{repo}/projects/{id}": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "repository" - ], - "summary": "List a single project", - "operationId": "repoGetProject", - "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": "id of the project", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "201": { - "$ref": "#/responses/Project" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - }, - "delete": { - "produces": [ - "application/json" - ], - "tags": [ - "repository" - ], - "summary": "Delete a project", - "operationId": "repoDeleteProject", - "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": "id of the project", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "404": { - "$ref": "#/responses/notFound" - } - } - }, - "patch": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "repository" - ], - "summary": "Edit a project", - "operationId": "repoEditProject", - "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": "id of the project", - "name": "id", - "in": "path", - "required": true } ], "responses": { @@ -7869,271 +7986,6 @@ }, "404": { "$ref": "#/responses/notFound" - }, - "412": { - "$ref": "#/responses/error" - } - } - } - }, - "/repos/{owner}/{repo}/projects/{projectId}/boards": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "repository" - ], - "summary": "Get list of project boards", - "operationId": "repoListProjectBoards", - "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": "project id", - "name": "projectId", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "$ref": "#/responses/ProjectBoardList" - }, - "403": { - "$ref": "#/responses/forbidden" - } - } - }, - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "repository" - ], - "summary": "Create a board", - "operationId": "repoCreateProjectBoard", - "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": "project id", - "name": "projectId", - "in": "path", - "required": true - }, - { - "name": "body", - "in": "body", - "schema": { - "$ref": "#/definitions/UpsertProjectBoardPayload" - } - } - ], - "responses": { - "201": { - "$ref": "#/responses/ProjectBoard" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "412": { - "$ref": "#/responses/error" - }, - "422": { - "$ref": "#/responses/validationError" - } - } - } - }, - "/repos/{owner}/{repo}/projects/{projectId}/boards/{id}": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "repository" - ], - "summary": "Create a board", - "operationId": "repoGetProjectBoard", - "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": "project id", - "name": "projectId", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "project id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "$ref": "#/responses/ProjectBoard" - }, - "403": { - "$ref": "#/responses/forbidden" - } - } - }, - "delete": { - "produces": [ - "application/json" - ], - "tags": [ - "repository" - ], - "summary": "Delete project board", - "operationId": "repoDeleteProjectBoard", - "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": "project id", - "name": "projectId", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "board id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "403": { - "$ref": "#/responses/forbidden" - } - } - }, - "patch": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "repository" - ], - "summary": "Edit Project Board", - "operationId": "repoEditProjectBoard", - "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": "project id", - "name": "projectId", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "board id", - "name": "id", - "in": "path", - "required": true - }, - { - "name": "body", - "in": "body", - "schema": { - "$ref": "#/definitions/UpsertProjectBoardPayload" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/ProjectBoard" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "412": { - "$ref": "#/responses/error" - }, - "422": { - "$ref": "#/responses/validationError" } } } From d32ab45f39755346ac774bed4559f800afa3378c Mon Sep 17 00:00:00 2001 From: Gusted Date: Mon, 4 Jul 2022 05:33:55 +0200 Subject: [PATCH 06/85] Remove `GO111MODULE` (#20221) - Given we use go1.18 for this and don't rely on the Go 1.11 modules behavior(we use the modern `go run` & `go get` which has the correct behavior by-default). --- Makefile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 2e2c86376b3b0..3e2f1524e4052 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,6 @@ else DIST := dist DIST_DIRS := $(DIST)/binaries $(DIST)/release IMPORT := code.gitea.io/gitea -export GO111MODULE=on GO ?= go SHASUM ?= shasum -a 256 @@ -363,7 +362,7 @@ test\#%: coverage: grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out - GO111MODULE=on $(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all || (echo "gocovmerge failed"; echo "integration.coverage.out"; cat integration.coverage.out; echo "coverage.out"; cat coverage.out; exit 1) + $(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all || (echo "gocovmerge failed"; echo "integration.coverage.out"; cat integration.coverage.out; echo "coverage.out"; cat coverage.out; exit 1) .PHONY: unit-test-coverage unit-test-coverage: @@ -754,11 +753,11 @@ update-translations: .PHONY: generate-license generate-license: - GO111MODULE=on $(GO) run build/generate-licenses.go + $(GO) run build/generate-licenses.go .PHONY: generate-gitignore generate-gitignore: - GO111MODULE=on $(GO) run build/generate-gitignores.go + $(GO) run build/generate-gitignores.go .PHONY: generate-images generate-images: | node_modules From fa3dc73da66544fe985086be3d871e036325c946 Mon Sep 17 00:00:00 2001 From: Steven Kriegler <61625851+justusbunsi@users.noreply.github.com> Date: Mon, 4 Jul 2022 11:21:14 +0200 Subject: [PATCH 07/85] Allow enable LDAP source and disable user sync via CLI (#20206) The current `admin auth` CLI for managing authentication source of type LDAP via BindDN and Simple LDAP does not allow enabling the respective source, once disabled via `--not-active`. The same applies to `--synchronize-users` specifially for LDAP via BindDN. These changes add two new flags to LDAP related CLI commands: - `--active` for both LDAP authentication source types - `--disable-synchronize-users` for LDAP via BindDN Signed-off-by: justusbunsi <61625851+justusbunsi@users.noreply.github.com> --- cmd/admin_auth_ldap.go | 14 +++++++++ cmd/admin_auth_ldap_test.go | 57 +++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index ec86b2c671d47..9040def822e37 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -34,6 +34,10 @@ var ( Name: "not-active", Usage: "Deactivate the authentication source.", }, + cli.BoolFlag{ + Name: "active", + Usage: "Activate the authentication source.", + }, cli.StringFlag{ Name: "security-protocol", Usage: "Security protocol name.", @@ -117,6 +121,10 @@ var ( Name: "synchronize-users", Usage: "Enable user synchronization.", }, + cli.BoolFlag{ + Name: "disable-synchronize-users", + Usage: "Disable user synchronization.", + }, cli.UintFlag{ Name: "page-size", Usage: "Search page size.", @@ -183,9 +191,15 @@ func parseAuthSource(c *cli.Context, authSource *auth.Source) { if c.IsSet("not-active") { authSource.IsActive = !c.Bool("not-active") } + if c.IsSet("active") { + authSource.IsActive = c.Bool("active") + } if c.IsSet("synchronize-users") { authSource.IsSyncEnabled = c.Bool("synchronize-users") } + if c.IsSet("disable-synchronize-users") { + authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users") + } } // parseLdapConfig assigns values on config according to command line flags. diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index f050b536fdf50..2180b24be58df 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -858,6 +858,36 @@ func TestUpdateLdapBindDn(t *testing.T) { }, errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2", }, + // case 24 + { + args: []string{ + "ldap-test", + "--id", "24", + "--name", "ldap (via Bind DN) flip 'active' and 'user sync' attributes", + "--active", + "--disable-synchronize-users", + }, + id: 24, + existingAuthSource: &auth.Source{ + Type: auth.LDAP, + IsActive: false, + IsSyncEnabled: true, + Cfg: &ldap.Source{ + Name: "ldap (via Bind DN) flip 'active' and 'user sync' attributes", + Enabled: true, + }, + }, + authSource: &auth.Source{ + Type: auth.LDAP, + Name: "ldap (via Bind DN) flip 'active' and 'user sync' attributes", + IsActive: true, + IsSyncEnabled: false, + Cfg: &ldap.Source{ + Name: "ldap (via Bind DN) flip 'active' and 'user sync' attributes", + Enabled: true, + }, + }, + }, } for n, c := range cases { @@ -1221,6 +1251,33 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { }, errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM", }, + // case 20 + { + args: []string{ + "ldap-test", + "--id", "20", + "--name", "ldap (simple auth) flip 'active' attribute", + "--active", + }, + id: 20, + existingAuthSource: &auth.Source{ + Type: auth.DLDAP, + IsActive: false, + Cfg: &ldap.Source{ + Name: "ldap (simple auth) flip 'active' attribute", + Enabled: true, + }, + }, + authSource: &auth.Source{ + Type: auth.DLDAP, + Name: "ldap (simple auth) flip 'active' attribute", + IsActive: true, + Cfg: &ldap.Source{ + Name: "ldap (simple auth) flip 'active' attribute", + Enabled: true, + }, + }, + }, } for n, c := range cases { From a7f1f6a1be6ba277d565c5874435b6f4db74dae5 Mon Sep 17 00:00:00 2001 From: zeripath Date: Mon, 4 Jul 2022 11:17:09 +0100 Subject: [PATCH 08/85] Allow dev i18n to be more concurrent (#20159) The recent changes to add live-reloading to the i18n translation files made the i18n code totally non-concurrent when using dev. This will make discovering other concurrency related issues far more difficult. This PR fixes these, adds some more comments to the code and slightly restructures a few functions. Signed-off-by: Andrew Thornton --- modules/translation/i18n/i18n.go | 242 +++++++++++++++++++++---------- 1 file changed, 168 insertions(+), 74 deletions(-) diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go index acce5f19fb0dc..bb906f3c08c1d 100644 --- a/modules/translation/i18n/i18n.go +++ b/modules/translation/i18n/i18n.go @@ -25,9 +25,13 @@ var ( ) type locale struct { + // This mutex will be set if we have live-reload enabled (e.g. dev mode) + reloadMu *sync.RWMutex + store *LocaleStore langName string - textMap map[int]string // the map key (idx) is generated by store's textIdxMap + + idxToMsgMap map[int]string // the map idx is generated by store's trKeyToIdxMap sourceFileName string sourceFileInfo os.FileInfo @@ -35,164 +39,254 @@ type locale struct { } type LocaleStore struct { - reloadMu *sync.Mutex // for non-prod(dev), use a mutex for live-reload. for prod, no mutex, no live-reload. + // This mutex will be set if we have live-reload enabled (e.g. dev mode) + reloadMu *sync.RWMutex langNames []string langDescs []string + localeMap map[string]*locale - localeMap map[string]*locale - textIdxMap map[string]int + // this needs to be locked when live-reloading + trKeyToIdxMap map[string]int defaultLang string } func NewLocaleStore(isProd bool) *LocaleStore { - ls := &LocaleStore{localeMap: make(map[string]*locale), textIdxMap: make(map[string]int)} + store := &LocaleStore{localeMap: make(map[string]*locale), trKeyToIdxMap: make(map[string]int)} if !isProd { - ls.reloadMu = &sync.Mutex{} + store.reloadMu = &sync.RWMutex{} } - return ls + return store } // AddLocaleByIni adds locale by ini into the store -// if source is a string, then the file is loaded. in dev mode, the file can be live-reloaded +// if source is a string, then the file is loaded. In dev mode, this file will be checked for live-reloading // if source is a []byte, then the content is used -func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, source interface{}) error { - if _, ok := ls.localeMap[langName]; ok { +// Note: this is not concurrent safe +func (store *LocaleStore) AddLocaleByIni(langName, langDesc string, source interface{}) error { + if _, ok := store.localeMap[langName]; ok { return ErrLocaleAlreadyExist } - lc := &locale{store: ls, langName: langName} + l := &locale{store: store, langName: langName} + if store.reloadMu != nil { + l.reloadMu = &sync.RWMutex{} + l.reloadMu.Lock() // Arguably this is not necessary as AddLocaleByIni isn't concurrent safe - but for consistency we do this + defer l.reloadMu.Unlock() + } + if fileName, ok := source.(string); ok { - lc.sourceFileName = fileName - lc.sourceFileInfo, _ = os.Stat(fileName) // live-reload only works for regular files. the error can be ignored + l.sourceFileName = fileName + l.sourceFileInfo, _ = os.Stat(fileName) // live-reload only works for regular files. the error can be ignored + } + + var err error + l.idxToMsgMap, err = store.readIniToIdxToMsgMap(source) + if err != nil { + return err } - ls.langNames = append(ls.langNames, langName) - ls.langDescs = append(ls.langDescs, langDesc) - ls.localeMap[lc.langName] = lc + store.langNames = append(store.langNames, langName) + store.langDescs = append(store.langDescs, langDesc) + + store.localeMap[l.langName] = l - return ls.reloadLocaleByIni(langName, source) + return nil } -func (ls *LocaleStore) reloadLocaleByIni(langName string, source interface{}) error { +// readIniToIdxToMsgMap will read a provided ini and creates an idxToMsgMap +func (store *LocaleStore) readIniToIdxToMsgMap(source interface{}) (map[int]string, error) { iniFile, err := ini.LoadSources(ini.LoadOptions{ IgnoreInlineComment: true, UnescapeValueCommentSymbols: true, }, source) if err != nil { - return fmt.Errorf("unable to load ini: %w", err) + return nil, fmt.Errorf("unable to load ini: %w", err) } iniFile.BlockMode = false - lc := ls.localeMap[langName] - lc.textMap = make(map[int]string) + idxToMsgMap := make(map[int]string) + + if store.reloadMu != nil { + store.reloadMu.Lock() + defer store.reloadMu.Unlock() + } + for _, section := range iniFile.Sections() { for _, key := range section.Keys() { + var trKey string if section.Name() == "" || section.Name() == "DEFAULT" { trKey = key.Name() } else { trKey = section.Name() + "." + key.Name() } - textIdx, ok := ls.textIdxMap[trKey] + + // Instead of storing the key strings in multiple different maps we compute a idx which will act as numeric code for key + // This reduces the size of the locale idxToMsgMaps + idx, ok := store.trKeyToIdxMap[trKey] if !ok { - textIdx = len(ls.textIdxMap) - ls.textIdxMap[trKey] = textIdx + idx = len(store.trKeyToIdxMap) + store.trKeyToIdxMap[trKey] = idx } - lc.textMap[textIdx] = key.Value() + idxToMsgMap[idx] = key.Value() } } iniFile = nil - return nil + return idxToMsgMap, nil +} + +func (store *LocaleStore) idxForTrKey(trKey string) (int, bool) { + if store.reloadMu != nil { + store.reloadMu.RLock() + defer store.reloadMu.RUnlock() + } + idx, ok := store.trKeyToIdxMap[trKey] + return idx, ok } -func (ls *LocaleStore) HasLang(langName string) bool { - _, ok := ls.localeMap[langName] +// HasLang reports if a language is available in the store +func (store *LocaleStore) HasLang(langName string) bool { + _, ok := store.localeMap[langName] return ok } -func (ls *LocaleStore) ListLangNameDesc() (names, desc []string) { - return ls.langNames, ls.langDescs +// ListLangNameDesc reports if a language available in the store +func (store *LocaleStore) ListLangNameDesc() (names, desc []string) { + return store.langNames, store.langDescs } // SetDefaultLang sets default language as a fallback -func (ls *LocaleStore) SetDefaultLang(lang string) { - ls.defaultLang = lang +func (store *LocaleStore) SetDefaultLang(lang string) { + store.defaultLang = lang } // Tr translates content to target language. fall back to default language. -func (ls *LocaleStore) Tr(lang, trKey string, trArgs ...interface{}) string { - l, ok := ls.localeMap[lang] +func (store *LocaleStore) Tr(lang, trKey string, trArgs ...interface{}) string { + l, ok := store.localeMap[lang] if !ok { - l, ok = ls.localeMap[ls.defaultLang] + l, ok = store.localeMap[store.defaultLang] } + if ok { return l.Tr(trKey, trArgs...) } return trKey } +// reloadIfNeeded will check if the locale needs to be reloaded +// this function will assume that the l.reloadMu has been RLocked if it already exists +func (l *locale) reloadIfNeeded() { + if l.reloadMu == nil { + return + } + + now := time.Now() + if now.Sub(l.lastReloadCheckTime) < time.Second || l.sourceFileInfo == nil || l.sourceFileName == "" { + return + } + + l.reloadMu.RUnlock() + l.reloadMu.Lock() // (NOTE: a pre-emption can occur between these two locks so we need to recheck) + defer l.reloadMu.RLock() + defer l.reloadMu.Unlock() + + if now.Sub(l.lastReloadCheckTime) < time.Second || l.sourceFileInfo == nil || l.sourceFileName == "" { + return + } + + l.lastReloadCheckTime = now + sourceFileInfo, err := os.Stat(l.sourceFileName) + if err != nil || sourceFileInfo.ModTime().Equal(l.sourceFileInfo.ModTime()) { + return + } + + idxToMsgMap, err := l.store.readIniToIdxToMsgMap(l.sourceFileName) + if err == nil { + l.idxToMsgMap = idxToMsgMap + } else { + log.Error("Unable to live-reload the locale file %q, err: %v", l.sourceFileName, err) + } + + // We will set the sourceFileInfo to this file to prevent repeated attempts to re-load this broken file + l.sourceFileInfo = sourceFileInfo +} + // Tr translates content to locale language. fall back to default language. func (l *locale) Tr(trKey string, trArgs ...interface{}) string { - if l.store.reloadMu != nil { - l.store.reloadMu.Lock() - defer l.store.reloadMu.Unlock() - now := time.Now() - if now.Sub(l.lastReloadCheckTime) >= time.Second && l.sourceFileInfo != nil && l.sourceFileName != "" { - l.lastReloadCheckTime = now - if sourceFileInfo, err := os.Stat(l.sourceFileName); err == nil && !sourceFileInfo.ModTime().Equal(l.sourceFileInfo.ModTime()) { - if err = l.store.reloadLocaleByIni(l.langName, l.sourceFileName); err == nil { - l.sourceFileInfo = sourceFileInfo - } else { - log.Error("unable to live-reload the locale file %q, err: %v", l.sourceFileName, err) - } - } - } + if l.reloadMu != nil { + l.reloadMu.RLock() + defer l.reloadMu.RUnlock() + l.reloadIfNeeded() } + msg, _ := l.tryTr(trKey, trArgs...) return msg } func (l *locale) tryTr(trKey string, trArgs ...interface{}) (msg string, found bool) { trMsg := trKey - textIdx, ok := l.store.textIdxMap[trKey] + + // convert the provided trKey to a common idx from the store + idx, ok := l.store.idxForTrKey(trKey) + if ok { - if msg, found = l.textMap[textIdx]; found { - trMsg = msg // use current translation + if msg, found = l.idxToMsgMap[idx]; found { + trMsg = msg // use the translation that we have found } else if l.langName != l.store.defaultLang { + // No translation available in our current language... fallback to the default language + + // Attempt to get the default language from the locale store if def, ok := l.store.localeMap[l.store.defaultLang]; ok { - return def.tryTr(trKey, trArgs...) + + if def.reloadMu != nil { + def.reloadMu.RLock() + def.reloadIfNeeded() + } + if msg, found = def.idxToMsgMap[idx]; found { + trMsg = msg // use the translation that we have found + } + if def.reloadMu != nil { + def.reloadMu.RUnlock() + } } - } else if !setting.IsProd { - log.Error("missing i18n translation key: %q", trKey) } } - if len(trArgs) > 0 { - fmtArgs := make([]interface{}, 0, len(trArgs)) - for _, arg := range trArgs { - val := reflect.ValueOf(arg) - if val.Kind() == reflect.Slice { - // before, it can accept Tr(lang, key, a, [b, c], d, [e, f]) as Sprintf(msg, a, b, c, d, e, f), it's an unstable behavior - // now, we restrict the strange behavior and only support: - // 1. Tr(lang, key, [slice-items]) as Sprintf(msg, items...) - // 2. Tr(lang, key, args...) as Sprintf(msg, args...) - if len(trArgs) == 1 { - for i := 0; i < val.Len(); i++ { - fmtArgs = append(fmtArgs, val.Index(i).Interface()) - } - } else { - log.Error("the args for i18n shouldn't contain uncertain slices, key=%q, args=%v", trKey, trArgs) - break + if !found && !setting.IsProd { + log.Error("missing i18n translation key: %q", trKey) + } + + if len(trArgs) == 0 { + return trMsg, found + } + + fmtArgs := make([]interface{}, 0, len(trArgs)) + for _, arg := range trArgs { + val := reflect.ValueOf(arg) + if val.Kind() == reflect.Slice { + // Previously, we would accept Tr(lang, key, a, [b, c], d, [e, f]) as Sprintf(msg, a, b, c, d, e, f) + // but this is an unstable behavior. + // + // So we restrict the accepted arguments to either: + // + // 1. Tr(lang, key, [slice-items]) as Sprintf(msg, items...) + // 2. Tr(lang, key, args...) as Sprintf(msg, args...) + if len(trArgs) == 1 { + for i := 0; i < val.Len(); i++ { + fmtArgs = append(fmtArgs, val.Index(i).Interface()) } } else { - fmtArgs = append(fmtArgs, arg) + log.Error("the args for i18n shouldn't contain uncertain slices, key=%q, args=%v", trKey, trArgs) + break } + } else { + fmtArgs = append(fmtArgs, arg) } - return fmt.Sprintf(trMsg, fmtArgs...), found } - return trMsg, found + + return fmt.Sprintf(trMsg, fmtArgs...), found } // ResetDefaultLocales resets the current default locales From 31c847c88f0424bea5e62557b234b38c58fa8305 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Mon, 4 Jul 2022 18:06:24 +0530 Subject: [PATCH 09/85] Add integration tests for the Gitea migration form (#20121) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * tests: integration tests for the Gitea migration form * use a mix of ` and " instead of backslash https://github.com/go-gitea/gitea/pull/20121#discussion_r906729415 Co-authored-by: Loïc Dachary --- integrations/migrate_test.go | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/integrations/migrate_test.go b/integrations/migrate_test.go index 9b59c85a4eb7e..f67e4ed2297de 100644 --- a/integrations/migrate_test.go +++ b/integrations/migrate_test.go @@ -5,12 +5,17 @@ package integrations import ( + "fmt" + "net/http" + "net/url" "os" "testing" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/services/migrations" "github.com/stretchr/testify/assert" @@ -40,3 +45,54 @@ func TestMigrateLocalPath(t *testing.T) { setting.ImportLocalPaths = old } + +func TestMigrateGiteaForm(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + AllowLocalNetworks := setting.Migrations.AllowLocalNetworks + setting.Migrations.AllowLocalNetworks = true + AppVer := setting.AppVer + // Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string. + setting.AppVer = "1.16.0" + defer func() { + setting.Migrations.AllowLocalNetworks = AllowLocalNetworks + setting.AppVer = AppVer + migrations.Init() + }() + assert.NoError(t, migrations.Init()) + + ownerName := "user2" + repoName := "repo1" + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: ownerName}).(*user_model.User) + session := loginUser(t, ownerName) + token := getTokenForLoggedInUser(t, session) + + // Step 0: verify the repo is available + req := NewRequestf(t, "GET", fmt.Sprintf("/%s/%s", ownerName, repoName)) + _ = session.MakeRequest(t, req, http.StatusOK) + // Step 1: get the Gitea migration form + req = NewRequestf(t, "GET", "/repo/migrate/?service_type=%d", structs.GiteaService) + resp := session.MakeRequest(t, req, http.StatusOK) + // Step 2: load the form + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find(`form.ui.form[action^="/repo/migrate"]`).Attr("action") + assert.True(t, exists, "The template has changed") + // Step 4: submit the migration to only migrate issues + migratedRepoName := "otherrepo" + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "service": fmt.Sprintf("%d", structs.GiteaService), + "clone_addr": fmt.Sprintf("%s%s/%s", u, ownerName, repoName), + "auth_token": token, + "issues": "on", + "repo_name": migratedRepoName, + "description": "", + "uid": fmt.Sprintf("%d", repoOwner.ID), + }) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + // Step 5: a redirection displays the migrated repository + loc := resp.Header().Get("Location") + assert.EqualValues(t, fmt.Sprintf("/%s/%s", ownerName, migratedRepoName), loc) + // Step 6: check the repo was created + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: migratedRepoName}) + }) +} From 3814116973022b1009d05ca2308c494422c41e16 Mon Sep 17 00:00:00 2001 From: Tyrone Yeh Date: Mon, 4 Jul 2022 22:44:34 +0800 Subject: [PATCH 10/85] Adjust template for #20069 smallbell (#20108) * Adjust template for #20069 smallbell * Adjust notification Unread Count variable to global and count bell position with mobile * Adjust bell icon style * Adjust smallbell to middle * Avoid using inline styles * move notificationUnreadCount to a general code block, reduce changed lines * Solved conflicts Co-authored-by: wxiaoguang Co-authored-by: Lunny Xiao Co-authored-by: 6543 <6543@obermui.de> --- templates/base/head_navbar.tmpl | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index b04d8bcbb3850..91529dc163ac5 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -1,8 +1,22 @@ - + {{svg "octicon-bell"}} - {{.locale.Tr "notifications"}} - {{$notificationUnreadCount := 0}} - {{if .NotificationUnreadCount}}{{$notificationUnreadCount = call .NotificationUnreadCount}}{{end}} {{$notificationUnreadCount}} From db82c7c7215ab089dfd826a142e86e57e8ab10f3 Mon Sep 17 00:00:00 2001 From: Tyrone Yeh Date: Tue, 5 Jul 2022 14:12:53 +0800 Subject: [PATCH 11/85] Adjust class for mobile has the problem of double small bells (#20236) * Adjust class for mobile has the problem of double small bells * Update templates/base/head_navbar.tmpl Co-authored-by: Gusted Co-authored-by: Gusted --- templates/base/head_navbar.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 91529dc163ac5..111cc02f933f5 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -114,7 +114,7 @@ - + {{svg "octicon-bell"}} From 33eb19b3ba016ad472e7cc55f3becdc2e6714445 Mon Sep 17 00:00:00 2001 From: Baekjun Kim <36013575+kimbj95@users.noreply.github.com> Date: Tue, 5 Jul 2022 06:30:05 -0500 Subject: [PATCH 12/85] Display full name (#20171) The setting `DEFAULT_SHOW_FULL_NAME` promises to use the user's full name everywhere it can be used. Unfortunately the function `*user_model.User.ShortName()` currently uses the `.Name` instead - but this should also use the `.FullName()`. Therefore we should make `*user_model.User.ShortName()` base its pre-shortened name on the `.FullName()` function. --- models/user/user.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/models/user/user.go b/models/user/user.go index 9460bd38fe428..000af585137d8 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -485,6 +485,9 @@ func (u *User) GitName() string { // ShortName ellipses username to length func (u *User) ShortName(length int) string { + if setting.UI.DefaultShowFullName && len(u.FullName) > 0 { + return base.EllipsisString(u.FullName, length) + } return base.EllipsisString(u.Name, length) } From be710dae7082d529f1d46caa1d9dea461947dfb3 Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 5 Jul 2022 12:33:05 +0100 Subject: [PATCH 13/85] Adjust max-widths for the repository file table (#20243) Adjust the max-widths for the repository file table to allow for nicer resizing of the names and commit messages. Fix #20040 Signed-off-by: Andrew Thornton ## Screenshots ## MediaXL ![Screenshot from 2022-07-05 10-22-12](https://user-images.githubusercontent.com/1824502/177295867-7ba8cf60-8f61-4227-892f-e5a0477e4146.png) ## MediaLg ![Screenshot from 2022-07-05 10-24-37](https://user-images.githubusercontent.com/1824502/177296301-e066e206-10f7-4a15-a68b-0f772a95f369.png) ## MediaMd ![Screenshot from 2022-07-05 10-23-03](https://user-images.githubusercontent.com/1824502/177295965-69397649-16ca-456a-bc0c-ed507fcb7f44.png) ## MediaSm ![Screenshot from 2022-07-05 10-26-44](https://user-images.githubusercontent.com/1824502/177296700-ca2a853b-c47b-4592-baf4-4bc08a7e1c9c.png) --- web_src/less/_repository.less | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less index bc1affb7703f0..7bbd412b6198d 100644 --- a/web_src/less/_repository.less +++ b/web_src/less/_repository.less @@ -352,11 +352,31 @@ overflow: initial; &.name { - max-width: 150px; + @media @mediaXl { + max-width: 150px; + } + @media @mediaLg { + max-width: 200px; + } + @media @mediaMd { + max-width: 300px; + } + width: 33%; + + max-width: calc(100vw - 140px); } &.message { - max-width: 400px; + @media @mediaXl { + max-width: 400px; + } + @media @mediaLg { + max-width: 350px; + } + @media @mediaMd { + max-width: 250px; + } + width: 66%; } &.age { From 1890c803f1139e20bf0327494f6d06cfb0f34154 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 5 Jul 2022 20:27:13 +0800 Subject: [PATCH 14/85] Bypass Firefox (iOS) bug (#20244) * https://github.com/go-gitea/gitea/issues/20240 At the moment, Firefox (iOS) (10x) has an engine bug. See https://github.com/go-gitea/gitea/issues/20240 If a script inserts a newly created (and content changed) element into DOM, there will be a nonsense error event reporting: Script error: line 0, col 0. This PR ignores such nonsense error event. Fix #20240 --- web_src/js/bootstrap.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web_src/js/bootstrap.js b/web_src/js/bootstrap.js index cf13b2a559d30..213c9e41df5db 100644 --- a/web_src/js/bootstrap.js +++ b/web_src/js/bootstrap.js @@ -20,6 +20,11 @@ export function showGlobalErrorMessage(msg) { * @param {ErrorEvent} e */ function processWindowErrorEvent(e) { + if (!e.error && e.lineno === 0 && e.colno === 0 && e.filename === '' && window.navigator.userAgent.includes('FxiOS/')) { + // At the moment, Firefox (iOS) (10x) has an engine bug. See https://github.com/go-gitea/gitea/issues/20240 + // If a script inserts a newly created (and content changed) element into DOM, there will be a nonsense error event reporting: Script error: line 0, col 0. + return; // ignore such nonsense error event + } showGlobalErrorMessage(`JavaScript error: ${e.message} (${e.filename} @ ${e.lineno}:${e.colno}). Open browser console to see more details.`); } From 2b86075896cd4651b3cd86751219d7e7f6a0fc6c Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 5 Jul 2022 14:28:31 +0200 Subject: [PATCH 15/85] Init popup for new code comment (#20234) - Initialize the popup for the tooltip inside the new code comment. - This works and is good enough to have this issue fixed for 1.17 Fix #20068 --- web_src/js/features/common-global.js | 23 ++++++++++++++--------- web_src/js/features/repo-diff.js | 2 ++ web_src/js/index.js | 2 ++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index a508db39c5e6a..419c5996dc651 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -75,6 +75,20 @@ export function initGlobalButtonClickOnEnter() { }); } +export function initPopup(target) { + const $el = $(target); + const attr = $el.attr('data-variation'); + const attrs = attr ? attr.split(' ') : []; + const variations = new Set([...attrs, 'inverted', 'tiny']); + $el.attr('data-variation', [...variations].join(' ')).popup(); +} + +export function initGlobalPopups() { + $('.tooltip').each((_, el) => { + initPopup(el); + }); +} + export function initGlobalCommon() { // Show exact time $('.time-since').each(function () { @@ -121,15 +135,6 @@ export function initGlobalCommon() { $('.ui.checkbox').checkbox(); - // init popups - $('.tooltip').each((_, el) => { - const $el = $(el); - const attr = $el.attr('data-variation'); - const attrs = attr ? attr.split(' ') : []; - const variations = new Set([...attrs, 'inverted', 'tiny']); - $el.attr('data-variation', [...variations].join(' ')).popup(); - }); - $('.top.menu .tooltip').popup({ onShow() { if ($('.top.menu .menu.transition').hasClass('visible')) { diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js index 8403a2fd42d13..92d8ecfc86096 100644 --- a/web_src/js/features/repo-diff.js +++ b/web_src/js/features/repo-diff.js @@ -3,6 +3,7 @@ import {initCompReactionSelector} from './comp/ReactionSelector.js'; import {initRepoIssueContentHistory} from './repo-issue-content.js'; import {validateTextareaNonEmpty} from './comp/EasyMDE.js'; import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles} from './pull-view-file.js'; +import {initPopup} from './common-global.js'; const {csrfToken} = window.config; @@ -52,6 +53,7 @@ export function initRepoDiffConversationForm() { const newConversationHolder = $(await $.post(form.attr('action'), form.serialize())); const {path, side, idx} = newConversationHolder.data(); + initPopup(newConversationHolder.find('.tooltip')); form.closest('.conversation-holder').replaceWith(newConversationHolder); if (form.closest('tr').data('line-type') === 'same') { $(`[data-path="${path}"] a.add-code-comment[data-idx="${idx}"]`).addClass('invisible'); diff --git a/web_src/js/index.js b/web_src/js/index.js index 0568da64aec51..6f872b5353378 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -56,6 +56,7 @@ import { initGlobalFormDirtyLeaveConfirm, initGlobalLinkActions, initHeadNavbarContentToggle, + initGlobalPopups, } from './features/common-global.js'; import {initRepoTopicBar} from './features/repo-home.js'; import {initAdminEmails} from './features/admin-emails.js'; @@ -99,6 +100,7 @@ initVueEnv(); $(document).ready(() => { initGlobalCommon(); + initGlobalPopups(); initGlobalButtonClickOnEnter(); initGlobalButtons(); initGlobalCopyToClipboardListener(); From b010ac6ecb3b7c111bc18a4093ec13b7cc97723f Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 5 Jul 2022 16:47:45 +0100 Subject: [PATCH 16/85] Only show Followers that current user can access (#20220) Users who are following or being followed by a user should only be displayed if the viewing user can see them. Signed-off-by: Andrew Thornton --- models/user/user.go | 59 ++++++++++++++++++++++++++++----- routers/api/v1/user/follower.go | 8 ++--- routers/web/user/profile.go | 8 ++--- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/models/user/user.go b/models/user/user.go index 000af585137d8..125c643f3ee88 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -316,37 +316,45 @@ func (u *User) GenerateEmailActivateCode(email string) string { } // GetUserFollowers returns range of user's followers. -func GetUserFollowers(u *User, listOptions db.ListOptions) ([]*User, error) { - sess := db.GetEngine(db.DefaultContext). +func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) { + sess := db.GetEngine(ctx). + Select("`user`.*"). + Join("LEFT", "follow", "`user`.id=follow.user_id"). Where("follow.follow_id=?", u.ID). - Join("LEFT", "follow", "`user`.id=follow.user_id") + And(isUserVisibleToViewerCond(viewer)) if listOptions.Page != 0 { sess = db.SetSessionPagination(sess, &listOptions) users := make([]*User, 0, listOptions.PageSize) - return users, sess.Find(&users) + count, err := sess.FindAndCount(&users) + return users, count, err } users := make([]*User, 0, 8) - return users, sess.Find(&users) + count, err := sess.FindAndCount(&users) + return users, count, err } // GetUserFollowing returns range of user's following. -func GetUserFollowing(u *User, listOptions db.ListOptions) ([]*User, error) { +func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) { sess := db.GetEngine(db.DefaultContext). + Select("`user`.*"). + Join("LEFT", "follow", "`user`.id=follow.follow_id"). Where("follow.user_id=?", u.ID). - Join("LEFT", "follow", "`user`.id=follow.follow_id") + And(isUserVisibleToViewerCond(viewer)) if listOptions.Page != 0 { sess = db.SetSessionPagination(sess, &listOptions) users := make([]*User, 0, listOptions.PageSize) - return users, sess.Find(&users) + count, err := sess.FindAndCount(&users) + return users, count, err } users := make([]*User, 0, 8) - return users, sess.Find(&users) + count, err := sess.FindAndCount(&users) + return users, count, err } // NewGitSig generates and returns the signature of given user. @@ -1222,6 +1230,39 @@ func GetAdminUser() (*User, error) { return &admin, nil } +func isUserVisibleToViewerCond(viewer *User) builder.Cond { + if viewer != nil && viewer.IsAdmin { + return builder.NewCond() + } + + if viewer == nil || viewer.IsRestricted { + return builder.Eq{ + "`user`.visibility": structs.VisibleTypePublic, + } + } + + return builder.Neq{ + "`user`.visibility": structs.VisibleTypePrivate, + }.Or( + builder.In("`user`.id", + builder. + Select("`follow`.user_id"). + From("follow"). + Where(builder.Eq{"`follow`.follow_id": viewer.ID})), + builder.In("`user`.id", + builder. + Select("`team_user`.uid"). + From("team_user"). + Join("INNER", "`team_user` AS t2", "`team_user`.id = `t2`.id"). + Where(builder.Eq{"`t2`.uid": viewer.ID})), + builder.In("`user`.id", + builder. + Select("`team_user`.uid"). + From("team_user"). + Join("INNER", "`team_user` AS t2", "`team_user`.org_id = `t2`.org_id"). + Where(builder.Eq{"`t2`.uid": viewer.ID}))) +} + // IsUserVisibleToViewer check if viewer is able to see user profile func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool { if viewer != nil && viewer.IsAdmin { diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go index 3c81b27f8dad4..22f8f40e1c810 100644 --- a/routers/api/v1/user/follower.go +++ b/routers/api/v1/user/follower.go @@ -24,13 +24,13 @@ func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) { } func listUserFollowers(ctx *context.APIContext, u *user_model.User) { - users, err := user_model.GetUserFollowers(u, utils.GetListOptions(ctx)) + users, count, err := user_model.GetUserFollowers(ctx, u, ctx.Doer, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserFollowers", err) return } - ctx.SetTotalCountHeader(int64(u.NumFollowers)) + ctx.SetTotalCountHeader(count) responseAPIUsers(ctx, users) } @@ -86,13 +86,13 @@ func ListFollowers(ctx *context.APIContext) { } func listUserFollowing(ctx *context.APIContext, u *user_model.User) { - users, err := user_model.GetUserFollowing(u, utils.GetListOptions(ctx)) + users, count, err := user_model.GetUserFollowing(ctx, u, ctx.Doer, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserFollowing", err) return } - ctx.SetTotalCountHeader(int64(u.NumFollowing)) + ctx.SetTotalCountHeader(count) responseAPIUsers(ctx, users) } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 609f3242c65e2..6f23d239e26a5 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -157,7 +157,7 @@ func Profile(ctx *context.Context) { switch tab { case "followers": - items, err := user_model.GetUserFollowers(ctx.ContextUser, db.ListOptions{ + items, count, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, }) @@ -167,9 +167,9 @@ func Profile(ctx *context.Context) { } ctx.Data["Cards"] = items - total = ctx.ContextUser.NumFollowers + total = int(count) case "following": - items, err := user_model.GetUserFollowing(ctx.ContextUser, db.ListOptions{ + items, count, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, }) @@ -179,7 +179,7 @@ func Profile(ctx *context.Context) { } ctx.Data["Cards"] = items - total = ctx.ContextUser.NumFollowing + total = int(count) case "activity": ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{ RequestedUser: ctx.ContextUser, From e0b93f59567da248cb6d09756057d66e939bf895 Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 5 Jul 2022 16:59:27 +0100 Subject: [PATCH 17/85] EscapeFilter the group dn membership (#20200) The uid provided to the group filter must be properly escaped using the provided ldap.EscapeFilter function. Fix #20181 Signed-off-by: Andrew Thornton --- services/auth/source/ldap/source_search.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index 988d56005e011..a97a1179d9bcd 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -199,7 +199,7 @@ func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool { // List all group memberships of a user func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string) []string { var ldapGroups []string - groupFilter := fmt.Sprintf("(%s=%s)", source.GroupMemberUID, uid) + groupFilter := fmt.Sprintf("(%s=%s)", source.GroupMemberUID, ldap.EscapeFilter(uid)) result, err := l.Search(ldap.NewSearchRequest( source.GroupDN, ldap.ScopeWholeSubtree, From 559b2bfa8230afc45381c3be9d4ca57cf69cefca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Jul 2022 09:41:54 +0800 Subject: [PATCH 18/85] Bump mermaid from 9.1.1 to 9.1.2 (#20256) Bumps [mermaid](https://github.com/knsv/mermaid) from 9.1.1 to 9.1.2. - [Release notes](https://github.com/knsv/mermaid/releases) - [Changelog](https://github.com/mermaid-js/mermaid/blob/develop/CHANGELOG.md) - [Commits](https://github.com/knsv/mermaid/compare/9.1.1...9.1.2) --- updated-dependencies: - dependency-name: mermaid dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 30 +++++++++++++++--------------- package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8f8f7013a51c2..5264acac2589e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "less": "4.1.2", "less-loader": "11.0.0", "license-checker-webpack-plugin": "0.2.1", - "mermaid": "9.1.1", + "mermaid": "9.1.2", "mini-css-extract-plugin": "2.6.0", "monaco-editor": "0.33.0", "monaco-editor-webpack-plugin": "7.0.1", @@ -3833,9 +3833,9 @@ } }, "node_modules/dompurify": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.6.tgz", - "integrity": "sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==" + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.8.tgz", + "integrity": "sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw==" }, "node_modules/domutils": { "version": "2.8.0", @@ -7741,15 +7741,15 @@ } }, "node_modules/mermaid": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.1.1.tgz", - "integrity": "sha512-2RVD+WkzZ4VDyO9gQvQAuQ/ux2gLigJtKDTlbwjYqOR/NwsVzTSfGm/kx648/qWJsg6Sv04tE9BWCO8s6a+pFA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.1.2.tgz", + "integrity": "sha512-RVf3hBKqiMfyORHboCaEjOAK1TomLO50hYRPvlTrZCXlCniM5pRpe8UlkHBjjpaLtioZnbdYv/vEVj7iKnwkJQ==", "dependencies": { "@braintree/sanitize-url": "^6.0.0", "d3": "^7.0.0", "dagre": "^0.8.5", "dagre-d3": "^0.6.4", - "dompurify": "2.3.6", + "dompurify": "2.3.8", "graphlib": "^2.1.8", "khroma": "^2.0.0", "moment-mini": "^2.24.0", @@ -13815,9 +13815,9 @@ } }, "dompurify": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.6.tgz", - "integrity": "sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==" + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.8.tgz", + "integrity": "sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw==" }, "domutils": { "version": "2.8.0", @@ -16860,15 +16860,15 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "mermaid": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.1.1.tgz", - "integrity": "sha512-2RVD+WkzZ4VDyO9gQvQAuQ/ux2gLigJtKDTlbwjYqOR/NwsVzTSfGm/kx648/qWJsg6Sv04tE9BWCO8s6a+pFA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.1.2.tgz", + "integrity": "sha512-RVf3hBKqiMfyORHboCaEjOAK1TomLO50hYRPvlTrZCXlCniM5pRpe8UlkHBjjpaLtioZnbdYv/vEVj7iKnwkJQ==", "requires": { "@braintree/sanitize-url": "^6.0.0", "d3": "^7.0.0", "dagre": "^0.8.5", "dagre-d3": "^0.6.4", - "dompurify": "2.3.6", + "dompurify": "2.3.8", "graphlib": "^2.1.8", "khroma": "^2.0.0", "moment-mini": "^2.24.0", diff --git a/package.json b/package.json index addf5633fbf98..2e1c05bb89ef3 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "less": "4.1.2", "less-loader": "11.0.0", "license-checker-webpack-plugin": "0.2.1", - "mermaid": "9.1.1", + "mermaid": "9.1.2", "mini-css-extract-plugin": "2.6.0", "monaco-editor": "0.33.0", "monaco-editor-webpack-plugin": "7.0.1", From 957836fbef15eb10434040409bea0757348af4a7 Mon Sep 17 00:00:00 2001 From: zeripath Date: Wed, 6 Jul 2022 18:33:10 +0100 Subject: [PATCH 19/85] Refix notification bell placement (#20251) The use of `m-4 text black` for the notification bell results in this icon being shifted upwards. Instead we should use the `item` class but adjust `not-mobile` and `mobile-only` to make their `display: none` settings `!important`. (As an aside: This is probably one of the only times we should use `!important` in our less files and the rest should be avoided or removed.) Ref #20069 Revert #20236 Signed-off-by: Andrew Thornton --- templates/base/head_navbar.tmpl | 2 +- web_src/less/_base.less | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 111cc02f933f5..91529dc163ac5 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -114,7 +114,7 @@ - + {{svg "octicon-bell"}} diff --git a/web_src/less/_base.less b/web_src/less/_base.less index 78f32956efce1..638801e3928c4 100644 --- a/web_src/less/_base.less +++ b/web_src/less/_base.less @@ -1329,7 +1329,7 @@ footer { @media @mediaMdAndUp { .mobile-only, .ui.button.mobile-only { - display: none; + display: none !important; } // has the same behaviour of sr-only, hiding the content for @@ -1341,7 +1341,7 @@ footer { @media @mediaSm { .not-mobile { - display: none; + display: none !important; } } From 053c5f683c0e7ddc0987a4de75c6702c4ed625d0 Mon Sep 17 00:00:00 2001 From: zeripath Date: Wed, 6 Jul 2022 21:49:27 +0100 Subject: [PATCH 20/85] Allow RSA 2047 bit keys (#20272) Unfortunately it appears that 2048 bit RSA keys can occasionally be created in such a way that they appear to have 2047 bit length. This PR simply changes our defaults to allow these. Fix #20249 Signed-off-by: Andrew Thornton --- custom/conf/app.example.ini | 2 +- docs/content/doc/advanced/config-cheat-sheet.en-us.md | 2 +- modules/setting/setting.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 01f5cb6a47926..927dc71166033 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1231,7 +1231,7 @@ PATH = ;; Define allowed algorithms and their minimum key length (use -1 to disable a type) ;ED25519 = 256 ;ECDSA = 256 -;RSA = 2048 +;RSA = 2047 ; we allow 2047 here because an otherwise valid 2048 bit RSA key can be reported as having 2047 bit length ;DSA = -1 ; set to 1024 to switch on ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 1d067d1f1ca9d..c16a500ef3e82 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -621,7 +621,7 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type - `ED25519`: **256** - `ECDSA`: **256** -- `RSA`: **2048** +- `RSA`: **2047**: We set 2047 here because an otherwise valid 2048 RSA key can be reported as 2047 length. - `DSA`: **-1**: DSA is now disabled by default. Set to **1024** to re-enable but ensure you may need to reconfigure your SSHD provider ## Webhook (`webhook`) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 5400bf5b9f95c..510e00e5ab120 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -170,7 +170,7 @@ var ( ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1"}, KeygenPath: "ssh-keygen", MinimumKeySizeCheck: true, - MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 2048}, + MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 2047}, ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, AuthorizedKeysCommandTemplate: "{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}", PerWriteTimeout: PerWriteTimeout, From 4d769e5e0d2f464e485c4831515f6dbdab4e3070 Mon Sep 17 00:00:00 2001 From: zeripath Date: Wed, 6 Jul 2022 22:03:52 +0100 Subject: [PATCH 21/85] Fix toolip on mobile notification bell (#20270) Unfortunately there is a bug in #20108 where the translation call was not updated to use `.locale` from `.i18n`. This PR updates the template to use `.locale`. Signed-off-by: Andrew Thornton --- templates/base/head_navbar.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 91529dc163ac5..b9e9ee7cf84fb 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -8,7 +8,7 @@ {{if .IsSigned}} - + {{svg "octicon-bell"}} From 392f27f1846f0bf4e0b1bd650560f03b7bb16836 Mon Sep 17 00:00:00 2001 From: Tyrone Yeh Date: Thu, 7 Jul 2022 05:05:12 +0800 Subject: [PATCH 22/85] Modify milestone search keywords to be case insensitive (#20266) Milestone search keywords are now sensitive, this modification is changed to insensitive --- models/issues/milestone.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/issues/milestone.go b/models/issues/milestone.go index fba599e6ece48..c49799f391dc3 100644 --- a/models/issues/milestone.go +++ b/models/issues/milestone.go @@ -361,7 +361,7 @@ func (opts GetMilestonesOption) toCond() builder.Cond { } if len(opts.Name) != 0 { - cond = cond.And(builder.Like{"name", opts.Name}) + cond = cond.And(builder.Like{"UPPER(name)", strings.ToUpper(opts.Name)}) } return cond From 92aede02ea5255f5a598f587c6055a95f057be2e Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 7 Jul 2022 17:46:49 +0200 Subject: [PATCH 23/85] Fix NPE when using non-numeric (#20277) - This code is only valid when `refNumeric` exist(otherwise we didn't find such numeric PR and can skip that check) and give a free-pas to the "BEFORE" check when `ref` is nil. - Resolves #20109 --- modules/markup/html.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/markup/html.go b/modules/markup/html.go index 69d9ba3ef2a46..6071180501c42 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -841,9 +841,10 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { // Repos with external issue trackers might still need to reference local PRs // We need to concern with the first one that shows up in the text, whichever it is - if hasExtTrackFormat && !isNumericStyle { + if hasExtTrackFormat && !isNumericStyle && refNumeric != nil { // If numeric (PR) was found, and it was BEFORE the non-numeric pattern, use that - if foundNumeric && refNumeric.RefLocation.Start < ref.RefLocation.Start { + // Allow a free-pass when non-numeric pattern wasn't found. + if found && (ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start) { found = foundNumeric ref = refNumeric } From 176015ce61911949f149d7fecde3f91d4c1d6dc1 Mon Sep 17 00:00:00 2001 From: Baoshuo Ren Date: Fri, 8 Jul 2022 04:17:41 +0800 Subject: [PATCH 24/85] Add tooltip to repo icons in explore page (#20241) * Add label to repo icons in explore page Co-authored-by: silverwind --- templates/explore/repo_list.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl index 10b503b219d94..cd75f48520cbe 100644 --- a/templates/explore/repo_list.tmpl +++ b/templates/explore/repo_list.tmpl @@ -32,9 +32,9 @@ {{end}} {{end}} {{if .IsFork}} - {{svg "octicon-repo-forked"}} + {{svg "octicon-repo-forked"}} {{else if .IsMirror}} - {{svg "octicon-mirror"}} + {{svg "octicon-mirror"}} {{end}} From c59d9c4187a9d436bc869658a63eefad2142353b Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 8 Jul 2022 16:09:07 +0800 Subject: [PATCH 25/85] Use git.HOME_PATH for Git HOME directory (#20114) * Add git.HOME_PATH * add legacy file check * Apply suggestions from code review Co-authored-by: zeripath * pass env GNUPGHOME to git command, move the existing .gitconfig to new home, make the fix for 1.17rc more clear. * set git.HOME_PATH for docker images to default HOME * Revert "set git.HOME_PATH for docker images to default HOME" This reverts commit f120101ddc267cef74e4f4b92c783d5fc8e275a1. * force Gitea to use a stable GNUPGHOME directory * extra check to ensure only process dir or symlink for legacy files * refactor variable name * The legacy dir check (for 1.17-rc1) could be removed with 1.18 release, since users should have upgraded from 1.17-rc to 1.17-stable * Update modules/git/git.go Co-authored-by: Steven Kriegler <61625851+justusbunsi@users.noreply.github.com> * remove initFixGitHome117rc * Update git.go * Update docs/content/doc/advanced/config-cheat-sheet.en-us.md Co-authored-by: zeripath Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Steven Kriegler <61625851+justusbunsi@users.noreply.github.com> Co-authored-by: Lunny Xiao --- custom/conf/app.example.ini | 5 ++- .../doc/advanced/config-cheat-sheet.en-us.md | 2 ++ docs/content/doc/advanced/signing.en-us.md | 7 ++-- models/unittest/testdb.go | 2 ++ modules/git/command.go | 33 +++++++++++++------ modules/git/git.go | 22 ++++++++----- modules/git/git_test.go | 6 ++-- modules/setting/git.go | 13 +++++++- 8 files changed, 63 insertions(+), 27 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 927dc71166033..fb43ea95a1d44 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -603,7 +603,10 @@ ROUTER = console ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; The path of git executable. If empty, Gitea searches through the PATH environment. -PATH = +;PATH = +;; +;; The HOME directory for Git +;HOME_PATH = %(APP_DATA_PATH)/home ;; ;; Disables highlight of added and removed changes ;DISABLE_DIFF_HIGHLIGHT = false diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index c16a500ef3e82..84e3c6ae33df3 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -948,6 +948,8 @@ Default templates for project boards: ## Git (`git`) - `PATH`: **""**: The path of Git executable. If empty, Gitea searches through the PATH environment. +- `HOME_PATH`: **%(APP_DATA_PATH)/home**: The HOME directory for Git. + This directory will be used to contain the `.gitconfig` and possible `.gnupg` directories that Gitea's git calls will use. If you can confirm Gitea is the only application running in this environment, you can set it to the normal home directory for Gitea user. - `DISABLE_DIFF_HIGHLIGHT`: **false**: Disables highlight of added and removed changes. - `MAX_GIT_DIFF_LINES`: **1000**: Max number of lines allowed of a single file in diff view. - `MAX_GIT_DIFF_LINE_CHARACTERS`: **5000**: Max character count per line highlighted in diff view. diff --git a/docs/content/doc/advanced/signing.en-us.md b/docs/content/doc/advanced/signing.en-us.md index 8ae2f94e9ece9..9ef94deb75ce4 100644 --- a/docs/content/doc/advanced/signing.en-us.md +++ b/docs/content/doc/advanced/signing.en-us.md @@ -97,10 +97,11 @@ repositories, `SIGNING_KEY=default` could be used to provide different signing keys on a per-repository basis. However, this is clearly not an ideal UI and therefore subject to change. -**Since 1.17**, Gitea runs git in its own home directory `[repository].ROOT` and uses its own config `{[repository].ROOT}/.gitconfig`. +**Since 1.17**, Gitea runs git in its own home directory `[git].HOME_PATH` (default to `%(APP_DATA_PATH)/home`) +and uses its own config `{[git].HOME_PATH}/.gitconfig`. If you have your own customized git config for Gitea, you should set these configs in system git config (aka `/etc/gitconfig`) -or the Gitea internal git config `{[repository].ROOT}/.gitconfig`. -Related home files for git command (like `.gnupg`) should also be put in Gitea's git home directory `[repository].ROOT`. +or the Gitea internal git config `{[git].HOME_PATH}/.gitconfig`. +Related home files for git command (like `.gnupg`) should also be put in Gitea's git home directory `[git].HOME_PATH`. ### `INITIAL_COMMIT` diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index baea46dbce68f..7f9af553747cb 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -107,6 +107,8 @@ func MainTest(m *testing.M, testOpts *TestOptions) { setting.Packages.Storage.Path = filepath.Join(setting.AppDataPath, "packages") + setting.Git.HomePath = filepath.Join(setting.AppDataPath, "home") + if err = storage.Init(); err != nil { fatalTestError("storage.Init: %v\n", err) } diff --git a/modules/git/command.go b/modules/git/command.go index d71497f1d791e..a1bacbb707a25 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -105,23 +105,36 @@ type RunOpts struct { PipelineFunc func(context.Context, context.CancelFunc) error } -// CommonGitCmdEnvs returns the common environment variables for a "git" command. -func CommonGitCmdEnvs() []string { +func commonBaseEnvs() []string { // at the moment, do not set "GIT_CONFIG_NOSYSTEM", users may have put some configs like "receive.certNonceSeed" in it - return []string{ - fmt.Sprintf("LC_ALL=%s", DefaultLocale), - "GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3 - "GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace) + envs := []string{ "HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config + "GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace) + } + + // some environment variables should be passed to git command + passThroughEnvKeys := []string{ + "GNUPGHOME", // git may call gnupg to do commit signing + } + for _, key := range passThroughEnvKeys { + if val, ok := os.LookupEnv(key); ok { + envs = append(envs, key+"="+val) + } } + return envs +} + +// CommonGitCmdEnvs returns the common environment variables for a "git" command. +func CommonGitCmdEnvs() []string { + return append(commonBaseEnvs(), []string{ + "LC_ALL=" + DefaultLocale, + "GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3 + }...) } // CommonCmdServEnvs is like CommonGitCmdEnvs but it only returns minimal required environment variables for the "gitea serv" command func CommonCmdServEnvs() []string { - return []string{ - "GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace) - "HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config - } + return commonBaseEnvs() } // Run runs the command with the RunOpts diff --git a/modules/git/git.go b/modules/git/git.go index 0652a75f9aeff..3bc08ff93b5fa 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -11,6 +11,7 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "regexp" "runtime" "strings" @@ -19,7 +20,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/hashicorp/go-version" ) @@ -126,8 +126,8 @@ func VersionInfo() string { } func checkInit() error { - if setting.RepoRootPath == "" { - return errors.New("can not init Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly") + if setting.Git.HomePath == "" { + return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules") } if DefaultContext != nil { log.Warn("git module has been initialized already, duplicate init should be fixed") @@ -137,14 +137,14 @@ func checkInit() error { // HomeDir is the home dir for git to store the global config file used by Gitea internally func HomeDir() string { - if setting.RepoRootPath == "" { + if setting.Git.HomePath == "" { // strict check, make sure the git module is initialized correctly. // attention: when the git module is called in gitea sub-command (serv/hook), the log module is not able to show messages to users. // for example: if there is gitea git hook code calling git.NewCommand before git.InitXxx, the integration test won't show the real failure reasons. - log.Fatal("can not get Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly") + log.Fatal("Unable to init Git's HomeDir, incorrect initialization of the setting and git modules") return "" } - return setting.RepoRootPath + return setting.Git.HomePath } // InitSimple initializes git module with a very simple step, no config changes, no global command arguments. @@ -175,11 +175,15 @@ func InitOnceWithSync(ctx context.Context) (err error) { } initOnce.Do(func() { - err = InitSimple(ctx) - if err != nil { + if err = InitSimple(ctx); err != nil { return } + // when git works with gnupg (commit signing), there should be a stable home for gnupg commands + if _, ok := os.LookupEnv("GNUPGHOME"); !ok { + _ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg")) + } + // Since git wire protocol has been released from git v2.18 if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2") @@ -206,7 +210,7 @@ func InitOnceWithSync(ctx context.Context) (err error) { // syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem) func syncGitConfig() (err error) { if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil { - return fmt.Errorf("unable to create directory %s, err: %w", setting.RepoRootPath, err) + return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err) } // Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults" diff --git a/modules/git/git_test.go b/modules/git/git_test.go index c1a9ec351aaa5..c5a63de0644c0 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -21,12 +21,12 @@ import ( func testRun(m *testing.M) error { _ = log.NewLogger(1000, "console", "console", `{"level":"trace","stacktracelevel":"NONE","stderr":true}`) - repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos") + gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home") if err != nil { return fmt.Errorf("unable to create temp dir: %w", err) } - defer util.RemoveAll(repoRootPath) - setting.RepoRootPath = repoRootPath + defer util.RemoveAll(gitHomePath) + setting.Git.HomePath = gitHomePath if err = InitOnceWithSync(context.Background()); err != nil { return fmt.Errorf("failed to call Init: %w", err) diff --git a/modules/setting/git.go b/modules/setting/git.go index 9b2698f01e780..266bbc3c5aa3a 100644 --- a/modules/setting/git.go +++ b/modules/setting/git.go @@ -5,6 +5,7 @@ package setting import ( + "path/filepath" "time" "code.gitea.io/gitea/modules/log" @@ -13,6 +14,7 @@ import ( // Git settings var Git = struct { Path string + HomePath string DisableDiffHighlight bool MaxGitDiffLines int MaxGitDiffLineCharacters int @@ -67,7 +69,16 @@ var Git = struct { } func newGit() { - if err := Cfg.Section("git").MapTo(&Git); err != nil { + sec := Cfg.Section("git") + + if err := sec.MapTo(&Git); err != nil { log.Fatal("Failed to map Git settings: %v", err) } + + Git.HomePath = sec.Key("HOME_PATH").MustString("home") + if !filepath.IsAbs(Git.HomePath) { + Git.HomePath = filepath.Join(AppDataPath, Git.HomePath) + } else { + Git.HomePath = filepath.Clean(Git.HomePath) + } } From b9fbab3d0e9950fc73e0687a31a7d8620ce8a888 Mon Sep 17 00:00:00 2001 From: Chongyi Zheng Date: Fri, 8 Jul 2022 15:45:12 -0400 Subject: [PATCH 26/85] Implement sync push mirror on commit (#19411) Support synchronizing with the push mirrors whenever new commits are pushed or synced from pull mirror. Related Issues: #18220 Co-authored-by: delvh Co-authored-by: zeripath Co-authored-by: Lunny Xiao --- models/migrations/migrations.go | 2 + models/migrations/v219.go | 30 +++++++++ models/repo/pushmirror.go | 9 +++ modules/mirror/mirror.go | 69 +++++++++++++++++++++ modules/notification/mirror/mirror.go | 45 ++++++++++++++ modules/notification/notification.go | 2 + options/locale/locale_en-US.ini | 3 +- routers/api/v1/repo/mirror.go | 4 +- routers/web/repo/setting.go | 14 +++-- services/forms/repo_form.go | 35 +++++------ services/mirror/mirror.go | 87 +++++---------------------- templates/repo/settings/options.tmpl | 6 ++ 12 files changed, 208 insertions(+), 98 deletions(-) create mode 100644 models/migrations/v219.go create mode 100644 modules/mirror/mirror.go create mode 100644 modules/notification/mirror/mirror.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 0d35ac78d3ece..1b2a743b6d357 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -396,6 +396,8 @@ var migrations = []Migration{ NewMigration("Alter hook_task table TEXT fields to LONGTEXT", alterHookTaskTextFieldsToLongText), // v218 -> v219 NewMigration("Improve Action table indices v2", improveActionTableIndices), + // v219 -> v220 + NewMigration("Add sync_on_commit column to push_mirror table", addSyncOnCommitColForPushMirror), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v219.go b/models/migrations/v219.go new file mode 100644 index 0000000000000..7b2eaa3292704 --- /dev/null +++ b/models/migrations/v219.go @@ -0,0 +1,30 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "time" + + "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/timeutil" + "xorm.io/xorm" +) + +func addSyncOnCommitColForPushMirror(x *xorm.Engine) error { + type PushMirror struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` + Repo *repo.Repository `xorm:"-"` + RemoteName string + + SyncOnCommit bool `xorm:"NOT NULL DEFAULT true"` + Interval time.Duration + CreatedUnix timeutil.TimeStamp `xorm:"created"` + LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"` + LastError string `xorm:"text"` + } + + return x.Sync2(new(PushMirror)) +} diff --git a/models/repo/pushmirror.go b/models/repo/pushmirror.go index 048c0c3487b75..0a7dea79c93b3 100644 --- a/models/repo/pushmirror.go +++ b/models/repo/pushmirror.go @@ -23,6 +23,7 @@ type PushMirror struct { Repo *Repository `xorm:"-"` RemoteName string + SyncOnCommit bool `xorm:"NOT NULL DEFAULT true"` Interval time.Duration CreatedUnix timeutil.TimeStamp `xorm:"created"` LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"` @@ -93,6 +94,14 @@ func GetPushMirrorsByRepoID(repoID int64) ([]*PushMirror, error) { return mirrors, db.GetEngine(db.DefaultContext).Where("repo_id=?", repoID).Find(&mirrors) } +// GetPushMirrorsSyncedOnCommit returns push-mirrors for this repo that should be updated by new commits +func GetPushMirrorsSyncedOnCommit(repoID int64) ([]*PushMirror, error) { + mirrors := make([]*PushMirror, 0, 10) + return mirrors, db.GetEngine(db.DefaultContext). + Where("repo_id=? AND sync_on_commit=?", repoID, true). + Find(&mirrors) +} + // PushMirrorsIterate iterates all push-mirror repositories. func PushMirrorsIterate(limit int, f func(idx int, bean interface{}) error) error { return db.GetEngine(db.DefaultContext). diff --git a/modules/mirror/mirror.go b/modules/mirror/mirror.go new file mode 100644 index 0000000000000..b261bd0242634 --- /dev/null +++ b/modules/mirror/mirror.go @@ -0,0 +1,69 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package mirror + +import ( + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/queue" + "code.gitea.io/gitea/modules/setting" +) + +var mirrorQueue queue.UniqueQueue + +// SyncType type of sync request +type SyncType int + +const ( + // PullMirrorType for pull mirrors + PullMirrorType SyncType = iota + // PushMirrorType for push mirrors + PushMirrorType +) + +// SyncRequest for the mirror queue +type SyncRequest struct { + Type SyncType + ReferenceID int64 // RepoID for pull mirror, MirrorID for push mirror +} + +// StartSyncMirrors starts a go routine to sync the mirrors +func StartSyncMirrors(queueHandle func(data ...queue.Data) []queue.Data) { + if !setting.Mirror.Enabled { + return + } + mirrorQueue = queue.CreateUniqueQueue("mirror", queueHandle, new(SyncRequest)) + + go graceful.GetManager().RunWithShutdownFns(mirrorQueue.Run) +} + +// AddPullMirrorToQueue adds repoID to mirror queue +func AddPullMirrorToQueue(repoID int64) { + addMirrorToQueue(PullMirrorType, repoID) +} + +// AddPushMirrorToQueue adds the push mirror to the queue +func AddPushMirrorToQueue(mirrorID int64) { + addMirrorToQueue(PushMirrorType, mirrorID) +} + +func addMirrorToQueue(syncType SyncType, referenceID int64) { + if !setting.Mirror.Enabled { + return + } + go func() { + if err := PushToQueue(syncType, referenceID); err != nil { + log.Error("Unable to push sync request for to the queue for pull mirror repo[%d]. Error: %v", referenceID, err) + } + }() +} + +// PushToQueue adds the sync request to the queue +func PushToQueue(mirrorType SyncType, referenceID int64) error { + return mirrorQueue.Push(&SyncRequest{ + Type: mirrorType, + ReferenceID: referenceID, + }) +} diff --git a/modules/notification/mirror/mirror.go b/modules/notification/mirror/mirror.go new file mode 100644 index 0000000000000..646b09a4ab6e6 --- /dev/null +++ b/modules/notification/mirror/mirror.go @@ -0,0 +1,45 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package mirror + +import ( + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + mirror_module "code.gitea.io/gitea/modules/mirror" + "code.gitea.io/gitea/modules/notification/base" + "code.gitea.io/gitea/modules/repository" +) + +type mirrorNotifier struct { + base.NullNotifier +} + +var _ base.Notifier = &mirrorNotifier{} + +// NewNotifier create a new mirrorNotifier notifier +func NewNotifier() base.Notifier { + return &mirrorNotifier{} +} + +func (m *mirrorNotifier) NotifyPushCommits(_ *user_model.User, repo *repo_model.Repository, _ *repository.PushUpdateOptions, _ *repository.PushCommits) { + syncPushMirrorWithSyncOnCommit(repo.ID) +} + +func (m *mirrorNotifier) NotifySyncPushCommits(_ *user_model.User, repo *repo_model.Repository, _ *repository.PushUpdateOptions, _ *repository.PushCommits) { + syncPushMirrorWithSyncOnCommit(repo.ID) +} + +func syncPushMirrorWithSyncOnCommit(repoID int64) { + pushMirrors, err := repo_model.GetPushMirrorsSyncedOnCommit(repoID) + if err != nil { + log.Error("repo_model.GetPushMirrorsSyncedOnCommit failed: %v", err) + return + } + + for _, mirror := range pushMirrors { + mirror_module.AddPushMirrorToQueue(mirror.ID) + } +} diff --git a/modules/notification/notification.go b/modules/notification/notification.go index d60a880bec6a2..bdfed90b7864e 100644 --- a/modules/notification/notification.go +++ b/modules/notification/notification.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/notification/base" "code.gitea.io/gitea/modules/notification/indexer" "code.gitea.io/gitea/modules/notification/mail" + "code.gitea.io/gitea/modules/notification/mirror" "code.gitea.io/gitea/modules/notification/ui" "code.gitea.io/gitea/modules/notification/webhook" "code.gitea.io/gitea/modules/repository" @@ -37,6 +38,7 @@ func NewContext() { RegisterNotifier(indexer.NewNotifier()) RegisterNotifier(webhook.NewNotifier()) RegisterNotifier(action.NewNotifier()) + RegisterNotifier(mirror.NewNotifier()) } // NotifyCreateIssueComment notifies issue comment related message to notifiers diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index eb7ae4774313b..464a7d396c0aa 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -861,8 +861,9 @@ default_branch = Default Branch default_branch_helper = The default branch is the base branch for pull requests and code commits. mirror_prune = Prune mirror_prune_desc = Remove obsolete remote-tracking references -mirror_interval = Mirror Interval (valid time units are 'h', 'm', 's'). 0 to disable automatic sync. (Minimum interval: %s) +mirror_interval = Mirror Interval (valid time units are 'h', 'm', 's'). 0 to disable periodic sync. (Minimum interval: %s) mirror_interval_invalid = The mirror interval is not valid. +mirror_sync_on_commit = Sync when commits are pushed mirror_address = Clone From URL mirror_address_desc = Put any required credentials in the Authorization section. mirror_address_url_invalid = The provided url is invalid. You must escape all components of the url correctly. diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go index 1af63c55be01a..3d29383550c32 100644 --- a/routers/api/v1/repo/mirror.go +++ b/routers/api/v1/repo/mirror.go @@ -11,8 +11,8 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/context" + mirror_module "code.gitea.io/gitea/modules/mirror" "code.gitea.io/gitea/modules/setting" - mirror_service "code.gitea.io/gitea/services/mirror" ) // MirrorSync adds a mirrored repository to the sync queue @@ -59,7 +59,7 @@ func MirrorSync(ctx *context.APIContext) { return } - mirror_service.StartToMirror(repo.ID) + mirror_module.AddPullMirrorToQueue(repo.ID) ctx.Status(http.StatusOK) } diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index fae62c102077c..5ded0561c65b5 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -29,6 +29,7 @@ import ( "code.gitea.io/gitea/modules/indexer/stats" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" + mirror_module "code.gitea.io/gitea/modules/mirror" "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" @@ -272,7 +273,7 @@ func SettingsPost(ctx *context.Context) { return } - mirror_service.StartToMirror(repo.ID) + mirror_module.AddPullMirrorToQueue(repo.ID) ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress")) ctx.Redirect(repo.Link() + "/settings") @@ -289,7 +290,7 @@ func SettingsPost(ctx *context.Context) { return } - mirror_service.AddPushMirrorToQueue(m.ID) + mirror_module.AddPushMirrorToQueue(m.ID) ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress")) ctx.Redirect(repo.Link() + "/settings") @@ -357,10 +358,11 @@ func SettingsPost(ctx *context.Context) { } m := &repo_model.PushMirror{ - RepoID: repo.ID, - Repo: repo, - RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), - Interval: interval, + RepoID: repo.ID, + Repo: repo, + RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), + SyncOnCommit: form.PushMirrorSyncOnCommit, + Interval: interval, } if err := repo_model.InsertPushMirror(m); err != nil { ctx.ServerError("InsertPushMirror", err) diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index c9327bbd9b0f8..afecc205f31e8 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -115,23 +115,24 @@ func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, err // RepoSettingForm form for changing repository settings type RepoSettingForm struct { - RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` - Description string `binding:"MaxSize(255)"` - Website string `binding:"ValidUrl;MaxSize(255)"` - Interval string - MirrorAddress string - MirrorUsername string - MirrorPassword string - LFS bool `form:"mirror_lfs"` - LFSEndpoint string `form:"mirror_lfs_endpoint"` - PushMirrorID string - PushMirrorAddress string - PushMirrorUsername string - PushMirrorPassword string - PushMirrorInterval string - Private bool - Template bool - EnablePrune bool + RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` + Description string `binding:"MaxSize(255)"` + Website string `binding:"ValidUrl;MaxSize(255)"` + Interval string + MirrorAddress string + MirrorUsername string + MirrorPassword string + LFS bool `form:"mirror_lfs"` + LFSEndpoint string `form:"mirror_lfs_endpoint"` + PushMirrorID string + PushMirrorAddress string + PushMirrorUsername string + PushMirrorPassword string + PushMirrorSyncOnCommit bool + PushMirrorInterval string + Private bool + Template bool + EnablePrune bool // Advanced settings EnableWiki bool diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go index 013adac0f4ed1..8321829ad26ba 100644 --- a/services/mirror/mirror.go +++ b/services/mirror/mirror.go @@ -11,38 +11,21 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + mirror_module "code.gitea.io/gitea/modules/mirror" "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" ) -var mirrorQueue queue.UniqueQueue - -// SyncType type of sync request -type SyncType int - -const ( - // PullMirrorType for pull mirrors - PullMirrorType SyncType = iota - // PushMirrorType for push mirrors - PushMirrorType -) - -// SyncRequest for the mirror queue -type SyncRequest struct { - Type SyncType - ReferenceID int64 // RepoID for pull mirror, MirrorID fro push mirror -} - // doMirrorSync causes this request to mirror itself -func doMirrorSync(ctx context.Context, req *SyncRequest) { +func doMirrorSync(ctx context.Context, req *mirror_module.SyncRequest) { if req.ReferenceID == 0 { log.Warn("Skipping mirror sync request, no mirror ID was specified") return } switch req.Type { - case PushMirrorType: + case mirror_module.PushMirrorType: _ = SyncPushMirror(ctx, req.ReferenceID) - case PullMirrorType: + case mirror_module.PullMirrorType: _ = SyncPullMirror(ctx, req.ReferenceID) default: log.Error("Unknown Request type in queue: %v for MirrorID[%d]", req.Type, req.ReferenceID) @@ -60,28 +43,26 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { log.Trace("Doing: Update") handler := func(idx int, bean interface{}) error { - var item SyncRequest var repo *repo_model.Repository + var mirrorType mirror_module.SyncType + var referenceID int64 + if m, ok := bean.(*repo_model.Mirror); ok { if m.GetRepository() == nil { log.Error("Disconnected mirror found: %d", m.ID) return nil } repo = m.Repo - item = SyncRequest{ - Type: PullMirrorType, - ReferenceID: m.RepoID, - } + mirrorType = mirror_module.PullMirrorType + referenceID = m.RepoID } else if m, ok := bean.(*repo_model.PushMirror); ok { if m.GetRepository() == nil { log.Error("Disconnected push-mirror found: %d", m.ID) return nil } repo = m.Repo - item = SyncRequest{ - Type: PushMirrorType, - ReferenceID: m.ID, - } + mirrorType = mirror_module.PushMirrorType + referenceID = m.ID } else { log.Error("Unknown bean: %v", bean) return nil @@ -95,9 +76,9 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { } // Push to the Queue - if err := mirrorQueue.Push(&item); err != nil { + if err := mirror_module.PushToQueue(mirrorType, referenceID); err != nil { if err == queue.ErrAlreadyInQueue { - if item.Type == PushMirrorType { + if mirrorType == mirror_module.PushMirrorType { log.Trace("PushMirrors for %-v already queued for sync", repo) } else { log.Trace("PullMirrors for %-v already queued for sync", repo) @@ -142,7 +123,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { func queueHandle(data ...queue.Data) []queue.Data { for _, datum := range data { - req := datum.(*SyncRequest) + req := datum.(*mirror_module.SyncRequest) doMirrorSync(graceful.GetManager().ShutdownContext(), req) } return nil @@ -150,43 +131,5 @@ func queueHandle(data ...queue.Data) []queue.Data { // InitSyncMirrors initializes a go routine to sync the mirrors func InitSyncMirrors() { - if !setting.Mirror.Enabled { - return - } - mirrorQueue = queue.CreateUniqueQueue("mirror", queueHandle, new(SyncRequest)) - - go graceful.GetManager().RunWithShutdownFns(mirrorQueue.Run) -} - -// StartToMirror adds repoID to mirror queue -func StartToMirror(repoID int64) { - if !setting.Mirror.Enabled { - return - } - go func() { - err := mirrorQueue.Push(&SyncRequest{ - Type: PullMirrorType, - ReferenceID: repoID, - }) - if err != nil { - log.Error("Unable to push sync request for to the queue for pull mirror repo[%d]: Error: %v", repoID, err) - return - } - }() -} - -// AddPushMirrorToQueue adds the push mirror to the queue -func AddPushMirrorToQueue(mirrorID int64) { - if !setting.Mirror.Enabled { - return - } - go func() { - err := mirrorQueue.Push(&SyncRequest{ - Type: PushMirrorType, - ReferenceID: mirrorID, - }) - if err != nil { - log.Error("Unable to push sync request to the queue for pull mirror repo[%d]: Error: %v", mirrorID, err) - } - }() + mirror_module.StartSyncMirrors(queueHandle) } diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index e76aba761a28d..98cf4f88c870f 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -219,6 +219,12 @@ +
+
+ + +
+
From 0b19a3b37187d039dc3ff591ef406750151779ba Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 9 Jul 2022 22:32:18 +0800 Subject: [PATCH 27/85] Do not create empty ".ssh" directory when loading config (#20289) Creating the directory automatically is not correct. In other places for ssh key writing (RewriteAllPrincipalKeys / appendAuthorizedKeysToFile, etc), the directory will still be created when updating the keys. This PR will resolve the confusing and annoying problem: the dummy and empty ".ssh" directory in new git home. --- modules/setting/setting.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 510e00e5ab120..1bd9e09a7a1fe 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -859,9 +859,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) { SSH.AuthorizedPrincipalsAllow, SSH.AuthorizedPrincipalsEnabled = parseAuthorizedPrincipalsAllow(sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").Strings(",")) if !SSH.Disabled && !SSH.StartBuiltinServer { - if err := os.MkdirAll(SSH.RootPath, 0o700); err != nil { - log.Fatal("Failed to create '%s': %v", SSH.RootPath, err) - } else if err = os.MkdirAll(SSH.KeyTestPath, 0o644); err != nil { + if err = os.MkdirAll(SSH.KeyTestPath, 0o644); err != nil { log.Fatal("Failed to create '%s': %v", SSH.KeyTestPath, err) } From a94f127af6cbe9f6b11253cf4f4cafda30e1e962 Mon Sep 17 00:00:00 2001 From: Dinesh Salunke Date: Wed, 13 Jul 2022 22:31:45 +0530 Subject: [PATCH 28/85] refactor(swagger): added the missing comments --- routers/api/v1/swagger/repo.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index 9132a858b8352..1726b4ce806dc 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -352,24 +352,28 @@ type swaggerRepoCollaboratorPermission struct { Body api.RepoCollaboratorPermission `json:"body"` } +// Project // swagger:response Project type swaggerProject struct { // in:body Body api.Project `json:"body"` } +// ProjectList // swagger:response ProjectList type swaggerProjectList struct { // in:body Body []api.Project `json:"body"` } +// ProjectBoard // swagger:response ProjectBoard type swaggerProjectBoard struct { // in:body Body api.ProjectBoard `json:"body"` } +// ProjectBoardList // swagger:response ProjectBoardList type swaggerProjectBoardList struct { // in:body From 6fb081bac33a8fe2993dc48892cc836dacf5cee7 Mon Sep 17 00:00:00 2001 From: Dinesh Salunke Date: Thu, 14 Jul 2022 21:11:15 +0530 Subject: [PATCH 29/85] refactor(api): add paging query for list endpoints --- routers/api/v1/repo/board.go | 8 ++++++++ routers/api/v1/repo/project.go | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/routers/api/v1/repo/board.go b/routers/api/v1/repo/board.go index fa323155776f9..8462697a19849 100644 --- a/routers/api/v1/repo/board.go +++ b/routers/api/v1/repo/board.go @@ -86,6 +86,14 @@ func ListProjectBoards(ctx *context.APIContext) { // description: projectId of the project // type: string // required: true + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer // responses: // "200": // "$ref": "#/responses/ProjectBoardList" diff --git a/routers/api/v1/repo/project.go b/routers/api/v1/repo/project.go index 2d82d330c4e11..df5ad147f8c9b 100644 --- a/routers/api/v1/repo/project.go +++ b/routers/api/v1/repo/project.go @@ -115,6 +115,14 @@ func ListRepositoryProjects(ctx *context.APIContext) { // description: repo // type: string // required: true + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer // responses: // "200": // "$ref": "#/responses/ProjectList" From e1920141fe28ddbc7ed7f2c097acac862f3b5963 Mon Sep 17 00:00:00 2001 From: Dinesh Salunke Date: Thu, 14 Jul 2022 21:15:54 +0530 Subject: [PATCH 30/85] chore(swagger): add missing swagger documentation --- templates/swagger/v1_json.tmpl | 62 ++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 0c103333eb44a..4e61edabf0efd 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -2333,6 +2333,18 @@ "name": "projectId", "in": "path", "required": true + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" } ], "responses": { @@ -7938,6 +7950,18 @@ "name": "repo", "in": "path", "required": true + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" } ], "responses": { @@ -17599,13 +17623,35 @@ "format": "uint8", "x-go-name": "BoardType" }, - "body": { + "closed_at": { + "type": "string", + "format": "date-time", + "x-go-name": "Closed" + }, + "created_at": { + "type": "string", + "format": "date-time", + "x-go-name": "Created" + }, + "description": { "type": "string", "x-go-name": "Description" }, + "is_closed": { + "type": "boolean", + "x-go-name": "IsClosed" + }, + "repository": { + "$ref": "#/definitions/RepositoryMeta" + }, "title": { "type": "string", "x-go-name": "Title" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "x-go-name": "Updated" } }, "x-go-package": "code.gitea.io/gitea/modules/structs" @@ -18894,13 +18940,17 @@ }, "UpsertProjectPayload": { "type": "object", + "required": [ + "title", + "board_type" + ], "properties": { "board_type": { "type": "integer", "format": "uint8", "x-go-name": "BoardType" }, - "body": { + "description": { "type": "string", "x-go-name": "Description" }, @@ -19720,19 +19770,19 @@ } }, "Project": { - "description": "", + "description": "Project", "schema": { "$ref": "#/definitions/Project" } }, "ProjectBoard": { - "description": "", + "description": "ProjectBoard", "schema": { "$ref": "#/definitions/ProjectBoard" } }, "ProjectBoardList": { - "description": "", + "description": "ProjectBoardList", "schema": { "type": "array", "items": { @@ -19741,7 +19791,7 @@ } }, "ProjectList": { - "description": "", + "description": "ProjectList", "schema": { "type": "array", "items": { From c060beb07d7121434ec28b5f17f16451c38ecf01 Mon Sep 17 00:00:00 2001 From: Dinesh Salunke Date: Thu, 14 Jul 2022 21:17:39 +0530 Subject: [PATCH 31/85] refactor(integrations): add boilerplate file project and board tests --- integrations/api_project_board_test.go | 29 ++++++++++++++++++++++++++ integrations/api_project_test.go | 29 ++++++++++++++++++++++++++ integrations/api_repo_project_test.go | 13 ------------ 3 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 integrations/api_project_board_test.go create mode 100644 integrations/api_project_test.go delete mode 100644 integrations/api_repo_project_test.go diff --git a/integrations/api_project_board_test.go b/integrations/api_project_board_test.go new file mode 100644 index 0000000000000..855377223692f --- /dev/null +++ b/integrations/api_project_board_test.go @@ -0,0 +1,29 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "testing" +) + +func TestAPIListProjectBoads(t *testing.T) { + defer prepareTestEnv(t)() +} + +func TestAPICreateProjectBoard(t *testing.T) { + defer prepareTestEnv(t)() +} + +func TestAPIGetProjectBoard(t *testing.T) { + defer prepareTestEnv(t)() +} + +func TestAPIUpdateProjectBoard(t *testing.T) { + defer prepareTestEnv(t)() +} + +func TestAPIDeleteProjectBoard(t *testing.T) { + defer prepareTestEnv(t)() +} diff --git a/integrations/api_project_test.go b/integrations/api_project_test.go new file mode 100644 index 0000000000000..75e51d97bfe7f --- /dev/null +++ b/integrations/api_project_test.go @@ -0,0 +1,29 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "testing" +) + +func TestAPIListRepositoryProjects(t *testing.T) { + defer prepareTestEnv(t)() +} + +func TestAPICreateRepositoryProject(t *testing.T) { + defer prepareTestEnv(t)() +} + +func TestAPIGetProject(t *testing.T) { + defer prepareTestEnv(t)() +} + +func TestAPIUpdateProject(t *testing.T) { + defer prepareTestEnv(t)() +} + +func TestAPIDeleteProject(t *testing.T) { + defer prepareTestEnv(t)() +} diff --git a/integrations/api_repo_project_test.go b/integrations/api_repo_project_test.go deleted file mode 100644 index 5d71f4ba1f5ed..0000000000000 --- a/integrations/api_repo_project_test.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package integrations - -import ( - "testing" -) - -func TestAPIRepoProjects(t *testing.T) { - defer prepareTestEnv(t)() -} From cef8c5421354fcc7c8b196a2a90e5486915e310b Mon Sep 17 00:00:00 2001 From: Dinesh Salunke Date: Fri, 15 Jul 2022 01:13:42 +0530 Subject: [PATCH 32/85] feat(projects): api for projects --- integrations/api_project_test.go | 121 ++++++++++++++++++++++++++++++ models/fixtures/project.yml | 10 +++ models/project/project.go | 39 ++++++++-- modules/convert/project.go | 53 +++++++++++++ modules/structs/project.go | 30 +++++++- routers/api/v1/api.go | 15 ++++ routers/api/v1/repo/project.go | 108 +++++++++++++++++++++++++- routers/api/v1/swagger/options.go | 5 +- templates/swagger/v1_json.tmpl | 14 ++++ 9 files changed, 381 insertions(+), 14 deletions(-) create mode 100644 modules/convert/project.go diff --git a/integrations/api_project_test.go b/integrations/api_project_test.go index 75e51d97bfe7f..03f059de01aba 100644 --- a/integrations/api_project_test.go +++ b/integrations/api_project_test.go @@ -5,25 +5,146 @@ package integrations import ( + "fmt" + "net/http" + "net/url" "testing" + + project_model "code.gitea.io/gitea/models/project" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "github.com/stretchr/testify/assert" ) func TestAPIListRepositoryProjects(t *testing.T) { defer prepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/projects", owner.Name, repo.Name)) + + link.RawQuery = url.Values{"token": {token}}.Encode() + + req := NewRequest(t, "GET", link.String()) + var apiProjects []*api.Project + + resp := session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiProjects) + + assert.Len(t, apiProjects, 2) + for _, apiProject := range apiProjects { + unittest.AssertExistsAndLoadBean(t, &project_model.Project{ID: apiProject.ID, RepoID: repo.ID, IsClosed: apiProject.IsClosed}) + } } func TestAPICreateRepositoryProject(t *testing.T) { defer prepareTestEnv(t)() + const title, description, board_type = "project_name", "project_description", uint8(project_model.BoardTypeBasicKanban) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/projects?token=%s", owner.Name, repo.Name, token) + + req := NewRequestWithJSON(t, "POST", urlStr, &api.NewProjectPayload{ + Title: title, + Description: description, + BoardType: board_type, + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + var apiProject api.Project + DecodeJSON(t, resp, &apiProject) + assert.Equal(t, title, apiProject.Title) + assert.Equal(t, description, apiProject.Description) + assert.Equal(t, board_type, apiProject.BoardType) + assert.Equal(t, owner.Name, apiProject.Creator.UserName) + assert.Equal(t, owner.FullName, apiProject.Creator.FullName) + assert.Equal(t, repo.ID, apiProject.Repo.ID) + assert.Equal(t, repo.Name, apiProject.Repo.Name) + assert.Equal(t, repo.FullName(), apiProject.Repo.FullName) + assert.Equal(t, owner.Name, apiProject.Repo.Owner) } func TestAPIGetProject(t *testing.T) { defer prepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User) + project := unittest.AssertExistsAndLoadBean(t, &project_model.Project{ID: 1}).(*project_model.Project) + project_repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: project.RepoID}).(*repo_model.Repository) + project_creator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: project.CreatorID}).(*user_model.User) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + + urlStr := fmt.Sprintf("/api/v1/projects/%d?token=%s", project.ID, token) + + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiProject api.Project + DecodeJSON(t, resp, &apiProject) + + assert.Equal(t, project.Title, apiProject.Title) + assert.Equal(t, project.Description, apiProject.Description) + assert.Equal(t, uint8(project.BoardType), apiProject.BoardType) + assert.Equal(t, project_creator.Name, apiProject.Creator.UserName) + assert.Equal(t, project_creator.FullName, apiProject.Creator.FullName) + assert.Equal(t, project_repo.ID, apiProject.Repo.ID) + assert.Equal(t, project_repo.Name, apiProject.Repo.Name) + assert.Equal(t, project_repo.FullName(), apiProject.Repo.FullName) + assert.Equal(t, project_repo.OwnerName, apiProject.Repo.Owner) } func TestAPIUpdateProject(t *testing.T) { defer prepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User) + project_before := unittest.AssertExistsAndLoadBean(t, &project_model.Project{ID: 1}).(*project_model.Project) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + + urlStr := fmt.Sprintf("/api/v1/projects/%d?token=%s", project_before.ID, token) + + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.UpdateProjectPayload{ + Title: "This is new title", + Description: "This is new description", + }) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiProject api.Project + DecodeJSON(t, resp, &apiProject) + project_after := unittest.AssertExistsAndLoadBean(t, &project_model.Project{ID: 1}).(*project_model.Project) + + assert.Equal(t, "This is new title", apiProject.Title) + assert.Equal(t, "This is new description", apiProject.Description) + assert.Equal(t, "This is new title", project_after.Title) + assert.Equal(t, "This is new description", project_after.Description) + } func TestAPIDeleteProject(t *testing.T) { defer prepareTestEnv(t)() + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User) + project_before := unittest.AssertExistsAndLoadBean(t, &project_model.Project{ID: 1}).(*project_model.Project) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + + urlStr := fmt.Sprintf("/api/v1/projects/%d?token=%s", project_before.ID, token) + + req := NewRequest(t, "DELETE", urlStr) + _ = session.MakeRequest(t, req, http.StatusNoContent) + + unittest.AssertNotExistsBean(t, &project_model.Project{ID: 1}) } diff --git a/models/fixtures/project.yml b/models/fixtures/project.yml index 3d42597c5e8ac..9cc14df3f12ba 100644 --- a/models/fixtures/project.yml +++ b/models/fixtures/project.yml @@ -7,6 +7,16 @@ board_type: 1 type: 2 +- + id: 4 + title: Fourth project + description: This is fourth project + repo_id: 1 + is_closed: true + creator_id: 2 + board_type: 1 + type: 2 + - id: 2 title: second project diff --git a/models/project/project.go b/models/project/project.go index 0aa37cc5c9071..b523cbbf729e6 100644 --- a/models/project/project.go +++ b/models/project/project.go @@ -14,6 +14,9 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "xorm.io/builder" ) @@ -70,14 +73,18 @@ func (err ErrProjectBoardNotExist) Error() string { return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID) } +type ProjectList []*Project + // Project represents a project board type Project struct { - ID int64 `xorm:"pk autoincr"` - Title string `xorm:"INDEX NOT NULL"` - Description string `xorm:"TEXT"` - RepoID int64 `xorm:"INDEX"` - CreatorID int64 `xorm:"NOT NULL"` - IsClosed bool `xorm:"INDEX"` + ID int64 `xorm:"pk autoincr"` + Title string `xorm:"INDEX NOT NULL"` + Description string `xorm:"TEXT"` + RepoID int64 `xorm:"INDEX"` + Repo *repo_model.Repository `xorm:"-"` + CreatorID int64 `xorm:"NOT NULL"` + Creator *user_model.User `xorm:"-"` + IsClosed bool `xorm:"INDEX"` BoardType BoardType Type Type @@ -330,3 +337,23 @@ func DeleteProjectByIDCtx(ctx context.Context, id int64) error { return updateRepositoryProjectCount(ctx, p.RepoID) } + +func (project *Project) LoadRepo(ctx context.Context) (err error) { + if project.Repo == nil { + project.Repo, err = repo_model.GetRepositoryByIDCtx(ctx, project.RepoID) + if err != nil { + return fmt.Errorf("getRepositoryByID [%d]: %v", project.RepoID, err) + } + } + return nil +} + +func (project *Project) LoadCreator(ctx context.Context) (err error) { + if project.Creator == nil { + project.Creator, err = user_model.GetUserByIDCtx(ctx, project.CreatorID) + if err != nil { + return fmt.Errorf("getUserByID [%d]: %v", project.CreatorID, err) + } + } + return nil +} diff --git a/modules/convert/project.go b/modules/convert/project.go new file mode 100644 index 0000000000000..c85ce1534969a --- /dev/null +++ b/modules/convert/project.go @@ -0,0 +1,53 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "code.gitea.io/gitea/models/db" + project_model "code.gitea.io/gitea/models/project" + api "code.gitea.io/gitea/modules/structs" +) + +func ToAPIProject(project *project_model.Project) *api.Project { + if err := project.LoadRepo(db.DefaultContext); err != nil { + return &api.Project{} + } + if err := project.LoadCreator(db.DefaultContext); err != nil { + return &api.Project{} + } + + apiProject := &api.Project{ + Title: project.Title, + Description: project.Description, + BoardType: uint8(project.BoardType), + IsClosed: project.IsClosed, + Created: project.CreatedUnix.AsTime(), + Updated: project.UpdatedUnix.AsTime(), + Closed: project.ClosedDateUnix.AsTime(), + } + + apiProject.Repo = &api.RepositoryMeta{ + ID: project.Repo.ID, + Name: project.Repo.Name, + Owner: project.Repo.OwnerName, + FullName: project.Repo.FullName(), + } + + apiProject.Creator = &api.User{ + ID: project.Creator.ID, + UserName: project.Creator.Name, + FullName: project.Creator.FullName, + } + + return apiProject +} + +func ToAPIProjectList(projects project_model.ProjectList) []*api.Project { + result := make([]*api.Project, len(projects)) + for i := range projects { + result[i] = ToAPIProject(projects[i]) + } + return result +} diff --git a/modules/structs/project.go b/modules/structs/project.go index a2c935e9d8663..1fbeba1e3ac8c 100644 --- a/modules/structs/project.go +++ b/modules/structs/project.go @@ -4,17 +4,39 @@ package structs +import "time" + // swagger:model -type UpsertProjectPayload struct { - Title string `json:"title" binding:"Required"` - Description string `json:"body"` +type NewProjectPayload struct { + // required:true + Title string `json:"title" binding:"Required"` + // required:true BoardType uint8 `json:"board_type"` + Description string `json:"description"` +} + +// swagger:model +type UpdateProjectPayload struct { + // required:true + Title string `json:"title" binding:"Required"` + Description string `json:"description"` } type Project struct { + ID int64 `json:"id"` Title string `json:"title"` - Description string `json:"body"` + Description string `json:"description"` BoardType uint8 `json:"board_type"` + IsClosed bool `json:"is_closed"` + // 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"` + + Repo *RepositoryMeta `json:"repository"` + Creator *User `json:"creator"` } type ProjectBoard struct { diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index c93606ae88308..a51f6e9341e39 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -937,6 +937,11 @@ func Routes() *web.Route { Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueReaction) }) }, mustEnableIssuesOrPulls) + m.Group("/projects", func() { + m.Combo(""). + Get(reqToken(), repo.ListRepositoryProjects). + Post(reqToken(), mustNotBeArchived, bind(api.NewProjectPayload{}), repo.CreateRepositoryProject) + }) m.Group("/labels", func() { m.Combo("").Get(repo.ListLabels). Post(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.CreateLabelOption{}), repo.CreateLabel) @@ -1167,6 +1172,16 @@ func Routes() *web.Route { m.Group("/topics", func() { m.Get("/search", repo.TopicSearch) }) + + // Projects + m.Group("/projects", func() { + m.Group("/{id}", func() { + m.Combo(""). + Get(reqToken(), repo.GetProject). + Patch(reqToken(), bind(api.UpdateProjectPayload{}), repo.UpdateProject). + Delete(reqToken(), repo.DeleteProject) + }) + }) }, sudo()) return m diff --git a/routers/api/v1/repo/project.go b/routers/api/v1/repo/project.go index df5ad147f8c9b..f5d48c8269567 100644 --- a/routers/api/v1/repo/project.go +++ b/routers/api/v1/repo/project.go @@ -6,7 +6,15 @@ package repo import ( + "fmt" + "net/http" + + project_model "code.gitea.io/gitea/models/project" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" ) func GetProject(ctx *context.APIContext) { @@ -28,6 +36,16 @@ func GetProject(ctx *context.APIContext) { // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" + project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) + if err != nil { + if project_model.IsErrProjectNotExist(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "GetProjectByID", err) + } + return + } + ctx.JSON(http.StatusOK, convert.ToAPIProject(project)) } func UpdateProject(ctx *context.APIContext) { @@ -36,12 +54,18 @@ func UpdateProject(ctx *context.APIContext) { // summary: Update project // produces: // - application/json + // consumes: + // - application/json // parameters: // - name: id // in: path // description: id of the project // type: string // required: true + // - name: project + // in: body + // required: true + // schema: { "$ref": "#/definitions/UpdateProjectPayload" } // responses: // "200": // "$ref": "#/responses/Project" @@ -49,14 +73,35 @@ func UpdateProject(ctx *context.APIContext) { // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" + form := web.GetForm(ctx).(*api.UpdateProjectPayload) + project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) + if err != nil { + if project_model.IsErrProjectNotExist(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "GetProjectByID", err) + } + return + } + if form.Title != project.Title { + project.Title = form.Title + } + if form.Description != project.Description { + project.Description = form.Description + } + err = project_model.UpdateProject(ctx, project) + if err != nil { + ctx.Error(http.StatusInternalServerError, "UpdateProject", err) + return + } + + ctx.JSON(http.StatusOK, project) } func DeleteProject(ctx *context.APIContext) { // swagger:operation DELETE /projects/{id} project projectDeleteProject // --- // summary: Delete project - // produces: - // - application/json // parameters: // - name: id // in: path @@ -64,12 +109,19 @@ func DeleteProject(ctx *context.APIContext) { // type: string // required: true // responses: - // "200": + // "204": // "description": "Deleted the project" // "403": // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" + err := project_model.DeleteProjectByIDCtx(ctx, ctx.ParamsInt64(":id")) + if err != nil { + ctx.Error(http.StatusInternalServerError, "DeleteProject", err) + return + } + + ctx.Status(http.StatusNoContent) } func CreateRepositoryProject(ctx *context.APIContext) { @@ -78,6 +130,8 @@ func CreateRepositoryProject(ctx *context.APIContext) { // summary: Create a repository project // produces: // - application/json + // consumes: + // - application/json // parameters: // - name: owner // in: path @@ -89,6 +143,10 @@ func CreateRepositoryProject(ctx *context.APIContext) { // description: repo // type: string // required: true + // - name: project + // in: body + // required: true + // schema: { "$ref": "#/definitions/NewProjectPayload" } // responses: // "201": // "$ref": "#/responses/Project" @@ -96,6 +154,30 @@ func CreateRepositoryProject(ctx *context.APIContext) { // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" + form := web.GetForm(ctx).(*api.NewProjectPayload) + project := &project_model.Project{ + RepoID: ctx.Repo.Repository.ID, + Title: form.Title, + Description: form.Description, + CreatorID: ctx.Doer.ID, + BoardType: project_model.BoardType(form.BoardType), + Type: project_model.TypeRepository, + } + + var err error + if err = project_model.NewProject(project); err != nil { + ctx.Error(http.StatusInternalServerError, "NewProject", err) + return + } + + project, err = project_model.GetProjectByID(ctx, project.ID) + + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetProjectByID", err) + return + } + + ctx.JSON(http.StatusCreated, convert.ToAPIProject(project)) } func ListRepositoryProjects(ctx *context.APIContext) { @@ -115,6 +197,10 @@ func ListRepositoryProjects(ctx *context.APIContext) { // description: repo // type: string // required: true + // - name: closed + // in: query + // description: include closed issues or not + // type: boolean // - name: page // in: query // description: page number of results to return (1-based) @@ -130,4 +216,20 @@ func ListRepositoryProjects(ctx *context.APIContext) { // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" + fmt.Print(ctx.FormOptionalBool("closed")) + projects, count, err := project_model.GetProjects(ctx, project_model.SearchOptions{ + RepoID: ctx.Repo.Repository.ID, + Page: ctx.FormInt("page"), + IsClosed: ctx.FormOptionalBool("closed"), + Type: project_model.TypeRepository, + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, "Projects", err) + return + } + + ctx.SetLinkHeader(int(count), setting.UI.IssuePagingNum) + ctx.SetTotalCountHeader(count) + + ctx.JSON(http.StatusOK, convert.ToAPIProjectList(projects)) } diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index 5271fe697343d..4612777604cff 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -174,7 +174,10 @@ type swaggerParameterBodies struct { CreateWikiPageOptions api.CreateWikiPageOptions // in:body - UpsertProjectPayload api.UpsertProjectPayload + NewProjectPayload api.NewProjectPayload + + // in:body + UpdateProjectPayload api.UpdateProjectPayload // in:body UpsertProjectBoardPayload api.UpsertProjectBoardPayload diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 4e61edabf0efd..02faa0cfc5029 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -7977,6 +7977,9 @@ } }, "post": { + "consumes": [ + "application/json" + ], "produces": [ "application/json" ], @@ -7999,6 +8002,14 @@ "name": "repo", "in": "path", "required": true + }, + { + "name": "project", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/UpsertProjectPayload" + } } ], "responses": { @@ -17633,6 +17644,9 @@ "format": "date-time", "x-go-name": "Created" }, + "creator": { + "$ref": "#/definitions/User" + }, "description": { "type": "string", "x-go-name": "Description" From 649042f925f7c24db74f02d669a0785db75ea9fe Mon Sep 17 00:00:00 2001 From: Baekjun Kim <36013575+kimbj95@users.noreply.github.com> Date: Fri, 15 Jul 2022 01:52:11 -0700 Subject: [PATCH 33/85] Include login_name in adminCreateUser response (#20283) `login_name` (Authentication Sign-in Name) is not included in the response of `adminUserCreate` API. This PR is to return user-specified `login_name` if there is one. --- modules/convert/user.go | 1 + modules/structs/user.go | 3 +++ templates/swagger/v1_json.tmpl | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/modules/convert/user.go b/modules/convert/user.go index 2b07d21838d71..093994856cae7 100644 --- a/modules/convert/user.go +++ b/modules/convert/user.go @@ -73,6 +73,7 @@ func toUser(user *user_model.User, signed, authed bool) *api.User { // only site admin will get these information and possibly user himself if authed { result.IsAdmin = user.IsAdmin + result.LoginName = user.LoginName result.LastLogin = user.LastLoginUnix.AsTime() result.Language = user.Language result.IsActive = user.IsActive diff --git a/modules/structs/user.go b/modules/structs/user.go index 431e230fac038..81516894a58f9 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -17,6 +17,9 @@ type User struct { ID int64 `json:"id"` // the user's username UserName string `json:"login"` + // the user's authentication sign-in name. + // default: empty + LoginName string `json:"login_name"` // the user's full name FullName string `json:"full_name"` // swagger:strfmt email diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 71a43012e929d..a19738b2a0501 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -18963,6 +18963,12 @@ "type": "string", "x-go-name": "UserName" }, + "login_name": { + "description": "the user's authentication sign-in name.", + "type": "string", + "default": "empty", + "x-go-name": "LoginName" + }, "prohibit_login": { "description": "Is user login prohibited", "type": "boolean", From 03d146cf5246fe21c5c3e7b7b52b861e333e596c Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 15 Jul 2022 11:38:18 +0200 Subject: [PATCH 34/85] Fix eslint parsing errors, remove eslint-plugin-html (#20323) Introduce a separate .eslintrc in the Vue components folder to selectively enable vue-eslint-parser there, so that the rest of the files can use eslint's core parser which can deal with hashbangs. The fact that the eslint-disable comments worked in HTML was a unintended side-effect of the files being parsed via vue-eslint-parser, so I had to disable the parsing of these files in .eslintrc.yaml to make it work, and finally decided to remove eslint-plugin-html as it causes more issues than it solves. --- .eslintrc.yaml | 19 +----- Makefile | 2 +- package-lock.json | 68 --------------------- package.json | 1 - templates/base/head_script.tmpl | 1 - templates/repo/issue/view_content/pull.tmpl | 1 - web_src/js/components/.eslintrc.yaml | 16 +++++ 7 files changed, 18 insertions(+), 90 deletions(-) create mode 100644 web_src/js/components/.eslintrc.yaml diff --git a/.eslintrc.yaml b/.eslintrc.yaml index ff62d9cc93b00..3a906c44cd840 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -11,13 +11,8 @@ parserOptions: plugins: - eslint-plugin-unicorn - eslint-plugin-import - - eslint-plugin-vue - - eslint-plugin-html - eslint-plugin-jquery -extends: - - plugin:vue/recommended - env: es2022: true node: true @@ -25,18 +20,11 @@ env: globals: __webpack_public_path__: true -settings: - html/html-extensions: [".tmpl"] - overrides: - - files: ["web_src/**/*.js", "web_src/**/*.vue", "templates/**/*.tmpl"] + - files: ["web_src/**/*.js", "docs/**/*.js"] env: browser: true node: false - - files: ["templates/**/*.tmpl"] - rules: - no-tabs: [0] - indent: [2, tab, {SwitchCase: 1}] - files: ["web_src/**/*worker.js"] env: worker: true @@ -502,11 +490,6 @@ rules: use-isnan: [2] valid-typeof: [2, {requireStringLiterals: true}] vars-on-top: [0] - vue/attributes-order: [0] - vue/component-definition-name-casing: [0] - vue/html-closing-bracket-spacing: [0] - vue/max-attributes-per-line: [0] - vue/one-component-per-file: [0] wrap-iife: [2, inside] wrap-regex: [0] yield-star-spacing: [2, after] diff --git a/Makefile b/Makefile index 5536788613c4f..5d1b54e852d35 100644 --- a/Makefile +++ b/Makefile @@ -310,7 +310,7 @@ lint: lint-frontend lint-backend .PHONY: lint-frontend lint-frontend: node_modules - npx eslint --color --max-warnings=0 web_src/js build templates *.config.js docs/assets/js + npx eslint --color --max-warnings=0 --ext js,vue web_src/js build *.config.js docs/assets/js npx stylelint --color --max-warnings=0 web_src/less npx spectral lint -q -F hint $(SWAGGER_SPEC) diff --git a/package-lock.json b/package-lock.json index 5032e50fc5250..605134201ebb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,6 @@ "@happy-dom/jest-environment": "4.0.1", "@stoplight/spectral-cli": "6.4.1", "eslint": "8.15.0", - "eslint-plugin-html": "6.2.0", "eslint-plugin-import": "2.26.0", "eslint-plugin-jquery": "1.5.1", "eslint-plugin-unicorn": "42.0.0", @@ -4839,18 +4838,6 @@ "node": ">=10.13.0" } }, - "node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/envinfo": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", @@ -5231,15 +5218,6 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-html": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-6.2.0.tgz", - "integrity": "sha512-vi3NW0E8AJombTvt8beMwkL1R/fdRWl4QSNRNMhVQKWm36/X0KF0unGNAY4mqUF06mnwVWZcIcerrCnfn9025g==", - "dev": true, - "dependencies": { - "htmlparser2": "^7.1.2" - } - }, "node_modules/eslint-plugin-import": { "version": "2.26.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", @@ -6262,25 +6240,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/htmlparser2": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", - "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.2", - "domutils": "^2.8.0", - "entities": "^3.0.1" - } - }, "node_modules/http-basic": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", @@ -16221,12 +16180,6 @@ "tapable": "^2.2.0" } }, - "entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dev": true - }, "envinfo": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", @@ -16519,15 +16472,6 @@ } } }, - "eslint-plugin-html": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-6.2.0.tgz", - "integrity": "sha512-vi3NW0E8AJombTvt8beMwkL1R/fdRWl4QSNRNMhVQKWm36/X0KF0unGNAY4mqUF06mnwVWZcIcerrCnfn9025g==", - "dev": true, - "requires": { - "htmlparser2": "^7.1.2" - } - }, "eslint-plugin-import": { "version": "2.26.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", @@ -17317,18 +17261,6 @@ "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", "dev": true }, - "htmlparser2": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", - "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.2", - "domutils": "^2.8.0", - "entities": "^3.0.1" - } - }, "http-basic": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", diff --git a/package.json b/package.json index b55da05b3dec0..afc8d2a374e96 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "@happy-dom/jest-environment": "4.0.1", "@stoplight/spectral-cli": "6.4.1", "eslint": "8.15.0", - "eslint-plugin-html": "6.2.0", "eslint-plugin-import": "2.26.0", "eslint-plugin-jquery": "1.5.1", "eslint-plugin-unicorn": "42.0.0", diff --git a/templates/base/head_script.tmpl b/templates/base/head_script.tmpl index 2f74b959e2a1a..48a3df693a39b 100644 --- a/templates/base/head_script.tmpl +++ b/templates/base/head_script.tmpl @@ -4,7 +4,6 @@ If you are customizing Gitea, please do not change this file. If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly. */}}