diff options
Diffstat (limited to 'routes/api/v1')
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()) +} |