From 56dd430a10bf5281caf648344e4660fbdc5d4dee Mon Sep 17 00:00:00 2001 From: Unknwon Date: Fri, 4 Dec 2015 17:16:42 -0500 Subject: refactor API routes and some work for #976 --- routers/api/v1/api.go | 185 ++++++++++++++++++++++++ routers/api/v1/misc/markdown.go | 42 ++++++ routers/api/v1/miscellaneous.go | 41 ------ routers/api/v1/repo.go | 313 ---------------------------------------- routers/api/v1/repo/file.go | 46 ++++++ routers/api/v1/repo/hooks.go | 165 +++++++++++++++++++++ routers/api/v1/repo/keys.go | 114 +++++++++++++++ routers/api/v1/repo/repo.go | 298 ++++++++++++++++++++++++++++++++++++++ routers/api/v1/repo_file.go | 46 ------ routers/api/v1/repo_hooks.go | 191 ------------------------ routers/api/v1/repo_keys.go | 126 ---------------- routers/api/v1/user.go | 82 ----------- routers/api/v1/user/app.go | 40 +++++ routers/api/v1/user/keys.go | 122 ++++++++++++++++ routers/api/v1/user/user.go | 71 +++++++++ routers/api/v1/user_app.go | 44 ------ routers/api/v1/user_keys.go | 111 -------------- routers/api/v1/utils/convert.go | 92 ++++++++++++ 18 files changed, 1175 insertions(+), 954 deletions(-) create mode 100644 routers/api/v1/api.go create mode 100644 routers/api/v1/misc/markdown.go delete mode 100644 routers/api/v1/miscellaneous.go delete mode 100644 routers/api/v1/repo.go create mode 100644 routers/api/v1/repo/file.go create mode 100644 routers/api/v1/repo/hooks.go create mode 100644 routers/api/v1/repo/keys.go create mode 100644 routers/api/v1/repo/repo.go delete mode 100644 routers/api/v1/repo_file.go delete mode 100644 routers/api/v1/repo_hooks.go delete mode 100644 routers/api/v1/repo_keys.go delete mode 100644 routers/api/v1/user.go create mode 100644 routers/api/v1/user/app.go create mode 100644 routers/api/v1/user/keys.go create mode 100644 routers/api/v1/user/user.go delete mode 100644 routers/api/v1/user_app.go delete mode 100644 routers/api/v1/user_keys.go create mode 100644 routers/api/v1/utils/convert.go (limited to 'routers/api/v1') diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go new file mode 100644 index 00000000..d2ebd1b1 --- /dev/null +++ b/routers/api/v1/api.go @@ -0,0 +1,185 @@ +// Copyright 2015 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 ( + "strings" + + "github.com/go-macaron/binding" + "gopkg.in/macaron.v1" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/auth" + "github.com/gogits/gogs/modules/middleware" + "github.com/gogits/gogs/routers/api/v1/misc" + "github.com/gogits/gogs/routers/api/v1/repo" + "github.com/gogits/gogs/routers/api/v1/user" +) + +func RepoAssignment() macaron.Handler { + return func(ctx *middleware.Context) { + ctx.Repo = &middleware.RepoContext{} + + userName := ctx.Params(":username") + repoName := ctx.Params(":reponame") + + var ( + owner *models.User + err error + ) + + // Check if the user is the same as the repository owner. + if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) { + owner = ctx.User + } else { + owner, err = models.GetUserByName(userName) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.Error(404) + } else { + ctx.APIError(500, "GetUserByName", err) + } + return + } + } + ctx.Repo.Owner = owner + + // Get repository. + repo, err := models.GetRepositoryByName(owner.Id, repoName) + if err != nil { + if models.IsErrRepoNotExist(err) { + ctx.Error(404) + } else { + ctx.APIError(500, "GetRepositoryByName", err) + } + return + } else if err = repo.GetOwner(); err != nil { + ctx.APIError(500, "GetOwner", err) + return + } + + mode, err := models.AccessLevel(ctx.User, repo) + if err != nil { + ctx.APIError(500, "AccessLevel", err) + return + } + + ctx.Repo.AccessMode = mode + + // Check access. + if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE { + ctx.Error(404) + return + } + + ctx.Repo.Repository = repo + } +} + +// Contexter middleware already checks token for user sign in process. +func ReqToken() macaron.Handler { + return func(ctx *middleware.Context) { + if !ctx.IsSigned { + ctx.Error(401) + return + } + } +} + +func ReqBasicAuth() macaron.Handler { + return func(ctx *middleware.Context) { + if !ctx.IsBasicAuth { + ctx.Error(401) + return + } + } +} + +func ReqAdmin() macaron.Handler { + return func(ctx *middleware.Context) { + if !ctx.User.IsAdmin { + ctx.Error(403) + return + } + } +} + +// RegisterRoutes registers all v1 APIs routes to web application. +// FIXME: custom form error response +func RegisterRoutes(m *macaron.Macaron) { + bind := binding.Bind + + m.Group("/v1", func() { + // Miscellaneous + m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown) + m.Post("/markdown/raw", misc.MarkdownRaw) + + // Users + m.Group("/users", func() { + m.Get("/search", user.Search) + + m.Group("/:username", func() { + m.Get("", user.GetInfo) + + m.Group("/tokens", func() { + m.Combo("").Get(user.ListAccessTokens). + Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken) + }, ReqBasicAuth()) + }) + }) + + m.Group("/users", func() { + m.Group("/:username", func() { + m.Combo("/keys").Get(user.ListPublicKeys). + Post(ReqAdmin(), user.CreateUserPublicKey) + }) + }, ReqToken()) + + m.Group("/user", func() { + m.Group("/keys", func() { + m.Combo("").Get(user.ListMyPublicKeys). + Post(bind(api.CreateKeyOption{}), user.CreatePublicKey) + m.Combo("/:id").Get(user.GetPublicKey). + Delete(user.DeletePublicKey) + }) + }, ReqToken()) + + // Repositories + m.Combo("/user/repos", ReqToken()).Get(repo.ListMyRepos). + Post(bind(api.CreateRepoOption{}), repo.Create) + m.Post("/org/:org/repos", ReqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo) + + m.Group("/repos", func() { + m.Get("/search", repo.Search) + }) + + m.Group("/repos", func() { + m.Post("/migrate", bind(auth.MigrateRepoForm{}), repo.Migrate) + m.Combo("/:username/:reponame").Get(repo.Get). + Delete(repo.Delete) + + m.Group("/:username/:reponame", func() { + m.Combo("/hooks").Get(repo.ListHooks). + Post(bind(api.CreateHookOption{}), repo.CreateHook) + m.Patch("/hooks/:id:int", bind(api.EditHookOption{}), repo.EditHook) + m.Get("/raw/*", middleware.RepoRef(), repo.GetRawFile) + m.Get("/archive/*", repo.GetArchive) + + m.Group("/keys", func() { + m.Combo("").Get(repo.ListDeployKeys). + Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) + m.Combo("/:id").Get(repo.GetDeployKey). + Delete(repo.DeleteDeploykey) + }) + }, RepoAssignment()) + }, ReqToken()) + + m.Any("/*", func(ctx *middleware.Context) { + ctx.Error(404) + }) + }) +} diff --git a/routers/api/v1/misc/markdown.go b/routers/api/v1/misc/markdown.go new file mode 100644 index 00000000..49979ef7 --- /dev/null +++ b/routers/api/v1/misc/markdown.go @@ -0,0 +1,42 @@ +// 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 misc + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/middleware" +) + +// https://github.com/gogits/go-gogs-client/wiki/Miscellaneous#render-an-arbitrary-markdown-document +func Markdown(ctx *middleware.Context, form api.MarkdownOption) { + if ctx.HasApiError() { + ctx.APIError(422, "", ctx.GetErrMsg()) + return + } + + if len(form.Text) == 0 { + ctx.Write([]byte("")) + return + } + + switch form.Mode { + case "gfm": + ctx.Write(base.RenderMarkdown([]byte(form.Text), form.Context)) + default: + ctx.Write(base.RenderRawMarkdown([]byte(form.Text), "")) + } +} + +// https://github.com/gogits/go-gogs-client/wiki/Miscellaneous#render-a-markdown-document-in-raw-mode +func MarkdownRaw(ctx *middleware.Context) { + body, err := ctx.Req.Body().Bytes() + if err != nil { + ctx.APIError(422, "", err) + return + } + ctx.Write(base.RenderRawMarkdown(body, "")) +} diff --git a/routers/api/v1/miscellaneous.go b/routers/api/v1/miscellaneous.go deleted file mode 100644 index a382e349..00000000 --- a/routers/api/v1/miscellaneous.go +++ /dev/null @@ -1,41 +0,0 @@ -// 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/modules/auth/apiv1" - "github.com/gogits/gogs/modules/base" - "github.com/gogits/gogs/modules/middleware" -) - -// https://github.com/gogits/go-gogs-client/wiki/Miscellaneous#render-an-arbitrary-markdown-document -func Markdown(ctx *middleware.Context, form apiv1.MarkdownForm) { - if ctx.HasApiError() { - ctx.APIError(422, "", ctx.GetErrMsg()) - return - } - - if len(form.Text) == 0 { - ctx.Write([]byte("")) - return - } - - switch form.Mode { - case "gfm": - ctx.Write(base.RenderMarkdown([]byte(form.Text), form.Context)) - default: - ctx.Write(base.RenderRawMarkdown([]byte(form.Text), "")) - } -} - -// https://github.com/gogits/go-gogs-client/wiki/Miscellaneous#render-a-markdown-document-in-raw-mode -func MarkdownRaw(ctx *middleware.Context) { - body, err := ctx.Req.Body().Bytes() - if err != nil { - ctx.APIError(422, "", err) - return - } - ctx.Write(base.RenderRawMarkdown(body, "")) -} diff --git a/routers/api/v1/repo.go b/routers/api/v1/repo.go deleted file mode 100644 index 119a52b4..00000000 --- a/routers/api/v1/repo.go +++ /dev/null @@ -1,313 +0,0 @@ -// 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 ( - "path" - - "github.com/Unknwon/com" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/modules/auth" - "github.com/gogits/gogs/modules/log" - "github.com/gogits/gogs/modules/middleware" - "github.com/gogits/gogs/modules/setting" -) - -// ToApiRepository converts repository to API format. -func ToApiRepository(owner *models.User, repo *models.Repository, permission api.Permission) *api.Repository { - cl := repo.CloneLink() - return &api.Repository{ - Id: repo.ID, - Owner: *ToApiUser(owner), - FullName: owner.Name + "/" + repo.Name, - Private: repo.IsPrivate, - Fork: repo.IsFork, - HtmlUrl: setting.AppUrl + owner.Name + "/" + repo.Name, - CloneUrl: cl.HTTPS, - SshUrl: cl.SSH, - Permissions: permission, - } -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#search-repositories -func SearchRepos(ctx *middleware.Context) { - opt := models.SearchOption{ - Keyword: path.Base(ctx.Query("q")), - Uid: com.StrTo(ctx.Query("uid")).MustInt64(), - Limit: com.StrTo(ctx.Query("limit")).MustInt(), - } - if opt.Limit == 0 { - opt.Limit = 10 - } - - // Check visibility. - if ctx.IsSigned && opt.Uid > 0 { - if ctx.User.Id == opt.Uid { - opt.Private = true - } else { - u, err := models.GetUserByID(opt.Uid) - if err != nil { - ctx.JSON(500, map[string]interface{}{ - "ok": false, - "error": err.Error(), - }) - return - } - if u.IsOrganization() && u.IsOwnedBy(ctx.User.Id) { - opt.Private = true - } - // FIXME: how about collaborators? - } - } - - repos, err := models.SearchRepositoryByName(opt) - if err != nil { - ctx.JSON(500, map[string]interface{}{ - "ok": false, - "error": err.Error(), - }) - return - } - - results := make([]*api.Repository, len(repos)) - for i := range repos { - if err = repos[i].GetOwner(); err != nil { - ctx.JSON(500, map[string]interface{}{ - "ok": false, - "error": err.Error(), - }) - return - } - results[i] = &api.Repository{ - Id: repos[i].ID, - FullName: path.Join(repos[i].Owner.Name, repos[i].Name), - } - } - - ctx.JSON(200, map[string]interface{}{ - "ok": true, - "data": results, - }) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#list-your-repositories -func ListMyRepos(ctx *middleware.Context) { - ownRepos, err := models.GetRepositories(ctx.User.Id, true) - if err != nil { - ctx.APIError(500, "GetRepositories", err) - return - } - numOwnRepos := len(ownRepos) - - accessibleRepos, err := ctx.User.GetRepositoryAccesses() - if err != nil { - ctx.APIError(500, "GetRepositoryAccesses", err) - return - } - - repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos)) - for i := range ownRepos { - repos[i] = ToApiRepository(ctx.User, ownRepos[i], api.Permission{true, true, true}) - } - i := numOwnRepos - - for repo, access := range accessibleRepos { - repos[i] = ToApiRepository(repo.Owner, repo, api.Permission{ - Admin: access >= models.ACCESS_MODE_ADMIN, - Push: access >= models.ACCESS_MODE_WRITE, - Pull: true, - }) - i++ - } - - ctx.JSON(200, &repos) -} - -func createRepo(ctx *middleware.Context, owner *models.User, opt api.CreateRepoOption) { - repo, err := models.CreateRepository(owner, models.CreateRepoOptions{ - Name: opt.Name, - Description: opt.Description, - Gitignores: opt.Gitignores, - License: opt.License, - Readme: opt.Readme, - IsPrivate: opt.Private, - AutoInit: opt.AutoInit, - }) - if err != nil { - if models.IsErrRepoAlreadyExist(err) || - models.IsErrNameReserved(err) || - models.IsErrNamePatternNotAllowed(err) { - ctx.APIError(422, "", err) - } else { - if repo != nil { - if err = models.DeleteRepository(ctx.User.Id, repo.ID); err != nil { - log.Error(4, "DeleteRepository: %v", err) - } - } - ctx.APIError(500, "CreateRepository", err) - } - return - } - - ctx.JSON(201, ToApiRepository(owner, repo, api.Permission{true, true, true})) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#create -func CreateRepo(ctx *middleware.Context, opt api.CreateRepoOption) { - // Shouldn't reach this condition, but just in case. - if ctx.User.IsOrganization() { - ctx.APIError(422, "", "not allowed creating repository for organization") - return - } - createRepo(ctx, ctx.User, opt) -} - -func CreateOrgRepo(ctx *middleware.Context, opt api.CreateRepoOption) { - org, err := models.GetOrgByName(ctx.Params(":org")) - if err != nil { - if models.IsErrUserNotExist(err) { - ctx.APIError(422, "", err) - } else { - ctx.APIError(500, "GetOrgByName", err) - } - return - } - - if !org.IsOwnedBy(ctx.User.Id) { - ctx.APIError(403, "", "Given user is not owner of organization.") - return - } - createRepo(ctx, org, opt) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#migrate -func MigrateRepo(ctx *middleware.Context, form auth.MigrateRepoForm) { - ctxUser := ctx.User - // Not equal means context user is an organization, - // or is another user/organization if current user is admin. - if form.Uid != ctxUser.Id { - org, err := models.GetUserByID(form.Uid) - if err != nil { - if models.IsErrUserNotExist(err) { - ctx.APIError(422, "", err) - } else { - ctx.APIError(500, "GetUserByID", err) - } - return - } - ctxUser = org - } - - if ctx.HasError() { - ctx.APIError(422, "", ctx.GetErrMsg()) - return - } - - if ctxUser.IsOrganization() && !ctx.User.IsAdmin { - // Check ownership of organization. - if !ctxUser.IsOwnedBy(ctx.User.Id) { - ctx.APIError(403, "", "Given user is not owner of organization.") - return - } - } - - remoteAddr, err := form.ParseRemoteAddr(ctx.User) - if err != nil { - if models.IsErrInvalidCloneAddr(err) { - addrErr := err.(models.ErrInvalidCloneAddr) - switch { - case addrErr.IsURLError: - ctx.APIError(422, "", err) - case addrErr.IsPermissionDenied: - ctx.APIError(422, "", "You are not allowed to import local repositories.") - case addrErr.IsInvalidPath: - ctx.APIError(422, "", "Invalid local path, it does not exist or not a directory.") - default: - ctx.APIError(500, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error()) - } - } else { - ctx.APIError(500, "ParseRemoteAddr", err) - } - return - } - - repo, err := models.MigrateRepository(ctxUser, models.MigrateRepoOptions{ - Name: form.RepoName, - Description: form.Description, - IsPrivate: form.Private || setting.Repository.ForcePrivate, - IsMirror: form.Mirror, - RemoteAddr: remoteAddr, - }) - if err != nil { - if repo != nil { - if errDelete := models.DeleteRepository(ctxUser.Id, repo.ID); errDelete != nil { - log.Error(4, "DeleteRepository: %v", errDelete) - } - } - ctx.APIError(500, "MigrateRepository", err) - return - } - - log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) - ctx.JSON(201, ToApiRepository(ctxUser, repo, api.Permission{true, true, true})) -} - -func parseOwnerAndRepo(ctx *middleware.Context) (*models.User, *models.Repository) { - owner, err := models.GetUserByName(ctx.Params(":username")) - if err != nil { - if models.IsErrUserNotExist(err) { - ctx.APIError(422, "", err) - } else { - ctx.APIError(500, "GetUserByName", err) - } - return nil, nil - } - - repo, err := models.GetRepositoryByName(owner.Id, ctx.Params(":reponame")) - if err != nil { - if models.IsErrRepoNotExist(err) { - ctx.Error(404) - } else { - ctx.APIError(500, "GetRepositoryByName", err) - } - return nil, nil - } - - return owner, repo -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#get -func GetRepo(ctx *middleware.Context) { - owner, repo := parseOwnerAndRepo(ctx) - if ctx.Written() { - return - } - - ctx.JSON(200, ToApiRepository(owner, repo, api.Permission{true, true, true})) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#delete -func DeleteRepo(ctx *middleware.Context) { - owner, repo := parseOwnerAndRepo(ctx) - if ctx.Written() { - return - } - - if owner.IsOrganization() && !owner.IsOwnedBy(ctx.User.Id) { - ctx.APIError(403, "", "Given user is not owner of organization.") - return - } - - if err := models.DeleteRepository(owner.Id, repo.ID); err != nil { - ctx.APIError(500, "DeleteRepository", err) - return - } - - log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name) - ctx.Status(204) -} diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go new file mode 100644 index 00000000..0279718b --- /dev/null +++ b/routers/api/v1/repo/file.go @@ -0,0 +1,46 @@ +// 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 repo + +import ( + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/git" + "github.com/gogits/gogs/modules/middleware" + "github.com/gogits/gogs/routers/repo" +) + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Contents#download-raw-content +func GetRawFile(ctx *middleware.Context) { + if !ctx.Repo.HasAccess() { + ctx.Error(404) + return + } + + blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreeName) + if err != nil { + if err == git.ErrNotExist { + ctx.Error(404) + } else { + ctx.APIError(500, "GetBlobByPath", err) + } + return + } + if err = repo.ServeBlob(ctx, blob); err != nil { + ctx.APIError(500, "ServeBlob", err) + } +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Contents#download-archive +func GetArchive(ctx *middleware.Context) { + repoPath := models.RepoPath(ctx.Params(":username"), ctx.Params(":reponame")) + gitRepo, err := git.OpenRepository(repoPath) + if err != nil { + ctx.APIError(500, "OpenRepository", err) + return + } + ctx.Repo.GitRepo = gitRepo + + repo.Download(ctx) +} diff --git a/routers/api/v1/repo/hooks.go b/routers/api/v1/repo/hooks.go new file mode 100644 index 00000000..1ec70886 --- /dev/null +++ b/routers/api/v1/repo/hooks.go @@ -0,0 +1,165 @@ +// 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 repo + +import ( + "encoding/json" + + "github.com/Unknwon/com" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/middleware" + to "github.com/gogits/gogs/routers/api/v1/utils" +) + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#list-hooks +func ListHooks(ctx *middleware.Context) { + hooks, err := models.GetWebhooksByRepoID(ctx.Repo.Repository.ID) + if err != nil { + ctx.APIError(500, "GetWebhooksByRepoID", err) + return + } + + apiHooks := make([]*api.Hook, len(hooks)) + for i := range hooks { + apiHooks[i] = to.ApiHook(ctx.Repo.RepoLink, hooks[i]) + } + + ctx.JSON(200, &apiHooks) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#create-a-hook +func CreateHook(ctx *middleware.Context, form api.CreateHookOption) { + if !models.IsValidHookTaskType(form.Type) { + ctx.APIError(422, "", "Invalid hook type") + return + } + for _, name := range []string{"url", "content_type"} { + if _, ok := form.Config[name]; !ok { + ctx.APIError(422, "", "Missing config option: "+name) + return + } + } + if !models.IsValidHookContentType(form.Config["content_type"]) { + ctx.APIError(422, "", "Invalid content type") + return + } + + if len(form.Events) == 0 { + form.Events = []string{"push"} + } + w := &models.Webhook{ + RepoID: ctx.Repo.Repository.ID, + URL: form.Config["url"], + ContentType: models.ToHookContentType(form.Config["content_type"]), + Secret: form.Config["secret"], + HookEvent: &models.HookEvent{ + ChooseEvents: true, + HookEvents: models.HookEvents{ + Create: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_CREATE)), + Push: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH)), + }, + }, + IsActive: form.Active, + HookTaskType: models.ToHookTaskType(form.Type), + } + if w.HookTaskType == models.SLACK { + channel, ok := form.Config["channel"] + if !ok { + ctx.APIError(422, "", "Missing config option: channel") + return + } + meta, err := json.Marshal(&models.SlackMeta{ + Channel: channel, + Username: form.Config["username"], + IconURL: form.Config["icon_url"], + Color: form.Config["color"], + }) + if err != nil { + ctx.APIError(500, "slack: JSON marshal failed", err) + return + } + w.Meta = string(meta) + } + + if err := w.UpdateEvent(); err != nil { + ctx.APIError(500, "UpdateEvent", err) + return + } else if err := models.CreateWebhook(w); err != nil { + ctx.APIError(500, "CreateWebhook", err) + return + } + + ctx.JSON(201, to.ApiHook(ctx.Repo.RepoLink, w)) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#edit-a-hook +func EditHook(ctx *middleware.Context, form api.EditHookOption) { + w, err := models.GetWebhookByID(ctx.ParamsInt64(":id")) + if err != nil { + if models.IsErrWebhookNotExist(err) { + ctx.Error(404) + } else { + ctx.APIError(500, "GetWebhookById", err) + } + return + } + + if form.Config != nil { + if url, ok := form.Config["url"]; ok { + w.URL = url + } + if ct, ok := form.Config["content_type"]; ok { + if !models.IsValidHookContentType(ct) { + ctx.APIError(422, "", "Invalid content type") + return + } + w.ContentType = models.ToHookContentType(ct) + } + + if w.HookTaskType == models.SLACK { + if channel, ok := form.Config["channel"]; ok { + meta, err := json.Marshal(&models.SlackMeta{ + Channel: channel, + Username: form.Config["username"], + IconURL: form.Config["icon_url"], + Color: form.Config["color"], + }) + if err != nil { + ctx.APIError(500, "slack: JSON marshal failed", err) + return + } + w.Meta = string(meta) + } + } + } + + // Update events + if len(form.Events) == 0 { + form.Events = []string{"push"} + } + w.PushOnly = false + w.SendEverything = false + w.ChooseEvents = true + w.Create = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_CREATE)) + w.Push = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH)) + if err = w.UpdateEvent(); err != nil { + ctx.APIError(500, "UpdateEvent", err) + return + } + + if form.Active != nil { + w.IsActive = *form.Active + } + + if err := models.UpdateWebhook(w); err != nil { + ctx.APIError(500, "UpdateWebhook", err) + return + } + + ctx.JSON(200, to.ApiHook(ctx.Repo.RepoLink, w)) +} diff --git a/routers/api/v1/repo/keys.go b/routers/api/v1/repo/keys.go new file mode 100644 index 00000000..b0d8f914 --- /dev/null +++ b/routers/api/v1/repo/keys.go @@ -0,0 +1,114 @@ +// Copyright 2015 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 repo + +import ( + "fmt" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/middleware" + "github.com/gogits/gogs/modules/setting" + to "github.com/gogits/gogs/routers/api/v1/utils" +) + +func composeDeployKeysAPILink(repoPath string) string { + return setting.AppUrl + "api/v1/repos/" + repoPath + "/keys/" +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#list-deploy-keys +func ListDeployKeys(ctx *middleware.Context) { + keys, err := models.ListDeployKeys(ctx.Repo.Repository.ID) + if err != nil { + ctx.Handle(500, "ListDeployKeys", err) + return + } + + apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) + apiKeys := make([]*api.DeployKey, len(keys)) + for i := range keys { + if err = keys[i].GetContent(); err != nil { + ctx.APIError(500, "GetContent", err) + return + } + apiKeys[i] = to.ApiDeployKey(apiLink, keys[i]) + } + + ctx.JSON(200, &apiKeys) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#get-a-deploy-key +func GetDeployKey(ctx *middleware.Context) { + key, err := models.GetDeployKeyByID(ctx.ParamsInt64(":id")) + if err != nil { + if models.IsErrDeployKeyNotExist(err) { + ctx.Error(404) + } else { + ctx.Handle(500, "GetDeployKeyByID", err) + } + return + } + + if err = key.GetContent(); err != nil { + ctx.APIError(500, "GetContent", err) + return + } + + apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) + ctx.JSON(200, to.ApiDeployKey(apiLink, key)) +} + +func HandleCheckKeyStringError(ctx *middleware.Context, err error) { + if models.IsErrKeyUnableVerify(err) { + ctx.APIError(422, "", "Unable to verify key content") + } else { + ctx.APIError(422, "", fmt.Errorf("Invalid key content: %v", err)) + } +} + +func HandleAddKeyError(ctx *middleware.Context, err error) { + switch { + case models.IsErrKeyAlreadyExist(err): + ctx.APIError(422, "", "Key content has been used as non-deploy key") + case models.IsErrKeyNameAlreadyUsed(err): + ctx.APIError(422, "", "Key title has been used") + default: + ctx.APIError(500, "AddKey", err) + } +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#add-a-new-deploy-key +func CreateDeployKey(ctx *middleware.Context, form api.CreateKeyOption) { + content, err := models.CheckPublicKeyString(form.Key) + if err != nil { + HandleCheckKeyStringError(ctx, err) + return + } + + key, err := models.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content) + if err != nil { + HandleAddKeyError(ctx, err) + return + } + + key.Content = content + apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) + ctx.JSON(201, to.ApiDeployKey(apiLink, key)) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#remove-a-deploy-key +func DeleteDeploykey(ctx *middleware.Context) { + if err := models.DeleteDeployKey(ctx.User, ctx.ParamsInt64(":id")); err != nil { + if models.IsErrKeyAccessDenied(err) { + ctx.APIError(403, "", "You do not have access to this key") + } else { + ctx.APIError(500, "DeleteDeployKey", err) + } + return + } + + ctx.Status(204) +} diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go new file mode 100644 index 00000000..864cb4dd --- /dev/null +++ b/routers/api/v1/repo/repo.go @@ -0,0 +1,298 @@ +// 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 repo + +import ( + "path" + + "github.com/Unknwon/com" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/auth" + "github.com/gogits/gogs/modules/log" + "github.com/gogits/gogs/modules/middleware" + "github.com/gogits/gogs/modules/setting" + to "github.com/gogits/gogs/routers/api/v1/utils" +) + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#search-repositories +func Search(ctx *middleware.Context) { + opt := models.SearchOption{ + Keyword: path.Base(ctx.Query("q")), + Uid: com.StrTo(ctx.Query("uid")).MustInt64(), + Limit: com.StrTo(ctx.Query("limit")).MustInt(), + } + if opt.Limit == 0 { + opt.Limit = 10 + } + + // Check visibility. + if ctx.IsSigned && opt.Uid > 0 { + if ctx.User.Id == opt.Uid { + opt.Private = true + } else { + u, err := models.GetUserByID(opt.Uid) + if err != nil { + ctx.JSON(500, map[string]interface{}{ + "ok": false, + "error": err.Error(), + }) + return + } + if u.IsOrganization() && u.IsOwnedBy(ctx.User.Id) { + opt.Private = true + } + // FIXME: how about collaborators? + } + } + + repos, err := models.SearchRepositoryByName(opt) + if err != nil { + ctx.JSON(500, map[string]interface{}{ + "ok": false, + "error": err.Error(), + }) + return + } + + results := make([]*api.Repository, len(repos)) + for i := range repos { + if err = repos[i].GetOwner(); err != nil { + ctx.JSON(500, map[string]interface{}{ + "ok": false, + "error": err.Error(), + }) + return + } + results[i] = &api.Repository{ + Id: repos[i].ID, + FullName: path.Join(repos[i].Owner.Name, repos[i].Name), + } + } + + ctx.JSON(200, map[string]interface{}{ + "ok": true, + "data": results, + }) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#list-your-repositories +func ListMyRepos(ctx *middleware.Context) { + ownRepos, err := models.GetRepositories(ctx.User.Id, true) + if err != nil { + ctx.APIError(500, "GetRepositories", err) + return + } + numOwnRepos := len(ownRepos) + + accessibleRepos, err := ctx.User.GetRepositoryAccesses() + if err != nil { + ctx.APIError(500, "GetRepositoryAccesses", err) + return + } + + repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos)) + for i := range ownRepos { + repos[i] = to.ApiRepository(ctx.User, ownRepos[i], api.Permission{true, true, true}) + } + i := numOwnRepos + + for repo, access := range accessibleRepos { + repos[i] = to.ApiRepository(repo.Owner, repo, api.Permission{ + Admin: access >= models.ACCESS_MODE_ADMIN, + Push: access >= models.ACCESS_MODE_WRITE, + Pull: true, + }) + i++ + } + + ctx.JSON(200, &repos) +} + +func createRepo(ctx *middleware.Context, owner *models.User, opt api.CreateRepoOption) { + repo, err := models.CreateRepository(owner, models.CreateRepoOptions{ + Name: opt.Name, + Description: opt.Description, + Gitignores: opt.Gitignores, + License: opt.License, + Readme: opt.Readme, + IsPrivate: opt.Private, + AutoInit: opt.AutoInit, + }) + if err != nil { + if models.IsErrRepoAlreadyExist(err) || + models.IsErrNameReserved(err) || + models.IsErrNamePatternNotAllowed(err) { + ctx.APIError(422, "", err) + } else { + if repo != nil { + if err = models.DeleteRepository(ctx.User.Id, repo.ID); err != nil { + log.Error(4, "DeleteRepository: %v", err) + } + } + ctx.APIError(500, "CreateRepository", err) + } + return + } + + ctx.JSON(201, to.ApiRepository(owner, repo, api.Permission{true, true, true})) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#create +func Create(ctx *middleware.Context, opt api.CreateRepoOption) { + // Shouldn't reach this condition, but just in case. + if ctx.User.IsOrganization() { + ctx.APIError(422, "", "not allowed creating repository for organization") + return + } + createRepo(ctx, ctx.User, opt) +} + +func CreateOrgRepo(ctx *middleware.Context, opt api.CreateRepoOption) { + org, err := models.GetOrgByName(ctx.Params(":org")) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.APIError(422, "", err) + } else { + ctx.APIError(500, "GetOrgByName", err) + } + return + } + + if !org.IsOwnedBy(ctx.User.Id) { + ctx.APIError(403, "", "Given user is not owner of organization.") + return + } + createRepo(ctx, org, opt) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#migrate +func Migrate(ctx *middleware.Context, form auth.MigrateRepoForm) { + ctxUser := ctx.User + // Not equal means context user is an organization, + // or is another user/organization if current user is admin. + if form.Uid != ctxUser.Id { + org, err := models.GetUserByID(form.Uid) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.APIError(422, "", err) + } else { + ctx.APIError(500, "GetUserByID", err) + } + return + } + ctxUser = org + } + + if ctx.HasError() { + ctx.APIError(422, "", ctx.GetErrMsg()) + return + } + + if ctxUser.IsOrganization() && !ctx.User.IsAdmin { + // Check ownership of organization. + if !ctxUser.IsOwnedBy(ctx.User.Id) { + ctx.APIError(403, "", "Given user is not owner of organization.") + return + } + } + + remoteAddr, err := form.ParseRemoteAddr(ctx.User) + if err != nil { + if models.IsErrInvalidCloneAddr(err) { + addrErr := err.(models.ErrInvalidCloneAddr) + switch { + case addrErr.IsURLError: + ctx.APIError(422, "", err) + case addrErr.IsPermissionDenied: + ctx.APIError(422, "", "You are not allowed to import local repositories.") + case addrErr.IsInvalidPath: + ctx.APIError(422, "", "Invalid local path, it does not exist or not a directory.") + default: + ctx.APIError(500, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error()) + } + } else { + ctx.APIError(500, "ParseRemoteAddr", err) + } + return + } + + repo, err := models.MigrateRepository(ctxUser, models.MigrateRepoOptions{ + Name: form.RepoName, + Description: form.Description, + IsPrivate: form.Private || setting.Repository.ForcePrivate, + IsMirror: form.Mirror, + RemoteAddr: remoteAddr, + }) + if err != nil { + if repo != nil { + if errDelete := models.DeleteRepository(ctxUser.Id, repo.ID); errDelete != nil { + log.Error(4, "DeleteRepository: %v", errDelete) + } + } + ctx.APIError(500, "MigrateRepository", err) + return + } + + log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) + ctx.JSON(201, to.ApiRepository(ctxUser, repo, api.Permission{true, true, true})) +} + +func parseOwnerAndRepo(ctx *middleware.Context) (*models.User, *models.Repository) { + owner, err := models.GetUserByName(ctx.Params(":username")) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.APIError(422, "", err) + } else { + ctx.APIError(500, "GetUserByName", err) + } + return nil, nil + } + + repo, err := models.GetRepositoryByName(owner.Id, ctx.Params(":reponame")) + if err != nil { + if models.IsErrRepoNotExist(err) { + ctx.Error(404) + } else { + ctx.APIError(500, "GetRepositoryByName", err) + } + return nil, nil + } + + return owner, repo +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#get +func Get(ctx *middleware.Context) { + owner, repo := parseOwnerAndRepo(ctx) + if ctx.Written() { + return + } + + ctx.JSON(200, to.ApiRepository(owner, repo, api.Permission{true, true, true})) +} + +// https://github.com/gogits/go-gogs-client/wiki/Repositories#delete +func Delete(ctx *middleware.Context) { + owner, repo := parseOwnerAndRepo(ctx) + if ctx.Written() { + return + } + + if owner.IsOrganization() && !owner.IsOwnedBy(ctx.User.Id) { + ctx.APIError(403, "", "Given user is not owner of organization.") + return + } + + if err := models.DeleteRepository(owner.Id, repo.ID); err != nil { + ctx.APIError(500, "DeleteRepository", err) + return + } + + log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name) + ctx.Status(204) +} diff --git a/routers/api/v1/repo_file.go b/routers/api/v1/repo_file.go deleted file mode 100644 index 8cf36aef..00000000 --- a/routers/api/v1/repo_file.go +++ /dev/null @@ -1,46 +0,0 @@ -// 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/git" - "github.com/gogits/gogs/modules/middleware" - "github.com/gogits/gogs/routers/repo" -) - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Contents#download-raw-content -func GetRepoRawFile(ctx *middleware.Context) { - if !ctx.Repo.HasAccess() { - ctx.Error(404) - return - } - - blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreeName) - if err != nil { - if err == git.ErrNotExist { - ctx.Error(404) - } else { - ctx.APIError(500, "GetBlobByPath", err) - } - return - } - if err = repo.ServeBlob(ctx, blob); err != nil { - ctx.APIError(500, "ServeBlob", err) - } -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Contents#download-archive -func GetRepoArchive(ctx *middleware.Context) { - repoPath := models.RepoPath(ctx.Params(":username"), ctx.Params(":reponame")) - gitRepo, err := git.OpenRepository(repoPath) - if err != nil { - ctx.APIError(500, "OpenRepository", err) - return - } - ctx.Repo.GitRepo = gitRepo - - repo.Download(ctx) -} diff --git a/routers/api/v1/repo_hooks.go b/routers/api/v1/repo_hooks.go deleted file mode 100644 index 77c2ba66..00000000 --- a/routers/api/v1/repo_hooks.go +++ /dev/null @@ -1,191 +0,0 @@ -// 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 ( - "encoding/json" - "fmt" - - "github.com/Unknwon/com" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/modules/middleware" -) - -// ToApiHook converts webhook to API format. -func ToApiHook(repoLink string, w *models.Webhook) *api.Hook { - config := map[string]string{ - "url": w.URL, - "content_type": w.ContentType.Name(), - } - if w.HookTaskType == models.SLACK { - s := w.GetSlackHook() - config["channel"] = s.Channel - config["username"] = s.Username - config["icon_url"] = s.IconURL - config["color"] = s.Color - } - - return &api.Hook{ - ID: w.ID, - Type: w.HookTaskType.Name(), - URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID), - Active: w.IsActive, - Config: config, - Events: w.EventsArray(), - Updated: w.Updated, - Created: w.Created, - } -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#list-hooks -func ListRepoHooks(ctx *middleware.Context) { - hooks, err := models.GetWebhooksByRepoID(ctx.Repo.Repository.ID) - if err != nil { - ctx.APIError(500, "GetWebhooksByRepoID", err) - return - } - - apiHooks := make([]*api.Hook, len(hooks)) - for i := range hooks { - apiHooks[i] = ToApiHook(ctx.Repo.RepoLink, hooks[i]) - } - - ctx.JSON(200, &apiHooks) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#create-a-hook -func CreateRepoHook(ctx *middleware.Context, form api.CreateHookOption) { - if !models.IsValidHookTaskType(form.Type) { - ctx.APIError(422, "", "Invalid hook type") - return - } - for _, name := range []string{"url", "content_type"} { - if _, ok := form.Config[name]; !ok { - ctx.APIError(422, "", "Missing config option: "+name) - return - } - } - if !models.IsValidHookContentType(form.Config["content_type"]) { - ctx.APIError(422, "", "Invalid content type") - return - } - - if len(form.Events) == 0 { - form.Events = []string{"push"} - } - w := &models.Webhook{ - RepoID: ctx.Repo.Repository.ID, - URL: form.Config["url"], - ContentType: models.ToHookContentType(form.Config["content_type"]), - Secret: form.Config["secret"], - HookEvent: &models.HookEvent{ - ChooseEvents: true, - HookEvents: models.HookEvents{ - Create: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_CREATE)), - Push: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH)), - }, - }, - IsActive: form.Active, - HookTaskType: models.ToHookTaskType(form.Type), - } - if w.HookTaskType == models.SLACK { - channel, ok := form.Config["channel"] - if !ok { - ctx.APIError(422, "", "Missing config option: channel") - return - } - meta, err := json.Marshal(&models.SlackMeta{ - Channel: channel, - Username: form.Config["username"], - IconURL: form.Config["icon_url"], - Color: form.Config["color"], - }) - if err != nil { - ctx.APIError(500, "slack: JSON marshal failed", err) - return - } - w.Meta = string(meta) - } - - if err := w.UpdateEvent(); err != nil { - ctx.APIError(500, "UpdateEvent", err) - return - } else if err := models.CreateWebhook(w); err != nil { - ctx.APIError(500, "CreateWebhook", err) - return - } - - ctx.JSON(201, ToApiHook(ctx.Repo.RepoLink, w)) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories#edit-a-hook -func EditRepoHook(ctx *middleware.Context, form api.EditHookOption) { - w, err := models.GetWebhookByID(ctx.ParamsInt64(":id")) - if err != nil { - if models.IsErrWebhookNotExist(err) { - ctx.Error(404) - } else { - ctx.APIError(500, "GetWebhookById", err) - } - return - } - - if form.Config != nil { - if url, ok := form.Config["url"]; ok { - w.URL = url - } - if ct, ok := form.Config["content_type"]; ok { - if !models.IsValidHookContentType(ct) { - ctx.APIError(422, "", "Invalid content type") - return - } - w.ContentType = models.ToHookContentType(ct) - } - - if w.HookTaskType == models.SLACK { - if channel, ok := form.Config["channel"]; ok { - meta, err := json.Marshal(&models.SlackMeta{ - Channel: channel, - Username: form.Config["username"], - IconURL: form.Config["icon_url"], - Color: form.Config["color"], - }) - if err != nil { - ctx.APIError(500, "slack: JSON marshal failed", err) - return - } - w.Meta = string(meta) - } - } - } - - // Update events - if len(form.Events) == 0 { - form.Events = []string{"push"} - } - w.PushOnly = false - w.SendEverything = false - w.ChooseEvents = true - w.Create = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_CREATE)) - w.Push = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH)) - if err = w.UpdateEvent(); err != nil { - ctx.APIError(500, "UpdateEvent", err) - return - } - - if form.Active != nil { - w.IsActive = *form.Active - } - - if err := models.UpdateWebhook(w); err != nil { - ctx.APIError(500, "UpdateWebhook", err) - return - } - - ctx.JSON(200, ToApiHook(ctx.Repo.RepoLink, w)) -} diff --git a/routers/api/v1/repo_keys.go b/routers/api/v1/repo_keys.go deleted file mode 100644 index d8371b5a..00000000 --- a/routers/api/v1/repo_keys.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2015 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 ( - "fmt" - - "github.com/Unknwon/com" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/modules/middleware" - "github.com/gogits/gogs/modules/setting" -) - -func ToApiDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey { - return &api.DeployKey{ - ID: key.ID, - Key: key.Content, - URL: apiLink + com.ToStr(key.ID), - Title: key.Name, - Created: key.Created, - ReadOnly: true, // All deploy keys are read-only. - } -} - -func composeDeployKeysAPILink(repoPath string) string { - return setting.AppUrl + "api/v1/repos/" + repoPath + "/keys/" -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#list-deploy-keys -func ListRepoDeployKeys(ctx *middleware.Context) { - keys, err := models.ListDeployKeys(ctx.Repo.Repository.ID) - if err != nil { - ctx.Handle(500, "ListDeployKeys", err) - return - } - - apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) - apiKeys := make([]*api.DeployKey, len(keys)) - for i := range keys { - if err = keys[i].GetContent(); err != nil { - ctx.APIError(500, "GetContent", err) - return - } - apiKeys[i] = ToApiDeployKey(apiLink, keys[i]) - } - - ctx.JSON(200, &apiKeys) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#get-a-deploy-key -func GetRepoDeployKey(ctx *middleware.Context) { - key, err := models.GetDeployKeyByID(ctx.ParamsInt64(":id")) - if err != nil { - if models.IsErrDeployKeyNotExist(err) { - ctx.Error(404) - } else { - ctx.Handle(500, "GetDeployKeyByID", err) - } - return - } - - if err = key.GetContent(); err != nil { - ctx.APIError(500, "GetContent", err) - return - } - - apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) - ctx.JSON(200, ToApiDeployKey(apiLink, key)) -} - -func handleCheckKeyStringError(ctx *middleware.Context, err error) { - if models.IsErrKeyUnableVerify(err) { - ctx.APIError(422, "", "Unable to verify key content") - } else { - ctx.APIError(422, "", fmt.Errorf("Invalid key content: %v", err)) - } -} - -func handleAddKeyError(ctx *middleware.Context, err error) { - switch { - case models.IsErrKeyAlreadyExist(err): - ctx.APIError(422, "", "Key content has been used as non-deploy key") - case models.IsErrKeyNameAlreadyUsed(err): - ctx.APIError(422, "", "Key title has been used") - default: - ctx.APIError(500, "AddKey", err) - } -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#add-a-new-deploy-key -func CreateRepoDeployKey(ctx *middleware.Context, form api.CreateKeyOption) { - content, err := models.CheckPublicKeyString(form.Key) - if err != nil { - handleCheckKeyStringError(ctx, err) - return - } - - key, err := models.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content) - if err != nil { - handleAddKeyError(ctx, err) - return - } - - key.Content = content - apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) - ctx.JSON(201, ToApiDeployKey(apiLink, key)) -} - -// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#remove-a-deploy-key -func DeleteRepoDeploykey(ctx *middleware.Context) { - if err := models.DeleteDeployKey(ctx.User, ctx.ParamsInt64(":id")); err != nil { - if models.IsErrKeyAccessDenied(err) { - ctx.APIError(403, "", "You do not have access to this key") - } else { - ctx.APIError(500, "DeleteDeployKey", err) - } - return - } - - ctx.Status(204) -} diff --git a/routers/api/v1/user.go b/routers/api/v1/user.go deleted file mode 100644 index 36a1872b..00000000 --- a/routers/api/v1/user.go +++ /dev/null @@ -1,82 +0,0 @@ -// 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/Unknwon/com" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/modules/middleware" -) - -// ToApiUser converts user to API format. -func ToApiUser(u *models.User) *api.User { - return &api.User{ - ID: u.Id, - UserName: u.Name, - FullName: u.FullName, - Email: u.Email, - AvatarUrl: u.AvatarLink(), - } -} - -// https://github.com/gogits/go-gogs-client/wiki/Users#search-users -func SearchUsers(ctx *middleware.Context) { - opt := models.SearchOption{ - Keyword: ctx.Query("q"), - Limit: com.StrTo(ctx.Query("limit")).MustInt(), - } - if opt.Limit == 0 { - opt.Limit = 10 - } - - us, err := models.SearchUserByName(opt) - if err != nil { - ctx.JSON(500, map[string]interface{}{ - "ok": false, - "error": err.Error(), - }) - return - } - - results := make([]*api.User, len(us)) - for i := range us { - results[i] = &api.User{ - ID: us[i].Id, - UserName: us[i].Name, - AvatarUrl: us[i].AvatarLink(), - FullName: us[i].FullName, - } - if ctx.IsSigned { - results[i].Email = us[i].Email - } - } - - ctx.JSON(200, map[string]interface{}{ - "ok": true, - "data": results, - }) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users#get-a-single-user -func GetUserInfo(ctx *middleware.Context) { - u, err := models.GetUserByName(ctx.Params(":username")) - if err != nil { - if models.IsErrUserNotExist(err) { - ctx.Error(404) - } else { - ctx.APIError(500, "GetUserByName", err) - } - return - } - - // Hide user e-mail when API caller isn't signed in. - if !ctx.IsSigned { - u.Email = "" - } - ctx.JSON(200, &api.User{u.Id, u.Name, u.FullName, u.Email, u.AvatarLink()}) -} diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go new file mode 100644 index 00000000..00aacbdc --- /dev/null +++ b/routers/api/v1/user/app.go @@ -0,0 +1,40 @@ +// 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 user + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/middleware" +) + +// https://github.com/gogits/go-gogs-client/wiki/Users#list-access-tokens-for-a-user +func ListAccessTokens(ctx *middleware.Context) { + tokens, err := models.ListAccessTokens(ctx.User.Id) + if err != nil { + ctx.APIError(500, "ListAccessTokens", err) + return + } + + apiTokens := make([]*api.AccessToken, len(tokens)) + for i := range tokens { + apiTokens[i] = &api.AccessToken{tokens[i].Name, tokens[i].Sha1} + } + ctx.JSON(200, &apiTokens) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users#create-a-access-token +func CreateAccessToken(ctx *middleware.Context, form api.CreateAccessTokenOption) { + t := &models.AccessToken{ + UID: ctx.User.Id, + Name: form.Name, + } + if err := models.NewAccessToken(t); err != nil { + ctx.APIError(500, "NewAccessToken", err) + return + } + ctx.JSON(201, &api.AccessToken{t.Name, t.Sha1}) +} diff --git a/routers/api/v1/user/keys.go b/routers/api/v1/user/keys.go new file mode 100644 index 00000000..597ee9f1 --- /dev/null +++ b/routers/api/v1/user/keys.go @@ -0,0 +1,122 @@ +// Copyright 2015 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 user + +import ( + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/middleware" + "github.com/gogits/gogs/modules/setting" + "github.com/gogits/gogs/routers/api/v1/repo" + to "github.com/gogits/gogs/routers/api/v1/utils" +) + +func getUserByParams(ctx *middleware.Context) *models.User { + user, err := models.GetUserByName(ctx.Params(":username")) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.Error(404) + } else { + ctx.APIError(500, "GetUserByName", err) + } + return nil + } + return user +} + +func composePublicKeysAPILink() string { + return setting.AppUrl + "api/v1/user/keys/" +} + +func listPublicKeys(ctx *middleware.Context, uid int64) { + keys, err := models.ListPublicKeys(uid) + if err != nil { + ctx.APIError(500, "ListPublicKeys", err) + return + } + + apiLink := composePublicKeysAPILink() + apiKeys := make([]*api.PublicKey, len(keys)) + for i := range keys { + apiKeys[i] = to.ApiPublicKey(apiLink, keys[i]) + } + + ctx.JSON(200, &apiKeys) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#list-your-public-keys +func ListMyPublicKeys(ctx *middleware.Context) { + listPublicKeys(ctx, ctx.User.Id) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#list-public-keys-for-a-user +func ListPublicKeys(ctx *middleware.Context) { + user := getUserByParams(ctx) + if ctx.Written() { + return + } + listPublicKeys(ctx, user.Id) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#get-a-single-public-key +func GetPublicKey(ctx *middleware.Context) { + key, err := models.GetPublicKeyByID(ctx.ParamsInt64(":id")) + if err != nil { + if models.IsErrKeyNotExist(err) { + ctx.Error(404) + } else { + ctx.Handle(500, "GetPublicKeyByID", err) + } + return + } + + apiLink := composePublicKeysAPILink() + ctx.JSON(200, to.ApiPublicKey(apiLink, key)) +} + +func createUserPublicKey(ctx *middleware.Context, form api.CreateKeyOption, uid int64) { + content, err := models.CheckPublicKeyString(form.Key) + if err != nil { + repo.HandleCheckKeyStringError(ctx, err) + return + } + + key, err := models.AddPublicKey(uid, form.Title, content) + if err != nil { + repo.HandleAddKeyError(ctx, err) + return + } + apiLink := composePublicKeysAPILink() + ctx.JSON(201, to.ApiPublicKey(apiLink, key)) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#create-a-public-key-for-user +func CreateUserPublicKey(ctx *middleware.Context, form api.CreateKeyOption) { + user := getUserByParams(ctx) + if ctx.Written() { + return + } + createUserPublicKey(ctx, form, user.Id) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#create-a-public-key +func CreatePublicKey(ctx *middleware.Context, form api.CreateKeyOption) { + createUserPublicKey(ctx, form, ctx.User.Id) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#delete-a-public-key +func DeletePublicKey(ctx *middleware.Context) { + if err := models.DeletePublicKey(ctx.User, ctx.ParamsInt64(":id")); err != nil { + if models.IsErrKeyAccessDenied(err) { + ctx.APIError(403, "", "You do not have access to this key") + } else { + ctx.APIError(500, "DeletePublicKey", err) + } + return + } + + ctx.Status(204) +} diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go new file mode 100644 index 00000000..6d4b52ff --- /dev/null +++ b/routers/api/v1/user/user.go @@ -0,0 +1,71 @@ +// 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 user + +import ( + "github.com/Unknwon/com" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/middleware" +) + +// https://github.com/gogits/go-gogs-client/wiki/Users#search-users +func Search(ctx *middleware.Context) { + opt := models.SearchOption{ + Keyword: ctx.Query("q"), + Limit: com.StrTo(ctx.Query("limit")).MustInt(), + } + if opt.Limit == 0 { + opt.Limit = 10 + } + + us, err := models.SearchUserByName(opt) + if err != nil { + ctx.JSON(500, map[string]interface{}{ + "ok": false, + "error": err.Error(), + }) + return + } + + results := make([]*api.User, len(us)) + for i := range us { + results[i] = &api.User{ + ID: us[i].Id, + UserName: us[i].Name, + AvatarUrl: us[i].AvatarLink(), + FullName: us[i].FullName, + } + if ctx.IsSigned { + results[i].Email = us[i].Email + } + } + + ctx.JSON(200, map[string]interface{}{ + "ok": true, + "data": results, + }) +} + +// https://github.com/gogits/go-gogs-client/wiki/Users#get-a-single-user +func GetInfo(ctx *middleware.Context) { + u, err := models.GetUserByName(ctx.Params(":username")) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.Error(404) + } else { + ctx.APIError(500, "GetUserByName", err) + } + return + } + + // Hide user e-mail when API caller isn't signed in. + if !ctx.IsSigned { + u.Email = "" + } + ctx.JSON(200, &api.User{u.Id, u.Name, u.FullName, u.Email, u.AvatarLink()}) +} diff --git a/routers/api/v1/user_app.go b/routers/api/v1/user_app.go deleted file mode 100644 index c1b83d60..00000000 --- a/routers/api/v1/user_app.go +++ /dev/null @@ -1,44 +0,0 @@ -// 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 ( - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/modules/middleware" -) - -// https://github.com/gogits/go-gogs-client/wiki/Users#list-access-tokens-for-a-user -func ListAccessTokens(ctx *middleware.Context) { - tokens, err := models.ListAccessTokens(ctx.User.Id) - if err != nil { - ctx.APIError(500, "ListAccessTokens", err) - return - } - - apiTokens := make([]*api.AccessToken, len(tokens)) - for i := range tokens { - apiTokens[i] = &api.AccessToken{tokens[i].Name, tokens[i].Sha1} - } - ctx.JSON(200, &apiTokens) -} - -type CreateAccessTokenForm struct { - Name string `json:"name" binding:"Required"` -} - -// https://github.com/gogits/go-gogs-client/wiki/Users#create-a-access-token -func CreateAccessToken(ctx *middleware.Context, form CreateAccessTokenForm) { - t := &models.AccessToken{ - UID: ctx.User.Id, - Name: form.Name, - } - if err := models.NewAccessToken(t); err != nil { - ctx.APIError(500, "NewAccessToken", err) - return - } - ctx.JSON(201, &api.AccessToken{t.Name, t.Sha1}) -} diff --git a/routers/api/v1/user_keys.go b/routers/api/v1/user_keys.go deleted file mode 100644 index 54a51ef3..00000000 --- a/routers/api/v1/user_keys.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2015 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/Unknwon/com" - - api "github.com/gogits/go-gogs-client" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/modules/middleware" - "github.com/gogits/gogs/modules/setting" -) - -func ToApiPublicKey(apiLink string, key *models.PublicKey) *api.PublicKey { - return &api.PublicKey{ - ID: key.ID, - Key: key.Content, - URL: apiLink + com.ToStr(key.ID), - Title: key.Name, - Created: key.Created, - } -} - -func composePublicKeysAPILink() string { - return setting.AppUrl + "api/v1/user/keys/" -} - -func listUserPublicKeys(ctx *middleware.Context, uid int64) { - keys, err := models.ListPublicKeys(uid) - if err != nil { - ctx.APIError(500, "ListPublicKeys", err) - return - } - - apiLink := composePublicKeysAPILink() - apiKeys := make([]*api.PublicKey, len(keys)) - for i := range keys { - apiKeys[i] = ToApiPublicKey(apiLink, keys[i]) - } - - ctx.JSON(200, &apiKeys) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#list-public-keys-for-a-user -func ListUserPublicKeys(ctx *middleware.Context) { - user, err := models.GetUserByName(ctx.Params(":username")) - if err != nil { - if models.IsErrUserNotExist(err) { - ctx.Error(404) - } else { - ctx.APIError(500, "GetUserByName", err) - } - return - } - listUserPublicKeys(ctx, user.Id) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#list-your-public-keys -func ListMyPublicKeys(ctx *middleware.Context) { - listUserPublicKeys(ctx, ctx.User.Id) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#get-a-single-public-key -func GetUserPublicKey(ctx *middleware.Context) { - key, err := models.GetPublicKeyByID(ctx.ParamsInt64(":id")) - if err != nil { - if models.IsErrKeyNotExist(err) { - ctx.Error(404) - } else { - ctx.Handle(500, "GetPublicKeyByID", err) - } - return - } - - apiLink := composePublicKeysAPILink() - ctx.JSON(200, ToApiPublicKey(apiLink, key)) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#create-a-public-key -func CreateUserPublicKey(ctx *middleware.Context, form api.CreateKeyOption) { - content, err := models.CheckPublicKeyString(form.Key) - if err != nil { - handleCheckKeyStringError(ctx, err) - return - } - - key, err := models.AddPublicKey(ctx.User.Id, form.Title, content) - if err != nil { - handleAddKeyError(ctx, err) - return - } - apiLink := composePublicKeysAPILink() - ctx.JSON(201, ToApiPublicKey(apiLink, key)) -} - -// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#delete-a-public-key -func DeleteUserPublicKey(ctx *middleware.Context) { - if err := models.DeletePublicKey(ctx.User, ctx.ParamsInt64(":id")); err != nil { - if models.IsErrKeyAccessDenied(err) { - ctx.APIError(403, "", "You do not have access to this key") - } else { - ctx.APIError(500, "DeletePublicKey", err) - } - return - } - - ctx.Status(204) -} diff --git a/routers/api/v1/utils/convert.go b/routers/api/v1/utils/convert.go new file mode 100644 index 00000000..7ac4edf2 --- /dev/null +++ b/routers/api/v1/utils/convert.go @@ -0,0 +1,92 @@ +// Copyright 2015 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 utils + +import ( + "fmt" + + "github.com/Unknwon/com" + + api "github.com/gogits/go-gogs-client" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/setting" +) + +// ApiUser converts user to its API format. +func ApiUser(u *models.User) *api.User { + return &api.User{ + ID: u.Id, + UserName: u.Name, + FullName: u.FullName, + Email: u.Email, + AvatarUrl: u.AvatarLink(), + } +} + +// ApiRepository converts repository to API format. +func ApiRepository(owner *models.User, repo *models.Repository, permission api.Permission) *api.Repository { + cl := repo.CloneLink() + return &api.Repository{ + Id: repo.ID, + Owner: *ApiUser(owner), + FullName: owner.Name + "/" + repo.Name, + Private: repo.IsPrivate, + Fork: repo.IsFork, + HtmlUrl: setting.AppUrl + owner.Name + "/" + repo.Name, + CloneUrl: cl.HTTPS, + SshUrl: cl.SSH, + Permissions: permission, + } +} + +// ApiPublicKey converts public key to its API format. +func ApiPublicKey(apiLink string, key *models.PublicKey) *api.PublicKey { + return &api.PublicKey{ + ID: key.ID, + Key: key.Content, + URL: apiLink + com.ToStr(key.ID), + Title: key.Name, + Created: key.Created, + } +} + +// ApiHook converts webhook to its API format. +func ApiHook(repoLink string, w *models.Webhook) *api.Hook { + config := map[string]string{ + "url": w.URL, + "content_type": w.ContentType.Name(), + } + if w.HookTaskType == models.SLACK { + s := w.GetSlackHook() + config["channel"] = s.Channel + config["username"] = s.Username + config["icon_url"] = s.IconURL + config["color"] = s.Color + } + + return &api.Hook{ + ID: w.ID, + Type: w.HookTaskType.Name(), + URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID), + Active: w.IsActive, + Config: config, + Events: w.EventsArray(), + Updated: w.Updated, + Created: w.Created, + } +} + +// ApiDeployKey converts deploy key to its API format. +func ApiDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey { + return &api.DeployKey{ + ID: key.ID, + Key: key.Content, + URL: apiLink + com.ToStr(key.ID), + Title: key.Name, + Created: key.Created, + ReadOnly: true, // All deploy keys are read-only. + } +} -- cgit v1.2.3