Skip to content

Commit

Permalink
Fixes go-gitea#7023 - API Org Visibility (go-gitea#7028)
Browse files Browse the repository at this point in the history
  • Loading branch information
richmahn authored and jeffliu27 committed Jul 18, 2019
1 parent 98b4460 commit 5c7bb56
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 40 deletions.
86 changes: 86 additions & 0 deletions integrations/api_admin_org_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2019 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 (
"net/http"
"net/url"
"strings"
"testing"

"code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs"

"github.com/stretchr/testify/assert"
)

func TestAPIAdminOrgCreate(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session)

var org = api.CreateOrgOption{
UserName: "user2_org",
FullName: "User2's organization",
Description: "This organization created by admin for user2",
Website: "https://try.gitea.io",
Location: "Shanghai",
Visibility: "private",
}
req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs?token="+token, &org)
resp := session.MakeRequest(t, req, http.StatusCreated)

var apiOrg api.Organization
DecodeJSON(t, resp, &apiOrg)

assert.Equal(t, org.UserName, apiOrg.UserName)
assert.Equal(t, org.FullName, apiOrg.FullName)
assert.Equal(t, org.Description, apiOrg.Description)
assert.Equal(t, org.Website, apiOrg.Website)
assert.Equal(t, org.Location, apiOrg.Location)
assert.Equal(t, org.Visibility, apiOrg.Visibility)

models.AssertExistsAndLoadBean(t, &models.User{
Name: org.UserName,
LowerName: strings.ToLower(org.UserName),
FullName: org.FullName,
})
})
}

func TestAPIAdminOrgCreateBadVisibility(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session)

var org = api.CreateOrgOption{
UserName: "user2_org",
FullName: "User2's organization",
Description: "This organization created by admin for user2",
Website: "https://try.gitea.io",
Location: "Shanghai",
Visibility: "notvalid",
}
req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs?token="+token, &org)
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
})
}

func TestAPIAdminOrgCreateNotAdmin(t *testing.T) {
prepareTestEnv(t)
nonAdminUsername := "user2"
session := loginUser(t, nonAdminUsername)
token := getTokenForLoggedInUser(t, session)
var org = api.CreateOrgOption{
UserName: "user2_org",
FullName: "User2's organization",
Description: "This organization created by admin for user2",
Website: "https://try.gitea.io",
Location: "Shanghai",
Visibility: "public",
}
req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs?token="+token, &org)
session.MakeRequest(t, req, http.StatusForbidden)
}
48 changes: 47 additions & 1 deletion integrations/api_org_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
"github.com/stretchr/testify/assert"
)

func TestAPIOrg(t *testing.T) {
func TestAPIOrgCreate(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1")

Expand All @@ -28,6 +28,7 @@ func TestAPIOrg(t *testing.T) {
Description: "This organization created by user1",
Website: "https://try.gitea.io",
Location: "Shanghai",
Visibility: "limited",
}
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &org)
resp := session.MakeRequest(t, req, http.StatusCreated)
Expand All @@ -40,6 +41,7 @@ func TestAPIOrg(t *testing.T) {
assert.Equal(t, org.Description, apiOrg.Description)
assert.Equal(t, org.Website, apiOrg.Website)
assert.Equal(t, org.Location, apiOrg.Location)
assert.Equal(t, org.Visibility, apiOrg.Visibility)

models.AssertExistsAndLoadBean(t, &models.User{
Name: org.UserName,
Expand Down Expand Up @@ -72,6 +74,50 @@ func TestAPIOrg(t *testing.T) {
})
}

func TestAPIOrgEdit(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1")

token := getTokenForLoggedInUser(t, session)
var org = api.EditOrgOption{
FullName: "User3 organization new full name",
Description: "A new description",
Website: "https://try.gitea.io/new",
Location: "Beijing",
Visibility: "private",
}
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/user3?token="+token, &org)
resp := session.MakeRequest(t, req, http.StatusOK)

var apiOrg api.Organization
DecodeJSON(t, resp, &apiOrg)

assert.Equal(t, "user3", apiOrg.UserName)
assert.Equal(t, org.FullName, apiOrg.FullName)
assert.Equal(t, org.Description, apiOrg.Description)
assert.Equal(t, org.Website, apiOrg.Website)
assert.Equal(t, org.Location, apiOrg.Location)
assert.Equal(t, org.Visibility, apiOrg.Visibility)
})
}

