some improvements
This commit is contained in:
@@ -10,25 +10,42 @@ import (
|
|||||||
// Project represents a project
|
// Project represents a project
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type Project struct {
|
type Project struct {
|
||||||
|
// Unique identifier of the project
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
|
// Project title
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
// Project description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
// Owner ID (for organization or user projects)
|
||||||
OwnerID int64 `json:"owner_id,omitempty"`
|
OwnerID int64 `json:"owner_id,omitempty"`
|
||||||
|
// Repository ID (for repository projects)
|
||||||
RepoID int64 `json:"repo_id,omitempty"`
|
RepoID int64 `json:"repo_id,omitempty"`
|
||||||
|
// Creator ID
|
||||||
CreatorID int64 `json:"creator_id"`
|
CreatorID int64 `json:"creator_id"`
|
||||||
|
// Whether the project is closed
|
||||||
IsClosed bool `json:"is_closed"`
|
IsClosed bool `json:"is_closed"`
|
||||||
|
// Template type: 0=none, 1=basic_kanban, 2=bug_triage
|
||||||
TemplateType int `json:"template_type"`
|
TemplateType int `json:"template_type"`
|
||||||
|
// Card type: 0=text_only, 1=images_and_text
|
||||||
CardType int `json:"card_type"`
|
CardType int `json:"card_type"`
|
||||||
|
// Project type: 1=individual, 2=repository, 3=organization
|
||||||
Type int `json:"type"`
|
Type int `json:"type"`
|
||||||
|
// Number of open issues
|
||||||
NumOpenIssues int64 `json:"num_open_issues,omitempty"`
|
NumOpenIssues int64 `json:"num_open_issues,omitempty"`
|
||||||
|
// Number of closed issues
|
||||||
NumClosedIssues int64 `json:"num_closed_issues,omitempty"`
|
NumClosedIssues int64 `json:"num_closed_issues,omitempty"`
|
||||||
|
// Total number of issues
|
||||||
NumIssues int64 `json:"num_issues,omitempty"`
|
NumIssues int64 `json:"num_issues,omitempty"`
|
||||||
|
// Created time
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
|
// Updated time
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
Updated time.Time `json:"updated"`
|
Updated time.Time `json:"updated"`
|
||||||
|
// Closed time
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
ClosedDate *time.Time `json:"closed_date,omitempty"`
|
ClosedDate *time.Time `json:"closed_date,omitempty"`
|
||||||
|
// Project URL
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,15 +54,20 @@ type Project struct {
|
|||||||
type CreateProjectOption struct {
|
type CreateProjectOption struct {
|
||||||
// required: true
|
// required: true
|
||||||
Title string `json:"title" binding:"Required"`
|
Title string `json:"title" binding:"Required"`
|
||||||
|
// Project description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
// Template type: 0=none, 1=basic_kanban, 2=bug_triage
|
||||||
TemplateType int `json:"template_type"`
|
TemplateType int `json:"template_type"`
|
||||||
|
// Card type: 0=text_only, 1=images_and_text
|
||||||
CardType int `json:"card_type"`
|
CardType int `json:"card_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditProjectOption represents options for editing a project
|
// EditProjectOption represents options for editing a project
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type EditProjectOption struct {
|
type EditProjectOption struct {
|
||||||
|
// Project title
|
||||||
Title *string `json:"title,omitempty"`
|
Title *string `json:"title,omitempty"`
|
||||||
|
// Project description
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
// Card type: 0=text_only, 1=images_and_text
|
// Card type: 0=text_only, 1=images_and_text
|
||||||
CardType *int `json:"card_type,omitempty"`
|
CardType *int `json:"card_type,omitempty"`
|
||||||
@@ -56,16 +78,26 @@ type EditProjectOption struct {
|
|||||||
// ProjectColumn represents a project column (board)
|
// ProjectColumn represents a project column (board)
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type ProjectColumn struct {
|
type ProjectColumn struct {
|
||||||
|
// Unique identifier of the column
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
|
// Column title
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
// Whether this is the default column
|
||||||
Default bool `json:"default"`
|
Default bool `json:"default"`
|
||||||
|
// Sorting order
|
||||||
Sorting int `json:"sorting"`
|
Sorting int `json:"sorting"`
|
||||||
|
// Column color (hex format)
|
||||||
Color string `json:"color,omitempty"`
|
Color string `json:"color,omitempty"`
|
||||||
|
// Project ID
|
||||||
ProjectID int64 `json:"project_id"`
|
ProjectID int64 `json:"project_id"`
|
||||||
|
// Creator ID
|
||||||
CreatorID int64 `json:"creator_id"`
|
CreatorID int64 `json:"creator_id"`
|
||||||
|
// Number of issues in this column
|
||||||
NumIssues int64 `json:"num_issues,omitempty"`
|
NumIssues int64 `json:"num_issues,omitempty"`
|
||||||
|
// Created time
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
|
// Updated time
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
Updated time.Time `json:"updated"`
|
Updated time.Time `json:"updated"`
|
||||||
}
|
}
|
||||||
@@ -75,22 +107,17 @@ type ProjectColumn struct {
|
|||||||
type CreateProjectColumnOption struct {
|
type CreateProjectColumnOption struct {
|
||||||
// required: true
|
// required: true
|
||||||
Title string `json:"title" binding:"Required"`
|
Title string `json:"title" binding:"Required"`
|
||||||
|
// Column color (hex format, e.g., #FF0000)
|
||||||
Color string `json:"color,omitempty"`
|
Color string `json:"color,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditProjectColumnOption represents options for editing a project column
|
// EditProjectColumnOption represents options for editing a project column
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type EditProjectColumnOption struct {
|
type EditProjectColumnOption struct {
|
||||||
|
// Column title
|
||||||
Title *string `json:"title,omitempty"`
|
Title *string `json:"title,omitempty"`
|
||||||
|
// Column color (hex format)
|
||||||
Color *string `json:"color,omitempty"`
|
Color *string `json:"color,omitempty"`
|
||||||
|
// Sorting order
|
||||||
Sorting *int `json:"sorting,omitempty"`
|
Sorting *int `json:"sorting,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// AddIssueToProjectColumnOption represents options for adding an issue to a project column
|
|
||||||
// swagger:model
|
|
||||||
type AddIssueToProjectColumnOption struct {
|
|
||||||
// required: true
|
|
||||||
IssueIDs []int64 `json:"issue_ids" binding:"Required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1589,7 +1589,9 @@ func Routes() *web.Router {
|
|||||||
m.Combo("").
|
m.Combo("").
|
||||||
Patch(reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, bind(api.EditProjectColumnOption{}), repo.EditProjectColumn).
|
Patch(reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, bind(api.EditProjectColumnOption{}), repo.EditProjectColumn).
|
||||||
Delete(reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, repo.DeleteProjectColumn)
|
Delete(reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, repo.DeleteProjectColumn)
|
||||||
m.Post("/issues", reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, bind(api.AddIssueToProjectColumnOption{}), repo.AddIssueToProjectColumn)
|
m.Get("/issues", repo.ListProjectColumnIssues)
|
||||||
|
m.Post("/issues/{issue_id}", reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, repo.AddIssueToProjectColumn)
|
||||||
|
m.Delete("/issues/{issue_id}", reqToken(), reqRepoWriter(unit.TypeProjects), mustNotBeArchived, repo.RemoveIssueFromProjectColumn)
|
||||||
})
|
})
|
||||||
}, reqRepoReader(unit.TypeProjects))
|
}, reqRepoReader(unit.TypeProjects))
|
||||||
}, repoAssignment(), checkTokenPublicOnly())
|
}, repoAssignment(), checkTokenPublicOnly())
|
||||||
|
|||||||
@@ -572,9 +572,78 @@ func DeleteProjectColumn(ctx *context.APIContext) {
|
|||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListProjectColumnIssues lists all issues in a project column
|
||||||
|
func ListProjectColumnIssues(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/projects/columns/{id}/issues repository repoListProjectColumnIssues
|
||||||
|
// ---
|
||||||
|
// summary: List issues in a project column
|
||||||
|
// 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 column
|
||||||
|
// type: integer
|
||||||
|
// format: int64
|
||||||
|
// 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/IssueList"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
column := getRepoProjectColumn(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
listOptions := utils.GetListOptions(ctx)
|
||||||
|
issuesOpts := &issues_model.IssuesOptions{
|
||||||
|
Paginator: &listOptions,
|
||||||
|
RepoIDs: []int64{ctx.Repo.Repository.ID},
|
||||||
|
ProjectID: column.ProjectID,
|
||||||
|
ProjectColumnID: column.ID,
|
||||||
|
SortType: "project-column-sorting",
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := issues_model.CountIssues(ctx, issuesOpts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
issues, err := issues_model.Issues(ctx, issuesOpts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
|
||||||
|
}
|
||||||
|
|
||||||
// AddIssueToProjectColumn adds an issue to a project column
|
// AddIssueToProjectColumn adds an issue to a project column
|
||||||
func AddIssueToProjectColumn(ctx *context.APIContext) {
|
func AddIssueToProjectColumn(ctx *context.APIContext) {
|
||||||
// swagger:operation POST /repos/{owner}/{repo}/projects/columns/{id}/issues repository repoAddIssueToProjectColumn
|
// swagger:operation POST /repos/{owner}/{repo}/projects/columns/{id}/issues/{issue_id} repository repoAddIssueToProjectColumn
|
||||||
// ---
|
// ---
|
||||||
// summary: Add an issue to a project column
|
// summary: Add an issue to a project column
|
||||||
// consumes:
|
// consumes:
|
||||||
@@ -598,10 +667,12 @@ func AddIssueToProjectColumn(ctx *context.APIContext) {
|
|||||||
// type: integer
|
// type: integer
|
||||||
// format: int64
|
// format: int64
|
||||||
// required: true
|
// required: true
|
||||||
// - name: body
|
// - name: issue_id
|
||||||
// in: body
|
// in: path
|
||||||
// schema:
|
// description: id of the issue
|
||||||
// "$ref": "#/definitions/AddIssueToProjectColumnOption"
|
// type: integer
|
||||||
|
// format: int64
|
||||||
|
// required: true
|
||||||
// responses:
|
// responses:
|
||||||
// "201":
|
// "201":
|
||||||
// "$ref": "#/responses/empty"
|
// "$ref": "#/responses/empty"
|
||||||
@@ -617,9 +688,7 @@ func AddIssueToProjectColumn(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form := web.GetForm(ctx).(*api.AddIssueToProjectColumnOption)
|
issue, err := issues_model.GetIssueByID(ctx, ctx.PathParamInt64("issue_id"))
|
||||||
|
|
||||||
issue, err := issues_model.GetIssueByID(ctx, form.IssueID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if issues_model.IsErrIssueNotExist(err) {
|
if issues_model.IsErrIssueNotExist(err) {
|
||||||
ctx.APIError(http.StatusUnprocessableEntity, "issue not found")
|
ctx.APIError(http.StatusUnprocessableEntity, "issue not found")
|
||||||
@@ -641,3 +710,74 @@ func AddIssueToProjectColumn(ctx *context.APIContext) {
|
|||||||
|
|
||||||
ctx.Status(http.StatusCreated)
|
ctx.Status(http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveIssueFromProjectColumn remove an issue from a project column
|
||||||
|
func RemoveIssueFromProjectColumn(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/projects/columns/{id}/issues/{issue_id} repository repoAddIssueToProjectColumn
|
||||||
|
// ---
|
||||||
|
// summary: Add an issue to a project column
|
||||||
|
// 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: id
|
||||||
|
// in: path
|
||||||
|
// description: id of the column
|
||||||
|
// type: integer
|
||||||
|
// format: int64
|
||||||
|
// required: true
|
||||||
|
// - name: issue_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the issue
|
||||||
|
// type: integer
|
||||||
|
// format: int64
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
|
||||||
|
column := getRepoProjectColumn(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := issues_model.GetIssueByID(ctx, ctx.PathParamInt64("issue_id"))
|
||||||
|
if err != nil {
|
||||||
|
if issues_model.IsErrIssueNotExist(err) {
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, "issue not found")
|
||||||
|
} else {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue.RepoID != ctx.Repo.Repository.ID {
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, "issue does not belong to this repository")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 means remove
|
||||||
|
if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, 0, column.ID); err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|||||||
@@ -243,7 +243,4 @@ type swaggerParameterBodies struct {
|
|||||||
CreateProjectColumnOption api.CreateProjectColumnOption
|
CreateProjectColumnOption api.CreateProjectColumnOption
|
||||||
// in:body
|
// in:body
|
||||||
EditProjectColumnOption api.EditProjectColumnOption
|
EditProjectColumnOption api.EditProjectColumnOption
|
||||||
|
|
||||||
// in:body
|
|
||||||
AddIssueToProjectColumnOption api.AddIssueToProjectColumnOption
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -538,9 +538,7 @@ func TestAPIAddIssueToProjectColumn(t *testing.T) {
|
|||||||
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
|
||||||
// Test adding issue to column
|
// Test adding issue to column
|
||||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column1.ID), &api.AddIssueToProjectColumnOption{
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues/%d", owner.Name, repo.Name, column1.ID, issue.ID), nil).AddTokenAuth(token)
|
||||||
IssueID: issue.ID,
|
|
||||||
}).AddTokenAuth(token)
|
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
// Verify issue is in the column
|
// Verify issue is in the column
|
||||||
@@ -551,9 +549,7 @@ func TestAPIAddIssueToProjectColumn(t *testing.T) {
|
|||||||
assert.Equal(t, column1.ID, projectIssue.ProjectColumnID)
|
assert.Equal(t, column1.ID, projectIssue.ProjectColumnID)
|
||||||
|
|
||||||
// Test moving issue to another column
|
// Test moving issue to another column
|
||||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column2.ID), &api.AddIssueToProjectColumnOption{
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues/%d", owner.Name, repo.Name, column2.ID, issue.ID), nil).AddTokenAuth(token)
|
||||||
IssueID: issue.ID,
|
|
||||||
}).AddTokenAuth(token)
|
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
// Verify issue moved to new column
|
// Verify issue moved to new column
|
||||||
@@ -564,24 +560,129 @@ func TestAPIAddIssueToProjectColumn(t *testing.T) {
|
|||||||
assert.Equal(t, column2.ID, projectIssue.ProjectColumnID)
|
assert.Equal(t, column2.ID, projectIssue.ProjectColumnID)
|
||||||
|
|
||||||
// Test adding same issue to same column (should be idempotent)
|
// Test adding same issue to same column (should be idempotent)
|
||||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column2.ID), &api.AddIssueToProjectColumnOption{
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues/%d", owner.Name, repo.Name, column2.ID, issue.ID), nil).AddTokenAuth(token)
|
||||||
IssueID: issue.ID,
|
|
||||||
}).AddTokenAuth(token)
|
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
// Test adding non-existent issue
|
// Test adding non-existent issue
|
||||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column1.ID), &api.AddIssueToProjectColumnOption{
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues/%d", owner.Name, repo.Name, column1.ID, 99999), nil).AddTokenAuth(token)
|
||||||
IssueID: 99999,
|
|
||||||
}).AddTokenAuth(token)
|
|
||||||
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||||
|
|
||||||
// Test adding to non-existent column
|
// Test adding to non-existent column
|
||||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/99999/issues", owner.Name, repo.Name), &api.AddIssueToProjectColumnOption{
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/99999/issues/%d", owner.Name, repo.Name, issue.ID), nil).AddTokenAuth(token)
|
||||||
IssueID: issue.ID,
|
|
||||||
}).AddTokenAuth(token)
|
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIListProjectColumnIssues(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID, IsPull: false})
|
||||||
|
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID, IsPull: true})
|
||||||
|
|
||||||
|
project := &project_model.Project{
|
||||||
|
Title: "Project for Column Issues",
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Type: project_model.TypeRepository,
|
||||||
|
CreatorID: owner.ID,
|
||||||
|
TemplateType: project_model.TemplateTypeNone,
|
||||||
|
}
|
||||||
|
err := project_model.NewProject(t.Context(), project)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
||||||
|
}()
|
||||||
|
|
||||||
|
column := &project_model.Column{
|
||||||
|
Title: "Column for Issues",
|
||||||
|
ProjectID: project.ID,
|
||||||
|
CreatorID: owner.ID,
|
||||||
|
}
|
||||||
|
err = project_model.NewColumn(t.Context(), column)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue, owner, project.ID, column.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = issues_model.IssueAssignOrRemoveProject(t.Context(), pull, owner, project.ID, column.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeReadIssue)
|
||||||
|
|
||||||
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column.ID).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var issues []api.Issue
|
||||||
|
DecodeJSON(t, resp, &issues)
|
||||||
|
assert.Len(t, issues, 2)
|
||||||
|
|
||||||
|
issueIDs := make(map[int64]struct{}, len(issues))
|
||||||
|
for _, apiIssue := range issues {
|
||||||
|
issueIDs[apiIssue.ID] = struct{}{}
|
||||||
|
}
|
||||||
|
assert.Contains(t, issueIDs, issue.ID)
|
||||||
|
assert.Contains(t, issueIDs, pull.ID)
|
||||||
|
|
||||||
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/columns/%d/issues?type=issues", owner.Name, repo.Name, column.ID).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
DecodeJSON(t, resp, &issues)
|
||||||
|
assert.Len(t, issues, 1)
|
||||||
|
assert.Equal(t, issue.ID, issues[0].ID)
|
||||||
|
|
||||||
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/columns/%d/issues?type=pulls", owner.Name, repo.Name, column.ID).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
DecodeJSON(t, resp, &issues)
|
||||||
|
assert.Len(t, issues, 1)
|
||||||
|
assert.Equal(t, pull.ID, issues[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIRemoveIssueFromProjectColumn(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
|
||||||
|
|
||||||
|
project := &project_model.Project{
|
||||||
|
Title: "Project for Issue Removal",
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Type: project_model.TypeRepository,
|
||||||
|
CreatorID: owner.ID,
|
||||||
|
TemplateType: project_model.TemplateTypeNone,
|
||||||
|
}
|
||||||
|
err := project_model.NewProject(t.Context(), project)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
||||||
|
}()
|
||||||
|
|
||||||
|
column := &project_model.Column{
|
||||||
|
Title: "Column for Issue Removal",
|
||||||
|
ProjectID: project.ID,
|
||||||
|
CreatorID: owner.ID,
|
||||||
|
}
|
||||||
|
err = project_model.NewColumn(t.Context(), column)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue, owner, project.ID, column.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues/%d", owner.Name, repo.Name, column.ID, issue.ID), nil).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
unittest.AssertNotExistsBean(t, &project_model.ProjectIssue{
|
||||||
|
ProjectID: project.ID,
|
||||||
|
IssueID: issue.ID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIProjectPermissions(t *testing.T) {
|
func TestAPIProjectPermissions(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user