aboutsummaryrefslogtreecommitdiff
path: root/routes/api/v1
diff options
context:
space:
mode:
Diffstat (limited to 'routes/api/v1')
-rw-r--r--routes/api/v1/admin/org.go44
-rw-r--r--routes/api/v1/admin/org_repo.go50
-rw-r--r--routes/api/v1/admin/org_team.go60
-rw-r--r--routes/api/v1/admin/repo.go23
-rw-r--r--routes/api/v1/admin/user.go160
-rw-r--r--routes/api/v1/api.go356
-rw-r--r--routes/api/v1/convert/convert.go127
-rw-r--r--routes/api/v1/convert/utils.go19
-rw-r--r--routes/api/v1/misc/markdown.go42
-rw-r--r--routes/api/v1/org/org.go66
-rw-r--r--routes/api/v1/org/team.go26
-rw-r--r--routes/api/v1/repo/branch.go55
-rw-r--r--routes/api/v1/repo/collaborators.go94
-rw-r--r--routes/api/v1/repo/file.go72
-rw-r--r--routes/api/v1/repo/hook.go186
-rw-r--r--routes/api/v1/repo/issue.go201
-rw-r--r--routes/api/v1/repo/issue_comment.go128
-rw-r--r--routes/api/v1/repo/issue_label.go169
-rw-r--r--routes/api/v1/repo/key.go114
-rw-r--r--routes/api/v1/repo/label.go110
-rw-r--r--routes/api/v1/repo/milestone.go103
-rw-r--r--routes/api/v1/repo/repo.go380
-rw-r--r--routes/api/v1/user/app.go40
-rw-r--r--routes/api/v1/user/email.go82
-rw-r--r--routes/api/v1/user/follower.go120
-rw-r--r--routes/api/v1/user/key.go120
-rw-r--r--routes/api/v1/user/user.go75
27 files changed, 3022 insertions, 0 deletions
diff --git a/routes/api/v1/admin/org.go b/routes/api/v1/admin/org.go
new file mode 100644
index 00000000..0f84ed2e
--- /dev/null
+++ b/routes/api/v1/admin/org.go
@@ -0,0 +1,44 @@
+// 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 admin
+
+import (
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/routes/api/v1/convert"
+ "github.com/gogits/gogs/routes/api/v1/user"
+)
+
+// https://github.com/gogits/go-gogs-client/wiki/Administration-Organizations#create-a-new-organization
+func CreateOrg(c *context.APIContext, form api.CreateOrgOption) {
+ u := user.GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+
+ org := &models.User{
+ Name: form.UserName,
+ FullName: form.FullName,
+ Description: form.Description,
+ Website: form.Website,
+ Location: form.Location,
+ IsActive: true,
+ Type: models.USER_TYPE_ORGANIZATION,
+ }
+ if err := models.CreateOrganization(org, u); err != nil {
+ if models.IsErrUserAlreadyExist(err) ||
+ models.IsErrNameReserved(err) ||
+ models.IsErrNamePatternNotAllowed(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "CreateOrganization", err)
+ }
+ return
+ }
+
+ c.JSON(201, convert.ToOrganization(org))
+}
diff --git a/routes/api/v1/admin/org_repo.go b/routes/api/v1/admin/org_repo.go
new file mode 100644
index 00000000..7abad1a8
--- /dev/null
+++ b/routes/api/v1/admin/org_repo.go
@@ -0,0 +1,50 @@
+// Copyright 2016 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 admin
+
+import (
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/models/errors"
+ "github.com/gogits/gogs/pkg/context"
+)
+
+func GetRepositoryByParams(c *context.APIContext) *models.Repository {
+ repo, err := models.GetRepositoryByName(c.Org.Team.OrgID, c.Params(":reponame"))
+ if err != nil {
+ if errors.IsRepoNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetRepositoryByName", err)
+ }
+ return nil
+ }
+ return repo
+}
+
+func AddTeamRepository(c *context.APIContext) {
+ repo := GetRepositoryByParams(c)
+ if c.Written() {
+ return
+ }
+ if err := c.Org.Team.AddRepository(repo); err != nil {
+ c.Error(500, "AddRepository", err)
+ return
+ }
+
+ c.Status(204)
+}
+
+func RemoveTeamRepository(c *context.APIContext) {
+ repo := GetRepositoryByParams(c)
+ if c.Written() {
+ return
+ }
+ if err := c.Org.Team.RemoveRepository(repo.ID); err != nil {
+ c.Error(500, "RemoveRepository", err)
+ return
+ }
+
+ c.Status(204)
+}
diff --git a/routes/api/v1/admin/org_team.go b/routes/api/v1/admin/org_team.go
new file mode 100644
index 00000000..ae748504
--- /dev/null
+++ b/routes/api/v1/admin/org_team.go
@@ -0,0 +1,60 @@
+// Copyright 2016 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 admin
+
+import (
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/routes/api/v1/convert"
+ "github.com/gogits/gogs/routes/api/v1/user"
+)
+
+func CreateTeam(c *context.APIContext, form api.CreateTeamOption) {
+ team := &models.Team{
+ OrgID: c.Org.Organization.ID,
+ Name: form.Name,
+ Description: form.Description,
+ Authorize: models.ParseAccessMode(form.Permission),
+ }
+ if err := models.NewTeam(team); err != nil {
+ if models.IsErrTeamAlreadyExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "NewTeam", err)
+ }
+ return
+ }
+
+ c.JSON(201, convert.ToTeam(team))
+}
+
+func AddTeamMember(c *context.APIContext) {
+ u := user.GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+ if err := c.Org.Team.AddMember(u.ID); err != nil {
+ c.Error(500, "AddMember", err)
+ return
+ }
+
+ c.Status(204)
+}
+
+func RemoveTeamMember(c *context.APIContext) {
+ u := user.GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+
+ if err := c.Org.Team.RemoveMember(u.ID); err != nil {
+ c.Error(500, "RemoveMember", err)
+ return
+ }
+
+ c.Status(204)
+}
diff --git a/routes/api/v1/admin/repo.go b/routes/api/v1/admin/repo.go
new file mode 100644
index 00000000..920bac8d
--- /dev/null
+++ b/routes/api/v1/admin/repo.go
@@ -0,0 +1,23 @@
+// 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 admin
+
+import (
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/routes/api/v1/repo"
+ "github.com/gogits/gogs/routes/api/v1/user"
+)
+
+// https://github.com/gogits/go-gogs-client/wiki/Administration-Repositories#create-a-new-repository
+func CreateRepo(c *context.APIContext, form api.CreateRepoOption) {
+ owner := user.GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+
+ repo.CreateUserRepo(c, owner, form)
+}
diff --git a/routes/api/v1/admin/user.go b/routes/api/v1/admin/user.go
new file mode 100644
index 00000000..623911fd
--- /dev/null
+++ b/routes/api/v1/admin/user.go
@@ -0,0 +1,160 @@
+// 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 admin
+
+import (
+ log "gopkg.in/clog.v1"
+
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/pkg/mailer"
+ "github.com/gogits/gogs/pkg/setting"
+ "github.com/gogits/gogs/routes/api/v1/user"
+)
+
+func parseLoginSource(c *context.APIContext, u *models.User, sourceID int64, loginName string) {
+ if sourceID == 0 {
+ return
+ }
+
+ source, err := models.GetLoginSourceByID(sourceID)
+ if err != nil {
+ if models.IsErrLoginSourceNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetLoginSourceByID", err)
+ }
+ return
+ }
+
+ u.LoginType = source.Type
+ u.LoginSource = source.ID
+ u.LoginName = loginName
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#create-a-new-user
+func CreateUser(c *context.APIContext, form api.CreateUserOption) {
+ u := &models.User{
+ Name: form.Username,
+ FullName: form.FullName,
+ Email: form.Email,
+ Passwd: form.Password,
+ IsActive: true,
+ LoginType: models.LOGIN_PLAIN,
+ }
+
+ parseLoginSource(c, u, form.SourceID, form.LoginName)
+ if c.Written() {
+ return
+ }
+
+ if err := models.CreateUser(u); err != nil {
+ if models.IsErrUserAlreadyExist(err) ||
+ models.IsErrEmailAlreadyUsed(err) ||
+ models.IsErrNameReserved(err) ||
+ models.IsErrNamePatternNotAllowed(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "CreateUser", err)
+ }
+ return
+ }
+ log.Trace("Account created by admin (%s): %s", c.User.Name, u.Name)
+
+ // Send email notification.
+ if form.SendNotify && setting.MailService != nil {
+ mailer.SendRegisterNotifyMail(c.Context.Context, models.NewMailerUser(u))
+ }
+
+ c.JSON(201, u.APIFormat())
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#edit-an-existing-user
+func EditUser(c *context.APIContext, form api.EditUserOption) {
+ u := user.GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+
+ parseLoginSource(c, u, form.SourceID, form.LoginName)
+ if c.Written() {
+ return
+ }
+
+ if len(form.Password) > 0 {
+ u.Passwd = form.Password
+ var err error
+ if u.Salt, err = models.GetUserSalt(); err != nil {
+ c.Error(500, "UpdateUser", err)
+ return
+ }
+ u.EncodePasswd()
+ }
+
+ u.LoginName = form.LoginName
+ u.FullName = form.FullName
+ u.Email = form.Email
+ u.Website = form.Website
+ u.Location = form.Location
+ if form.Active != nil {
+ u.IsActive = *form.Active
+ }
+ if form.Admin != nil {
+ u.IsAdmin = *form.Admin
+ }
+ if form.AllowGitHook != nil {
+ u.AllowGitHook = *form.AllowGitHook
+ }
+ if form.AllowImportLocal != nil {
+ u.AllowImportLocal = *form.AllowImportLocal
+ }
+ if form.MaxRepoCreation != nil {
+ u.MaxRepoCreation = *form.MaxRepoCreation
+ }
+
+ if err := models.UpdateUser(u); err != nil {
+ if models.IsErrEmailAlreadyUsed(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "UpdateUser", err)
+ }
+ return
+ }
+ log.Trace("Account profile updated by admin (%s): %s", c.User.Name, u.Name)
+
+ c.JSON(200, u.APIFormat())
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#delete-a-user
+func DeleteUser(c *context.APIContext) {
+ u := user.GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+
+ if err := models.DeleteUser(u); err != nil {
+ if models.IsErrUserOwnRepos(err) ||
+ models.IsErrUserHasOrgs(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "DeleteUser", err)
+ }
+ return
+ }
+ log.Trace("Account deleted by admin(%s): %s", c.User.Name, u.Name)
+
+ c.Status(204)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#create-a-public-key-for-user
+func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) {
+ u := user.GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+ user.CreateUserPublicKey(c, form, u.ID)
+}
diff --git a/routes/api/v1/api.go b/routes/api/v1/api.go
new file mode 100644
index 00000000..510c54cf
--- /dev/null
+++ b/routes/api/v1/api.go
@@ -0,0 +1,356 @@
+// 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/models/errors"
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/pkg/form"
+ "github.com/gogits/gogs/routes/api/v1/admin"
+ "github.com/gogits/gogs/routes/api/v1/misc"
+ "github.com/gogits/gogs/routes/api/v1/org"
+ "github.com/gogits/gogs/routes/api/v1/repo"
+ "github.com/gogits/gogs/routes/api/v1/user"
+)
+
+func repoAssignment() macaron.Handler {
+ return func(c *context.APIContext) {
+ userName := c.Params(":username")
+ repoName := c.Params(":reponame")
+
+ var (
+ owner *models.User
+ err error
+ )
+
+ // Check if the user is the same as the repository owner.
+ if c.IsLogged && c.User.LowerName == strings.ToLower(userName) {
+ owner = c.User
+ } else {
+ owner, err = models.GetUserByName(userName)
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+ }
+ c.Repo.Owner = owner
+
+ // Get repository.
+ repo, err := models.GetRepositoryByName(owner.ID, repoName)
+ if err != nil {
+ if errors.IsRepoNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetRepositoryByName", err)
+ }
+ return
+ } else if err = repo.GetOwner(); err != nil {
+ c.Error(500, "GetOwner", err)
+ return
+ }
+
+ if c.IsLogged && c.User.IsAdmin {
+ c.Repo.AccessMode = models.ACCESS_MODE_OWNER
+ } else {
+ mode, err := models.AccessLevel(c.User.ID, repo)
+ if err != nil {
+ c.Error(500, "AccessLevel", err)
+ return
+ }
+ c.Repo.AccessMode = mode
+ }
+
+ if !c.Repo.HasAccess() {
+ c.Status(404)
+ return
+ }
+
+ c.Repo.Repository = repo
+ }
+}
+
+// Contexter middleware already checks token for user sign in process.
+func reqToken() macaron.Handler {
+ return func(c *context.Context) {
+ if !c.IsLogged {
+ c.Error(401)
+ return
+ }
+ }
+}
+
+func reqBasicAuth() macaron.Handler {
+ return func(c *context.Context) {
+ if !c.IsBasicAuth {
+ c.Error(401)
+ return
+ }
+ }
+}
+
+func reqAdmin() macaron.Handler {
+ return func(c *context.Context) {
+ if !c.IsLogged || !c.User.IsAdmin {
+ c.Error(403)
+ return
+ }
+ }
+}
+
+func reqRepoWriter() macaron.Handler {
+ return func(c *context.Context) {
+ if !c.Repo.IsWriter() {
+ c.Error(403)
+ return
+ }
+ }
+}
+
+func orgAssignment(args ...bool) macaron.Handler {
+ var (
+ assignOrg bool
+ assignTeam bool
+ )
+ if len(args) > 0 {
+ assignOrg = args[0]
+ }
+ if len(args) > 1 {
+ assignTeam = args[1]
+ }
+ return func(c *context.APIContext) {
+ c.Org = new(context.APIOrganization)
+
+ var err error
+ if assignOrg {
+ c.Org.Organization, err = models.GetUserByName(c.Params(":orgname"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+ }
+
+ if assignTeam {
+ c.Org.Team, err = models.GetTeamByID(c.ParamsInt64(":teamid"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetTeamById", err)
+ }
+ return
+ }
+ }
+ }
+}
+
+func mustEnableIssues(c *context.APIContext) {
+ if !c.Repo.Repository.EnableIssues || c.Repo.Repository.EnableExternalTracker {
+ c.Status(404)
+ 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() {
+ // Handle preflight OPTIONS request
+ m.Options("/*", 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.Get("/keys", user.ListPublicKeys)
+
+ m.Get("/followers", user.ListFollowers)
+ m.Group("/following", func() {
+ m.Get("", user.ListFollowing)
+ m.Get("/:target", user.CheckFollowing)
+ })
+ })
+ }, reqToken())
+
+ m.Group("/user", func() {
+ m.Get("", user.GetAuthenticatedUser)
+ m.Combo("/emails").Get(user.ListEmails).
+ Post(bind(api.CreateEmailOption{}), user.AddEmail).
+ Delete(bind(api.CreateEmailOption{}), user.DeleteEmail)
+
+ m.Get("/followers", user.ListMyFollowers)
+ m.Group("/following", func() {
+ m.Get("", user.ListMyFollowing)
+ m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow)
+ })
+
+ m.Group("/keys", func() {
+ m.Combo("").Get(user.ListMyPublicKeys).
+ Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
+ m.Combo("/:id").Get(user.GetPublicKey).
+ Delete(user.DeletePublicKey)
+ })
+
+ m.Combo("/issues").Get(repo.ListUserIssues)
+ }, reqToken())
+
+ // Repositories
+ m.Get("/users/:username/repos", reqToken(), repo.ListUserRepositories)
+ m.Get("/orgs/:org/repos", reqToken(), repo.ListOrgRepositories)
+ 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(form.MigrateRepo{}), repo.Migrate)
+ m.Combo("/:username/:reponame", repoAssignment()).Get(repo.Get).
+ Delete(repo.Delete)
+
+ m.Group("/:username/:reponame", func() {
+ m.Group("/hooks", func() {
+ m.Combo("").Get(repo.ListHooks).
+ Post(bind(api.CreateHookOption{}), repo.CreateHook)
+ m.Combo("/:id").Patch(bind(api.EditHookOption{}), repo.EditHook).
+ Delete(repo.DeleteHook)
+ })
+ 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.Get("/forks", repo.ListForks)
+ m.Group("/branches", func() {
+ m.Get("", repo.ListBranches)
+ m.Get("/*", repo.GetBranch)
+ })
+ m.Group("/keys", func() {
+ m.Combo("").Get(repo.ListDeployKeys).
+ Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
+ m.Combo("/:id").Get(repo.GetDeployKey).
+ Delete(repo.DeleteDeploykey)
+ })
+ m.Group("/issues", func() {
+ m.Combo("").Get(repo.ListIssues).Post(bind(api.CreateIssueOption{}), repo.CreateIssue)
+ m.Group("/comments", func() {
+ m.Get("", repo.ListRepoIssueComments)
+ m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment)
+ })
+ m.Group("/:index", func() {
+ m.Combo("").Get(repo.GetIssue).Patch(bind(api.EditIssueOption{}), repo.EditIssue)
+
+ m.Group("/comments", func() {
+ m.Combo("").Get(repo.ListIssueComments).Post(bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
+ m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
+ Delete(repo.DeleteIssueComment)
+ })
+
+ m.Group("/labels", func() {
+ m.Combo("").Get(repo.ListIssueLabels).
+ Post(bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
+ Put(bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
+ Delete(repo.ClearIssueLabels)
+ m.Delete("/:id", repo.DeleteIssueLabel)
+ })
+
+ })
+ }, mustEnableIssues)
+ m.Group("/labels", func() {
+ m.Combo("").Get(repo.ListLabels).
+ Post(bind(api.CreateLabelOption{}), repo.CreateLabel)
+ m.Combo("/:id").Get(repo.GetLabel).Patch(bind(api.EditLabelOption{}), repo.EditLabel).
+ Delete(repo.DeleteLabel)
+ })
+ m.Group("/milestones", func() {
+ m.Combo("").Get(repo.ListMilestones).
+ Post(reqRepoWriter(), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
+ m.Combo("/:id").Get(repo.GetMilestone).
+ Patch(reqRepoWriter(), bind(api.EditMilestoneOption{}), repo.EditMilestone).
+ Delete(reqRepoWriter(), repo.DeleteMilestone)
+ })
+ m.Post("/mirror-sync", repo.MirrorSync)
+ m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig)
+ }, repoAssignment())
+ }, reqToken())
+
+ m.Get("/issues", reqToken(), repo.ListUserIssues)
+
+ // Organizations
+ m.Get("/user/orgs", reqToken(), org.ListMyOrgs)
+ m.Get("/users/:username/orgs", org.ListUserOrgs)
+ m.Group("/orgs/:orgname", func() {
+ m.Combo("").Get(org.Get).Patch(bind(api.EditOrgOption{}), org.Edit)
+ m.Combo("/teams").Get(org.ListTeams)
+ }, orgAssignment(true))
+
+ m.Any("/*", func(c *context.Context) {
+ c.Error(404)
+ })
+
+ m.Group("/admin", func() {
+ m.Group("/users", func() {
+ m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
+
+ m.Group("/:username", func() {
+ m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
+ Delete(admin.DeleteUser)
+ m.Post("/keys", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
+ m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
+ m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
+ })
+ })
+
+ m.Group("/orgs/:orgname", func() {
+ m.Group("/teams", func() {
+ m.Post("", orgAssignment(true), bind(api.CreateTeamOption{}), admin.CreateTeam)
+ })
+ })
+ m.Group("/teams", func() {
+ m.Group("/:teamid", func() {
+ m.Combo("/members/:username").Put(admin.AddTeamMember).Delete(admin.RemoveTeamMember)
+ m.Combo("/repos/:reponame").Put(admin.AddTeamRepository).Delete(admin.RemoveTeamRepository)
+ }, orgAssignment(false, true))
+ })
+ }, reqAdmin())
+ }, context.APIContexter())
+}
diff --git a/routes/api/v1/convert/convert.go b/routes/api/v1/convert/convert.go
new file mode 100644
index 00000000..fcadb51f
--- /dev/null
+++ b/routes/api/v1/convert/convert.go
@@ -0,0 +1,127 @@
+// 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 convert
+
+import (
+ "fmt"
+
+ "github.com/Unknwon/com"
+
+ "github.com/gogits/git-module"
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+)
+
+func ToEmail(email *models.EmailAddress) *api.Email {
+ return &api.Email{
+ Email: email.Email,
+ Verified: email.IsActivated,
+ Primary: email.IsPrimary,
+ }
+}
+
+func ToBranch(b *models.Branch, c *git.Commit) *api.Branch {
+ return &api.Branch{
+ Name: b.Name,
+ Commit: ToCommit(c),
+ }
+}
+
+func ToCommit(c *git.Commit) *api.PayloadCommit {
+ authorUsername := ""
+ author, err := models.GetUserByEmail(c.Author.Email)
+ if err == nil {
+ authorUsername = author.Name
+ }
+ committerUsername := ""
+ committer, err := models.GetUserByEmail(c.Committer.Email)
+ if err == nil {
+ committerUsername = committer.Name
+ }
+ return &api.PayloadCommit{
+ ID: c.ID.String(),
+ Message: c.Message(),
+ URL: "Not implemented",
+ Author: &api.PayloadUser{
+ Name: c.Author.Name,
+ Email: c.Author.Email,
+ UserName: authorUsername,
+ },
+ Committer: &api.PayloadUser{
+ Name: c.Committer.Name,
+ Email: c.Committer.Email,
+ UserName: committerUsername,
+ },
+ Timestamp: c.Author.When,
+ }
+}
+
+func ToPublicKey(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 ToHook(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,
+ }
+}
+
+func ToDeployKey(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 ToOrganization(org *models.User) *api.Organization {
+ return &api.Organization{
+ ID: org.ID,
+ AvatarUrl: org.AvatarLink(),
+ UserName: org.Name,
+ FullName: org.FullName,
+ Description: org.Description,
+ Website: org.Website,
+ Location: org.Location,
+ }
+}
+
+func ToTeam(team *models.Team) *api.Team {
+ return &api.Team{
+ ID: team.ID,
+ Name: team.Name,
+ Description: team.Description,
+ Permission: team.Authorize.String(),
+ }
+}
diff --git a/routes/api/v1/convert/utils.go b/routes/api/v1/convert/utils.go
new file mode 100644
index 00000000..d0beab3d
--- /dev/null
+++ b/routes/api/v1/convert/utils.go
@@ -0,0 +1,19 @@
+// Copyright 2016 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 convert
+
+import (
+ "github.com/gogits/gogs/pkg/setting"
+)
+
+// ToCorrectPageSize makes sure page size is in allowed range.
+func ToCorrectPageSize(size int) int {
+ if size <= 0 {
+ size = 10
+ } else if size > setting.API.MaxResponseItems {
+ size = setting.API.MaxResponseItems
+ }
+ return size
+}
diff --git a/routes/api/v1/misc/markdown.go b/routes/api/v1/misc/markdown.go
new file mode 100644
index 00000000..98bfd7d0
--- /dev/null
+++ b/routes/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/pkg/context"
+ "github.com/gogits/gogs/pkg/markup"
+)
+
+// https://github.com/gogits/go-gogs-client/wiki/Miscellaneous#render-an-arbitrary-markdown-document
+func Markdown(c *context.APIContext, form api.MarkdownOption) {
+ if c.HasApiError() {
+ c.Error(422, "", c.GetErrMsg())
+ return
+ }
+
+ if len(form.Text) == 0 {
+ c.Write([]byte(""))
+ return
+ }
+
+ switch form.Mode {
+ case "gfm":
+ c.Write(markup.Markdown([]byte(form.Text), form.Context, nil))
+ default:
+ c.Write(markup.RawMarkdown([]byte(form.Text), ""))
+ }
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Miscellaneous#render-a-markdown-document-in-raw-mode
+func MarkdownRaw(c *context.APIContext) {
+ body, err := c.Req.Body().Bytes()
+ if err != nil {
+ c.Error(422, "", err)
+ return
+ }
+ c.Write(markup.RawMarkdown(body, ""))
+}
diff --git a/routes/api/v1/org/org.go b/routes/api/v1/org/org.go
new file mode 100644
index 00000000..2f8832ca
--- /dev/null
+++ b/routes/api/v1/org/org.go
@@ -0,0 +1,66 @@
+// 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 org
+
+import (
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/routes/api/v1/convert"
+ "github.com/gogits/gogs/routes/api/v1/user"
+)
+
+func listUserOrgs(c *context.APIContext, u *models.User, all bool) {
+ if err := u.GetOrganizations(all); err != nil {
+ c.Error(500, "GetOrganizations", err)
+ return
+ }
+
+ apiOrgs := make([]*api.Organization, len(u.Orgs))
+ for i := range u.Orgs {
+ apiOrgs[i] = convert.ToOrganization(u.Orgs[i])
+ }
+ c.JSON(200, &apiOrgs)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Organizations#list-your-organizations
+func ListMyOrgs(c *context.APIContext) {
+ listUserOrgs(c, c.User, true)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Organizations#list-user-organizations
+func ListUserOrgs(c *context.APIContext) {
+ u := user.GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+ listUserOrgs(c, u, false)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Organizations#get-an-organization
+func Get(c *context.APIContext) {
+ c.JSON(200, convert.ToOrganization(c.Org.Organization))
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Organizations#edit-an-organization
+func Edit(c *context.APIContext, form api.EditOrgOption) {
+ org := c.Org.Organization
+ if !org.IsOwnedBy(c.User.ID) {
+ c.Status(403)
+ return
+ }
+
+ org.FullName = form.FullName
+ org.Description = form.Description
+ org.Website = form.Website
+ org.Location = form.Location
+ if err := models.UpdateUser(org); err != nil {
+ c.Error(500, "UpdateUser", err)
+ return
+ }
+
+ c.JSON(200, convert.ToOrganization(org))
+}
diff --git a/routes/api/v1/org/team.go b/routes/api/v1/org/team.go
new file mode 100644
index 00000000..fd92e728
--- /dev/null
+++ b/routes/api/v1/org/team.go
@@ -0,0 +1,26 @@
+// Copyright 2016 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 org
+
+import (
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/routes/api/v1/convert"
+)
+
+func ListTeams(c *context.APIContext) {
+ org := c.Org.Organization
+ if err := org.GetTeams(); err != nil {
+ c.Error(500, "GetTeams", err)
+ return
+ }
+
+ apiTeams := make([]*api.Team, len(org.Teams))
+ for i := range org.Teams {
+ apiTeams[i] = convert.ToTeam(org.Teams[i])
+ }
+ c.JSON(200, apiTeams)
+}
diff --git a/routes/api/v1/repo/branch.go b/routes/api/v1/repo/branch.go
new file mode 100644
index 00000000..d8c2697b
--- /dev/null
+++ b/routes/api/v1/repo/branch.go
@@ -0,0 +1,55 @@
+// Copyright 2016 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 (
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/routes/api/v1/convert"
+)
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories#get-branch
+func GetBranch(c *context.APIContext) {
+ branch, err := c.Repo.Repository.GetBranch(c.Params("*"))
+ if err != nil {
+ if models.IsErrBranchNotExist(err) {
+ c.Error(404, "GetBranch", err)
+ } else {
+ c.Error(500, "GetBranch", err)
+ }
+ return
+ }
+
+ commit, err := branch.GetCommit()
+ if err != nil {
+ c.Error(500, "GetCommit", err)
+ return
+ }
+
+ c.JSON(200, convert.ToBranch(branch, commit))
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories#list-branches
+func ListBranches(c *context.APIContext) {
+ branches, err := c.Repo.Repository.GetBranches()
+ if err != nil {
+ c.Error(500, "GetBranches", err)
+ return
+ }
+
+ apiBranches := make([]*api.Branch, len(branches))
+ for i := range branches {
+ commit, err := branches[i].GetCommit()
+ if err != nil {
+ c.Error(500, "GetCommit", err)
+ return
+ }
+ apiBranches[i] = convert.ToBranch(branches[i], commit)
+ }
+
+ c.JSON(200, &apiBranches)
+}
diff --git a/routes/api/v1/repo/collaborators.go b/routes/api/v1/repo/collaborators.go
new file mode 100644
index 00000000..d295ac0f
--- /dev/null
+++ b/routes/api/v1/repo/collaborators.go
@@ -0,0 +1,94 @@
+// Copyright 2016 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 (
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/models/errors"
+ "github.com/gogits/gogs/pkg/context"
+)
+
+func ListCollaborators(c *context.APIContext) {
+ collaborators, err := c.Repo.Repository.GetCollaborators()
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetCollaborators", err)
+ }
+ return
+ }
+
+ apiCollaborators := make([]*api.Collaborator, len(collaborators))
+ for i := range collaborators {
+ apiCollaborators[i] = collaborators[i].APIFormat()
+ }
+ c.JSON(200, &apiCollaborators)
+}
+
+func AddCollaborator(c *context.APIContext, form api.AddCollaboratorOption) {
+ collaborator, err := models.GetUserByName(c.Params(":collaborator"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+
+ if err := c.Repo.Repository.AddCollaborator(collaborator); err != nil {
+ c.Error(500, "AddCollaborator", err)
+ return
+ }
+
+ if form.Permission != nil {
+ if err := c.Repo.Repository.ChangeCollaborationAccessMode(collaborator.ID, models.ParseAccessMode(*form.Permission)); err != nil {
+ c.Error(500, "ChangeCollaborationAccessMode", err)
+ return
+ }
+ }
+
+ c.Status(204)
+}
+
+func IsCollaborator(c *context.APIContext) {
+ collaborator, err := models.GetUserByName(c.Params(":collaborator"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+
+ if !c.Repo.Repository.IsCollaborator(collaborator.ID) {
+ c.Status(404)
+ } else {
+ c.Status(204)
+ }
+}
+
+func DeleteCollaborator(c *context.APIContext) {
+ collaborator, err := models.GetUserByName(c.Params(":collaborator"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+
+ if err := c.Repo.Repository.DeleteCollaboration(collaborator.ID); err != nil {
+ c.Error(500, "DeleteCollaboration", err)
+ return
+ }
+
+ c.Status(204)
+}
diff --git a/routes/api/v1/repo/file.go b/routes/api/v1/repo/file.go
new file mode 100644
index 00000000..c783e81f
--- /dev/null
+++ b/routes/api/v1/repo/file.go
@@ -0,0 +1,72 @@
+// 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/git-module"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/routes/repo"
+)
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories-Contents#download-raw-content
+func GetRawFile(c *context.APIContext) {
+ if !c.Repo.HasAccess() {
+ c.Status(404)
+ return
+ }
+
+ if c.Repo.Repository.IsBare {
+ c.Status(404)
+ return
+ }
+
+ blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath)
+ if err != nil {
+ if git.IsErrNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetBlobByPath", err)
+ }
+ return
+ }
+ if err = repo.ServeBlob(c.Context, blob); err != nil {
+ c.Error(500, "ServeBlob", err)
+ }
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories-Contents#download-archive
+func GetArchive(c *context.APIContext) {
+ repoPath := models.RepoPath(c.Params(":username"), c.Params(":reponame"))
+ gitRepo, err := git.OpenRepository(repoPath)
+ if err != nil {
+ c.Error(500, "OpenRepository", err)
+ return
+ }
+ c.Repo.GitRepo = gitRepo
+
+ repo.Download(c.Context)
+}
+
+func GetEditorconfig(c *context.APIContext) {
+ ec, err := c.Repo.GetEditorconfig()
+ if err != nil {
+ if git.IsErrNotExist(err) {
+ c.Error(404, "GetEditorconfig", err)
+ } else {
+ c.Error(500, "GetEditorconfig", err)
+ }
+ return
+ }
+
+ fileName := c.Params("filename")
+ def := ec.GetDefinitionForFilename(fileName)
+ if def == nil {
+ c.Error(404, "GetDefinitionForFilename", err)
+ return
+ }
+ c.JSON(200, def)
+}
diff --git a/routes/api/v1/repo/hook.go b/routes/api/v1/repo/hook.go
new file mode 100644
index 00000000..66125c50
--- /dev/null
+++ b/routes/api/v1/repo/hook.go
@@ -0,0 +1,186 @@
+// 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/models/errors"
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/routes/api/v1/convert"
+)
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories#list-hooks
+func ListHooks(c *context.APIContext) {
+ hooks, err := models.GetWebhooksByRepoID(c.Repo.Repository.ID)
+ if err != nil {
+ c.Error(500, "GetWebhooksByRepoID", err)
+ return
+ }
+
+ apiHooks := make([]*api.Hook, len(hooks))
+ for i := range hooks {
+ apiHooks[i] = convert.ToHook(c.Repo.RepoLink, hooks[i])
+ }
+ c.JSON(200, &apiHooks)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories#create-a-hook
+func CreateHook(c *context.APIContext, form api.CreateHookOption) {
+ if !models.IsValidHookTaskType(form.Type) {
+ c.Error(422, "", "Invalid hook type")
+ return
+ }
+ for _, name := range []string{"url", "content_type"} {
+ if _, ok := form.Config[name]; !ok {
+ c.Error(422, "", "Missing config option: "+name)
+ return
+ }
+ }
+ if !models.IsValidHookContentType(form.Config["content_type"]) {
+ c.Error(422, "", "Invalid content type")
+ return
+ }
+
+ if len(form.Events) == 0 {
+ form.Events = []string{"push"}
+ }
+ w := &models.Webhook{
+ RepoID: c.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)),
+ Delete: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_DELETE)),
+ Fork: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_FORK)),
+ Push: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH)),
+ Issues: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUES)),
+ IssueComment: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUE_COMMENT)),
+ PullRequest: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PULL_REQUEST)),
+ Release: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_RELEASE)),
+ },
+ },
+ IsActive: form.Active,
+ HookTaskType: models.ToHookTaskType(form.Type),
+ }
+ if w.HookTaskType == models.SLACK {
+ channel, ok := form.Config["channel"]
+ if !ok {
+ c.Error(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 {
+ c.Error(500, "slack: JSON marshal failed", err)
+ return
+ }
+ w.Meta = string(meta)
+ }
+
+ if err := w.UpdateEvent(); err != nil {
+ c.Error(500, "UpdateEvent", err)
+ return
+ } else if err := models.CreateWebhook(w); err != nil {
+ c.Error(500, "CreateWebhook", err)
+ return
+ }
+
+ c.JSON(201, convert.ToHook(c.Repo.RepoLink, w))
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories#edit-a-hook
+func EditHook(c *context.APIContext, form api.EditHookOption) {
+ w, err := models.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
+ if err != nil {
+ if errors.IsWebhookNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetWebhookOfRepoByID", 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) {
+ c.Error(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 {
+ c.Error(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.Delete = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_DELETE))
+ w.Fork = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_FORK))
+ w.Push = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH))
+ w.Issues = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUES))
+ w.IssueComment = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_ISSUE_COMMENT))
+ w.PullRequest = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PULL_REQUEST))
+ w.Release = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_RELEASE))
+ if err = w.UpdateEvent(); err != nil {
+ c.Error(500, "UpdateEvent", err)
+ return
+ }
+
+ if form.Active != nil {
+ w.IsActive = *form.Active
+ }
+
+ if err := models.UpdateWebhook(w); err != nil {
+ c.Error(500, "UpdateWebhook", err)
+ return
+ }
+
+ c.JSON(200, convert.ToHook(c.Repo.RepoLink, w))
+}
+
+func DeleteHook(c *context.APIContext) {
+ if err := models.DeleteWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
+ c.Error(500, "DeleteWebhookByRepoID", err)
+ return
+ }
+
+ c.Status(204)
+}
diff --git a/routes/api/v1/repo/issue.go b/routes/api/v1/repo/issue.go
new file mode 100644
index 00000000..d6ae7b4d
--- /dev/null
+++ b/routes/api/v1/repo/issue.go
@@ -0,0 +1,201 @@
+// Copyright 2016 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"
+ "strings"
+
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/models/errors"
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/pkg/setting"
+)
+
+func listIssues(c *context.APIContext, opts *models.IssuesOptions) {
+ issues, err := models.Issues(opts)
+ if err != nil {
+ c.Error(500, "Issues", err)
+ return
+ }
+
+ count, err := models.IssuesCount(opts)
+ if err != nil {
+ c.Error(500, "IssuesCount", err)
+ return
+ }
+
+ // FIXME: use IssueList to improve performance.
+ apiIssues := make([]*api.Issue, len(issues))
+ for i := range issues {
+ if err = issues[i].LoadAttributes(); err != nil {
+ c.Error(500, "LoadAttributes", err)
+ return
+ }
+ apiIssues[i] = issues[i].APIFormat()
+ }
+
+ c.SetLinkHeader(int(count), setting.UI.IssuePagingNum)
+ c.JSON(200, &apiIssues)
+}
+
+func ListUserIssues(c *context.APIContext) {
+ opts := models.IssuesOptions{
+ AssigneeID: c.User.ID,
+ Page: c.QueryInt("page"),
+ IsClosed: api.StateType(c.Query("state")) == api.STATE_CLOSED,
+ }
+
+ listIssues(c, &opts)
+}
+
+func ListIssues(c *context.APIContext) {
+ opts := models.IssuesOptions{
+ RepoID: c.Repo.Repository.ID,
+ Page: c.QueryInt("page"),
+ IsClosed: api.StateType(c.Query("state")) == api.STATE_CLOSED,
+ }
+
+ listIssues(c, &opts)
+}
+
+func GetIssue(c *context.APIContext) {
+ issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ if errors.IsIssueNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetIssueByIndex", err)
+ }
+ return
+ }
+ c.JSON(200, issue.APIFormat())
+}
+
+func CreateIssue(c *context.APIContext, form api.CreateIssueOption) {
+ issue := &models.Issue{
+ RepoID: c.Repo.Repository.ID,
+ Title: form.Title,
+ PosterID: c.User.ID,
+ Poster: c.User,
+ Content: form.Body,
+ }
+
+ if c.Repo.IsWriter() {
+ if len(form.Assignee) > 0 {
+ assignee, err := models.GetUserByName(form.Assignee)
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", form.Assignee))
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+ issue.AssigneeID = assignee.ID
+ }
+ issue.MilestoneID = form.Milestone
+ } else {
+ form.Labels = nil
+ }
+
+ if err := models.NewIssue(c.Repo.Repository, issue, form.Labels, nil); err != nil {
+ c.Error(500, "NewIssue", err)
+ return
+ }
+
+ if form.Closed {
+ if err := issue.ChangeStatus(c.User, c.Repo.Repository, true); err != nil {
+ c.Error(500, "ChangeStatus", err)
+ return
+ }
+ }
+
+ // Refetch from database to assign some automatic values
+ var err error
+ issue, err = models.GetIssueByID(issue.ID)
+ if err != nil {
+ c.Error(500, "GetIssueByID", err)
+ return
+ }
+ c.JSON(201, issue.APIFormat())
+}
+
+func EditIssue(c *context.APIContext, form api.EditIssueOption) {
+ issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ if errors.IsIssueNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetIssueByIndex", err)
+ }
+ return
+ }
+
+ if !issue.IsPoster(c.User.ID) && !c.Repo.IsWriter() {
+ c.Status(403)
+ return
+ }
+
+ if len(form.Title) > 0 {
+ issue.Title = form.Title
+ }
+ if form.Body != nil {
+ issue.Content = *form.Body
+ }
+
+ if c.Repo.IsWriter() && form.Assignee != nil &&
+ (issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(*form.Assignee)) {
+ if len(*form.Assignee) == 0 {
+ issue.AssigneeID = 0
+ } else {
+ assignee, err := models.GetUserByName(*form.Assignee)
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", *form.Assignee))
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+ issue.AssigneeID = assignee.ID
+ }
+
+ if err = models.UpdateIssueUserByAssignee(issue); err != nil {
+ c.Error(500, "UpdateIssueUserByAssignee", err)
+ return
+ }
+ }
+ if c.Repo.IsWriter() && form.Milestone != nil &&
+ issue.MilestoneID != *form.Milestone {
+ oldMilestoneID := issue.MilestoneID
+ issue.MilestoneID = *form.Milestone
+ if err = models.ChangeMilestoneAssign(c.User, issue, oldMilestoneID); err != nil {
+ c.Error(500, "ChangeMilestoneAssign", err)
+ return
+ }
+ }
+
+ if err = models.UpdateIssue(issue); err != nil {
+ c.Error(500, "UpdateIssue", err)
+ return
+ }
+ if form.State != nil {
+ if err = issue.ChangeStatus(c.User, c.Repo.Repository, api.STATE_CLOSED == api.StateType(*form.State)); err != nil {
+ c.Error(500, "ChangeStatus", err)
+ return
+ }
+ }
+
+ // Refetch from database to assign some automatic values
+ issue, err = models.GetIssueByID(issue.ID)
+ if err != nil {
+ c.Error(500, "GetIssueByID", err)
+ return
+ }
+ c.JSON(201, issue.APIFormat())
+}
diff --git a/routes/api/v1/repo/issue_comment.go b/routes/api/v1/repo/issue_comment.go
new file mode 100644
index 00000000..4a057d76
--- /dev/null
+++ b/routes/api/v1/repo/issue_comment.go
@@ -0,0 +1,128 @@
+// 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 (
+ "time"
+
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/pkg/context"
+)
+
+func ListIssueComments(c *context.APIContext) {
+ var since time.Time
+ if len(c.Query("since")) > 0 {
+ since, _ = time.Parse(time.RFC3339, c.Query("since"))
+ }
+
+ // comments,err:=models.GetCommentsByIssueIDSince(, since)
+ issue, err := models.GetRawIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ c.Error(500, "GetRawIssueByIndex", err)
+ return
+ }
+
+ comments, err := models.GetCommentsByIssueIDSince(issue.ID, since.Unix())
+ if err != nil {
+ c.Error(500, "GetCommentsByIssueIDSince", err)
+ return
+ }
+
+ apiComments := make([]*api.Comment, len(comments))
+ for i := range comments {
+ apiComments[i] = comments[i].APIFormat()
+ }
+ c.JSON(200, &apiComments)
+}
+
+func ListRepoIssueComments(c *context.APIContext) {
+ var since time.Time
+ if len(c.Query("since")) > 0 {
+ since, _ = time.Parse(time.RFC3339, c.Query("since"))
+ }
+
+ comments, err := models.GetCommentsByRepoIDSince(c.Repo.Repository.ID, since.Unix())
+ if err != nil {
+ c.Error(500, "GetCommentsByRepoIDSince", err)
+ return
+ }
+
+ apiComments := make([]*api.Comment, len(comments))
+ for i := range comments {
+ apiComments[i] = comments[i].APIFormat()
+ }
+ c.JSON(200, &apiComments)
+}
+
+func CreateIssueComment(c *context.APIContext, form api.CreateIssueCommentOption) {
+ issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ c.Error(500, "GetIssueByIndex", err)
+ return
+ }
+
+ comment, err := models.CreateIssueComment(c.User, c.Repo.Repository, issue, form.Body, nil)
+ if err != nil {
+ c.Error(500, "CreateIssueComment", err)
+ return
+ }
+
+ c.JSON(201, comment.APIFormat())
+}
+
+func EditIssueComment(c *context.APIContext, form api.EditIssueCommentOption) {
+ comment, err := models.GetCommentByID(c.ParamsInt64(":id"))
+ if err != nil {
+ if models.IsErrCommentNotExist(err) {
+ c.Error(404, "GetCommentByID", err)
+ } else {
+ c.Error(500, "GetCommentByID", err)
+ }
+ return
+ }
+
+ if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() {
+ c.Status(403)
+ return
+ } else if comment.Type != models.COMMENT_TYPE_COMMENT {
+ c.Status(204)
+ return
+ }
+
+ oldContent := comment.Content
+ comment.Content = form.Body
+ if err := models.UpdateComment(c.User, comment, oldContent); err != nil {
+ c.Error(500, "UpdateComment", err)
+ return
+ }
+ c.JSON(200, comment.APIFormat())
+}
+
+func DeleteIssueComment(c *context.APIContext) {
+ comment, err := models.GetCommentByID(c.ParamsInt64(":id"))
+ if err != nil {
+ if models.IsErrCommentNotExist(err) {
+ c.Error(404, "GetCommentByID", err)
+ } else {
+ c.Error(500, "GetCommentByID", err)
+ }
+ return
+ }
+
+ if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() {
+ c.Status(403)
+ return
+ } else if comment.Type != models.COMMENT_TYPE_COMMENT {
+ c.Status(204)
+ return
+ }
+
+ if err = models.DeleteCommentByID(c.User, comment.ID); err != nil {
+ c.Error(500, "DeleteCommentByID", err)
+ return
+ }
+ c.Status(204)
+}
diff --git a/routes/api/v1/repo/issue_label.go b/routes/api/v1/repo/issue_label.go
new file mode 100644
index 00000000..f3f2d730
--- /dev/null
+++ b/routes/api/v1/repo/issue_label.go
@@ -0,0 +1,169 @@
+// Copyright 2016 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 (
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/models/errors"
+ "github.com/gogits/gogs/pkg/context"
+)
+
+func ListIssueLabels(c *context.APIContext) {
+ issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ if errors.IsIssueNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetIssueByIndex", err)
+ }
+ return
+ }
+
+ apiLabels := make([]*api.Label, len(issue.Labels))
+ for i := range issue.Labels {
+ apiLabels[i] = issue.Labels[i].APIFormat()
+ }
+ c.JSON(200, &apiLabels)
+}
+
+func AddIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
+ if !c.Repo.IsWriter() {
+ c.Status(403)
+ return
+ }
+
+ issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ if errors.IsIssueNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetIssueByIndex", err)
+ }
+ return
+ }
+
+ labels, err := models.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels)
+ if err != nil {
+ c.Error(500, "GetLabelsInRepoByIDs", err)
+ return
+ }
+
+ if err = issue.AddLabels(c.User, labels); err != nil {
+ c.Error(500, "AddLabels", err)
+ return
+ }
+
+ labels, err = models.GetLabelsByIssueID(issue.ID)
+ if err != nil {
+ c.Error(500, "GetLabelsByIssueID", err)
+ return
+ }
+
+ apiLabels := make([]*api.Label, len(labels))
+ for i := range labels {
+ apiLabels[i] = issue.Labels[i].APIFormat()
+ }
+ c.JSON(200, &apiLabels)
+}
+
+func DeleteIssueLabel(c *context.APIContext) {
+ if !c.Repo.IsWriter() {
+ c.Status(403)
+ return
+ }
+
+ issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ if errors.IsIssueNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetIssueByIndex", err)
+ }
+ return
+ }
+
+ label, err := models.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
+ if err != nil {
+ if models.IsErrLabelNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetLabelInRepoByID", err)
+ }
+ return
+ }
+
+ if err := models.DeleteIssueLabel(issue, label); err != nil {
+ c.Error(500, "DeleteIssueLabel", err)
+ return
+ }
+
+ c.Status(204)
+}
+
+func ReplaceIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
+ if !c.Repo.IsWriter() {
+ c.Status(403)
+ return
+ }
+
+ issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ if errors.IsIssueNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetIssueByIndex", err)
+ }
+ return
+ }
+
+ labels, err := models.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels)
+ if err != nil {
+ c.Error(500, "GetLabelsInRepoByIDs", err)
+ return
+ }
+
+ if err := issue.ReplaceLabels(labels); err != nil {
+ c.Error(500, "ReplaceLabels", err)
+ return
+ }
+
+ labels, err = models.GetLabelsByIssueID(issue.ID)
+ if err != nil {
+ c.Error(500, "GetLabelsByIssueID", err)
+ return
+ }
+
+ apiLabels := make([]*api.Label, len(labels))
+ for i := range labels {
+ apiLabels[i] = issue.Labels[i].APIFormat()
+ }
+ c.JSON(200, &apiLabels)
+}
+
+func ClearIssueLabels(c *context.APIContext) {
+ if !c.Repo.IsWriter() {
+ c.Status(403)
+ return
+ }
+
+ issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ if errors.IsIssueNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetIssueByIndex", err)
+ }
+ return
+ }
+
+ if err := issue.ClearLabels(c.User); err != nil {
+ c.Error(500, "ClearLabels", err)
+ return
+ }
+
+ c.Status(204)
+}
diff --git a/routes/api/v1/repo/key.go b/routes/api/v1/repo/key.go
new file mode 100644
index 00000000..405440d2
--- /dev/null
+++ b/routes/api/v1/repo/key.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/pkg/context"
+ "github.com/gogits/gogs/pkg/setting"
+ "github.com/gogits/gogs/routes/api/v1/convert"
+)
+
+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(c *context.APIContext) {
+ keys, err := models.ListDeployKeys(c.Repo.Repository.ID)
+ if err != nil {
+ c.Error(500, "ListDeployKeys", err)
+ return
+ }
+
+ apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
+ apiKeys := make([]*api.DeployKey, len(keys))
+ for i := range keys {
+ if err = keys[i].GetContent(); err != nil {
+ c.Error(500, "GetContent", err)
+ return
+ }
+ apiKeys[i] = convert.ToDeployKey(apiLink, keys[i])
+ }
+
+ c.JSON(200, &apiKeys)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#get-a-deploy-key
+func GetDeployKey(c *context.APIContext) {
+ key, err := models.GetDeployKeyByID(c.ParamsInt64(":id"))
+ if err != nil {
+ if models.IsErrDeployKeyNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetDeployKeyByID", err)
+ }
+ return
+ }
+
+ if err = key.GetContent(); err != nil {
+ c.Error(500, "GetContent", err)
+ return
+ }
+
+ apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
+ c.JSON(200, convert.ToDeployKey(apiLink, key))
+}
+
+func HandleCheckKeyStringError(c *context.APIContext, err error) {
+ if models.IsErrKeyUnableVerify(err) {
+ c.Error(422, "", "Unable to verify key content")
+ } else {
+ c.Error(422, "", fmt.Errorf("Invalid key content: %v", err))
+ }
+}
+
+func HandleAddKeyError(c *context.APIContext, err error) {
+ switch {
+ case models.IsErrKeyAlreadyExist(err):
+ c.Error(422, "", "Key content has been used as non-deploy key")
+ case models.IsErrKeyNameAlreadyUsed(err):
+ c.Error(422, "", "Key title has been used")
+ default:
+ c.Error(500, "AddKey", err)
+ }
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#add-a-new-deploy-key
+func CreateDeployKey(c *context.APIContext, form api.CreateKeyOption) {
+ content, err := models.CheckPublicKeyString(form.Key)
+ if err != nil {
+ HandleCheckKeyStringError(c, err)
+ return
+ }
+
+ key, err := models.AddDeployKey(c.Repo.Repository.ID, form.Title, content)
+ if err != nil {
+ HandleAddKeyError(c, err)
+ return
+ }
+
+ key.Content = content
+ apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
+ c.JSON(201, convert.ToDeployKey(apiLink, key))
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#remove-a-deploy-key
+func DeleteDeploykey(c *context.APIContext) {
+ if err := models.DeleteDeployKey(c.User, c.ParamsInt64(":id")); err != nil {
+ if models.IsErrKeyAccessDenied(err) {
+ c.Error(403, "", "You do not have access to this key")
+ } else {
+ c.Error(500, "DeleteDeployKey", err)
+ }
+ return
+ }
+
+ c.Status(204)
+}
diff --git a/routes/api/v1/repo/label.go b/routes/api/v1/repo/label.go
new file mode 100644
index 00000000..1161d633
--- /dev/null
+++ b/routes/api/v1/repo/label.go
@@ -0,0 +1,110 @@
+// Copyright 2016 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/Unknwon/com"
+
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/pkg/context"
+)
+
+func ListLabels(c *context.APIContext) {
+ labels, err := models.GetLabelsByRepoID(c.Repo.Repository.ID)
+ if err != nil {
+ c.Error(500, "GetLabelsByRepoID", err)
+ return
+ }
+
+ apiLabels := make([]*api.Label, len(labels))
+ for i := range labels {
+ apiLabels[i] = labels[i].APIFormat()
+ }
+ c.JSON(200, &apiLabels)
+}
+
+func GetLabel(c *context.APIContext) {
+ var label *models.Label
+ var err error
+ idStr := c.Params(":id")
+ if id := com.StrTo(idStr).MustInt64(); id > 0 {
+ label, err = models.GetLabelOfRepoByID(c.Repo.Repository.ID, id)
+ } else {
+ label, err = models.GetLabelOfRepoByName(c.Repo.Repository.ID, idStr)
+ }
+ if err != nil {
+ if models.IsErrLabelNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetLabelByRepoID", err)
+ }
+ return
+ }
+
+ c.JSON(200, label.APIFormat())
+}
+
+func CreateLabel(c *context.APIContext, form api.CreateLabelOption) {
+ if !c.Repo.IsWriter() {
+ c.Status(403)
+ return
+ }
+
+ label := &models.Label{
+ Name: form.Name,
+ Color: form.Color,
+ RepoID: c.Repo.Repository.ID,
+ }
+ if err := models.NewLabels(label); err != nil {
+ c.Error(500, "NewLabel", err)
+ return
+ }
+ c.JSON(201, label.APIFormat())
+}
+
+func EditLabel(c *context.APIContext, form api.EditLabelOption) {
+ if !c.Repo.IsWriter() {
+ c.Status(403)
+ return
+ }
+
+ label, err := models.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
+ if err != nil {
+ if models.IsErrLabelNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetLabelByRepoID", err)
+ }
+ return
+ }
+
+ if form.Name != nil {
+ label.Name = *form.Name
+ }
+ if form.Color != nil {
+ label.Color = *form.Color
+ }
+ if err := models.UpdateLabel(label); err != nil {
+ c.Handle(500, "UpdateLabel", err)
+ return
+ }
+ c.JSON(200, label.APIFormat())
+}
+
+func DeleteLabel(c *context.APIContext) {
+ if !c.Repo.IsWriter() {
+ c.Status(403)
+ return
+ }
+
+ if err := models.DeleteLabel(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
+ c.Error(500, "DeleteLabel", err)
+ return
+ }
+
+ c.Status(204)
+}
diff --git a/routes/api/v1/repo/milestone.go b/routes/api/v1/repo/milestone.go
new file mode 100644
index 00000000..baf8eb2f
--- /dev/null
+++ b/routes/api/v1/repo/milestone.go
@@ -0,0 +1,103 @@
+// Copyright 2016 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 (
+ "time"
+
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/pkg/context"
+)
+
+func ListMilestones(c *context.APIContext) {
+ milestones, err := models.GetMilestonesByRepoID(c.Repo.Repository.ID)
+ if err != nil {
+ c.Error(500, "GetMilestonesByRepoID", err)
+ return
+ }
+
+ apiMilestones := make([]*api.Milestone, len(milestones))
+ for i := range milestones {
+ apiMilestones[i] = milestones[i].APIFormat()
+ }
+ c.JSON(200, &apiMilestones)
+}
+
+func GetMilestone(c *context.APIContext) {
+ milestone, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
+ if err != nil {
+ if models.IsErrMilestoneNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetMilestoneByRepoID", err)
+ }
+ return
+ }
+ c.JSON(200, milestone.APIFormat())
+}
+
+func CreateMilestone(c *context.APIContext, form api.CreateMilestoneOption) {
+ if form.Deadline == nil {
+ defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local)
+ form.Deadline = &defaultDeadline
+ }
+
+ milestone := &models.Milestone{
+ RepoID: c.Repo.Repository.ID,
+ Name: form.Title,
+ Content: form.Description,
+ Deadline: *form.Deadline,
+ }
+
+ if err := models.NewMilestone(milestone); err != nil {
+ c.Error(500, "NewMilestone", err)
+ return
+ }
+ c.JSON(201, milestone.APIFormat())
+}
+
+func EditMilestone(c *context.APIContext, form api.EditMilestoneOption) {
+ milestone, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
+ if err != nil {
+ if models.IsErrMilestoneNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetMilestoneByRepoID", err)
+ }
+ return
+ }
+
+ if len(form.Title) > 0 {
+ milestone.Name = form.Title
+ }
+ if form.Description != nil {
+ milestone.Content = *form.Description
+ }
+ if form.Deadline != nil && !form.Deadline.IsZero() {
+ milestone.Deadline = *form.Deadline
+ }
+
+ if form.State != nil {
+ if err = milestone.ChangeStatus(api.STATE_CLOSED == api.StateType(*form.State)); err != nil {
+ c.Error(500, "ChangeStatus", err)
+ return
+ }
+ } else if err = models.UpdateMilestone(milestone); err != nil {
+ c.Handle(500, "UpdateMilestone", err)
+ return
+ }
+
+ c.JSON(200, milestone.APIFormat())
+}
+
+func DeleteMilestone(c *context.APIContext) {
+ if err := models.DeleteMilestoneOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
+ c.Error(500, "DeleteMilestoneByRepoID", err)
+ return
+ }
+ c.Status(204)
+}
diff --git a/routes/api/v1/repo/repo.go b/routes/api/v1/repo/repo.go
new file mode 100644
index 00000000..8410dcca
--- /dev/null
+++ b/routes/api/v1/repo/repo.go
@@ -0,0 +1,380 @@
+// 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"
+
+ log "gopkg.in/clog.v1"
+
+ api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/models/errors"
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/pkg/form"
+ "github.com/gogits/gogs/pkg/setting"
+ "github.com/gogits/gogs/routes/api/v1/convert"
+)
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories#search-repositories
+func Search(c *context.APIContext) {
+ opts := &models.SearchRepoOptions{
+ Keyword: path.Base(c.Query("q")),
+ OwnerID: c.QueryInt64("uid"),
+ PageSize: convert.ToCorrectPageSize(c.QueryInt("limit")),
+ }
+
+ // Check visibility.
+ if c.IsLogged && opts.OwnerID > 0 {
+ if c.User.ID == opts.OwnerID {
+ opts.Private = true
+ } else {
+ u, err := models.GetUserByID(opts.OwnerID)
+ if err != nil {
+ c.JSON(500, map[string]interface{}{
+ "ok": false,
+ "error": err.Error(),
+ })
+ return
+ }
+ if u.IsOrganization() && u.IsOwnedBy(c.User.ID) {
+ opts.Private = true
+ }
+ // FIXME: how about collaborators?
+ }
+ }
+
+ repos, count, err := models.SearchRepositoryByName(opts)
+ if err != nil {
+ c.JSON(500, map[string]interface{}{
+ "ok": false,
+ "error": err.Error(),
+ })
+ return
+ }
+
+ if err = models.RepositoryList(repos).LoadAttributes(); err != nil {
+ c.JSON(500, map[string]interface{}{
+ "ok": false,
+ "error": err.Error(),
+ })
+ return
+ }
+
+ results := make([]*api.Repository, len(repos))
+ for i := range repos {
+ results[i] = repos[i].APIFormat(nil)
+ }
+
+ c.SetLinkHeader(int(count), setting.API.MaxResponseItems)
+ c.JSON(200, map[string]interface{}{
+ "ok": true,
+ "data": results,
+ })
+}
+
+func listUserRepositories(c *context.APIContext, username string) {
+ user, err := models.GetUserByName(username)
+ if err != nil {
+ c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
+ return
+ }
+
+ // Only list public repositories if user requests someone else's repository list,
+ // or an organization isn't a member of.
+ var ownRepos []*models.Repository
+ if user.IsOrganization() {
+ ownRepos, _, err = user.GetUserRepositories(c.User.ID, 1, user.NumRepos)
+ } else {
+ ownRepos, err = models.GetUserRepositories(&models.UserRepoOptions{
+ UserID: user.ID,
+ Private: c.User.ID == user.ID,
+ Page: 1,
+ PageSize: user.NumRepos,
+ })
+ }
+ if err != nil {
+ c.Error(500, "GetUserRepositories", err)
+ return
+ }
+
+ if c.User.ID != user.ID {
+ repos := make([]*api.Repository, len(ownRepos))
+ for i := range ownRepos {
+ repos[i] = ownRepos[i].APIFormat(&api.Permission{true, true, true})
+ }
+ c.JSON(200, &repos)
+ return
+ }
+
+ accessibleRepos, err := user.GetRepositoryAccesses()
+ if err != nil {
+ c.Error(500, "GetRepositoryAccesses", err)
+ return
+ }
+
+ numOwnRepos := len(ownRepos)
+ repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos))
+ for i := range ownRepos {
+ repos[i] = ownRepos[i].APIFormat(&api.Permission{true, true, true})
+ }
+
+ i := numOwnRepos
+ for repo, access := range accessibleRepos {
+ repos[i] = repo.APIFormat(&api.Permission{
+ Admin: access >= models.ACCESS_MODE_ADMIN,
+ Push: access >= models.ACCESS_MODE_WRITE,
+ Pull: true,
+ })
+ i++
+ }
+
+ c.JSON(200, &repos)
+}
+
+func ListMyRepos(c *context.APIContext) {
+ listUserRepositories(c, c.User.Name)
+}
+
+func ListUserRepositories(c *context.APIContext) {
+ listUserRepositories(c, c.Params(":username"))
+}
+
+func ListOrgRepositories(c *context.APIContext) {
+ listUserRepositories(c, c.Params(":org"))
+}
+
+func CreateUserRepo(c *context.APIContext, owner *models.User, opt api.CreateRepoOption) {
+ repo, err := models.CreateRepository(c.User, 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) {
+ c.Error(422, "", err)
+ } else {
+ if repo != nil {
+ if err = models.DeleteRepository(c.User.ID, repo.ID); err != nil {
+ log.Error(2, "DeleteRepository: %v", err)
+ }
+ }
+ c.Error(500, "CreateRepository", err)
+ }
+ return
+ }
+
+ c.JSON(201, repo.APIFormat(&api.Permission{true, true, true}))
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories#create
+func Create(c *context.APIContext, opt api.CreateRepoOption) {
+ // Shouldn't reach this condition, but just in case.
+ if c.User.IsOrganization() {
+ c.Error(422, "", "not allowed creating repository for organization")
+ return
+ }
+ CreateUserRepo(c, c.User, opt)
+}
+
+func CreateOrgRepo(c *context.APIContext, opt api.CreateRepoOption) {
+ org, err := models.GetOrgByName(c.Params(":org"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetOrgByName", err)
+ }
+ return
+ }
+
+ if !org.IsOwnedBy(c.User.ID) {
+ c.Error(403, "", "Given user is not owner of organization.")
+ return
+ }
+ CreateUserRepo(c, org, opt)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories#migrate
+func Migrate(c *context.APIContext, f form.MigrateRepo) {
+ ctxUser := c.User
+ // Not equal means context user is an organization,
+ // or is another user/organization if current user is admin.
+ if f.Uid != ctxUser.ID {
+ org, err := models.GetUserByID(f.Uid)
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetUserByID", err)
+ }
+ return
+ } else if !org.IsOrganization() && !c.User.IsAdmin {
+ c.Error(403, "", "Given user is not an organization")
+ return
+ }
+ ctxUser = org
+ }
+
+ if c.HasError() {
+ c.Error(422, "", c.GetErrMsg())
+ return
+ }
+
+ if ctxUser.IsOrganization() && !c.User.IsAdmin {
+ // Check ownership of organization.
+ if !ctxUser.IsOwnedBy(c.User.ID) {
+ c.Error(403, "", "Given user is not owner of organization")
+ return
+ }
+ }
+
+ remoteAddr, err := f.ParseRemoteAddr(c.User)
+ if err != nil {
+ if models.IsErrInvalidCloneAddr(err) {
+ addrErr := err.(models.ErrInvalidCloneAddr)
+ switch {
+ case addrErr.IsURLError:
+ c.Error(422, "", err)
+ case addrErr.IsPermissionDenied:
+ c.Error(422, "", "You are not allowed to import local repositories")
+ case addrErr.IsInvalidPath:
+ c.Error(422, "", "Invalid local path, it does not exist or not a directory")
+ default:
+ c.Error(500, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
+ }
+ } else {
+ c.Error(500, "ParseRemoteAddr", err)
+ }
+ return
+ }
+
+ repo, err := models.MigrateRepository(c.User, ctxUser, models.MigrateRepoOptions{
+ Name: f.RepoName,
+ Description: f.Description,
+ IsPrivate: f.Private || setting.Repository.ForcePrivate,
+ IsMirror: f.Mirror,
+ RemoteAddr: remoteAddr,
+ })
+ if err != nil {
+ if repo != nil {
+ if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil {
+ log.Error(2, "DeleteRepository: %v", errDelete)
+ }
+ }
+
+ if errors.IsReachLimitOfRepo(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "MigrateRepository", models.HandleMirrorCredentials(err.Error(), true))
+ }
+ return
+ }
+
+ log.Trace("Repository migrated: %s/%s", ctxUser.Name, f.RepoName)
+ c.JSON(201, repo.APIFormat(&api.Permission{true, true, true}))
+}
+
+func parseOwnerAndRepo(c *context.APIContext) (*models.User, *models.Repository) {
+ owner, err := models.GetUserByName(c.Params(":username"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return nil, nil
+ }
+
+ repo, err := models.GetRepositoryByName(owner.ID, c.Params(":reponame"))
+ if err != nil {
+ if errors.IsRepoNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetRepositoryByName", err)
+ }
+ return nil, nil
+ }
+
+ return owner, repo
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories#get
+func Get(c *context.APIContext) {
+ _, repo := parseOwnerAndRepo(c)
+ if c.Written() {
+ return
+ }
+
+ c.JSON(200, repo.APIFormat(&api.Permission{
+ Admin: c.Repo.IsAdmin(),
+ Push: c.Repo.IsWriter(),
+ Pull: true,
+ }))
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Repositories#delete
+func Delete(c *context.APIContext) {
+ owner, repo := parseOwnerAndRepo(c)
+ if c.Written() {
+ return
+ }
+
+ if owner.IsOrganization() && !owner.IsOwnedBy(c.User.ID) {
+ c.Error(403, "", "Given user is not owner of organization.")
+ return
+ }
+
+ if err := models.DeleteRepository(owner.ID, repo.ID); err != nil {
+ c.Error(500, "DeleteRepository", err)
+ return
+ }
+
+ log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
+ c.Status(204)
+}
+
+func ListForks(c *context.APIContext) {
+ forks, err := c.Repo.Repository.GetForks()
+ if err != nil {
+ c.Error(500, "GetForks", err)
+ return
+ }
+
+ apiForks := make([]*api.Repository, len(forks))
+ for i := range forks {
+ if err := forks[i].GetOwner(); err != nil {
+ c.Error(500, "GetOwner", err)
+ return
+ }
+ apiForks[i] = forks[i].APIFormat(&api.Permission{
+ Admin: c.User.IsAdminOfRepo(forks[i]),
+ Push: c.User.IsWriterOfRepo(forks[i]),
+ Pull: true,
+ })
+ }
+
+ c.JSON(200, &apiForks)
+}
+
+func MirrorSync(c *context.APIContext) {
+ _, repo := parseOwnerAndRepo(c)
+ if c.Written() {
+ return
+ } else if !repo.IsMirror {
+ c.Status(404)
+ return
+ }
+
+ go models.MirrorQueue.Add(repo.ID)
+ c.Status(202)
+}
diff --git a/routes/api/v1/user/app.go b/routes/api/v1/user/app.go
new file mode 100644
index 00000000..bda1e23f
--- /dev/null
+++ b/routes/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/pkg/context"
+)
+
+// https://github.com/gogits/go-gogs-client/wiki/Users#list-access-tokens-for-a-user
+func ListAccessTokens(c *context.APIContext) {
+ tokens, err := models.ListAccessTokens(c.User.ID)
+ if err != nil {
+ c.Error(500, "ListAccessTokens", err)
+ return
+ }
+
+ apiTokens := make([]*api.AccessToken, len(tokens))
+ for i := range tokens {
+ apiTokens[i] = &api.AccessToken{tokens[i].Name, tokens[i].Sha1}
+ }
+ c.JSON(200, &apiTokens)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users#create-a-access-token
+func CreateAccessToken(c *context.APIContext, form api.CreateAccessTokenOption) {
+ t := &models.AccessToken{
+ UID: c.User.ID,
+ Name: form.Name,
+ }
+ if err := models.NewAccessToken(t); err != nil {
+ c.Error(500, "NewAccessToken", err)
+ return
+ }
+ c.JSON(201, &api.AccessToken{t.Name, t.Sha1})
+}
diff --git a/routes/api/v1/user/email.go b/routes/api/v1/user/email.go
new file mode 100644
index 00000000..bd1ea52b
--- /dev/null
+++ b/routes/api/v1/user/email.go
@@ -0,0 +1,82 @@
+// 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/pkg/context"
+ "github.com/gogits/gogs/pkg/setting"
+ "github.com/gogits/gogs/routes/api/v1/convert"
+)
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Emails#list-email-addresses-for-a-user
+func ListEmails(c *context.APIContext) {
+ emails, err := models.GetEmailAddresses(c.User.ID)
+ if err != nil {
+ c.Error(500, "GetEmailAddresses", err)
+ return
+ }
+ apiEmails := make([]*api.Email, len(emails))
+ for i := range emails {
+ apiEmails[i] = convert.ToEmail(emails[i])
+ }
+ c.JSON(200, &apiEmails)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Emails#add-email-addresses
+func AddEmail(c *context.APIContext, form api.CreateEmailOption) {
+ if len(form.Emails) == 0 {
+ c.Status(422)
+ return
+ }
+
+ emails := make([]*models.EmailAddress, len(form.Emails))
+ for i := range form.Emails {
+ emails[i] = &models.EmailAddress{
+ UID: c.User.ID,
+ Email: form.Emails[i],
+ IsActivated: !setting.Service.RegisterEmailConfirm,
+ }
+ }
+
+ if err := models.AddEmailAddresses(emails); err != nil {
+ if models.IsErrEmailAlreadyUsed(err) {
+ c.Error(422, "", "Email address has been used: "+err.(models.ErrEmailAlreadyUsed).Email)
+ } else {
+ c.Error(500, "AddEmailAddresses", err)
+ }
+ return
+ }
+
+ apiEmails := make([]*api.Email, len(emails))
+ for i := range emails {
+ apiEmails[i] = convert.ToEmail(emails[i])
+ }
+ c.JSON(201, &apiEmails)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Emails#delete-email-addresses
+func DeleteEmail(c *context.APIContext, form api.CreateEmailOption) {
+ if len(form.Emails) == 0 {
+ c.Status(204)
+ return
+ }
+
+ emails := make([]*models.EmailAddress, len(form.Emails))
+ for i := range form.Emails {
+ emails[i] = &models.EmailAddress{
+ UID: c.User.ID,
+ Email: form.Emails[i],
+ }
+ }
+
+ if err := models.DeleteEmailAddresses(emails); err != nil {
+ c.Error(500, "DeleteEmailAddresses", err)
+ return
+ }
+ c.Status(204)
+}
diff --git a/routes/api/v1/user/follower.go b/routes/api/v1/user/follower.go
new file mode 100644
index 00000000..6bbd4c7e
--- /dev/null
+++ b/routes/api/v1/user/follower.go
@@ -0,0 +1,120 @@
+// 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/pkg/context"
+)
+
+func responseApiUsers(c *context.APIContext, users []*models.User) {
+ apiUsers := make([]*api.User, len(users))
+ for i := range users {
+ apiUsers[i] = users[i].APIFormat()
+ }
+ c.JSON(200, &apiUsers)
+}
+
+func listUserFollowers(c *context.APIContext, u *models.User) {
+ users, err := u.GetFollowers(c.QueryInt("page"))
+ if err != nil {
+ c.Error(500, "GetUserFollowers", err)
+ return
+ }
+ responseApiUsers(c, users)
+}
+
+func ListMyFollowers(c *context.APIContext) {
+ listUserFollowers(c, c.User)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#list-followers-of-a-user
+func ListFollowers(c *context.APIContext) {
+ u := GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+ listUserFollowers(c, u)
+}
+
+func listUserFollowing(c *context.APIContext, u *models.User) {
+ users, err := u.GetFollowing(c.QueryInt("page"))
+ if err != nil {
+ c.Error(500, "GetFollowing", err)
+ return
+ }
+ responseApiUsers(c, users)
+}
+
+func ListMyFollowing(c *context.APIContext) {
+ listUserFollowing(c, c.User)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#list-users-followed-by-another-user
+func ListFollowing(c *context.APIContext) {
+ u := GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+ listUserFollowing(c, u)
+}
+
+func checkUserFollowing(c *context.APIContext, u *models.User, followID int64) {
+ if u.IsFollowing(followID) {
+ c.Status(204)
+ } else {
+ c.Status(404)
+ }
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#check-if-you-are-following-a-user
+func CheckMyFollowing(c *context.APIContext) {
+ target := GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+ checkUserFollowing(c, c.User, target.ID)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#check-if-one-user-follows-another
+func CheckFollowing(c *context.APIContext) {
+ u := GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+ target := GetUserByParamsName(c, ":target")
+ if c.Written() {
+ return
+ }
+ checkUserFollowing(c, u, target.ID)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#follow-a-user
+func Follow(c *context.APIContext) {
+ target := GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+ if err := models.FollowUser(c.User.ID, target.ID); err != nil {
+ c.Error(500, "FollowUser", err)
+ return
+ }
+ c.Status(204)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#unfollow-a-user
+func Unfollow(c *context.APIContext) {
+ target := GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+ if err := models.UnfollowUser(c.User.ID, target.ID); err != nil {
+ c.Error(500, "UnfollowUser", err)
+ return
+ }
+ c.Status(204)
+}
diff --git a/routes/api/v1/user/key.go b/routes/api/v1/user/key.go
new file mode 100644
index 00000000..aef42afc
--- /dev/null
+++ b/routes/api/v1/user/key.go
@@ -0,0 +1,120 @@
+// 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/models/errors"
+ "github.com/gogits/gogs/pkg/context"
+ "github.com/gogits/gogs/pkg/setting"
+ "github.com/gogits/gogs/routes/api/v1/convert"
+ "github.com/gogits/gogs/routes/api/v1/repo"
+)
+
+func GetUserByParamsName(c *context.APIContext, name string) *models.User {
+ user, err := models.GetUserByName(c.Params(name))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return nil
+ }
+ return user
+}
+
+// GetUserByParams returns user whose name is presented in URL paramenter.
+func GetUserByParams(c *context.APIContext) *models.User {
+ return GetUserByParamsName(c, ":username")
+}
+
+func composePublicKeysAPILink() string {
+ return setting.AppURL + "api/v1/user/keys/"
+}
+
+func listPublicKeys(c *context.APIContext, uid int64) {
+ keys, err := models.ListPublicKeys(uid)
+ if err != nil {
+ c.Error(500, "ListPublicKeys", err)
+ return
+ }
+
+ apiLink := composePublicKeysAPILink()
+ apiKeys := make([]*api.PublicKey, len(keys))
+ for i := range keys {
+ apiKeys[i] = convert.ToPublicKey(apiLink, keys[i])
+ }
+
+ c.JSON(200, &apiKeys)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#list-your-public-keys
+func ListMyPublicKeys(c *context.APIContext) {
+ listPublicKeys(c, c.User.ID)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#list-public-keys-for-a-user
+func ListPublicKeys(c *context.APIContext) {
+ user := GetUserByParams(c)
+ if c.Written() {
+ return
+ }
+ listPublicKeys(c, user.ID)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#get-a-single-public-key
+func GetPublicKey(c *context.APIContext) {
+ key, err := models.GetPublicKeyByID(c.ParamsInt64(":id"))
+ if err != nil {
+ if models.IsErrKeyNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetPublicKeyByID", err)
+ }
+ return
+ }
+
+ apiLink := composePublicKeysAPILink()
+ c.JSON(200, convert.ToPublicKey(apiLink, key))
+}
+
+// CreateUserPublicKey creates new public key to given user by ID.
+func CreateUserPublicKey(c *context.APIContext, form api.CreateKeyOption, uid int64) {
+ content, err := models.CheckPublicKeyString(form.Key)
+ if err != nil {
+ repo.HandleCheckKeyStringError(c, err)
+ return
+ }
+
+ key, err := models.AddPublicKey(uid, form.Title, content)
+ if err != nil {
+ repo.HandleAddKeyError(c, err)
+ return
+ }
+ apiLink := composePublicKeysAPILink()
+ c.JSON(201, convert.ToPublicKey(apiLink, key))
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#create-a-public-key
+func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) {
+ CreateUserPublicKey(c, form, c.User.ID)
+}
+
+// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#delete-a-public-key
+func DeletePublicKey(c *context.APIContext) {
+ if err := models.DeletePublicKey(c.User, c.ParamsInt64(":id")); err != nil {
+ if models.IsErrKeyAccessDenied(err) {
+ c.Error(403, "", "You do not have access to this key")
+ } else {
+ c.Error(500, "DeletePublicKey", err)
+ }
+ return
+ }
+
+ c.Status(204)
+}
diff --git a/routes/api/v1/user/user.go b/routes/api/v1/user/user.go
new file mode 100644
index 00000000..dbf727de
--- /dev/null
+++ b/routes/api/v1/user/user.go
@@ -0,0 +1,75 @@
+// 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/models/errors"
+ "github.com/gogits/gogs/pkg/context"
+)
+
+func Search(c *context.APIContext) {
+ opts := &models.SearchUserOptions{
+ Keyword: c.Query("q"),
+ Type: models.USER_TYPE_INDIVIDUAL,
+ PageSize: com.StrTo(c.Query("limit")).MustInt(),
+ }
+ if opts.PageSize == 0 {
+ opts.PageSize = 10
+ }
+
+ users, _, err := models.SearchUserByName(opts)
+ if err != nil {
+ c.JSON(500, map[string]interface{}{
+ "ok": false,
+ "error": err.Error(),
+ })
+ return
+ }
+
+ results := make([]*api.User, len(users))
+ for i := range users {
+ results[i] = &api.User{
+ ID: users[i].ID,
+ UserName: users[i].Name,
+ AvatarUrl: users[i].AvatarLink(),
+ FullName: users[i].FullName,
+ }
+ if c.IsLogged {
+ results[i].Email = users[i].Email
+ }
+ }
+
+ c.JSON(200, map[string]interface{}{
+ "ok": true,
+ "data": results,
+ })
+}
+
+func GetInfo(c *context.APIContext) {
+ u, err := models.GetUserByName(c.Params(":username"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+
+ // Hide user e-mail when API caller isn't signed in.
+ if !c.IsLogged {
+ u.Email = ""
+ }
+ c.JSON(200, u.APIFormat())
+}
+
+func GetAuthenticatedUser(c *context.APIContext) {
+ c.JSON(200, c.User.APIFormat())
+}