refactor GET /projects

This commit is contained in:
Wenkai Yin 2017-06-09 14:11:44 +08:00
parent 42984fe1c9
commit e97d7dab85
11 changed files with 192 additions and 99 deletions

View File

@ -43,21 +43,36 @@ paths:
description: Unexpected internal errors.
/projects:
get:
summary: Return projects created by Harbor
summary: List projects
description: |
This endpoint returns all projects created by Harbor, and can be filtered by project name.
parameters:
- name: project_name
- name: name
in: query
description: Project name for filtering results.
description: The name of project.
required: false
type: string
- name: is_public
- name: public
in: query
description: Public sign for filtering projects.
description: The project is public or private.
required: false
type: boolean
format: int32
- name: owner
in: query
description: The name of project owner.
required: false
type: string
- name: member
in: query
description: The name of project member.
required: false
type: string
- name: role
in: query
description: The role of member, only take effect when member is provided.
required: false
type: integer
format: int32
- name: page
in: query
type: integer

View File

@ -190,7 +190,7 @@ func GetHasReadPermProjects(username string) ([]*models.Project, error) {
// GetTotalOfProjects returns the total count of projects
// according to the query conditions
func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) {
func GetTotalOfProjects(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error) {
var (
owner string
@ -210,7 +210,7 @@ func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) {
}
}
sql, params := projectQueryConditions(owner, name, public, member, role)
sql, params := projectQueryConditions(owner, name, public, member, role, base...)
sql = `select count(*) ` + sql
@ -220,7 +220,7 @@ func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) {
}
// GetProjects returns a project list according to the query conditions
func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) {
func GetProjects(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error) {
var (
owner string
@ -246,7 +246,7 @@ func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) {
}
}
sql, params := projectQueryConditions(owner, name, public, member, role)
sql, params := projectQueryConditions(owner, name, public, member, role, base...)
sql = `select distinct p.project_id, p.name, p.public, p.owner_id,
p.creation_time, p.update_time ` + sql
@ -266,10 +266,37 @@ func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) {
}
func projectQueryConditions(owner, name string, public *bool, member string,
role int) (string, []interface{}) {
role int, base ...*models.BaseProjectCollection) (string, []interface{}) {
params := []interface{}{}
sql := ` from project p`
// the base project collections:
// 1. all projects
// 2. public projects
// 3. public projects and projects which the user is a member of
// 4. projects which the user is a member of
collection := `project `
if len(base) != 0 && base[0] != nil {
if len(base[0].Member) > 0 {
collection = `(select pr.project_id, pr.owner_id, pr.name, pr.
creation_time, pr.update_time, pr.deleted, pr.public
from project pr
join project_member prm
on pr.project_id = prm.project_id
join user ur
on prm.user_id=ur.user_id
where ur.username=? `
if base[0].Public {
collection += ` or pr.public=1 `
}
collection += `) `
params = append(params, base[0].Member)
} else if base[0].Public {
collection = `(select * from project pr
where pr.public=1) `
}
}
sql := ` from ` + collection + ` as p`
if len(owner) != 0 {
sql += ` join user u1

View File

@ -88,3 +88,11 @@ type Pagination struct {
Page int64
Size int64
}
// BaseProjectCollection contains the query conditions which can be used
// to get a project collection. The collection can be used as the base to
// do other filter
type BaseProjectCollection struct {
Public bool
Member string
}

View File

@ -106,7 +106,7 @@ func (f *fakePM) Update(projectIDOrName interface{}, project *models.Project) er
}
// nil implement
func (f *fakePM) GetAll(*models.ProjectQueryParam) ([]*models.Project, error) {
func (f *fakePM) GetAll(*models.ProjectQueryParam, ...*models.BaseProjectCollection) ([]*models.Project, error) {
return []*models.Project{}, nil
}
@ -116,7 +116,7 @@ func (f *fakePM) GetHasReadPerm(username ...string) ([]*models.Project, error) {
}
// nil implement
func (f *fakePM) GetTotal(*models.ProjectQueryParam) (int64, error) {
func (f *fakePM) GetTotal(*models.ProjectQueryParam, ...*models.BaseProjectCollection) (int64, error) {
return 0, nil
}

View File

@ -329,17 +329,10 @@ func (a testapi) ProjectsGetByPID(projectID string) (int, apilib.Project, error)
}
//Search projects by projectName and isPublic
func (a testapi) ProjectsGet(projectName string, isPublic int32, authInfo ...usrInfo) (int, []apilib.Project, error) {
_sling := sling.New().Get(a.basePath)
//create api path
path := "api/projects"
_sling = _sling.Path(path)
type QueryParams struct {
ProjectName string `url:"project_name,omitempty"`
IsPubilc int32 `url:"is_public,omitempty"`
}
_sling = _sling.QueryStruct(&QueryParams{ProjectName: projectName, IsPubilc: isPublic})
func (a testapi) ProjectsGet(query *apilib.ProjectQuery, authInfo ...usrInfo) (int, []apilib.Project, error) {
_sling := sling.New().Get(a.basePath).
Path("api/projects").
QueryStruct(query)
var successPayload []apilib.Project
@ -349,6 +342,7 @@ func (a testapi) ProjectsGet(projectName string, isPublic int32, authInfo ...usr
if len(authInfo) > 0 {
httpStatusCode, body, err = request(_sling, jsonAcceptHeader, authInfo[0])
} else {
log.Println("enter no auth $$$$$$$$$$$$$$$$$$$")
httpStatusCode, body, err = request(_sling, jsonAcceptHeader)
}

View File

@ -23,28 +23,24 @@ import (
)
func TestLogGet(t *testing.T) {
fmt.Println("Testing Log API")
assert := assert.New(t)
apiTest := newHarborAPI()
assert := assert.New(t)
//prepare for test
CommonAddUser()
var project apilib.ProjectReq
project.ProjectName = "my_project"
project.Public = 1
statusCode, result, err := apiTest.LogGet(*testUser)
if err != nil {
t.Error("Error while get log information", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), statusCode, "Log get should return 200")
}
statusCode, result, err := apiTest.LogGet(*testUser)
assert.Nil(err)
assert.Equal(200, statusCode)
logNum := len(result)
fmt.Println("result", result)
//add the project first.
fmt.Println("add the project first.")
project := apilib.ProjectReq{
ProjectName: "project_for_test_log",
Public: 1,
}
reply, err := apiTest.ProjectsPost(*testUser, project)
if err != nil {
t.Error("Error while creat project", err.Error())
@ -63,7 +59,7 @@ func TestLogGet(t *testing.T) {
if num != 1 {
assert.Equal(1, num, "add my_project log number should be 1")
} else {
assert.Equal("my_project/", result[index].RepoName, "RepoName should be equal")
assert.Equal("project_for_test_log/", result[index].RepoName)
assert.Equal("N/A", result[index].RepoTag, "RepoTag should be equal")
assert.Equal("create", result[index].Operation, "Operation should be equal")
}
@ -73,7 +69,12 @@ func TestLogGet(t *testing.T) {
//get the project
var projects []apilib.Project
var addProjectID int32
httpStatusCode, projects, err := apiTest.ProjectsGet(project.ProjectName, 1)
httpStatusCode, projects, err := apiTest.ProjectsGet(
&apilib.ProjectQuery{
Name: project.ProjectName,
Owner: testUser.Name,
Public: true,
})
if err != nil {
t.Error("Error while search project by proName and isPublic", err.Error())
t.Log(err)
@ -81,6 +82,7 @@ func TestLogGet(t *testing.T) {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
addProjectID = projects[0].ProjectId
}
t.Logf("%%%%%%%%%%%%% %v", projects)
//delete the project
projectID := strconv.Itoa(int(addProjectID))
@ -99,7 +101,7 @@ func TestLogGet(t *testing.T) {
func getLog(result []apilib.AccessLog) (int, int) {
var num, index int
for i := 0; i < len(result); i++ {
if result[i].RepoName == "my_project/" {
if result[i].RepoName == "project_for_test_log/" {
num++
index = i
}

View File

@ -256,58 +256,81 @@ func projectContainsPolicy(id int64) (bool, error) {
}
// List ...
// TODO refacter pattern to:
// /api/repositories?owner=xxx&name=xxx&public=true&member=xxx&role=1&page=1&size=3
func (p *ProjectAPI) List() {
query := &models.ProjectQueryParam{}
// query strings
page, size := p.GetPaginationParams()
query := &models.ProjectQueryParam{
Name: p.GetString("name"),
Owner: p.GetString("owner"),
Pagination: &models.Pagination{
Page: page,
Size: size,
},
}
query.Name = p.GetString("project_name")
public := p.GetString("is_public")
if len(public) != 0 {
if public != "0" && public != "1" {
p.HandleBadRequest("is_public should be 0 or 1")
public := p.GetString("public")
if len(public) > 0 {
pub, err := strconv.ParseBool(public)
if err != nil {
p.HandleBadRequest(fmt.Sprintf("invalid public: %s", public))
return
}
if public == "1" {
t := true
query.Public = &t
query.Public = &pub
}
member := p.GetString("member")
if len(member) > 0 {
query.Member = &models.Member{
Name: member,
}
role := p.GetString("role")
if len(role) > 0 {
r, err := strconv.Atoi(role)
if err != nil {
if err != nil {
p.HandleBadRequest(fmt.Sprintf("invalid role: %s", role))
return
}
}
query.Member.Role = r
}
}
if query.Public == nil || *query.Public == false {
//if the request is not for public projects, user must login or provide credential
if !p.SecurityCtx.IsAuthenticated() {
// base project collection from which filter is done
base := &models.BaseProjectCollection{}
if !p.SecurityCtx.IsAuthenticated() {
if query.Member != nil && len(query.Member.Name) > 0 {
// must login if query member
p.HandleUnauthorized()
return
}
base.Public = true
} else {
if !p.SecurityCtx.IsSysAdmin() {
query.Member = &models.Member{
Name: p.SecurityCtx.GetUsername(),
base.Member = p.SecurityCtx.GetUsername()
if query.Member != nil && len(query.Member.Name) > 0 {
base.Public = false
} else {
base.Public = true
}
}
}
total, err := p.ProjectMgr.GetTotal(query)
total, err := p.ProjectMgr.GetTotal(query, base)
if err != nil {
p.HandleInternalServerError(fmt.Sprintf("failed to get total of projects: %v", err))
return
}
page, size := p.GetPaginationParams()
query.Pagination = &models.Pagination{
Page: page,
Size: size,
}
projects, err := p.ProjectMgr.GetAll(query)
projects, err := p.ProjectMgr.GetAll(query, base)
if err != nil {
p.HandleInternalServerError(fmt.Sprintf("failed to get projects: %v", err))
return
}
for _, project := range projects {
if query.Public == nil || *query.Public == false {
if p.SecurityCtx.IsAuthenticated() {
roles, err := p.ProjectMgr.GetRoles(p.SecurityCtx.GetUsername(), project.ProjectID)
if err != nil {
p.HandleInternalServerError(fmt.Sprintf("failed to get roles of user %s to project %d: %v",

View File

@ -90,8 +90,7 @@ func TestAddProject(t *testing.T) {
}
//Get project by proName
func TestProGetByName(t *testing.T) {
func TestListProjects(t *testing.T) {
fmt.Println("\nTest for Project GET API by project name")
assert := assert.New(t)
@ -100,29 +99,39 @@ func TestProGetByName(t *testing.T) {
//----------------------------case 1 : Response Code=200----------------------------//
fmt.Println("case 1: respose code:200")
httpStatusCode, result, err := apiTest.ProjectsGet(addProject.ProjectName, 1)
if err != nil {
t.Error("Error while search project by proName and isPublic", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong")
assert.Equal(int32(1), result[0].Public, "Public is wrong")
//find add projectID
addPID = int(result[0].ProjectId)
}
//----------------------------case 2 : Response Code=401:is_public=0----------------------------//
fmt.Println("case 2: respose code:401,isPublic = 0")
httpStatusCode, result, err = apiTest.ProjectsGet("library", 0)
if err != nil {
t.Error("Error while search project by proName and isPublic", err.Error())
t.Log(err)
} else {
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 200")
}
httpStatusCode, result, err := apiTest.ProjectsGet(
&apilib.ProjectQuery{
Name: addProject.ProjectName,
Owner: admin.Name,
Public: true,
})
assert.Nil(err)
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong")
assert.Equal(int32(1), result[0].Public, "Public is wrong")
//find add projectID
addPID = int(result[0].ProjectId)
//----------------------------case 2 : Response Code=401----------------------------//
fmt.Println("case 2: respose code:401")
httpStatusCode, result, err = apiTest.ProjectsGet(
&apilib.ProjectQuery{
Member: admin.Name,
})
t.Logf("$$$$$$$$$$$$$$$$$$$%v", result)
assert.Nil(err)
assert.Equal(int(401), httpStatusCode)
//-------------------case 3 : check admin project role------------------------//
httpStatusCode, result, err = apiTest.ProjectsGet(addProject.ProjectName, 0, *admin)
httpStatusCode, result, err = apiTest.ProjectsGet(
&apilib.ProjectQuery{
Name: addProject.ProjectName,
Owner: admin.Name,
Public: true,
Member: admin.Name,
Role: 1,
}, *admin)
if err != nil {
t.Error("Error while search project by proName and isPublic", err.Error())
t.Log(err)
@ -144,7 +153,12 @@ func TestProGetByName(t *testing.T) {
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
}
httpStatusCode, result, err = apiTest.ProjectsGet(addProject.ProjectName, 0, *testUser)
httpStatusCode, result, err = apiTest.ProjectsGet(
&apilib.ProjectQuery{
Name: addProject.ProjectName,
Member: TestUserName,
Role: 2,
}, *testUser)
if err != nil {
t.Error("Error while search project by proName and isPublic", err.Error())
t.Log(err)

View File

@ -190,15 +190,15 @@ func (p *ProjectManager) Update(projectIDOrName interface{},
}
// GetAll returns a project list according to the query parameters
func (p *ProjectManager) GetAll(query *models.ProjectQueryParam) (
func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (
[]*models.Project, error) {
return dao.GetProjects(query)
return dao.GetProjects(query, base...)
}
// GetTotal returns the total count according to the query parameters
func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam) (
func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (
int64, error) {
return dao.GetTotalOfProjects(query)
return dao.GetTotalOfProjects(query, base...)
}
// GetHasReadPerm returns projects which are public or the user is a member of

View File

@ -33,9 +33,9 @@ type ProjectManager interface {
Delete(projectIDOrName interface{}) error
Update(projectIDOrName interface{}, project *models.Project) error
// GetAll returns a project list according to the query parameters
GetAll(query *models.ProjectQueryParam) ([]*models.Project, error)
GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error)
// GetTotal returns the total count according to the query parameters
GetTotal(query *models.ProjectQueryParam) (int64, error)
GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error)
// GetHasReadPerm returns a project list which the user has read
// permission of. The list should contains all public projects and
// projects which the user is a member of if the username is not nil

View File

@ -57,3 +57,13 @@ type Project struct {
// The number of the repositories under this project.
RepoCount int32 `json:"repo_count,omitempty"`
}
type ProjectQuery struct {
Name string `json:"name"`
Owner string `json:"owner"`
Public bool `json:"public"`
Member string `json:"member"`
Role int `json:"role"`
Page int64 `json:"page"`
PageSize int64 `json:"page_size"`
}