func TestAPIOrgEditBadVisibility(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1")

token := getTokenForLoggedInUser(t, session)
var org = api.EditOrgOption{
FullName: "User3 organization new full name",
Description: "A new description",
Website: "https://try.gitea.io/new",
Location: "Beijing",
Visibility: "badvisibility",
}
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/user3?token="+token, &org)
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
})
}

func TestAPIOrgDeny(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
setting.Service.RequireSignInView = true
Expand Down
2 changes: 2 additions & 0 deletions integrations/api_user_orgs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func TestUserOrgs(t *testing.T) {
Description: "",
Website: "",
Location: "",
Visibility: "public",
},
}, orgs)
}
Expand All @@ -63,6 +64,7 @@ func TestMyOrgs(t *testing.T) {
Description: "",
Website: "",
Location: "",
Visibility: "public",
},
}, orgs)
}
33 changes: 19 additions & 14 deletions modules/structs/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,27 @@ package structs

// Organization represents an organization
type Organization struct {
ID int64 `json:"id"`
UserName string `json:"username"`
FullName string `json:"full_name"`
AvatarURL string `json:"avatar_url"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
Visibility VisibleType `json:"visibility"`
ID int64 `json:"id"`
UserName string `json:"username"`
FullName string `json:"full_name"`
AvatarURL string `json:"avatar_url"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
Visibility string `json:"visibility"`
}

// CreateOrgOption options for creating an organization
type CreateOrgOption struct {
// required: true
UserName string `json:"username" binding:"Required"`
FullName string `json:"full_name"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
Visibility VisibleType `json:"visibility"`
UserName string `json:"username" binding:"Required"`
FullName string `json:"full_name"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
// possible values are `public` (default), `limited` or `private`
// enum: public,limited,private
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
}

// EditOrgOption options for editing an organization
Expand All @@ -33,4 +35,7 @@ type EditOrgOption struct {
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
// possible values are `public`, `limited` or `private`
// enum: public,limited,private
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
}
10 changes: 10 additions & 0 deletions modules/structs/org_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ func (vt VisibleType) IsPrivate() bool {
return vt == VisibleTypePrivate
}

// VisibilityString provides the mode string of the visibility type (public, limited, private)
func (vt VisibleType) String() string {
for k, v := range VisibilityModes {
if vt == v {
return k
}
}
return ""
}

