From 2342df183b64794db74f399759f16c71e2dd543e Mon Sep 17 00:00:00 2001
From: Ethan Koenig <etk39@cornell.edu>
Date: Mon, 26 Dec 2016 02:37:01 -0500
Subject: [PATCH] API Endpoints for collaborators (#375)

---
 models/repo_collaboration.go         |  9 +++
 routers/api/v1/api.go                |  7 ++-
 routers/api/v1/repo/collaborators.go | 93 ++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+), 1 deletion(-)

diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go
index 308f57b7d2..39df5b9efa 100644
--- a/models/repo_collaboration.go
+++ b/models/repo_collaboration.go
@@ -103,6 +103,15 @@ func (repo *Repository) GetCollaborators() ([]*Collaborator, error) {
 	return repo.getCollaborators(x)
 }
 
+func (repo *Repository) isCollaborator(e Engine, userID int64) (bool, error) {
+	return e.Get(&Collaboration{RepoID: repo.ID, UserID: userID})
+}
+
+// IsCollaborator check if a user is a collaborator of a repository
+func (repo *Repository) IsCollaborator(userID int64) (bool, error) {
+	return repo.isCollaborator(x, userID)
+}
+
 // ChangeCollaborationAccessMode sets new access mode for the collaboration.
 func (repo *Repository) ChangeCollaborationAccessMode(uid int64, mode AccessMode) error {
 	// Discard invalid input
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index d697786e27..17e1f9bfc2 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -270,7 +270,12 @@ func RegisterRoutes(m *macaron.Macaron) {
 						Patch(bind(api.EditHookOption{}), repo.EditHook).
 						Delete(repo.DeleteHook)
 				})
-				m.Put("/collaborators/:collaborator", bind(api.AddCollaboratorOption{}), repo.AddCollaborator)
+				m.Group("/collaborators", func() {
+					m.Get("", repo.ListCollaborators)
+					m.Combo("/:collaborator").Get(repo.IsCollaborator).
+						Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
+						Delete(repo.DeleteCollaborator)
+				})
 				m.Get("/raw/*", context.RepoRef(), repo.GetRawFile)
 				m.Get("/archive/*", repo.GetArchive)
 				m.Group("/branches", func() {
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index a1e8ec6535..4ffb5554af 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -11,8 +11,72 @@ import (
 	"code.gitea.io/gitea/modules/context"
 )
 
+// ListCollaborators list a repository's collaborators
+func ListCollaborators(ctx *context.APIContext) {
+	access, err := models.AccessLevel(ctx.User, ctx.Repo.Repository)
+	if err != nil {
+		ctx.Error(500, "AccessLevel", err)
+		return
+	}
+	if access < models.AccessModeWrite {
+		ctx.Error(403, "", "User does not have push access")
+		return
+	}
+	collaborators, err := ctx.Repo.Repository.GetCollaborators()
+	if err != nil {
+		ctx.Error(500, "ListCollaborators", err)
+		return
+	}
+	users := make([]*api.User, len(collaborators))
+	for i, collaborator := range collaborators {
+		users[i] = collaborator.APIFormat()
+	}
+	ctx.JSON(200, users)
+}
+
+// IsCollaborator check if a user is a collaborator of a repository
+func IsCollaborator(ctx *context.APIContext) {
+	access, err := models.AccessLevel(ctx.User, ctx.Repo.Repository)
+	if err != nil {
+		ctx.Error(500, "AccessLevel", err)
+		return
+	}
+	if access < models.AccessModeWrite {
+		ctx.Error(403, "", "User does not have push access")
+		return
+	}
+	user, err := models.GetUserByName(ctx.Params(":collaborator"))
+	if err != nil {
+		if models.IsErrUserNotExist(err) {
+			ctx.Error(422, "", err)
+		} else {
+			ctx.Error(500, "GetUserByName", err)
+		}
+		return
+	}
+	isColab, err := ctx.Repo.Repository.IsCollaborator(user.ID)
+	if err != nil {
+		ctx.Error(500, "IsCollaborator", err)
+		return
+	}
+	if isColab {
+		ctx.Status(204)
+	} else {
+		ctx.Status(404)
+	}
+}
+
 // AddCollaborator add a collaborator of a repository
 func AddCollaborator(ctx *context.APIContext, form api.AddCollaboratorOption) {
+	access, err := models.AccessLevel(ctx.User, ctx.Repo.Repository)
+	if err != nil {
+		ctx.Error(500, "AccessLevel", err)
+		return
+	}
+	if access < models.AccessModeWrite {
+		ctx.Error(403, "", "User does not have push access")
+		return
+	}
 	collaborator, err := models.GetUserByName(ctx.Params(":collaborator"))
 	if err != nil {
 		if models.IsErrUserNotExist(err) {
@@ -37,3 +101,32 @@ func AddCollaborator(ctx *context.APIContext, form api.AddCollaboratorOption) {
 
 	ctx.Status(204)
 }
+
+// DeleteCollaborator delete a collaborator from a repository
+func DeleteCollaborator(ctx *context.APIContext) {
+	access, err := models.AccessLevel(ctx.User, ctx.Repo.Repository)
+	if err != nil {
+		ctx.Error(500, "AccessLevel", err)
+		return
+	}
+	if access < models.AccessModeWrite {
+		ctx.Error(403, "", "User does not have push access")
+		return
+	}
+
+	collaborator, err := models.GetUserByName(ctx.Params(":collaborator"))
+	if err != nil {
+		if models.IsErrUserNotExist(err) {
+			ctx.Error(422, "", err)
+		} else {
+			ctx.Error(500, "GetUserByName", err)
+		}
+		return
+	}
+
+	if err := ctx.Repo.Repository.DeleteCollaboration(collaborator.ID); err != nil {
+		ctx.Error(500, "DeleteCollaboration", err)
+		return
+	}
+	ctx.Status(204)
+}