浏览代码

#12, API: list user repos, list repo hooks

Unknwon 10 年之前
父节点
当前提交
8eb5120fbd

+ 7 - 2
cmd/web.go

@@ -157,7 +157,7 @@ func runWeb(*cli.Context) {
 		m.Get("/issues", user.Issues)
 	}, reqSignIn)
 
-	// API routers.
+	// API.
 	m.Group("/api", func() {
 		m.Group("/v1", func() {
 			// Miscellaneous.
@@ -170,9 +170,14 @@ func runWeb(*cli.Context) {
 			})
 
 			// Repositories.
+			m.Get("/user/repos", v1.ListMyRepos)
 			m.Group("/repos", func() {
 				m.Get("/search", v1.SearchRepos)
 				m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.Migrate)
+
+				m.Group("/:username/:reponame", func() {
+					m.Combo("/hooks").Get(v1.ListRepoHooks)
+				}, middleware.ApiRepoAssignment())
 			})
 
 			m.Any("/*", func(ctx *middleware.Context) {
@@ -181,7 +186,7 @@ func runWeb(*cli.Context) {
 		})
 	})
 
-	// User routers.
+	// User.
 	m.Group("/user", func() {
 		m.Get("/login", user.SignIn)
 		m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)

+ 1 - 1
gogs.go

@@ -17,7 +17,7 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-const APP_VER = "0.5.8.1112 Beta"
+const APP_VER = "0.5.8.1113 Beta"
 
 func init() {
 	runtime.GOMAXPROCS(runtime.NumCPU())

+ 9 - 3
models/repo.go

@@ -1080,15 +1080,21 @@ func GetCollaboratorNames(repoName string) ([]string, error) {
 	return names, nil
 }
 
+// CollaborativeRepository represents a repository with collaborative information.
+type CollaborativeRepository struct {
+	*Repository
+	CanPush bool
+}
+
 // GetCollaborativeRepos returns a list of repositories that user is collaborator.
-func GetCollaborativeRepos(uname string) ([]*Repository, error) {
+func GetCollaborativeRepos(uname string) ([]*CollaborativeRepository, error) {
 	uname = strings.ToLower(uname)
 	accesses := make([]*Access, 0, 10)
 	if err := x.Find(&accesses, &Access{UserName: uname}); err != nil {
 		return nil, err
 	}
 
-	repos := make([]*Repository, 0, 10)
+	repos := make([]*CollaborativeRepository, 0, 10)
 	for _, access := range accesses {
 		infos := strings.Split(access.RepoName, "/")
 		if infos[0] == uname {
@@ -1105,7 +1111,7 @@ func GetCollaborativeRepos(uname string) ([]*Repository, error) {
 			return nil, err
 		}
 		repo.Owner = u
-		repos = append(repos, repo)
+		repos = append(repos, &CollaborativeRepository{repo, access.Mode == WRITABLE})
 	}
 	return repos, nil
 }

+ 20 - 0
models/webhook.go

@@ -27,6 +27,16 @@ const (
 	FORM
 )
 
+func (t HookContentType) Name() string {
+	switch t {
+	case JSON:
+		return "json"
+	case FORM:
+		return "form"
+	}
+	return ""
+}
+
 // HookEvent represents events that will delivery hook.
 type HookEvent struct {
 	PushOnly bool `json:"push_only"`
@@ -147,6 +157,16 @@ const (
 	SLACK
 )
 
+func (t HookTaskType) Name() string {
+	switch t {
+	case GOGS:
+		return "gogs"
+	case SLACK:
+		return "slack"
+	}
+	return ""
+}
+
 type HookEventType string
 
 const (

+ 15 - 15
modules/auth/auth.go

@@ -25,21 +25,7 @@ func SignedInId(req *http.Request, sess session.Store) int64 {
 		return 0
 	}
 
-	uid := sess.Get("uid")
-	if uid == nil {
-		return 0
-	}
-	if id, ok := uid.(int64); ok {
-		if _, err := models.GetUserById(id); err != nil {
-			if err != models.ErrUserNotExist {
-				log.Error(4, "GetUserById: %v", err)
-			}
-			return 0
-		}
-		return id
-	}
-
-	// API calls also need to check access token.
+	// API calls need to check access token.
 	if strings.HasPrefix(req.URL.Path, "/api/") {
 		auHead := req.Header.Get("Authorization")
 		if len(auHead) > 0 {
@@ -56,6 +42,20 @@ func SignedInId(req *http.Request, sess session.Store) int64 {
 			}
 		}
 	}
+
+	uid := sess.Get("uid")
+	if uid == nil {
+		return 0
+	}
+	if id, ok := uid.(int64); ok {
+		if _, err := models.GetUserById(id); err != nil {
+			if err != models.ErrUserNotExist {
+				log.Error(4, "GetUserById: %v", err)
+			}
+			return 0
+		}
+		return id
+	}
 	return 0
 }
 

+ 96 - 0
modules/middleware/repo.go

@@ -18,6 +18,102 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
+// FIXME: response error in JSON.
+func ApiRepoAssignment() macaron.Handler {
+	return func(ctx *Context) {
+		userName := ctx.Params(":username")
+		repoName := ctx.Params(":reponame")
+
+		var (
+			u   *models.User
+			err error
+		)
+
+		// Collaborators who have write access can be seen as owners.
+		if ctx.IsSigned {
+			ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE)
+			if err != nil {
+				ctx.Handle(500, "HasAccess", err)
+				return
+			}
+			ctx.Repo.IsTrueOwner = ctx.User.LowerName == strings.ToLower(userName)
+		}
+
+		if !ctx.Repo.IsTrueOwner {
+			u, err = models.GetUserByName(userName)
+			if err != nil {
+				if err == models.ErrUserNotExist {
+					ctx.Error(404)
+				} else {
+					ctx.Handle(500, "GetUserByName", err)
+				}
+				return
+			}
+		} else {
+			u = ctx.User
+		}
+		ctx.Repo.Owner = u
+
+		// Organization owner team members are true owners as well.
+		if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) {
+			ctx.Repo.IsTrueOwner = true
+		}
+
+		// Get repository.
+		repo, err := models.GetRepositoryByName(u.Id, repoName)
+		if err != nil {
+			if err == models.ErrRepoNotExist {
+				ctx.Error(404)
+				return
+			}
+			ctx.Handle(500, "GetRepositoryByName", err)
+			return
+		} else if err = repo.GetOwner(); err != nil {
+			ctx.Handle(500, "GetOwner", err)
+			return
+		}
+
+		// Check if the mirror repository owner(mirror repository doesn't have access).
+		if ctx.IsSigned && !ctx.Repo.IsOwner {
+			if repo.OwnerId == ctx.User.Id {
+				ctx.Repo.IsOwner = true
+			}
+			// Check if current user has admin permission to repository.
+			if u.IsOrganization() {
+				auth, err := models.GetHighestAuthorize(u.Id, ctx.User.Id, repo.Id, 0)
+				if err != nil {
+					ctx.Handle(500, "GetHighestAuthorize", err)
+					return
+				}
+				if auth == models.ORG_ADMIN {
+					ctx.Repo.IsOwner = true
+					ctx.Repo.IsAdmin = true
+				}
+			}
+		}
+
+		// Check access.
+		if repo.IsPrivate && !ctx.Repo.IsOwner {
+			if ctx.User == nil {
+				ctx.Error(404)
+				return
+			}
+
+			hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE)
+			if err != nil {
+				ctx.Handle(500, "HasAccess", err)
+				return
+			} else if !hasAccess {
+				ctx.Error(404)
+				return
+			}
+		}
+		ctx.Repo.HasAccess = true
+
+		ctx.Repo.Repository = repo
+	}
+}
+
 // RepoRef handles repository reference name including those contain `/`.
 func RepoRef() macaron.Handler {
 	return func(ctx *Context) {

+ 2 - 2
public/ng/js/gogs.js

@@ -210,7 +210,7 @@ var Gogs = {};
                 if (json.ok && json.data.length) {
                     var html = '';
                     $.each(json.data, function (i, item) {
-                        html += '<li><a><img src="' + item.avatar + '">' + item.username + '</a></li>';
+                        html += '<li><a><img src="' + item.avatar_url + '">' + item.username + '</a></li>';
                     });
                     $target.html(html);
                     $target.toggleShow();
@@ -230,7 +230,7 @@ var Gogs = {};
                 if (json.ok && json.data.length) {
                     var html = '';
                     $.each(json.data, function (i, item) {
-                        html += '<li><a><span class="octicon octicon-repo"></span> ' + item.repolink + '</a></li>';
+                        html += '<li><a><span class="octicon octicon-repo"></span> ' + item.full_name + '</a></li>';
                     });
                     $target.html(html);
                     $target.toggleShow();

文件差异内容过多而无法显示
+ 0 - 0
public/ng/js/min/gogs-min.js


+ 105 - 5
routers/api/v1/repos.go → routers/api/v1/repo.go

@@ -15,10 +15,25 @@ import (
 	"github.com/gogits/gogs/modules/auth"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/middleware"
+	"github.com/gogits/gogs/modules/setting"
 )
 
-type repo struct {
-	RepoLink string `json:"repolink"`
+type ApiPermission struct {
+	Admin bool `json:"admin"`
+	Push  bool `json:"push"`
+	Pull  bool `json:"pull"`
+}
+
+type ApiRepository struct {
+	Id          int64         `json:"id"`
+	Owner       ApiUser       `json:"owner"`
+	FullName    string        `json:"full_name"`
+	Private     bool          `json:"private"`
+	Fork        bool          `json:"fork"`
+	HtmlUrl     string        `json:"html_url"`
+	CloneUrl    string        `json:"clone_url"`
+	SshUrl      string        `json:"ssh_url"`
+	Permissions ApiPermission `json:"permissions"`
 }
 
 func SearchRepos(ctx *middleware.Context) {
@@ -60,7 +75,7 @@ func SearchRepos(ctx *middleware.Context) {
 		return
 	}
 
-	results := make([]*repo, len(repos))
+	results := make([]*ApiRepository, len(repos))
 	for i := range repos {
 		if err = repos[i].GetOwner(); err != nil {
 			ctx.JSON(500, map[string]interface{}{
@@ -69,8 +84,9 @@ func SearchRepos(ctx *middleware.Context) {
 			})
 			return
 		}
-		results[i] = &repo{
-			RepoLink: path.Join(repos[i].Owner.Name, repos[i].Name),
+		results[i] = &ApiRepository{
+			Id:       repos[i].Id,
+			FullName: path.Join(repos[i].Owner.Name, repos[i].Name),
 		}
 	}
 
@@ -155,3 +171,87 @@ func Migrate(ctx *middleware.Context, form auth.MigrateRepoForm) {
 		"error": err.Error(),
 	})
 }
+
+// /user/repos: https://developer.github.com/v3/repos/#list-your-repositories
+func ListMyRepos(ctx *middleware.Context) {
+	if !ctx.IsSigned {
+		ctx.Error(403)
+		return
+	}
+
+	ownRepos, err := models.GetRepositories(ctx.User.Id, true)
+	if err != nil {
+		ctx.JSON(500, map[string]interface{}{
+			"ok":    false,
+			"error": err.Error(),
+		})
+		return
+	}
+	numOwnRepos := len(ownRepos)
+
+	collaRepos, err := models.GetCollaborativeRepos(ctx.User.Name)
+	if err != nil {
+		ctx.JSON(500, map[string]interface{}{
+			"ok":    false,
+			"error": err.Error(),
+		})
+		return
+	}
+
+	sshUrlFmt := "%s@%s:%s/%s.git"
+	if setting.SshPort != 22 {
+		sshUrlFmt = "ssh://%s@%s:%d/%s/%s.git"
+	}
+
+	repos := make([]*ApiRepository, numOwnRepos+len(collaRepos))
+	// FIXME: make only one loop
+	for i := range ownRepos {
+		repos[i] = &ApiRepository{
+			Id: ownRepos[i].Id,
+			Owner: ApiUser{
+				Id:        ctx.User.Id,
+				UserName:  ctx.User.Name,
+				AvatarUrl: string(setting.Protocol) + ctx.User.AvatarLink(),
+			},
+			FullName:    ctx.User.Name + "/" + ownRepos[i].Name,
+			Private:     ownRepos[i].IsPrivate,
+			Fork:        ownRepos[i].IsFork,
+			HtmlUrl:     setting.AppUrl + ctx.User.Name + "/" + ownRepos[i].Name,
+			SshUrl:      fmt.Sprintf(sshUrlFmt, setting.RunUser, setting.Domain, ctx.User.LowerName, ownRepos[i].LowerName),
+			Permissions: ApiPermission{true, true, true},
+		}
+		repos[i].CloneUrl = repos[i].HtmlUrl + ".git"
+	}
+	for i := range collaRepos {
+		if err = collaRepos[i].GetOwner(); err != nil {
+			ctx.JSON(500, map[string]interface{}{
+				"ok":    false,
+				"error": err.Error(),
+			})
+			return
+		}
+		j := i + numOwnRepos
+		repos[j] = &ApiRepository{
+			Id: collaRepos[i].Id,
+			Owner: ApiUser{
+				Id:        collaRepos[i].Owner.Id,
+				UserName:  collaRepos[i].Owner.Name,
+				AvatarUrl: string(setting.Protocol) + collaRepos[i].Owner.AvatarLink(),
+			},
+			FullName:    collaRepos[i].Owner.Name + "/" + collaRepos[i].Name,
+			Private:     collaRepos[i].IsPrivate,
+			Fork:        collaRepos[i].IsFork,
+			HtmlUrl:     setting.AppUrl + collaRepos[i].Owner.Name + "/" + collaRepos[i].Name,
+			SshUrl:      fmt.Sprintf(sshUrlFmt, setting.RunUser, setting.Domain, collaRepos[i].Owner.LowerName, collaRepos[i].LowerName),
+			Permissions: ApiPermission{false, collaRepos[i].CanPush, true},
+		}
+		repos[j].CloneUrl = repos[j].HtmlUrl + ".git"
+
+		// FIXME: cache result to reduce DB query?
+		if collaRepos[i].Owner.IsOrganization() && collaRepos[i].Owner.IsOrgOwner(ctx.User.Id) {
+			repos[j].Permissions.Admin = true
+		}
+	}
+
+	ctx.JSON(200, &repos)
+}

+ 50 - 0
routers/api/v1/repo_hooks.go

@@ -0,0 +1,50 @@
+// 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 v1
+
+import (
+	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/middleware"
+)
+
+type apiHookConfig struct {
+	Url         string `json:"url"`
+	ContentType string `json:"content_type"`
+}
+
+type ApiHook struct {
+	Id     int64         `json:"id"`
+	Type   string        `json:"type"`
+	Events []string      `json:"events"`
+	Active bool          `json:"active"`
+	Config apiHookConfig `json:"config"`
+}
+
+// /repos/:username/:reponame/hooks: https://developer.github.com/v3/repos/hooks/#list-hooks
+func ListRepoHooks(ctx *middleware.Context) {
+	hooks, err := models.GetWebhooksByRepoId(ctx.Repo.Repository.Id)
+	if err != nil {
+		ctx.JSON(500, map[string]interface{}{
+			"ok":    false,
+			"error": err.Error(),
+		})
+		return
+	}
+
+	apiHooks := make([]*ApiHook, len(hooks))
+	for i := range hooks {
+		apiHooks[i] = &ApiHook{
+			Id:     hooks[i].Id,
+			Type:   hooks[i].HookTaskType.Name(),
+			Active: hooks[i].IsActive,
+			Config: apiHookConfig{hooks[i].Url, hooks[i].ContentType.Name()},
+		}
+
+		// Currently, onle have push event.
+		apiHooks[i].Events = []string{"push"}
+	}
+
+	ctx.JSON(200, &apiHooks)
+}

+ 8 - 7
routers/api/v1/users.go

@@ -11,9 +11,10 @@ import (
 	"github.com/gogits/gogs/modules/middleware"
 )
 
-type user struct {
-	UserName   string `json:"username"`
-	AvatarLink string `json:"avatar"`
+type ApiUser struct {
+	Id        int64  `json:"id"`
+	UserName  string `json:"username"`
+	AvatarUrl string `json:"avatar_url"`
 }
 
 func SearchUsers(ctx *middleware.Context) {
@@ -34,11 +35,11 @@ func SearchUsers(ctx *middleware.Context) {
 		return
 	}
 
-	results := make([]*user, len(us))
+	results := make([]*ApiUser, len(us))
 	for i := range us {
-		results[i] = &user{
-			UserName:   us[i].Name,
-			AvatarLink: us[i].AvatarLink(),
+		results[i] = &ApiUser{
+			UserName:  us[i].Name,
+			AvatarUrl: us[i].AvatarLink(),
 		}
 	}
 

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
-0.5.8.1112 Beta
+0.5.8.1113 Beta

部分文件因为文件数量过多而无法显示