// ExtractKeysFromMapString provides a slice of keys from map
func ExtractKeysFromMapString(in map[string]VisibleType) (keys []string) {
for k := range in {
Expand Down
26 changes: 20 additions & 6 deletions modules/structs/repo_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,43 @@ package structs

// FileOptions options for all file APIs
type FileOptions struct {
Message string `json:"message" binding:"Required"`
BranchName string `json:"branch"`
NewBranchName string `json:"new_branch"`
Author Identity `json:"author"`
Committer Identity `json:"committer"`
// message (optional) for the commit of this file. if not supplied, a default message will be used
Message string `json:"message" binding:"Required"`
// branch (optional) to base this file from. if not given, the default branch is used
BranchName string `json:"branch"`
// new_branch (optional) will make a new branch from `branch` before creating the file
NewBranchName string `json:"new_branch"`
// `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
Author Identity `json:"author"`
Committer Identity `json:"committer"`
}

// CreateFileOptions options for creating files
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type CreateFileOptions struct {
FileOptions
// content must be base64 encoded
// required: true
Content string `json:"content"`
}

// DeleteFileOptions options for deleting files (used for other File structs below)
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type DeleteFileOptions struct {
FileOptions
// sha is the SHA for the file that already exists
// required: true
SHA string `json:"sha" binding:"Required"`
}

// UpdateFileOptions options for updating files
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type UpdateFileOptions struct {
DeleteFileOptions
Content string `json:"content"`
// content must be base64 encoded
// required: true
Content string `json:"content"`
// from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL
FromPath string `json:"from_path" binding:"MaxSize(500)"`
}

Expand Down
7 changes: 7 additions & 0 deletions routers/api/v1/admin/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ func CreateOrg(ctx *context.APIContext, form api.CreateOrgOption) {
return
}

visibility := api.VisibleTypePublic
if form.Visibility != "" {
visibility = api.VisibilityModes[form.Visibility]
}

org := &models.User{
Name: form.UserName,
FullName: form.FullName,
Expand All @@ -53,7 +58,9 @@ func CreateOrg(ctx *context.APIContext, form api.CreateOrgOption) {
Location: form.Location,
IsActive: true,
Type: models.UserTypeOrganization,
Visibility: visibility,
}

if err := models.CreateOrganization(org, u); err != nil {
if models.IsErrUserAlreadyExist(err) ||
models.IsErrNameReserved(err) ||
Expand Down
1 change: 1 addition & 0 deletions routers/api/v1/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ func ToOrganization(org *models.User) *api.Organization {
Description: org.Description,
Website: org.Website,
Location: org.Location,
Visibility: org.Visibility.String(),
}
}

Expand Down
14 changes: 12 additions & 2 deletions routers/api/v1/org/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ func Create(ctx *context.APIContext, form api.CreateOrgOption) {
return
}

visibility := api.VisibleTypePublic
if form.Visibility != "" {
visibility = api.VisibilityModes[form.Visibility]
}

org := &models.User{
Name: form.UserName,
FullName: form.FullName,
Expand All @@ -98,6 +103,7 @@ func Create(ctx *context.APIContext, form api.CreateOrgOption) {
Location: form.Location,
IsActive: true,
Type: models.UserTypeOrganization,
Visibility: visibility,
}
if err := models.CreateOrganization(org, ctx.User); err != nil {
if models.IsErrUserAlreadyExist(err) ||
Expand Down Expand Up @@ -153,6 +159,7 @@ func Edit(ctx *context.APIContext, form api.EditOrgOption) {
// required: true
// - name: body
// in: body
// required: true
// schema:
// "$ref": "#/definitions/EditOrgOption"
// responses:
Expand All @@ -163,8 +170,11 @@ func Edit(ctx *context.APIContext, form api.EditOrgOption) {
org.Description = form.Description
org.Website = form.Website
org.Location = form.Location
if err := models.UpdateUserCols(org, "full_name", "description", "website", "location"); err != nil {
ctx.Error(500, "UpdateUser", err)
if form.Visibility != "" {
org.Visibility = api.VisibilityModes[form.Visibility]
}
if err := models.UpdateUserCols(org, "full_name", "description", "website", "location", "visibility"); err != nil {
ctx.Error(500, "EditOrganization", err)
return
}

Expand Down
6 changes: 3 additions & 3 deletions routers/api/v1/repo/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func CreateFile(ctx *context.APIContext, apiOpts api.CreateFileOptions) {
// required: true
// - name: body
// in: body
// description: "'content' must be base64 encoded\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'sha' is the SHA for the file that already exists\n\n 'new_branch' (optional) will make a new branch from 'branch' before creating the file"
// required: true
// schema:
// "$ref": "#/definitions/CreateFileOptions"
// responses:
Expand Down Expand Up @@ -238,7 +238,7 @@ func UpdateFile(ctx *context.APIContext, apiOpts api.UpdateFileOptions) {
// required: true
// - name: body
// in: body
// description: "'content' must be base64 encoded\n\n 'sha' is the SHA for the file that already exists\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before updating the file"
// required: true
// schema:
// "$ref": "#/definitions/UpdateFileOptions"
// responses:
Expand Down Expand Up @@ -316,7 +316,7 @@ func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) {
// required: true
// - name: body
// in: body
// description: "'sha' is the SHA for the file to be deleted\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before deleting the file"
// required: true
// schema:
// "$ref": "#/definitions/DeleteFileOptions"
// responses:
Expand Down
Loading

0 comments on commit 5c7bb56

Please sign in to comment.