1
0
Просмотр исходного кода

all: decouple API types from go-gogs-client SDK (#8171)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ᴊᴏᴇ ᴄʜᴇɴ 10 часов назад
Родитель
Сommit
a1fa62b270
64 измененных файлов с 1634 добавлено и 963 удалено
  1. 0 1
      go.mod
  2. 0 2
      go.sum
  3. 14 14
      internal/database/actions.go
  4. 11 12
      internal/database/comment.go
  5. 46 47
      internal/database/issue.go
  6. 3 3
      internal/database/issue_label.go
  7. 11 11
      internal/database/milestone.go
  8. 12 12
      internal/database/pull.go
  9. 5 5
      internal/database/release.go
  10. 5 5
      internal/database/repo.go
  11. 5 5
      internal/database/repo_collaboration.go
  12. 5 5
      internal/database/repositories.go
  13. 4 4
      internal/database/users.go
  14. 30 31
      internal/database/webhook.go
  15. 28 28
      internal/database/webhook_dingtalk.go
  16. 51 52
      internal/database/webhook_discord.go
  17. 42 43
      internal/database/webhook_slack.go
  18. 303 0
      internal/route/api/v1/adapters.go
  19. 0 13
      internal/route/api/v1/admin/org.go
  20. 0 18
      internal/route/api/v1/admin/repo.go
  21. 9 0
      internal/route/api/v1/admin_org.go
  22. 6 6
      internal/route/api/v1/admin_org_repo.go
  23. 17 14
      internal/route/api/v1/admin_org_team.go
  24. 14 0
      internal/route/api/v1/admin_repo.go
  25. 39 16
      internal/route/api/v1/admin_user.go
  26. 108 115
      internal/route/api/v1/api.go
  27. 0 135
      internal/route/api/v1/convert/convert.go
  28. 0 15
      internal/route/api/v1/convert/utils.go
  29. 9 5
      internal/route/api/v1/markdown.go
  30. 33 21
      internal/route/api/v1/org.go
  31. 0 22
      internal/route/api/v1/org/team.go
  32. 20 0
      internal/route/api/v1/org_team.go
  33. 2 2
      internal/route/api/v1/repo_blob.go
  34. 7 9
      internal/route/api/v1/repo_branch.go
  35. 12 9
      internal/route/api/v1/repo_collaborators.go
  36. 24 22
      internal/route/api/v1/repo_commits.go
  37. 5 5
      internal/route/api/v1/repo_contents.go
  38. 7 7
      internal/route/api/v1/repo_file.go
  39. 23 12
      internal/route/api/v1/repo_hook.go
  40. 35 18
      internal/route/api/v1/repo_issue.go
  41. 21 14
      internal/route/api/v1/repo_issue_comment.go
  42. 17 14
      internal/route/api/v1/repo_issue_label.go
  43. 19 15
      internal/route/api/v1/repo_key.go
  44. 22 13
      internal/route/api/v1/repo_label.go
  45. 26 14
      internal/route/api/v1/repo_milestone.go
  46. 64 40
      internal/route/api/v1/repo_repo.go
  47. 4 5
      internal/route/api/v1/repo_tag.go
  48. 2 2
      internal/route/api/v1/repo_tree.go
  49. 8 0
      internal/route/api/v1/tag.go
  50. 29 0
      internal/route/api/v1/types/commit.go
  51. 23 0
      internal/route/api/v1/types/hook.go
  52. 58 0
      internal/route/api/v1/types/issue.go
  53. 18 0
      internal/route/api/v1/types/org.go
  54. 26 0
      internal/route/api/v1/types/pull_request.go
  55. 52 0
      internal/route/api/v1/types/repo.go
  56. 36 0
      internal/route/api/v1/types/user.go
  57. 158 0
      internal/route/api/v1/types/webhook.go
  58. 11 18
      internal/route/api/v1/user.go
  59. 25 15
      internal/route/api/v1/user_access_tokens.go
  60. 16 15
      internal/route/api/v1/user_email.go
  61. 19 20
      internal/route/api/v1/user_follower.go
  62. 27 26
      internal/route/api/v1/user_key.go
  63. 3 3
      internal/route/repo/branch.go
  64. 5 5
      internal/route/repo/webhook.go

+ 0 - 1
go.mod

@@ -22,7 +22,6 @@ require (
 	github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561
 	github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
 	github.com/gogs/git-module v1.8.6
-	github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4
 	github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0
 	github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a
 	github.com/google/go-github v17.0.0+incompatible

+ 0 - 2
go.sum

@@ -153,8 +153,6 @@ github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQ
 github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk=
 github.com/gogs/git-module v1.8.6 h1:4Io9vWZYQyIjdIPxfKgeYZXnDKNgydc6OZTxII5xCH4=
 github.com/gogs/git-module v1.8.6/go.mod h1:IiMSJqi8XH62Kjqjt5Rw8IawSo+DHfM2dDjkSzWLjhs=
-github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4 h1:C7NryI/RQhsIWwC2bHN601P1wJKeuQ6U/UCOYTn3Cic=
-github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
 github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0 h1:K02vod+sn3M1OOkdqi2tPxN2+xESK4qyITVQ3JkGEv4=
 github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0/go.mod h1:Zas3BtO88pk1cwUfEYlvnl/CRwh0ybDxRWSwRjG8I3w=
 github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a h1:8DZwxETOVWIinYxDK+i6L+rMb7eGATGaakD6ZucfHVk=

+ 14 - 14
internal/database/actions.go

@@ -12,13 +12,13 @@ import (
 
 	"github.com/cockroachdb/errors"
 	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
 	"gorm.io/gorm"
 	log "unknwon.dev/clog/v2"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/lazyregexp"
 	"gogs.io/gogs/internal/repoutil"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 	"gogs.io/gogs/internal/strutil"
 	"gogs.io/gogs/internal/testutil"
 	"gogs.io/gogs/internal/tool"
@@ -230,7 +230,7 @@ func (s *ActionsStore) MirrorSyncPush(ctx context.Context, opts MirrorSyncPushOp
 	err = PrepareWebhooks(
 		opts.Repo,
 		HookEventTypePush,
-		&api.PushPayload{
+		&apiv1types.WebhookPushPayload{
 			Ref:        opts.RefName,
 			Before:     opts.OldCommitID,
 			After:      opts.NewCommitID,
@@ -496,10 +496,10 @@ func (s *ActionsStore) CommitRepo(ctx context.Context, opts CommitRepoOptions) e
 		err = PrepareWebhooks(
 			opts.Repo,
 			HookEventTypeDelete,
-			&api.DeletePayload{
+			&apiv1types.WebhookDeletePayload{
 				Ref:        refName,
 				RefType:    "branch",
-				PusherType: api.PUSHER_TYPE_USER,
+				PusherType: apiv1types.WebhookPusherTypeUser,
 				Repo:       apiRepo,
 				Sender:     apiPusher,
 			},
@@ -540,7 +540,7 @@ func (s *ActionsStore) CommitRepo(ctx context.Context, opts CommitRepoOptions) e
 		err = PrepareWebhooks(
 			opts.Repo,
 			HookEventTypeCreate,
-			&api.CreatePayload{
+			&apiv1types.WebhookCreatePayload{
 				Ref:           refName,
 				RefType:       "branch",
 				DefaultBranch: opts.Repo.DefaultBranch,
@@ -573,7 +573,7 @@ func (s *ActionsStore) CommitRepo(ctx context.Context, opts CommitRepoOptions) e
 	err = PrepareWebhooks(
 		opts.Repo,
 		HookEventTypePush,
-		&api.PushPayload{
+		&apiv1types.WebhookPushPayload{
 			Ref:        opts.RefFullName,
 			Before:     opts.OldCommitID,
 			After:      opts.NewCommitID,
@@ -635,10 +635,10 @@ func (s *ActionsStore) PushTag(ctx context.Context, opts PushTagOptions) error {
 		err = PrepareWebhooks(
 			opts.Repo,
 			HookEventTypeDelete,
-			&api.DeletePayload{
+			&apiv1types.WebhookDeletePayload{
 				Ref:        refName,
 				RefType:    "tag",
-				PusherType: api.PUSHER_TYPE_USER,
+				PusherType: apiv1types.WebhookPusherTypeUser,
 				Repo:       apiRepo,
 				Sender:     apiPusher,
 			},
@@ -658,7 +658,7 @@ func (s *ActionsStore) PushTag(ctx context.Context, opts PushTagOptions) error {
 	err = PrepareWebhooks(
 		opts.Repo,
 		HookEventTypeCreate,
-		&api.CreatePayload{
+		&apiv1types.WebhookCreatePayload{
 			Ref:           refName,
 			RefType:       "tag",
 			Sha:           opts.NewCommitID,
@@ -848,7 +848,7 @@ func NewPushCommits() *PushCommits {
 	}
 }
 
-func (pcs *PushCommits) APIFormat(ctx context.Context, usersStore *UsersStore, repoPath, repoURL string) ([]*api.PayloadCommit, error) {
+func (pcs *PushCommits) APIFormat(ctx context.Context, usersStore *UsersStore, repoPath, repoURL string) ([]*apiv1types.WebhookPayloadCommit, error) {
 	// NOTE: We cache query results in case there are many commits in a single push.
 	usernameByEmail := make(map[string]string)
 	getUsernameByEmail := func(email string) (string, error) {
@@ -870,7 +870,7 @@ func (pcs *PushCommits) APIFormat(ctx context.Context, usersStore *UsersStore, r
 		return user.Name, nil
 	}
 
-	commits := make([]*api.PayloadCommit, len(pcs.Commits))
+	commits := make([]*apiv1types.WebhookPayloadCommit, len(pcs.Commits))
 	for i, commit := range pcs.Commits {
 		authorUsername, err := getUsernameByEmail(commit.AuthorEmail)
 		if err != nil {
@@ -890,16 +890,16 @@ func (pcs *PushCommits) APIFormat(ctx context.Context, usersStore *UsersStore, r
 			}
 		}
 
-		commits[i] = &api.PayloadCommit{
+		commits[i] = &apiv1types.WebhookPayloadCommit{
 			ID:      commit.Sha1,
 			Message: commit.Message,
 			URL:     fmt.Sprintf("%s/commit/%s", repoURL, commit.Sha1),
-			Author: &api.PayloadUser{
+			Author: &apiv1types.WebhookPayloadUser{
 				Name:     commit.AuthorName,
 				Email:    commit.AuthorEmail,
 				UserName: authorUsername,
 			},
-			Committer: &api.PayloadUser{
+			Committer: &apiv1types.WebhookPayloadUser{
 				Name:     commit.CommitterName,
 				Email:    commit.CommitterEmail,
 				UserName: committerUsername,

+ 11 - 12
internal/database/comment.go

@@ -11,10 +11,9 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/errutil"
 	"gogs.io/gogs/internal/markup"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 )
 
 // CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
@@ -136,8 +135,8 @@ func (c *Comment) HTMLURL() string {
 
 // This method assumes following fields have been assigned with valid values:
 // Required - Poster, Issue
-func (c *Comment) APIFormat() *api.Comment {
-	return &api.Comment{
+func (c *Comment) APIFormat() *apiv1types.IssueComment {
+	return &apiv1types.IssueComment{
 		ID:      c.ID,
 		HTMLURL: c.HTMLURL(),
 		Poster:  c.Poster.APIFormat(),
@@ -347,8 +346,8 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri
 	}
 
 	comment.Issue = issue
-	if err = PrepareWebhooks(repo, HookEventTypeIssueComment, &api.IssueCommentPayload{
-		Action:     api.HOOK_ISSUE_COMMENT_CREATED,
+	if err = PrepareWebhooks(repo, HookEventTypeIssueComment, &apiv1types.WebhookIssueCommentPayload{
+		Action:     apiv1types.WebhookIssueCommentCreated,
 		Issue:      issue.APIFormat(),
 		Comment:    comment.APIFormat(),
 		Repository: repo.APIFormatLegacy(nil),
@@ -483,12 +482,12 @@ func UpdateComment(doer *User, c *Comment, oldContent string) (err error) {
 
 	if err = c.Issue.LoadAttributes(); err != nil {
 		log.Error("Issue.LoadAttributes [issue_id: %d]: %v", c.IssueID, err)
-	} else if err = PrepareWebhooks(c.Issue.Repo, HookEventTypeIssueComment, &api.IssueCommentPayload{
-		Action:  api.HOOK_ISSUE_COMMENT_EDITED,
+	} else if err = PrepareWebhooks(c.Issue.Repo, HookEventTypeIssueComment, &apiv1types.WebhookIssueCommentPayload{
+		Action:  apiv1types.WebhookIssueCommentEdited,
 		Issue:   c.Issue.APIFormat(),
 		Comment: c.APIFormat(),
-		Changes: &api.ChangesPayload{
-			Body: &api.ChangesFromPayload{
+		Changes: &apiv1types.WebhookChangesPayload{
+			Body: &apiv1types.WebhookChangesFromPayload{
 				From: oldContent,
 			},
 		},
@@ -538,8 +537,8 @@ func DeleteCommentByID(doer *User, id int64) error {
 
 	if err = comment.Issue.LoadAttributes(); err != nil {
 		log.Error("Issue.LoadAttributes [issue_id: %d]: %v", comment.IssueID, err)
-	} else if err = PrepareWebhooks(comment.Issue.Repo, HookEventTypeIssueComment, &api.IssueCommentPayload{
-		Action:     api.HOOK_ISSUE_COMMENT_DELETED,
+	} else if err = PrepareWebhooks(comment.Issue.Repo, HookEventTypeIssueComment, &apiv1types.WebhookIssueCommentPayload{
+		Action:     apiv1types.WebhookIssueCommentDeleted,
 		Issue:      comment.Issue.APIFormat(),
 		Comment:    comment.APIFormat(),
 		Repository: comment.Issue.Repo.APIFormatLegacy(nil),

+ 46 - 47
internal/database/issue.go

@@ -11,11 +11,10 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/errutil"
 	"gogs.io/gogs/internal/markup"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -172,23 +171,23 @@ func (issue *Issue) HTMLURL() string {
 }
 
 // State returns string representation of issue status.
-func (issue *Issue) State() api.StateType {
+func (issue *Issue) State() apiv1types.IssueStateType {
 	if issue.IsClosed {
-		return api.STATE_CLOSED
+		return apiv1types.IssueStateClosed
 	}
-	return api.STATE_OPEN
+	return apiv1types.IssueStateOpen
 }
 
 // This method assumes some fields assigned with values:
 // Required - Poster, Labels,
 // Optional - Milestone, Assignee, PullRequest
-func (issue *Issue) APIFormat() *api.Issue {
-	apiLabels := make([]*api.Label, len(issue.Labels))
+func (issue *Issue) APIFormat() *apiv1types.Issue {
+	apiLabels := make([]*apiv1types.IssueLabel, len(issue.Labels))
 	for i := range issue.Labels {
 		apiLabels[i] = issue.Labels[i].APIFormat()
 	}
 
-	apiIssue := &api.Issue{
+	apiIssue := &apiv1types.Issue{
 		ID:       issue.ID,
 		Index:    issue.Index,
 		Poster:   issue.Poster.APIFormat(),
@@ -208,7 +207,7 @@ func (issue *Issue) APIFormat() *api.Issue {
 		apiIssue.Assignee = issue.Assignee.APIFormat()
 	}
 	if issue.IsPull {
-		apiIssue.PullRequest = &api.PullRequestMeta{
+		apiIssue.PullRequest = &apiv1types.PullRequestMeta{
 			HasMerged: issue.PullRequest.HasMerged,
 		}
 		if issue.PullRequest.HasMerged {
@@ -246,16 +245,16 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) {
 			log.Error("LoadIssue: %v", err)
 			return
 		}
-		err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
-			Action:      api.HOOK_ISSUE_LABEL_UPDATED,
+		err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
+			Action:      apiv1types.WebhookIssueLabelUpdated,
 			Index:       issue.Index,
 			PullRequest: issue.PullRequest.APIFormat(),
 			Repository:  issue.Repo.APIFormatLegacy(nil),
 			Sender:      doer.APIFormat(),
 		})
 	} else {
-		err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &api.IssuesPayload{
-			Action:     api.HOOK_ISSUE_LABEL_UPDATED,
+		err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
+			Action:     apiv1types.WebhookIssueLabelUpdated,
 			Index:      issue.Index,
 			Issue:      issue.APIFormat(),
 			Repository: issue.Repo.APIFormatLegacy(nil),
@@ -359,16 +358,16 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
 			log.Error("LoadIssue: %v", err)
 			return err
 		}
-		err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
-			Action:      api.HOOK_ISSUE_LABEL_CLEARED,
+		err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
+			Action:      apiv1types.WebhookIssueLabelCleared,
 			Index:       issue.Index,
 			PullRequest: issue.PullRequest.APIFormat(),
 			Repository:  issue.Repo.APIFormatLegacy(nil),
 			Sender:      doer.APIFormat(),
 		})
 	} else {
-		err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &api.IssuesPayload{
-			Action:     api.HOOK_ISSUE_LABEL_CLEARED,
+		err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
+			Action:     apiv1types.WebhookIssueLabelCleared,
 			Index:      issue.Index,
 			Issue:      issue.APIFormat(),
 			Repository: issue.Repo.APIFormatLegacy(nil),
@@ -487,29 +486,29 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e
 	if issue.IsPull {
 		// Merge pull request calls issue.changeStatus so we need to handle separately.
 		issue.PullRequest.Issue = issue
-		apiPullRequest := &api.PullRequestPayload{
+		apiPullRequest := &apiv1types.WebhookPullRequestPayload{
 			Index:       issue.Index,
 			PullRequest: issue.PullRequest.APIFormat(),
 			Repository:  repo.APIFormatLegacy(nil),
 			Sender:      doer.APIFormat(),
 		}
 		if isClosed {
-			apiPullRequest.Action = api.HOOK_ISSUE_CLOSED
+			apiPullRequest.Action = apiv1types.WebhookIssueClosed
 		} else {
-			apiPullRequest.Action = api.HOOK_ISSUE_REOPENED
+			apiPullRequest.Action = apiv1types.WebhookIssueReopened
 		}
 		err = PrepareWebhooks(repo, HookEventTypePullRequest, apiPullRequest)
 	} else {
-		apiIssues := &api.IssuesPayload{
+		apiIssues := &apiv1types.WebhookIssuesPayload{
 			Index:      issue.Index,
 			Issue:      issue.APIFormat(),
 			Repository: repo.APIFormatLegacy(nil),
 			Sender:     doer.APIFormat(),
 		}
 		if isClosed {
-			apiIssues.Action = api.HOOK_ISSUE_CLOSED
+			apiIssues.Action = apiv1types.WebhookIssueClosed
 		} else {
-			apiIssues.Action = api.HOOK_ISSUE_REOPENED
+			apiIssues.Action = apiv1types.WebhookIssueReopened
 		}
 		err = PrepareWebhooks(repo, HookEventTypeIssues, apiIssues)
 	}
@@ -529,12 +528,12 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
 
 	if issue.IsPull {
 		issue.PullRequest.Issue = issue
-		err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
-			Action:      api.HOOK_ISSUE_EDITED,
+		err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
+			Action:      apiv1types.WebhookIssueEdited,
 			Index:       issue.Index,
 			PullRequest: issue.PullRequest.APIFormat(),
-			Changes: &api.ChangesPayload{
-				Title: &api.ChangesFromPayload{
+			Changes: &apiv1types.WebhookChangesPayload{
+				Title: &apiv1types.WebhookChangesFromPayload{
 					From: oldTitle,
 				},
 			},
@@ -542,12 +541,12 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
 			Sender:     doer.APIFormat(),
 		})
 	} else {
-		err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &api.IssuesPayload{
-			Action: api.HOOK_ISSUE_EDITED,
+		err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
+			Action: apiv1types.WebhookIssueEdited,
 			Index:  issue.Index,
 			Issue:  issue.APIFormat(),
-			Changes: &api.ChangesPayload{
-				Title: &api.ChangesFromPayload{
+			Changes: &apiv1types.WebhookChangesPayload{
+				Title: &apiv1types.WebhookChangesFromPayload{
 					From: oldTitle,
 				},
 			},
@@ -571,12 +570,12 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
 
 	if issue.IsPull {
 		issue.PullRequest.Issue = issue
-		err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
-			Action:      api.HOOK_ISSUE_EDITED,
+		err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
+			Action:      apiv1types.WebhookIssueEdited,
 			Index:       issue.Index,
 			PullRequest: issue.PullRequest.APIFormat(),
-			Changes: &api.ChangesPayload{
-				Body: &api.ChangesFromPayload{
+			Changes: &apiv1types.WebhookChangesPayload{
+				Body: &apiv1types.WebhookChangesFromPayload{
 					From: oldContent,
 				},
 			},
@@ -584,12 +583,12 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
 			Sender:     doer.APIFormat(),
 		})
 	} else {
-		err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &api.IssuesPayload{
-			Action: api.HOOK_ISSUE_EDITED,
+		err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
+			Action: apiv1types.WebhookIssueEdited,
 			Index:  issue.Index,
 			Issue:  issue.APIFormat(),
-			Changes: &api.ChangesPayload{
-				Body: &api.ChangesFromPayload{
+			Changes: &apiv1types.WebhookChangesPayload{
+				Body: &apiv1types.WebhookChangesFromPayload{
 					From: oldContent,
 				},
 			},
@@ -620,29 +619,29 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
 	isRemoveAssignee := err != nil
 	if issue.IsPull {
 		issue.PullRequest.Issue = issue
-		apiPullRequest := &api.PullRequestPayload{
+		apiPullRequest := &apiv1types.WebhookPullRequestPayload{
 			Index:       issue.Index,
 			PullRequest: issue.PullRequest.APIFormat(),
 			Repository:  issue.Repo.APIFormatLegacy(nil),
 			Sender:      doer.APIFormat(),
 		}
 		if isRemoveAssignee {
-			apiPullRequest.Action = api.HOOK_ISSUE_UNASSIGNED
+			apiPullRequest.Action = apiv1types.WebhookIssueUnassigned
 		} else {
-			apiPullRequest.Action = api.HOOK_ISSUE_ASSIGNED
+			apiPullRequest.Action = apiv1types.WebhookIssueAssigned
 		}
 		err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, apiPullRequest)
 	} else {
-		apiIssues := &api.IssuesPayload{
+		apiIssues := &apiv1types.WebhookIssuesPayload{
 			Index:      issue.Index,
 			Issue:      issue.APIFormat(),
 			Repository: issue.Repo.APIFormatLegacy(nil),
 			Sender:     doer.APIFormat(),
 		}
 		if isRemoveAssignee {
-			apiIssues.Action = api.HOOK_ISSUE_UNASSIGNED
+			apiIssues.Action = apiv1types.WebhookIssueUnassigned
 		} else {
-			apiIssues.Action = api.HOOK_ISSUE_ASSIGNED
+			apiIssues.Action = apiv1types.WebhookIssueAssigned
 		}
 		err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, apiIssues)
 	}
@@ -789,8 +788,8 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
 		log.Error("MailParticipants: %v", err)
 	}
 
-	if err = PrepareWebhooks(repo, HookEventTypeIssues, &api.IssuesPayload{
-		Action:     api.HOOK_ISSUE_OPENED,
+	if err = PrepareWebhooks(repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
+		Action:     apiv1types.WebhookIssueOpened,
 		Index:      issue.Index,
 		Issue:      issue.APIFormat(),
 		Repository: repo.APIFormatLegacy(nil),

+ 3 - 3
internal/database/issue_label.go

@@ -9,10 +9,10 @@ import (
 	"xorm.io/xorm"
 
 	"github.com/cockroachdb/errors"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/errutil"
 	"gogs.io/gogs/internal/lazyregexp"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 	"gogs.io/gogs/internal/tool"
 )
 
@@ -62,8 +62,8 @@ type Label struct {
 	IsChecked       bool `xorm:"-" json:"-" gorm:"-"`
 }
 
-func (l *Label) APIFormat() *api.Label {
-	return &api.Label{
+func (l *Label) APIFormat() *apiv1types.IssueLabel {
+	return &apiv1types.IssueLabel{
 		ID:    l.ID,
 		Name:  l.Name,
 		Color: strings.TrimLeft(l.Color, "#"),

+ 11 - 11
internal/database/milestone.go

@@ -8,10 +8,10 @@ import (
 	"xorm.io/xorm"
 
 	"github.com/cockroachdb/errors"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/errutil"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 )
 
 // Milestone represents a milestone of repository.
@@ -72,19 +72,19 @@ func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
 }
 
 // State returns string representation of milestone status.
-func (m *Milestone) State() api.StateType {
+func (m *Milestone) State() apiv1types.IssueStateType {
 	if m.IsClosed {
-		return api.STATE_CLOSED
+		return apiv1types.IssueStateClosed
 	}
-	return api.STATE_OPEN
+	return apiv1types.IssueStateOpen
 }
 
 func (m *Milestone) ChangeStatus(isClosed bool) error {
 	return ChangeMilestoneStatus(m, isClosed)
 }
 
-func (m *Milestone) APIFormat() *api.Milestone {
-	apiMilestone := &api.Milestone{
+func (m *Milestone) APIFormat() *apiv1types.IssueMilestone {
+	apiMilestone := &apiv1types.IssueMilestone{
 		ID:           m.ID,
 		State:        m.State(),
 		Title:        m.Name,
@@ -343,11 +343,11 @@ func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err
 		return errors.Newf("commit: %v", err)
 	}
 
-	var hookAction api.HookIssueAction
+	var hookAction apiv1types.WebhookIssueAction
 	if issue.MilestoneID > 0 {
-		hookAction = api.HOOK_ISSUE_MILESTONED
+		hookAction = apiv1types.WebhookIssueMilestoned
 	} else {
-		hookAction = api.HOOK_ISSUE_DEMILESTONED
+		hookAction = apiv1types.WebhookIssueDemilestoned
 	}
 
 	if issue.IsPull {
@@ -356,7 +356,7 @@ func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err
 			log.Error("LoadIssue: %v", err)
 			return err
 		}
-		err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
+		err = PrepareWebhooks(issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
 			Action:      hookAction,
 			Index:       issue.Index,
 			PullRequest: issue.PullRequest.APIFormat(),
@@ -364,7 +364,7 @@ func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err
 			Sender:      doer.APIFormat(),
 		})
 	} else {
-		err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &api.IssuesPayload{
+		err = PrepareWebhooks(issue.Repo, HookEventTypeIssues, &apiv1types.WebhookIssuesPayload{
 			Action:     hookAction,
 			Index:      issue.Index,
 			Issue:      issue.APIFormat(),

+ 12 - 12
internal/database/pull.go

@@ -13,12 +13,12 @@ import (
 	"xorm.io/xorm"
 
 	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/errutil"
 	"gogs.io/gogs/internal/osutil"
 	"gogs.io/gogs/internal/process"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 	"gogs.io/gogs/internal/sync"
 )
 
@@ -127,11 +127,11 @@ func (pr *PullRequest) LoadIssue() (err error) {
 // This method assumes following fields have been assigned with valid values:
 // Required - Issue, BaseRepo
 // Optional - HeadRepo, Merger
-func (pr *PullRequest) APIFormat() *api.PullRequest {
+func (pr *PullRequest) APIFormat() *apiv1types.PullRequest {
 	// In case of head repo has been deleted.
-	var apiHeadRepo *api.Repository
+	var apiHeadRepo *apiv1types.Repository
 	if pr.HeadRepo == nil {
-		apiHeadRepo = &api.Repository{
+		apiHeadRepo = &apiv1types.Repository{
 			Name: "deleted",
 		}
 	} else {
@@ -139,7 +139,7 @@ func (pr *PullRequest) APIFormat() *api.PullRequest {
 	}
 
 	apiIssue := pr.Issue.APIFormat()
-	apiPullRequest := &api.PullRequest{
+	apiPullRequest := &apiv1types.PullRequest{
 		ID:         pr.ID,
 		Index:      pr.Index,
 		Poster:     apiIssue.Poster,
@@ -341,8 +341,8 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 		log.Error("LoadAttributes: %v", err)
 		return nil
 	}
-	if err = PrepareWebhooks(pr.Issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
-		Action:      api.HOOK_ISSUE_CLOSED,
+	if err = PrepareWebhooks(pr.Issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
+		Action:      apiv1types.WebhookIssueClosed,
 		Index:       pr.Index,
 		PullRequest: pr.APIFormat(),
 		Repository:  pr.Issue.Repo.APIFormatLegacy(nil),
@@ -376,7 +376,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 		return nil
 	}
 
-	p := &api.PushPayload{
+	p := &apiv1types.WebhookPushPayload{
 		Ref:        git.RefsHeads + pr.BaseBranch,
 		Before:     pr.MergeBase,
 		After:      mergeCommit.ID.String(),
@@ -500,8 +500,8 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
 
 	pr.Issue = pull
 	pull.PullRequest = pr
-	if err = PrepareWebhooks(repo, HookEventTypePullRequest, &api.PullRequestPayload{
-		Action:      api.HOOK_ISSUE_OPENED,
+	if err = PrepareWebhooks(repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
+		Action:      apiv1types.WebhookIssueOpened,
 		Index:       pull.Index,
 		PullRequest: pr.APIFormat(),
 		Repository:  repo.APIFormatLegacy(nil),
@@ -792,8 +792,8 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool
 					log.Error("LoadAttributes: %v", err)
 					continue
 				}
-				if err = PrepareWebhooks(pr.Issue.Repo, HookEventTypePullRequest, &api.PullRequestPayload{
-					Action:      api.HOOK_ISSUE_SYNCHRONIZED,
+				if err = PrepareWebhooks(pr.Issue.Repo, HookEventTypePullRequest, &apiv1types.WebhookPullRequestPayload{
+					Action:      apiv1types.WebhookIssueSynchronized,
 					Index:       pr.Issue.Index,
 					PullRequest: pr.Issue.PullRequest.APIFormat(),
 					Repository:  pr.Issue.Repo.APIFormatLegacy(nil),

+ 5 - 5
internal/database/release.go

@@ -11,10 +11,10 @@ import (
 
 	"github.com/cockroachdb/errors"
 	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/errutil"
 	"gogs.io/gogs/internal/process"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 )
 
 // Release represents a release of repository.
@@ -90,8 +90,8 @@ func (r *Release) LoadAttributes() error {
 
 // This method assumes some fields assigned with values:
 // Required - Publisher
-func (r *Release) APIFormat() *api.Release {
-	return &api.Release{
+func (r *Release) APIFormat() *apiv1types.RepositoryRelease {
+	return &apiv1types.RepositoryRelease{
 		ID:              r.ID,
 		TagName:         r.TagName,
 		TargetCommitish: r.Target,
@@ -147,8 +147,8 @@ func createTag(gitRepo *git.Repository, r *Release) error {
 }
 
 func (r *Release) preparePublishWebhooks() {
-	if err := PrepareWebhooks(r.Repo, HookEventTypeRelease, &api.ReleasePayload{
-		Action:     api.HOOK_RELEASE_PUBLISHED,
+	if err := PrepareWebhooks(r.Repo, HookEventTypeRelease, &apiv1types.WebhookReleasePayload{
+		Action:     apiv1types.WebhookReleasePublished,
 		Release:    r.APIFormat(),
 		Repository: r.Repo.APIFormatLegacy(nil),
 		Sender:     r.Publisher.APIFormat(),

+ 5 - 5
internal/database/repo.go

@@ -26,7 +26,6 @@ import (
 	"xorm.io/xorm"
 
 	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
 
 	embedConf "gogs.io/gogs/conf"
 	"gogs.io/gogs/internal/avatar"
@@ -37,6 +36,7 @@ import (
 	"gogs.io/gogs/internal/osutil"
 	"gogs.io/gogs/internal/process"
 	"gogs.io/gogs/internal/repoutil"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 	"gogs.io/gogs/internal/semverutil"
 	"gogs.io/gogs/internal/strutil"
 	"gogs.io/gogs/internal/sync"
@@ -377,9 +377,9 @@ func (r *Repository) DeleteAvatar() error {
 // Arguments that are allowed to be nil: permission
 //
 // Deprecated: Use APIFormat instead.
-func (r *Repository) APIFormatLegacy(permission *api.Permission, user ...*User) *api.Repository {
+func (r *Repository) APIFormatLegacy(permission *apiv1types.RepositoryPermission, user ...*User) *apiv1types.Repository {
 	cloneLink := r.CloneLink()
-	apiRepo := &api.Repository{
+	apiRepo := &apiv1types.Repository{
 		ID:            r.ID,
 		Owner:         r.Owner.APIFormat(),
 		Name:          r.Name,
@@ -406,7 +406,7 @@ func (r *Repository) APIFormatLegacy(permission *api.Permission, user ...*User)
 		//		AvatarUrl:     r.AvatarLink(),
 	}
 	if r.IsFork {
-		p := &api.Permission{Pull: true}
+		p := &apiv1types.RepositoryPermission{Pull: true}
 		if len(user) != 0 {
 			accessMode := Handle.Permissions().AccessMode(
 				context.TODO(),
@@ -2609,7 +2609,7 @@ func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string)
 	if err = repo.UpdateSize(); err != nil {
 		log.Error("UpdateSize [repo_id: %d]: %v", repo.ID, err)
 	}
-	if err = PrepareWebhooks(baseRepo, HookEventTypeFork, &api.ForkPayload{
+	if err = PrepareWebhooks(baseRepo, HookEventTypeFork, &apiv1types.WebhookForkPayload{
 		Forkee: repo.APIFormatLegacy(nil),
 		Repo:   baseRepo.APIFormatLegacy(nil),
 		Sender: doer.APIFormat(),

+ 5 - 5
internal/database/repo_collaboration.go

@@ -1,10 +1,10 @@
 package database
 
 import (
+	"github.com/cockroachdb/errors"
 	log "unknwon.dev/clog/v2"
 
-	"github.com/cockroachdb/errors"
-	api "github.com/gogs/go-gogs-client"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 )
 
 // Collaboration represent the relation between an individual and a repository.
@@ -87,10 +87,10 @@ type Collaborator struct {
 	Collaboration *Collaboration
 }
 
-func (c *Collaborator) APIFormat() *api.Collaborator {
-	return &api.Collaborator{
+func (c *Collaborator) APIFormat() *apiv1types.RepositoryCollaborator {
+	return &apiv1types.RepositoryCollaborator{
 		User: c.User.APIFormat(),
-		Permissions: api.Permission{
+		Permissions: apiv1types.RepositoryPermission{
 			Admin: c.Collaboration.Mode >= AccessModeAdmin,
 			Push:  c.Collaboration.Mode >= AccessModeWrite,
 			Pull:  c.Collaboration.Mode >= AccessModeRead,

+ 5 - 5
internal/database/repositories.go

@@ -7,11 +7,11 @@ import (
 	"time"
 
 	"github.com/cockroachdb/errors"
-	api "github.com/gogs/go-gogs-client"
 	"gorm.io/gorm"
 
 	"gogs.io/gogs/internal/errutil"
 	"gogs.io/gogs/internal/repoutil"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 )
 
 // BeforeCreate implements the GORM create hook.
@@ -36,19 +36,19 @@ func (r *Repository) AfterFind(_ *gorm.DB) error {
 }
 
 type RepositoryAPIFormatOptions struct {
-	Permission *api.Permission
-	Parent     *api.Repository
+	Permission *apiv1types.RepositoryPermission
+	Parent     *apiv1types.Repository
 }
 
 // APIFormat returns the API format of a repository.
-func (r *Repository) APIFormat(owner *User, opts ...RepositoryAPIFormatOptions) *api.Repository {
+func (r *Repository) APIFormat(owner *User, opts ...RepositoryAPIFormatOptions) *apiv1types.Repository {
 	var opt RepositoryAPIFormatOptions
 	if len(opts) > 0 {
 		opt = opts[0]
 	}
 
 	cloneLink := repoutil.NewCloneLink(owner.Name, r.Name, false)
-	return &api.Repository{
+	return &apiv1types.Repository{
 		ID:            r.ID,
 		Owner:         owner.APIFormat(),
 		Name:          r.Name,

+ 4 - 4
internal/database/users.go

@@ -11,7 +11,6 @@ import (
 
 	"github.com/cockroachdb/errors"
 	"github.com/go-macaron/binding"
-	api "github.com/gogs/go-gogs-client"
 	"gorm.io/gorm"
 	log "unknwon.dev/clog/v2"
 
@@ -23,6 +22,7 @@ import (
 	"gogs.io/gogs/internal/markup"
 	"gogs.io/gogs/internal/osutil"
 	"gogs.io/gogs/internal/repoutil"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 	"gogs.io/gogs/internal/strutil"
 	"gogs.io/gogs/internal/tool"
 	"gogs.io/gogs/internal/userutil"
@@ -1297,14 +1297,14 @@ func (u *User) IsOrganization() bool {
 }
 
 // APIFormat returns the API format of a user.
-func (u *User) APIFormat() *api.User {
-	return &api.User{
+func (u *User) APIFormat() *apiv1types.User {
+	return &apiv1types.User{
 		ID:        u.ID,
 		UserName:  u.Name,
 		Login:     u.Name,
 		FullName:  u.FullName,
 		Email:     u.Email,
-		AvatarUrl: u.AvatarURL(),
+		AvatarURL: u.AvatarURL(),
 	}
 }
 

+ 30 - 31
internal/database/webhook.go

@@ -17,12 +17,11 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/errutil"
 	"gogs.io/gogs/internal/httplib"
 	"gogs.io/gogs/internal/netutil"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 	"gogs.io/gogs/internal/sync"
 	"gogs.io/gogs/internal/testutil"
 )
@@ -430,21 +429,21 @@ type HookResponse struct {
 
 // HookTask represents a hook task.
 type HookTask struct {
-	ID              int64
-	RepoID          int64 `xorm:"INDEX"`
-	HookID          int64
-	UUID            string
-	Type            HookTaskType
-	URL             string `xorm:"TEXT"`
-	Signature       string `xorm:"TEXT"`
-	api.Payloader   `xorm:"-" json:"-" gorm:"-"`
-	PayloadContent  string `xorm:"TEXT"`
-	ContentType     HookContentType
-	EventType       HookEventType
-	IsSSL           bool
-	IsDelivered     bool
-	Delivered       int64
-	DeliveredString string `xorm:"-" json:"-" gorm:"-"`
+	ID                          int64
+	RepoID                      int64 `xorm:"INDEX"`
+	HookID                      int64
+	UUID                        string
+	Type                        HookTaskType
+	URL                         string `xorm:"TEXT"`
+	Signature                   string `xorm:"TEXT"`
+	apiv1types.WebhookPayloader `xorm:"-" json:"-" gorm:"-"`
+	PayloadContent              string `xorm:"TEXT"`
+	ContentType                 HookContentType
+	EventType                   HookEventType
+	IsSSL                       bool
+	IsDelivered                 bool
+	Delivered                   int64
+	DeliveredString             string `xorm:"-" json:"-" gorm:"-"`
 
 	// History info.
 	IsSucceed       bool
@@ -559,12 +558,12 @@ func UpdateHookTask(t *HookTask) error {
 }
 
 // prepareHookTasks adds list of webhooks to task queue.
-func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Payloader, webhooks []*Webhook) (err error) {
+func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p apiv1types.WebhookPayloader, webhooks []*Webhook) (err error) {
 	if len(webhooks) == 0 {
 		return nil
 	}
 
-	var payloader api.Payloader
+	var payloader apiv1types.WebhookPayloader
 	for _, w := range webhooks {
 		switch event {
 		case HookEventTypeCreate:
@@ -634,15 +633,15 @@ func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Pay
 		}
 
 		if err = createHookTask(e, &HookTask{
-			RepoID:      repo.ID,
-			HookID:      w.ID,
-			Type:        w.HookTaskType,
-			URL:         w.URL,
-			Signature:   signature,
-			Payloader:   payloader,
-			ContentType: w.ContentType,
-			EventType:   event,
-			IsSSL:       w.IsSSL,
+			RepoID:           repo.ID,
+			HookID:           w.ID,
+			Type:             w.HookTaskType,
+			URL:              w.URL,
+			Signature:        signature,
+			WebhookPayloader: payloader,
+			ContentType:      w.ContentType,
+			EventType:        event,
+			IsSSL:            w.IsSSL,
 		}); err != nil {
 			return errors.Newf("createHookTask: %v", err)
 		}
@@ -655,7 +654,7 @@ func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Pay
 	return nil
 }
 
-func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payloader) error {
+func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p apiv1types.WebhookPayloader) error {
 	webhooks, err := getActiveWebhooksByRepoID(e, repo.ID)
 	if err != nil {
 		return errors.Newf("getActiveWebhooksByRepoID [%d]: %v", repo.ID, err)
@@ -674,7 +673,7 @@ func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payl
 }
 
 // PrepareWebhooks adds all active webhooks to task queue.
-func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error {
+func PrepareWebhooks(repo *Repository, event HookEventType, p apiv1types.WebhookPayloader) error {
 	// NOTE: To prevent too many cascading changes in a single refactoring PR, we
 	// choose to ignore this function in tests.
 	if x == nil && testutil.InTest {
@@ -684,7 +683,7 @@ func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) err
 }
 
 // TestWebhook adds the test webhook matches the ID to task queue.
-func TestWebhook(repo *Repository, event HookEventType, p api.Payloader, webhookID int64) error {
+func TestWebhook(repo *Repository, event HookEventType, p apiv1types.WebhookPayloader, webhookID int64) error {
 	webhook, err := GetWebhookOfRepoByID(repo.ID, webhookID)
 	if err != nil {
 		return errors.Newf("GetWebhookOfRepoByID [repo_id: %d, id: %d]: %v", repo.ID, webhookID, err)

+ 28 - 28
internal/database/webhook_dingtalk.go

@@ -6,9 +6,9 @@ import (
 	"strings"
 
 	"github.com/cockroachdb/errors"
-
 	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
+
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 )
 
 const (
@@ -55,31 +55,31 @@ func NewDingtalkActionCard(singleTitle, singleURL string) DingtalkActionCard {
 }
 
 // TODO: add content
-func GetDingtalkPayload(p api.Payloader, event HookEventType) (payload *DingtalkPayload, err error) {
+func GetDingtalkPayload(p apiv1types.WebhookPayloader, event HookEventType) (payload *DingtalkPayload, err error) {
 	switch event {
 	case HookEventTypeCreate:
-		payload = getDingtalkCreatePayload(p.(*api.CreatePayload))
+		payload = getDingtalkCreatePayload(p.(*apiv1types.WebhookCreatePayload))
 	case HookEventTypeDelete:
-		payload = getDingtalkDeletePayload(p.(*api.DeletePayload))
+		payload = getDingtalkDeletePayload(p.(*apiv1types.WebhookDeletePayload))
 	case HookEventTypeFork:
-		payload = getDingtalkForkPayload(p.(*api.ForkPayload))
+		payload = getDingtalkForkPayload(p.(*apiv1types.WebhookForkPayload))
 	case HookEventTypePush:
-		payload = getDingtalkPushPayload(p.(*api.PushPayload))
+		payload = getDingtalkPushPayload(p.(*apiv1types.WebhookPushPayload))
 	case HookEventTypeIssues:
-		payload = getDingtalkIssuesPayload(p.(*api.IssuesPayload))
+		payload = getDingtalkIssuesPayload(p.(*apiv1types.WebhookIssuesPayload))
 	case HookEventTypeIssueComment:
-		payload = getDingtalkIssueCommentPayload(p.(*api.IssueCommentPayload))
+		payload = getDingtalkIssueCommentPayload(p.(*apiv1types.WebhookIssueCommentPayload))
 	case HookEventTypePullRequest:
-		payload = getDingtalkPullRequestPayload(p.(*api.PullRequestPayload))
+		payload = getDingtalkPullRequestPayload(p.(*apiv1types.WebhookPullRequestPayload))
 	case HookEventTypeRelease:
-		payload = getDingtalkReleasePayload(p.(*api.ReleasePayload))
+		payload = getDingtalkReleasePayload(p.(*apiv1types.WebhookReleasePayload))
 	default:
 		return nil, errors.Errorf("unexpected event %q", event)
 	}
 	return payload, nil
 }
 
-func getDingtalkCreatePayload(p *api.CreatePayload) *DingtalkPayload {
+func getDingtalkCreatePayload(p *apiv1types.WebhookCreatePayload) *DingtalkPayload {
 	refName := git.RefShortName(p.Ref)
 	refType := strings.Title(p.RefType)
 
@@ -94,7 +94,7 @@ func getDingtalkCreatePayload(p *api.CreatePayload) *DingtalkPayload {
 	}
 }
 
-func getDingtalkDeletePayload(p *api.DeletePayload) *DingtalkPayload {
+func getDingtalkDeletePayload(p *apiv1types.WebhookDeletePayload) *DingtalkPayload {
 	refName := git.RefShortName(p.Ref)
 	refType := strings.Title(p.RefType)
 
@@ -109,7 +109,7 @@ func getDingtalkDeletePayload(p *api.DeletePayload) *DingtalkPayload {
 	}
 }
 
-func getDingtalkForkPayload(p *api.ForkPayload) *DingtalkPayload {
+func getDingtalkForkPayload(p *apiv1types.WebhookForkPayload) *DingtalkPayload {
 	actionCard := NewDingtalkActionCard("View Fork", p.Forkee.HTMLURL)
 	actionCard.Text += "# Repo Fork Event"
 	actionCard.Text += "\n- From Repo: **" + MarkdownLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) + "**"
@@ -121,7 +121,7 @@ func getDingtalkForkPayload(p *api.ForkPayload) *DingtalkPayload {
 	}
 }
 
-func getDingtalkPushPayload(p *api.PushPayload) *DingtalkPayload {
+func getDingtalkPushPayload(p *apiv1types.WebhookPushPayload) *DingtalkPayload {
 	refName := git.RefShortName(p.Ref)
 
 	pusher := p.Pusher.FullName
@@ -150,7 +150,7 @@ func getDingtalkPushPayload(p *api.PushPayload) *DingtalkPayload {
 	}
 }
 
-func getDingtalkIssuesPayload(p *api.IssuesPayload) *DingtalkPayload {
+func getDingtalkIssuesPayload(p *apiv1types.WebhookIssuesPayload) *DingtalkPayload {
 	issueName := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title)
 	issueURL := fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index)
 
@@ -159,11 +159,11 @@ func getDingtalkIssuesPayload(p *api.IssuesPayload) *DingtalkPayload {
 	actionCard.Text += "\n- Issue: **" + MarkdownLinkFormatter(issueURL, issueName) + "**"
 
 	switch p.Action {
-	case api.HOOK_ISSUE_ASSIGNED:
+	case apiv1types.WebhookIssueAssigned:
 		actionCard.Text += "\n- New Assignee: **" + p.Issue.Assignee.UserName + "**"
-	case api.HOOK_ISSUE_MILESTONED:
+	case apiv1types.WebhookIssueMilestoned:
 		actionCard.Text += "\n- New Milestone: **" + p.Issue.Milestone.Title + "**"
-	case api.HOOK_ISSUE_LABEL_UPDATED:
+	case apiv1types.WebhookIssueLabelUpdated:
 		if len(p.Issue.Labels) > 0 {
 			labels := make([]string, len(p.Issue.Labels))
 			for i, label := range p.Issue.Labels {
@@ -185,10 +185,10 @@ func getDingtalkIssuesPayload(p *api.IssuesPayload) *DingtalkPayload {
 	}
 }
 
-func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) *DingtalkPayload {
+func getDingtalkIssueCommentPayload(p *apiv1types.WebhookIssueCommentPayload) *DingtalkPayload {
 	issueName := fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)
 	commentURL := fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index)
-	if p.Action != api.HOOK_ISSUE_COMMENT_DELETED {
+	if p.Action != apiv1types.WebhookIssueCommentDeleted {
 		commentURL += "#" + CommentHashTag(p.Comment.ID)
 	}
 
@@ -206,9 +206,9 @@ func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) *DingtalkPayload
 	}
 }
 
-func getDingtalkPullRequestPayload(p *api.PullRequestPayload) *DingtalkPayload {
+func getDingtalkPullRequestPayload(p *apiv1types.WebhookPullRequestPayload) *DingtalkPayload {
 	title := "# Pull Request " + strings.Title(string(p.Action))
-	if p.Action == api.HOOK_ISSUE_CLOSED && p.PullRequest.HasMerged {
+	if p.Action == apiv1types.WebhookIssueClosed && p.PullRequest.HasMerged {
 		title = "# Pull Request Merged"
 	}
 
@@ -216,11 +216,11 @@ func getDingtalkPullRequestPayload(p *api.PullRequestPayload) *DingtalkPayload {
 
 	content := "- PR: " + MarkdownLinkFormatter(pullRequestURL, fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
 	switch p.Action {
-	case api.HOOK_ISSUE_ASSIGNED:
+	case apiv1types.WebhookIssueAssigned:
 		content += "\n- New Assignee: **" + p.PullRequest.Assignee.UserName + "**"
-	case api.HOOK_ISSUE_MILESTONED:
+	case apiv1types.WebhookIssueMilestoned:
 		content += "\n- New Milestone: *" + p.PullRequest.Milestone.Title + "*"
-	case api.HOOK_ISSUE_LABEL_UPDATED:
+	case apiv1types.WebhookIssueLabelUpdated:
 		labels := make([]string, len(p.PullRequest.Labels))
 		for i, label := range p.PullRequest.Labels {
 			labels[i] = "**" + label.Name + "**"
@@ -231,7 +231,7 @@ func getDingtalkPullRequestPayload(p *api.PullRequestPayload) *DingtalkPayload {
 	actionCard := NewDingtalkActionCard("View Pull Request", pullRequestURL)
 	actionCard.Text += title + "\n" + content
 
-	if p.Action == api.HOOK_ISSUE_OPENED || p.Action == api.HOOK_ISSUE_EDITED {
+	if p.Action == apiv1types.WebhookIssueOpened || p.Action == apiv1types.WebhookIssueEdited {
 		actionCard.Text += "\n> " + p.PullRequest.Body
 	}
 
@@ -241,7 +241,7 @@ func getDingtalkPullRequestPayload(p *api.PullRequestPayload) *DingtalkPayload {
 	}
 }
 
-func getDingtalkReleasePayload(p *api.ReleasePayload) *DingtalkPayload {
+func getDingtalkReleasePayload(p *apiv1types.WebhookReleasePayload) *DingtalkPayload {
 	releaseURL := p.Repository.HTMLURL + "/src/" + p.Release.TagName
 
 	author := p.Release.Author.FullName

+ 51 - 52
internal/database/webhook_discord.go

@@ -7,11 +7,10 @@ import (
 	"strings"
 
 	"github.com/cockroachdb/errors"
-
 	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/conf"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 )
 
 type DiscordEmbedFooterObject struct {
@@ -67,7 +66,7 @@ func DiscordSHALinkFormatter(url, text string) string {
 }
 
 // getDiscordCreatePayload composes Discord payload for create new branch or tag.
-func getDiscordCreatePayload(p *api.CreatePayload) *DiscordPayload {
+func getDiscordCreatePayload(p *apiv1types.WebhookCreatePayload) *DiscordPayload {
 	refName := git.RefShortName(p.Ref)
 	repoLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
 	refLink := DiscordLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName)
@@ -78,14 +77,14 @@ func getDiscordCreatePayload(p *api.CreatePayload) *DiscordPayload {
 			URL:         conf.Server.ExternalURL + p.Sender.UserName,
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
-				IconURL: p.Sender.AvatarUrl,
+				IconURL: p.Sender.AvatarURL,
 			},
 		}},
 	}
 }
 
 // getDiscordDeletePayload composes Discord payload for delete a branch or tag.
-func getDiscordDeletePayload(p *api.DeletePayload) *DiscordPayload {
+func getDiscordDeletePayload(p *apiv1types.WebhookDeletePayload) *DiscordPayload {
 	refName := git.RefShortName(p.Ref)
 	repoLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
 	content := fmt.Sprintf("Deleted %s: %s/%s", p.RefType, repoLink, refName)
@@ -95,14 +94,14 @@ func getDiscordDeletePayload(p *api.DeletePayload) *DiscordPayload {
 			URL:         conf.Server.ExternalURL + p.Sender.UserName,
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
-				IconURL: p.Sender.AvatarUrl,
+				IconURL: p.Sender.AvatarURL,
 			},
 		}},
 	}
 }
 
 // getDiscordForkPayload composes Discord payload for forked by a repository.
-func getDiscordForkPayload(p *api.ForkPayload) *DiscordPayload {
+func getDiscordForkPayload(p *apiv1types.WebhookForkPayload) *DiscordPayload {
 	baseLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
 	forkLink := DiscordLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName)
 	content := fmt.Sprintf("%s is forked to %s", baseLink, forkLink)
@@ -112,13 +111,13 @@ func getDiscordForkPayload(p *api.ForkPayload) *DiscordPayload {
 			URL:         conf.Server.ExternalURL + p.Sender.UserName,
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
-				IconURL: p.Sender.AvatarUrl,
+				IconURL: p.Sender.AvatarURL,
 			},
 		}},
 	}
 }
 
-func getDiscordPushPayload(p *api.PushPayload, slack *SlackMeta) *DiscordPayload {
+func getDiscordPushPayload(p *apiv1types.WebhookPushPayload, slack *SlackMeta) *DiscordPayload {
 	// n new commits
 	var (
 		branchName   = git.RefShortName(p.Ref)
@@ -162,37 +161,37 @@ func getDiscordPushPayload(p *api.PushPayload, slack *SlackMeta) *DiscordPayload
 			Color:       int(color),
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
-				IconURL: p.Sender.AvatarUrl,
+				IconURL: p.Sender.AvatarURL,
 			},
 		}},
 	}
 }
 
-func getDiscordIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) *DiscordPayload {
+func getDiscordIssuesPayload(p *apiv1types.WebhookIssuesPayload, slack *SlackMeta) *DiscordPayload {
 	title := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title)
 	url := fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index)
 	content := ""
 	fields := make([]*DiscordEmbedFieldObject, 0, 1)
 	switch p.Action {
-	case api.HOOK_ISSUE_OPENED:
+	case apiv1types.WebhookIssueOpened:
 		title = "New issue: " + title
 		content = p.Issue.Body
-	case api.HOOK_ISSUE_CLOSED:
+	case apiv1types.WebhookIssueClosed:
 		title = "Issue closed: " + title
-	case api.HOOK_ISSUE_REOPENED:
+	case apiv1types.WebhookIssueReopened:
 		title = "Issue re-opened: " + title
-	case api.HOOK_ISSUE_EDITED:
+	case apiv1types.WebhookIssueEdited:
 		title = "Issue edited: " + title
 		content = p.Issue.Body
-	case api.HOOK_ISSUE_ASSIGNED:
+	case apiv1types.WebhookIssueAssigned:
 		title = "Issue assigned: " + title
 		fields = []*DiscordEmbedFieldObject{{
 			Name:  "New Assignee",
 			Value: p.Issue.Assignee.UserName,
 		}}
-	case api.HOOK_ISSUE_UNASSIGNED:
+	case apiv1types.WebhookIssueUnassigned:
 		title = "Issue unassigned: " + title
-	case api.HOOK_ISSUE_LABEL_UPDATED:
+	case apiv1types.WebhookIssueLabelUpdated:
 		title = "Issue labels updated: " + title
 		labels := make([]string, len(p.Issue.Labels))
 		for i := range p.Issue.Labels {
@@ -205,17 +204,17 @@ func getDiscordIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) *DiscordPay
 			Name:  "Labels",
 			Value: strings.Join(labels, ", "),
 		}}
-	case api.HOOK_ISSUE_LABEL_CLEARED:
+	case apiv1types.WebhookIssueLabelCleared:
 		title = "Issue labels cleared: " + title
-	case api.HOOK_ISSUE_SYNCHRONIZED:
+	case apiv1types.WebhookIssueSynchronized:
 		title = "Issue synchronized: " + title
-	case api.HOOK_ISSUE_MILESTONED:
+	case apiv1types.WebhookIssueMilestoned:
 		title = "Issue milestoned: " + title
 		fields = []*DiscordEmbedFieldObject{{
 			Name:  "New Milestone",
 			Value: p.Issue.Milestone.Title,
 		}}
-	case api.HOOK_ISSUE_DEMILESTONED:
+	case apiv1types.WebhookIssueDemilestoned:
 		title = "Issue demilestoned: " + title
 	}
 
@@ -233,26 +232,26 @@ func getDiscordIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) *DiscordPay
 			},
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
-				IconURL: p.Sender.AvatarUrl,
+				IconURL: p.Sender.AvatarURL,
 			},
 			Fields: fields,
 		}},
 	}
 }
 
-func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) *DiscordPayload {
+func getDiscordIssueCommentPayload(p *apiv1types.WebhookIssueCommentPayload, slack *SlackMeta) *DiscordPayload {
 	title := fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)
 	url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID))
 	content := ""
 	fields := make([]*DiscordEmbedFieldObject, 0, 1)
 	switch p.Action {
-	case api.HOOK_ISSUE_COMMENT_CREATED:
+	case apiv1types.WebhookIssueCommentCreated:
 		title = "New comment: " + title
 		content = p.Comment.Body
-	case api.HOOK_ISSUE_COMMENT_EDITED:
+	case apiv1types.WebhookIssueCommentEdited:
 		title = "Comment edited: " + title
 		content = p.Comment.Body
-	case api.HOOK_ISSUE_COMMENT_DELETED:
+	case apiv1types.WebhookIssueCommentDeleted:
 		title = "Comment deleted: " + title
 		url = fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index)
 		content = p.Comment.Body
@@ -272,42 +271,42 @@ func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta)
 			},
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
-				IconURL: p.Sender.AvatarUrl,
+				IconURL: p.Sender.AvatarURL,
 			},
 			Fields: fields,
 		}},
 	}
 }
 
-func getDiscordPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) *DiscordPayload {
+func getDiscordPullRequestPayload(p *apiv1types.WebhookPullRequestPayload, slack *SlackMeta) *DiscordPayload {
 	title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)
 	url := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index)
 	content := ""
 	fields := make([]*DiscordEmbedFieldObject, 0, 1)
 	switch p.Action {
-	case api.HOOK_ISSUE_OPENED:
+	case apiv1types.WebhookIssueOpened:
 		title = "New pull request: " + title
 		content = p.PullRequest.Body
-	case api.HOOK_ISSUE_CLOSED:
+	case apiv1types.WebhookIssueClosed:
 		if p.PullRequest.HasMerged {
 			title = "Pull request merged: " + title
 		} else {
 			title = "Pull request closed: " + title
 		}
-	case api.HOOK_ISSUE_REOPENED:
+	case apiv1types.WebhookIssueReopened:
 		title = "Pull request re-opened: " + title
-	case api.HOOK_ISSUE_EDITED:
+	case apiv1types.WebhookIssueEdited:
 		title = "Pull request edited: " + title
 		content = p.PullRequest.Body
-	case api.HOOK_ISSUE_ASSIGNED:
+	case apiv1types.WebhookIssueAssigned:
 		title = "Pull request assigned: " + title
 		fields = []*DiscordEmbedFieldObject{{
 			Name:  "New Assignee",
 			Value: p.PullRequest.Assignee.UserName,
 		}}
-	case api.HOOK_ISSUE_UNASSIGNED:
+	case apiv1types.WebhookIssueUnassigned:
 		title = "Pull request unassigned: " + title
-	case api.HOOK_ISSUE_LABEL_UPDATED:
+	case apiv1types.WebhookIssueLabelUpdated:
 		title = "Pull request labels updated: " + title
 		labels := make([]string, len(p.PullRequest.Labels))
 		for i := range p.PullRequest.Labels {
@@ -317,17 +316,17 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) *
 			Name:  "Labels",
 			Value: strings.Join(labels, ", "),
 		}}
-	case api.HOOK_ISSUE_LABEL_CLEARED:
+	case apiv1types.WebhookIssueLabelCleared:
 		title = "Pull request labels cleared: " + title
-	case api.HOOK_ISSUE_SYNCHRONIZED:
+	case apiv1types.WebhookIssueSynchronized:
 		title = "Pull request synchronized: " + title
-	case api.HOOK_ISSUE_MILESTONED:
+	case apiv1types.WebhookIssueMilestoned:
 		title = "Pull request milestoned: " + title
 		fields = []*DiscordEmbedFieldObject{{
 			Name:  "New Milestone",
 			Value: p.PullRequest.Milestone.Title,
 		}}
-	case api.HOOK_ISSUE_DEMILESTONED:
+	case apiv1types.WebhookIssueDemilestoned:
 		title = "Pull request demilestoned: " + title
 	}
 
@@ -345,14 +344,14 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) *
 			},
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
-				IconURL: p.Sender.AvatarUrl,
+				IconURL: p.Sender.AvatarURL,
 			},
 			Fields: fields,
 		}},
 	}
 }
 
-func getDiscordReleasePayload(p *api.ReleasePayload) *DiscordPayload {
+func getDiscordReleasePayload(p *apiv1types.WebhookReleasePayload) *DiscordPayload {
 	repoLink := DiscordLinkFormatter(p.Repository.HTMLURL, p.Repository.Name)
 	refLink := DiscordLinkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName)
 	content := fmt.Sprintf("Published new release %s of %s", refLink, repoLink)
@@ -362,13 +361,13 @@ func getDiscordReleasePayload(p *api.ReleasePayload) *DiscordPayload {
 			URL:         conf.Server.ExternalURL + p.Sender.UserName,
 			Author: &DiscordEmbedAuthorObject{
 				Name:    p.Sender.UserName,
-				IconURL: p.Sender.AvatarUrl,
+				IconURL: p.Sender.AvatarURL,
 			},
 		}},
 	}
 }
 
-func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (payload *DiscordPayload, err error) {
+func GetDiscordPayload(p apiv1types.WebhookPayloader, event HookEventType, meta string) (payload *DiscordPayload, err error) {
 	slack := &SlackMeta{}
 	if err := json.Unmarshal([]byte(meta), slack); err != nil {
 		return nil, errors.Newf("unmarshal: %v", err)
@@ -376,21 +375,21 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (paylo
 
 	switch event {
 	case HookEventTypeCreate:
-		payload = getDiscordCreatePayload(p.(*api.CreatePayload))
+		payload = getDiscordCreatePayload(p.(*apiv1types.WebhookCreatePayload))
 	case HookEventTypeDelete:
-		payload = getDiscordDeletePayload(p.(*api.DeletePayload))
+		payload = getDiscordDeletePayload(p.(*apiv1types.WebhookDeletePayload))
 	case HookEventTypeFork:
-		payload = getDiscordForkPayload(p.(*api.ForkPayload))
+		payload = getDiscordForkPayload(p.(*apiv1types.WebhookForkPayload))
 	case HookEventTypePush:
-		payload = getDiscordPushPayload(p.(*api.PushPayload), slack)
+		payload = getDiscordPushPayload(p.(*apiv1types.WebhookPushPayload), slack)
 	case HookEventTypeIssues:
-		payload = getDiscordIssuesPayload(p.(*api.IssuesPayload), slack)
+		payload = getDiscordIssuesPayload(p.(*apiv1types.WebhookIssuesPayload), slack)
 	case HookEventTypeIssueComment:
-		payload = getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
+		payload = getDiscordIssueCommentPayload(p.(*apiv1types.WebhookIssueCommentPayload), slack)
 	case HookEventTypePullRequest:
-		payload = getDiscordPullRequestPayload(p.(*api.PullRequestPayload), slack)
+		payload = getDiscordPullRequestPayload(p.(*apiv1types.WebhookPullRequestPayload), slack)
 	case HookEventTypeRelease:
-		payload = getDiscordReleasePayload(p.(*api.ReleasePayload))
+		payload = getDiscordReleasePayload(p.(*apiv1types.WebhookReleasePayload))
 	default:
 		return nil, errors.Errorf("unexpected event %q", event)
 	}

+ 42 - 43
internal/database/webhook_slack.go

@@ -6,11 +6,10 @@ import (
 	"strings"
 
 	"github.com/cockroachdb/errors"
-
 	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/conf"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 )
 
 type SlackMeta struct {
@@ -68,7 +67,7 @@ func SlackLinkFormatter(url, text string) string {
 }
 
 // getSlackCreatePayload composes Slack payload for create new branch or tag.
-func getSlackCreatePayload(p *api.CreatePayload) *SlackPayload {
+func getSlackCreatePayload(p *apiv1types.WebhookCreatePayload) *SlackPayload {
 	refName := git.RefShortName(p.Ref)
 	repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
 	refLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName)
@@ -79,7 +78,7 @@ func getSlackCreatePayload(p *api.CreatePayload) *SlackPayload {
 }
 
 // getSlackDeletePayload composes Slack payload for delete a branch or tag.
-func getSlackDeletePayload(p *api.DeletePayload) *SlackPayload {
+func getSlackDeletePayload(p *apiv1types.WebhookDeletePayload) *SlackPayload {
 	refName := git.RefShortName(p.Ref)
 	repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
 	text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName)
@@ -89,7 +88,7 @@ func getSlackDeletePayload(p *api.DeletePayload) *SlackPayload {
 }
 
 // getSlackForkPayload composes Slack payload for forked by a repository.
-func getSlackForkPayload(p *api.ForkPayload) *SlackPayload {
+func getSlackForkPayload(p *apiv1types.WebhookForkPayload) *SlackPayload {
 	baseLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
 	forkLink := SlackLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName)
 	text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink)
@@ -98,7 +97,7 @@ func getSlackForkPayload(p *api.ForkPayload) *SlackPayload {
 	}
 }
 
-func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) *SlackPayload {
+func getSlackPushPayload(p *apiv1types.WebhookPushPayload, slack *SlackMeta) *SlackPayload {
 	// n new commits
 	var (
 		branchName   = git.RefShortName(p.Ref)
@@ -143,36 +142,36 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) *SlackPayload {
 	}
 }
 
-func getSlackIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) *SlackPayload {
+func getSlackIssuesPayload(p *apiv1types.WebhookIssuesPayload, slack *SlackMeta) *SlackPayload {
 	senderLink := SlackLinkFormatter(conf.Server.ExternalURL+p.Sender.UserName, p.Sender.UserName)
 	titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index),
 		fmt.Sprintf("#%d %s", p.Index, p.Issue.Title))
 	var text, title, attachmentText string
 	switch p.Action {
-	case api.HOOK_ISSUE_OPENED:
+	case apiv1types.WebhookIssueOpened:
 		text = fmt.Sprintf("[%s] New issue created by %s", p.Repository.FullName, senderLink)
 		title = titleLink
 		attachmentText = SlackTextFormatter(p.Issue.Body)
-	case api.HOOK_ISSUE_CLOSED:
+	case apiv1types.WebhookIssueClosed:
 		text = fmt.Sprintf("[%s] Issue closed: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_REOPENED:
+	case apiv1types.WebhookIssueReopened:
 		text = fmt.Sprintf("[%s] Issue re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_EDITED:
+	case apiv1types.WebhookIssueEdited:
 		text = fmt.Sprintf("[%s] Issue edited: %s by %s", p.Repository.FullName, titleLink, senderLink)
 		attachmentText = SlackTextFormatter(p.Issue.Body)
-	case api.HOOK_ISSUE_ASSIGNED:
+	case apiv1types.WebhookIssueAssigned:
 		text = fmt.Sprintf("[%s] Issue assigned to %s: %s by %s", p.Repository.FullName,
 			SlackLinkFormatter(conf.Server.ExternalURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName),
 			titleLink, senderLink)
-	case api.HOOK_ISSUE_UNASSIGNED:
+	case apiv1types.WebhookIssueUnassigned:
 		text = fmt.Sprintf("[%s] Issue unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_LABEL_UPDATED:
+	case apiv1types.WebhookIssueLabelUpdated:
 		text = fmt.Sprintf("[%s] Issue labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_LABEL_CLEARED:
+	case apiv1types.WebhookIssueLabelCleared:
 		text = fmt.Sprintf("[%s] Issue labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_MILESTONED:
+	case apiv1types.WebhookIssueMilestoned:
 		text = fmt.Sprintf("[%s] Issue milestoned: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_DEMILESTONED:
+	case apiv1types.WebhookIssueDemilestoned:
 		text = fmt.Sprintf("[%s] Issue demilestoned: %s by %s", p.Repository.FullName, titleLink, senderLink)
 	}
 
@@ -189,21 +188,21 @@ func getSlackIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) *SlackPayload
 	}
 }
 
-func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) *SlackPayload {
+func getSlackIssueCommentPayload(p *apiv1types.WebhookIssueCommentPayload, slack *SlackMeta) *SlackPayload {
 	senderLink := SlackLinkFormatter(conf.Server.ExternalURL+p.Sender.UserName, p.Sender.UserName)
 	titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)),
 		fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
 	var text, title, attachmentText string
 	switch p.Action {
-	case api.HOOK_ISSUE_COMMENT_CREATED:
+	case apiv1types.WebhookIssueCommentCreated:
 		text = fmt.Sprintf("[%s] New comment created by %s", p.Repository.FullName, senderLink)
 		title = titleLink
 		attachmentText = SlackTextFormatter(p.Comment.Body)
-	case api.HOOK_ISSUE_COMMENT_EDITED:
+	case apiv1types.WebhookIssueCommentEdited:
 		text = fmt.Sprintf("[%s] Comment edited by %s", p.Repository.FullName, senderLink)
 		title = titleLink
 		attachmentText = SlackTextFormatter(p.Comment.Body)
-	case api.HOOK_ISSUE_COMMENT_DELETED:
+	case apiv1types.WebhookIssueCommentDeleted:
 		text = fmt.Sprintf("[%s] Comment deleted by %s", p.Repository.FullName, senderLink)
 		title = SlackLinkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index),
 			fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
@@ -223,42 +222,42 @@ func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) *
 	}
 }
 
-func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) *SlackPayload {
+func getSlackPullRequestPayload(p *apiv1types.WebhookPullRequestPayload, slack *SlackMeta) *SlackPayload {
 	senderLink := SlackLinkFormatter(conf.Server.ExternalURL+p.Sender.UserName, p.Sender.UserName)
 	titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
 		fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
 	var text, title, attachmentText string
 	switch p.Action {
-	case api.HOOK_ISSUE_OPENED:
+	case apiv1types.WebhookIssueOpened:
 		text = fmt.Sprintf("[%s] Pull request submitted by %s", p.Repository.FullName, senderLink)
 		title = titleLink
 		attachmentText = SlackTextFormatter(p.PullRequest.Body)
-	case api.HOOK_ISSUE_CLOSED:
+	case apiv1types.WebhookIssueClosed:
 		if p.PullRequest.HasMerged {
 			text = fmt.Sprintf("[%s] Pull request merged: %s by %s", p.Repository.FullName, titleLink, senderLink)
 		} else {
 			text = fmt.Sprintf("[%s] Pull request closed: %s by %s", p.Repository.FullName, titleLink, senderLink)
 		}
-	case api.HOOK_ISSUE_REOPENED:
+	case apiv1types.WebhookIssueReopened:
 		text = fmt.Sprintf("[%s] Pull request re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_EDITED:
+	case apiv1types.WebhookIssueEdited:
 		text = fmt.Sprintf("[%s] Pull request edited: %s by %s", p.Repository.FullName, titleLink, senderLink)
 		attachmentText = SlackTextFormatter(p.PullRequest.Body)
-	case api.HOOK_ISSUE_ASSIGNED:
+	case apiv1types.WebhookIssueAssigned:
 		text = fmt.Sprintf("[%s] Pull request assigned to %s: %s by %s", p.Repository.FullName,
 			SlackLinkFormatter(conf.Server.ExternalURL+p.PullRequest.Assignee.UserName, p.PullRequest.Assignee.UserName),
 			titleLink, senderLink)
-	case api.HOOK_ISSUE_UNASSIGNED:
+	case apiv1types.WebhookIssueUnassigned:
 		text = fmt.Sprintf("[%s] Pull request unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_LABEL_UPDATED:
+	case apiv1types.WebhookIssueLabelUpdated:
 		text = fmt.Sprintf("[%s] Pull request labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_LABEL_CLEARED:
+	case apiv1types.WebhookIssueLabelCleared:
 		text = fmt.Sprintf("[%s] Pull request labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_SYNCHRONIZED:
+	case apiv1types.WebhookIssueSynchronized:
 		text = fmt.Sprintf("[%s] Pull request synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_MILESTONED:
+	case apiv1types.WebhookIssueMilestoned:
 		text = fmt.Sprintf("[%s] Pull request milestoned: %s by %s", p.Repository.FullName, titleLink, senderLink)
-	case api.HOOK_ISSUE_DEMILESTONED:
+	case apiv1types.WebhookIssueDemilestoned:
 		text = fmt.Sprintf("[%s] Pull request demilestoned: %s by %s", p.Repository.FullName, titleLink, senderLink)
 	}
 
@@ -275,7 +274,7 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) *Sl
 	}
 }
 
-func getSlackReleasePayload(p *api.ReleasePayload) *SlackPayload {
+func getSlackReleasePayload(p *apiv1types.WebhookReleasePayload) *SlackPayload {
 	repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.Name)
 	refLink := SlackLinkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName)
 	text := fmt.Sprintf("[%s] new release %s published by %s", repoLink, refLink, p.Sender.UserName)
@@ -284,7 +283,7 @@ func getSlackReleasePayload(p *api.ReleasePayload) *SlackPayload {
 	}
 }
 
-func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (payload *SlackPayload, err error) {
+func GetSlackPayload(p apiv1types.WebhookPayloader, event HookEventType, meta string) (payload *SlackPayload, err error) {
 	slack := &SlackMeta{}
 	if err := json.Unmarshal([]byte(meta), slack); err != nil {
 		return nil, errors.Newf("unmarshal: %v", err)
@@ -292,21 +291,21 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (payload
 
 	switch event {
 	case HookEventTypeCreate:
-		payload = getSlackCreatePayload(p.(*api.CreatePayload))
+		payload = getSlackCreatePayload(p.(*apiv1types.WebhookCreatePayload))
 	case HookEventTypeDelete:
-		payload = getSlackDeletePayload(p.(*api.DeletePayload))
+		payload = getSlackDeletePayload(p.(*apiv1types.WebhookDeletePayload))
 	case HookEventTypeFork:
-		payload = getSlackForkPayload(p.(*api.ForkPayload))
+		payload = getSlackForkPayload(p.(*apiv1types.WebhookForkPayload))
 	case HookEventTypePush:
-		payload = getSlackPushPayload(p.(*api.PushPayload), slack)
+		payload = getSlackPushPayload(p.(*apiv1types.WebhookPushPayload), slack)
 	case HookEventTypeIssues:
-		payload = getSlackIssuesPayload(p.(*api.IssuesPayload), slack)
+		payload = getSlackIssuesPayload(p.(*apiv1types.WebhookIssuesPayload), slack)
 	case HookEventTypeIssueComment:
-		payload = getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
+		payload = getSlackIssueCommentPayload(p.(*apiv1types.WebhookIssueCommentPayload), slack)
 	case HookEventTypePullRequest:
-		payload = getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
+		payload = getSlackPullRequestPayload(p.(*apiv1types.WebhookPullRequestPayload), slack)
 	case HookEventTypeRelease:
-		payload = getSlackReleasePayload(p.(*api.ReleasePayload))
+		payload = getSlackReleasePayload(p.(*apiv1types.WebhookReleasePayload))
 	default:
 		return nil, errors.Errorf("unexpected event %q", event)
 	}

+ 303 - 0
internal/route/api/v1/adapters.go

@@ -0,0 +1,303 @@
+package v1
+
+import (
+	"context"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/gogs/git-module"
+
+	"gogs.io/gogs/internal/conf"
+	"gogs.io/gogs/internal/database"
+	"gogs.io/gogs/internal/markup"
+	"gogs.io/gogs/internal/route/api/v1/types"
+)
+
+// toAllowedPageSize makes sure page size is in allowed range.
+func toAllowedPageSize(size int) int {
+	if size <= 0 {
+		size = 10
+	} else if size > conf.API.MaxResponseItems {
+		size = conf.API.MaxResponseItems
+	}
+	return size
+}
+
+// toUser converts a database user to an API user with the full name sanitized
+// for safe HTML rendering.
+func toUser(u *database.User) *types.User {
+	return &types.User{
+		ID:        u.ID,
+		UserName:  u.Name,
+		Login:     u.Name,
+		FullName:  markup.Sanitize(u.FullName),
+		Email:     u.Email,
+		AvatarURL: u.AvatarURL(),
+	}
+}
+
+func toUserEmail(email *database.EmailAddress) *types.UserEmail {
+	return &types.UserEmail{
+		Email:    email.Email,
+		Verified: email.IsActivated,
+		Primary:  email.IsPrimary,
+	}
+}
+
+func toBranch(b *database.Branch, c *git.Commit) *types.RepositoryBranch {
+	return &types.RepositoryBranch{
+		Name:   b.Name,
+		Commit: toPayloadCommit(c),
+	}
+}
+
+func toTag(b *database.Tag, c *git.Commit) *tag {
+	return &tag{
+		Name:   b.Name,
+		Commit: toPayloadCommit(c),
+	}
+}
+
+func toPayloadCommit(c *git.Commit) *types.WebhookPayloadCommit {
+	authorUsername := ""
+	author, err := database.Handle.Users().GetByEmail(context.TODO(), c.Author.Email)
+	if err == nil {
+		authorUsername = author.Name
+	}
+	committerUsername := ""
+	committer, err := database.Handle.Users().GetByEmail(context.TODO(), c.Committer.Email)
+	if err == nil {
+		committerUsername = committer.Name
+	}
+	return &types.WebhookPayloadCommit{
+		ID:      c.ID.String(),
+		Message: c.Message,
+		URL:     "Not implemented",
+		Author: &types.WebhookPayloadUser{
+			Name:     c.Author.Name,
+			Email:    c.Author.Email,
+			UserName: authorUsername,
+		},
+		Committer: &types.WebhookPayloadUser{
+			Name:     c.Committer.Name,
+			Email:    c.Committer.Email,
+			UserName: committerUsername,
+		},
+		Timestamp: c.Author.When,
+	}
+}
+
+func toUserPublicKey(apiLink string, key *database.PublicKey) *types.UserPublicKey {
+	return &types.UserPublicKey{
+		ID:      key.ID,
+		Key:     key.Content,
+		URL:     apiLink + strconv.FormatInt(key.ID, 10),
+		Title:   key.Name,
+		Created: key.Created,
+	}
+}
+
+func toRepositoryHook(repoLink string, w *database.Webhook) *types.RepositoryHook {
+	config := map[string]string{
+		"url":          w.URL,
+		"content_type": w.ContentType.Name(),
+	}
+	if w.HookTaskType == database.SLACK {
+		s := w.SlackMeta()
+		config["channel"] = s.Channel
+		config["username"] = s.Username
+		config["icon_url"] = s.IconURL
+		config["color"] = s.Color
+	}
+
+	return &types.RepositoryHook{
+		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 *database.DeployKey) *types.RepositoryDeployKey {
+	return &types.RepositoryDeployKey{
+		ID:       key.ID,
+		Key:      key.Content,
+		URL:      apiLink + strconv.FormatInt(key.ID, 10),
+		Title:    key.Name,
+		Created:  key.Created,
+		ReadOnly: true, // All deploy keys are read-only.
+	}
+}
+
+func toOrganization(org *database.User) *types.Organization {
+	return &types.Organization{
+		ID:          org.ID,
+		AvatarURL:   org.AvatarURL(),
+		UserName:    org.Name,
+		FullName:    org.FullName,
+		Description: org.Description,
+		Website:     org.Website,
+		Location:    org.Location,
+	}
+}
+
+func toOrganizationTeam(team *database.Team) *types.OrganizationTeam {
+	return &types.OrganizationTeam{
+		ID:          team.ID,
+		Name:        team.Name,
+		Description: team.Description,
+		Permission:  team.Authorize.String(),
+	}
+}
+
+func toIssueLabel(l *database.Label) *types.IssueLabel {
+	return &types.IssueLabel{
+		ID:    l.ID,
+		Name:  l.Name,
+		Color: strings.TrimLeft(l.Color, "#"),
+	}
+}
+
+func issueState(isClosed bool) types.IssueStateType {
+	if isClosed {
+		return types.IssueStateClosed
+	}
+	return types.IssueStateOpen
+}
+
+// toIssue converts a database issue to an API issue.
+// It assumes the following fields have been assigned with valid values:
+// Required - Poster, Labels
+// Optional - Milestone, Assignee, PullRequest
+func toIssue(issue *database.Issue) *types.Issue {
+	labels := make([]*types.IssueLabel, len(issue.Labels))
+	for i := range issue.Labels {
+		labels[i] = toIssueLabel(issue.Labels[i])
+	}
+
+	apiIssue := &types.Issue{
+		ID:       issue.ID,
+		Index:    issue.Index,
+		Poster:   toUser(issue.Poster),
+		Title:    issue.Title,
+		Body:     issue.Content,
+		Labels:   labels,
+		State:    issueState(issue.IsClosed),
+		Comments: issue.NumComments,
+		Created:  issue.Created,
+		Updated:  issue.Updated,
+	}
+
+	if issue.Milestone != nil {
+		apiIssue.Milestone = toIssueMilestone(issue.Milestone)
+	}
+	if issue.Assignee != nil {
+		apiIssue.Assignee = toUser(issue.Assignee)
+	}
+	if issue.IsPull {
+		apiIssue.PullRequest = &types.PullRequestMeta{
+			HasMerged: issue.PullRequest.HasMerged,
+		}
+		if issue.PullRequest.HasMerged {
+			apiIssue.PullRequest.Merged = &issue.PullRequest.Merged
+		}
+	}
+
+	return apiIssue
+}
+
+func toIssueComment(c *database.Comment) *types.IssueComment {
+	return &types.IssueComment{
+		ID:      c.ID,
+		HTMLURL: c.HTMLURL(),
+		Poster:  toUser(c.Poster),
+		Body:    c.Content,
+		Created: c.Created,
+		Updated: c.Updated,
+	}
+}
+
+func toIssueMilestone(m *database.Milestone) *types.IssueMilestone {
+	ms := &types.IssueMilestone{
+		ID:           m.ID,
+		State:        issueState(m.IsClosed),
+		Title:        m.Name,
+		Description:  m.Content,
+		OpenIssues:   m.NumOpenIssues,
+		ClosedIssues: m.NumClosedIssues,
+	}
+	if m.IsClosed {
+		ms.Closed = &m.ClosedDate
+	}
+	if m.Deadline.Year() < 9999 {
+		ms.Deadline = &m.Deadline
+	}
+	return ms
+}
+
+// toRelease converts a database release to an API release.
+// It assumes the Publisher field has been assigned.
+func toRelease(r *database.Release) *types.RepositoryRelease {
+	return &types.RepositoryRelease{
+		ID:              r.ID,
+		TagName:         r.TagName,
+		TargetCommitish: r.Target,
+		Name:            r.Title,
+		Body:            r.Note,
+		Draft:           r.IsDraft,
+		Prerelease:      r.IsPrerelease,
+		Author:          toUser(r.Publisher),
+		Created:         r.Created,
+	}
+}
+
+func toRepositoryCollaborator(c *database.Collaborator) *types.RepositoryCollaborator {
+	return &types.RepositoryCollaborator{
+		User: toUser(c.User),
+		Permissions: types.RepositoryPermission{
+			Admin: c.Collaboration.Mode >= database.AccessModeAdmin,
+			Push:  c.Collaboration.Mode >= database.AccessModeWrite,
+			Pull:  c.Collaboration.Mode >= database.AccessModeRead,
+		},
+	}
+}
+
+// toRepository converts a database repository to an API repository.
+// It assumes the Owner field has been loaded on the repo.
+func toRepository(repo *database.Repository, perm *types.RepositoryPermission) *types.Repository {
+	cloneLink := repo.CloneLink()
+	apiRepo := &types.Repository{
+		ID:            repo.ID,
+		Owner:         toUser(repo.Owner),
+		Name:          repo.Name,
+		FullName:      repo.FullName(),
+		Description:   repo.Description,
+		Private:       repo.IsPrivate,
+		Fork:          repo.IsFork,
+		Empty:         repo.IsBare,
+		Mirror:        repo.IsMirror,
+		Size:          repo.Size,
+		HTMLURL:       repo.HTMLURL(),
+		SSHURL:        cloneLink.SSH,
+		CloneURL:      cloneLink.HTTPS,
+		Website:       repo.Website,
+		Stars:         repo.NumStars,
+		Forks:         repo.NumForks,
+		Watchers:      repo.NumWatches,
+		OpenIssues:    repo.NumOpenIssues,
+		DefaultBranch: repo.DefaultBranch,
+		Created:       repo.Created,
+		Updated:       repo.Updated,
+		Permissions:   perm,
+	}
+	if repo.IsFork {
+		p := &types.RepositoryPermission{Pull: true}
+		apiRepo.Parent = toRepository(repo.BaseRepo, p)
+	}
+	return apiRepo
+}

+ 0 - 13
internal/route/api/v1/admin/org.go

@@ -1,13 +0,0 @@
-package admin
-
-import (
-	api "github.com/gogs/go-gogs-client"
-
-	"gogs.io/gogs/internal/context"
-	"gogs.io/gogs/internal/route/api/v1/org"
-	"gogs.io/gogs/internal/route/api/v1/user"
-)
-
-func CreateOrg(c *context.APIContext, form api.CreateOrgOption) {
-	org.CreateOrgForUser(c, form, user.GetUserByParams(c))
-}

+ 0 - 18
internal/route/api/v1/admin/repo.go

@@ -1,18 +0,0 @@
-package admin
-
-import (
-	api "github.com/gogs/go-gogs-client"
-
-	"gogs.io/gogs/internal/context"
-	"gogs.io/gogs/internal/route/api/v1/repo"
-	"gogs.io/gogs/internal/route/api/v1/user"
-)
-
-func CreateRepo(c *context.APIContext, form api.CreateRepoOption) {
-	owner := user.GetUserByParams(c)
-	if c.Written() {
-		return
-	}
-
-	repo.CreateUserRepo(c, owner, form)
-}

+ 9 - 0
internal/route/api/v1/admin_org.go

@@ -0,0 +1,9 @@
+package v1
+
+import (
+	"gogs.io/gogs/internal/context"
+)
+
+func adminCreateOrg(c *context.APIContext, form createOrgRequest) {
+	createOrgForUser(c, form, getUserByParams(c))
+}

+ 6 - 6
internal/route/api/v1/admin/org_repo.go → internal/route/api/v1/admin_org_repo.go

@@ -1,11 +1,11 @@
-package admin
+package v1
 
 import (
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
 )
 
-func GetRepositoryByParams(c *context.APIContext) *database.Repository {
+func getRepositoryByParams(c *context.APIContext) *database.Repository {
 	repo, err := database.GetRepositoryByName(c.Org.Team.OrgID, c.Params(":reponame"))
 	if err != nil {
 		c.NotFoundOrError(err, "get repository by name")
@@ -14,8 +14,8 @@ func GetRepositoryByParams(c *context.APIContext) *database.Repository {
 	return repo
 }
 
-func AddTeamRepository(c *context.APIContext) {
-	repo := GetRepositoryByParams(c)
+func adminAddTeamRepository(c *context.APIContext) {
+	repo := getRepositoryByParams(c)
 	if c.Written() {
 		return
 	}
@@ -27,8 +27,8 @@ func AddTeamRepository(c *context.APIContext) {
 	c.NoContent()
 }
 
-func RemoveTeamRepository(c *context.APIContext) {
-	repo := GetRepositoryByParams(c)
+func adminRemoveTeamRepository(c *context.APIContext) {
+	repo := getRepositoryByParams(c)
 	if c.Written() {
 		return
 	}

+ 17 - 14
internal/route/api/v1/admin/org_team.go → internal/route/api/v1/admin_org_team.go

@@ -1,17 +1,20 @@
-package admin
+package v1
 
 import (
 	"net/http"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
-	"gogs.io/gogs/internal/route/api/v1/convert"
-	"gogs.io/gogs/internal/route/api/v1/user"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func CreateTeam(c *context.APIContext, form api.CreateTeamOption) {
+type adminCreateTeamRequest struct {
+	Name        string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"`
+	Description string `json:"description" binding:"MaxSize(255)"`
+	Permission  string `json:"permission"`
+}
+
+func adminCreateTeam(c *context.APIContext, form adminCreateTeamRequest) {
 	team := &database.Team{
 		OrgID:       c.Org.Organization.ID,
 		Name:        form.Name,
@@ -27,11 +30,11 @@ func CreateTeam(c *context.APIContext, form api.CreateTeamOption) {
 		return
 	}
 
-	c.JSON(http.StatusCreated, convert.ToTeam(team))
+	c.JSON(http.StatusCreated, toOrganizationTeam(team))
 }
 
-func AddTeamMember(c *context.APIContext) {
-	u := user.GetUserByParams(c)
+func adminAddTeamMember(c *context.APIContext) {
+	u := getUserByParams(c)
 	if c.Written() {
 		return
 	}
@@ -43,8 +46,8 @@ func AddTeamMember(c *context.APIContext) {
 	c.NoContent()
 }
 
-func RemoveTeamMember(c *context.APIContext) {
-	u := user.GetUserByParams(c)
+func adminRemoveTeamMember(c *context.APIContext) {
+	u := getUserByParams(c)
 	if c.Written() {
 		return
 	}
@@ -57,16 +60,16 @@ func RemoveTeamMember(c *context.APIContext) {
 	c.NoContent()
 }
 
-func ListTeamMembers(c *context.APIContext) {
+func adminListTeamMembers(c *context.APIContext) {
 	team := c.Org.Team
 	if err := team.GetMembers(); err != nil {
 		c.Error(err, "get team members")
 		return
 	}
 
-	apiMembers := make([]*api.User, len(team.Members))
+	apiMembers := make([]*types.User, len(team.Members))
 	for i := range team.Members {
-		apiMembers[i] = team.Members[i].APIFormat()
+		apiMembers[i] = toUser(team.Members[i])
 	}
 	c.JSONSuccess(apiMembers)
 }

+ 14 - 0
internal/route/api/v1/admin_repo.go

@@ -0,0 +1,14 @@
+package v1
+
+import (
+	"gogs.io/gogs/internal/context"
+)
+
+func adminCreateRepo(c *context.APIContext, form createRepoRequest) {
+	owner := getUserByParams(c)
+	if c.Written() {
+		return
+	}
+
+	createUserRepo(c, owner, form)
+}

+ 39 - 16
internal/route/api/v1/admin/user.go → internal/route/api/v1/admin_user.go

@@ -1,16 +1,14 @@
-package admin
+package v1
 
 import (
 	"net/http"
 
-	api "github.com/gogs/go-gogs-client"
 	log "unknwon.dev/clog/v2"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
 	"gogs.io/gogs/internal/email"
-	"gogs.io/gogs/internal/route/api/v1/user"
 )
 
 func parseLoginSource(c *context.APIContext, sourceID int64) {
@@ -29,13 +27,23 @@ func parseLoginSource(c *context.APIContext, sourceID int64) {
 	}
 }
 
-func CreateUser(c *context.APIContext, form api.CreateUserOption) {
+type adminCreateUserRequest struct {
+	SourceID   int64  `json:"source_id"`
+	LoginName  string `json:"login_name"`
+	Username   string `json:"username" binding:"Required;AlphaDashDot;MaxSize(35)"`
+	FullName   string `json:"full_name" binding:"MaxSize(100)"`
+	Email      string `json:"email" binding:"Required;Email;MaxSize(254)"`
+	Password   string `json:"password" binding:"MaxSize(255)"`
+	SendNotify bool   `json:"send_notify"`
+}
+
+func adminCreateUser(c *context.APIContext, form adminCreateUserRequest) {
 	parseLoginSource(c, form.SourceID)
 	if c.Written() {
 		return
 	}
 
-	user, err := database.Handle.Users().Create(
+	u, err := database.Handle.Users().Create(
 		c.Req.Context(),
 		form.Username,
 		form.Email,
@@ -57,18 +65,33 @@ func CreateUser(c *context.APIContext, form api.CreateUserOption) {
 		}
 		return
 	}
-	log.Trace("Account %q created by admin %q", user.Name, c.User.Name)
+	log.Trace("Account %q created by admin %q", u.Name, c.User.Name)
 
 	// Send email notification.
 	if form.SendNotify && conf.Email.Enabled {
-		email.SendRegisterNotifyMail(c.Context.Context, database.NewMailerUser(user))
+		email.SendRegisterNotifyMail(c.Context.Context, database.NewMailerUser(u))
 	}
 
-	c.JSON(http.StatusCreated, user.APIFormat())
+	c.JSON(http.StatusCreated, toUser(u))
+}
+
+type adminEditUserRequest struct {
+	SourceID         int64  `json:"source_id"`
+	LoginName        string `json:"login_name"`
+	FullName         string `json:"full_name" binding:"MaxSize(100)"`
+	Email            string `json:"email" binding:"Required;Email;MaxSize(254)"`
+	Password         string `json:"password" binding:"MaxSize(255)"`
+	Website          string `json:"website" binding:"MaxSize(50)"`
+	Location         string `json:"location" binding:"MaxSize(50)"`
+	Active           *bool  `json:"active"`
+	Admin            *bool  `json:"admin"`
+	AllowGitHook     *bool  `json:"allow_git_hook"`
+	AllowImportLocal *bool  `json:"allow_import_local"`
+	MaxRepoCreation  *int   `json:"max_repo_creation"`
 }
 
-func EditUser(c *context.APIContext, form api.EditUserOption) {
-	u := user.GetUserByParams(c)
+func adminEditUser(c *context.APIContext, form adminEditUserRequest) {
+	u := getUserByParams(c)
 	if c.Written() {
 		return
 	}
@@ -116,11 +139,11 @@ func EditUser(c *context.APIContext, form api.EditUserOption) {
 		c.Error(err, "get user")
 		return
 	}
-	c.JSONSuccess(u.APIFormat())
+	c.JSONSuccess(toUser(u))
 }
 
-func DeleteUser(c *context.APIContext) {
-	u := user.GetUserByParams(c)
+func adminDeleteUser(c *context.APIContext) {
+	u := getUserByParams(c)
 	if c.Written() {
 		return
 	}
@@ -139,10 +162,10 @@ func DeleteUser(c *context.APIContext) {
 	c.NoContent()
 }
 
-func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) {
-	u := user.GetUserByParams(c)
+func adminCreatePublicKey(c *context.APIContext, form createPublicKeyRequest) {
+	u := getUserByParams(c)
 	if c.Written() {
 		return
 	}
-	user.CreateUserPublicKey(c, form, u.ID)
+	createUserPublicKey(c, form, u.ID)
 }

+ 108 - 115
internal/route/api/v1/api.go

@@ -7,16 +7,9 @@ import (
 	"github.com/go-macaron/binding"
 	"gopkg.in/macaron.v1"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
 	"gogs.io/gogs/internal/form"
-	"gogs.io/gogs/internal/route/api/v1/admin"
-	"gogs.io/gogs/internal/route/api/v1/misc"
-	"gogs.io/gogs/internal/route/api/v1/org"
-	"gogs.io/gogs/internal/route/api/v1/repo"
-	"gogs.io/gogs/internal/route/api/v1/user"
 )
 
 // repoAssignment extracts information from URL parameters to retrieve the repository,
@@ -181,245 +174,245 @@ func RegisterRoutes(m *macaron.Macaron) {
 		m.Options("/*", func() {})
 
 		// Miscellaneous
-		m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
-		m.Post("/markdown/raw", misc.MarkdownRaw)
+		m.Post("/markdown", bind(markdownRequest{}), markdown)
+		m.Post("/markdown/raw", markdownRaw)
 
 		// Users
 		m.Group("/users", func() {
-			m.Get("/search", user.Search)
+			m.Get("/search", searchUsers)
 
 			m.Group("/:username", func() {
-				m.Get("", user.GetInfo)
+				m.Get("", getUserProfile)
 
 				m.Group("/tokens", func() {
-					accessTokensHandler := user.NewAccessTokensHandler(user.NewAccessTokensStore())
+					accessTokensHandler := newAccessTokensHandler(newAccessTokensStore())
 					m.Combo("").
 						Get(accessTokensHandler.List()).
-						Post(bind(api.CreateAccessTokenOption{}), accessTokensHandler.Create())
+						Post(bind(createAccessTokenRequest{}), accessTokensHandler.Create())
 				}, reqBasicAuth())
 			})
 		})
 
 		m.Group("/users", func() {
 			m.Group("/:username", func() {
-				m.Get("/keys", user.ListPublicKeys)
+				m.Get("/keys", listPublicKeys)
 
-				m.Get("/followers", user.ListFollowers)
+				m.Get("/followers", listFollowers)
 				m.Group("/following", func() {
-					m.Get("", user.ListFollowing)
-					m.Get("/:target", user.CheckFollowing)
+					m.Get("", listFollowing)
+					m.Get("/:target", checkFollowing)
 				})
 			})
 		}, reqToken())
 
 		m.Group("/user", func() {
-			m.Get("", user.GetAuthenticatedUser)
+			m.Get("", getAuthenticatedUser)
 			m.Combo("/emails").
-				Get(user.ListEmails).
-				Post(bind(api.CreateEmailOption{}), user.AddEmail).
-				Delete(bind(api.CreateEmailOption{}), user.DeleteEmail)
+				Get(listEmails).
+				Post(bind(createEmailRequest{}), addEmail).
+				Delete(bind(createEmailRequest{}), deleteEmail)
 
-			m.Get("/followers", user.ListMyFollowers)
+			m.Get("/followers", listMyFollowers)
 			m.Group("/following", func() {
-				m.Get("", user.ListMyFollowing)
+				m.Get("", listMyFollowing)
 				m.Combo("/:username").
-					Get(user.CheckMyFollowing).
-					Put(user.Follow).
-					Delete(user.Unfollow)
+					Get(checkMyFollowing).
+					Put(follow).
+					Delete(unfollow)
 			})
 
 			m.Group("/keys", func() {
 				m.Combo("").
-					Get(user.ListMyPublicKeys).
-					Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
+					Get(listMyPublicKeys).
+					Post(bind(createPublicKeyRequest{}), createPublicKey)
 				m.Combo("/:id").
-					Get(user.GetPublicKey).
-					Delete(user.DeletePublicKey)
+					Get(getPublicKey).
+					Delete(deletePublicKey)
 			})
 
-			m.Get("/issues", repo.ListUserIssues)
+			m.Get("/issues", listUserIssues)
 		}, reqToken())
 
 		// Repositories
-		m.Get("/users/:username/repos", reqToken(), repo.ListUserRepositories)
-		m.Get("/orgs/:org/repos", reqToken(), repo.ListOrgRepositories)
+		m.Get("/users/:username/repos", reqToken(), listUserRepositories)
+		m.Get("/orgs/:org/repos", reqToken(), 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)
+			Get(listMyRepos).
+			Post(bind(createRepoRequest{}), createRepo)
+		m.Post("/org/:org/repos", reqToken(), bind(createRepoRequest{}), createOrgRepo)
 
 		m.Group("/repos", func() {
-			m.Get("/search", repo.Search)
+			m.Get("/search", searchRepos)
 
-			m.Get("/:username/:reponame", repoAssignment(), repo.Get)
-			m.Get("/:username/:reponame/releases", repoAssignment(), repo.Releases)
+			m.Get("/:username/:reponame", repoAssignment(), getRepo)
+			m.Get("/:username/:reponame/releases", repoAssignment(), releases)
 		})
 
 		m.Group("/repos", func() {
-			m.Post("/migrate", bind(form.MigrateRepo{}), repo.Migrate)
-			m.Delete("/:username/:reponame", repoAssignment(), reqRepoOwner(), repo.Delete)
+			m.Post("/migrate", bind(form.MigrateRepo{}), migrate)
+			m.Delete("/:username/:reponame", repoAssignment(), reqRepoOwner(), deleteRepo)
 
 			m.Group("/:username/:reponame", func() {
 				m.Group("/hooks", func() {
 					m.Combo("").
-						Get(repo.ListHooks).
-						Post(bind(api.CreateHookOption{}), repo.CreateHook)
+						Get(listHooks).
+						Post(bind(createHookRequest{}), createHook)
 					m.Combo("/:id").
-						Patch(bind(api.EditHookOption{}), repo.EditHook).
-						Delete(repo.DeleteHook)
+						Patch(bind(editHookRequest{}), editHook).
+						Delete(deleteHook)
 				}, reqRepoAdmin())
 
 				m.Group("/collaborators", func() {
-					m.Get("", repo.ListCollaborators)
+					m.Get("", listCollaborators)
 					m.Combo("/:collaborator").
-						Get(repo.IsCollaborator).
-						Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
-						Delete(repo.DeleteCollaborator)
+						Get(isCollaborator).
+						Put(bind(addCollaboratorRequest{}), addCollaborator).
+						Delete(deleteCollaborator)
 				}, reqRepoAdmin())
 
-				m.Get("/raw/*", context.RepoRef(), repo.GetRawFile)
+				m.Get("/raw/*", context.RepoRef(), getRawFile)
 				m.Group("/contents", func() {
-					m.Get("", repo.GetContents)
+					m.Get("", getContents)
 					m.Combo("/*").
-						Get(repo.GetContents).
-						Put(reqRepoWriter(), bind(repo.PutContentsRequest{}), repo.PutContents)
+						Get(getContents).
+						Put(reqRepoWriter(), bind(putContentsRequest{}), putContents)
 				})
-				m.Get("/archive/*", repo.GetArchive)
+				m.Get("/archive/*", getArchive)
 				m.Group("/git", func() {
 					m.Group("/trees", func() {
-						m.Get("/:sha", repo.GetRepoGitTree)
+						m.Get("/:sha", getRepoGitTree)
 					})
 					m.Group("/blobs", func() {
-						m.Get("/:sha", repo.RepoGitBlob)
+						m.Get("/:sha", repoGitBlob)
 					})
 				})
-				m.Get("/forks", repo.ListForks)
-				m.Get("/tags", repo.ListTags)
+				m.Get("/forks", listForks)
+				m.Get("/tags", listTags)
 				m.Group("/branches", func() {
-					m.Get("", repo.ListBranches)
-					m.Get("/*", repo.GetBranch)
+					m.Get("", listBranches)
+					m.Get("/*", getBranch)
 				})
 				m.Group("/commits", func() {
-					m.Get("/:sha", repo.GetSingleCommit)
-					m.Get("", repo.GetAllCommits)
-					m.Get("/*", repo.GetReferenceSHA)
+					m.Get("/:sha", getSingleCommit)
+					m.Get("", getAllCommits)
+					m.Get("/*", getReferenceSHA)
 				})
 
 				m.Group("/keys", func() {
 					m.Combo("").
-						Get(repo.ListDeployKeys).
-						Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
+						Get(listDeployKeys).
+						Post(bind(createDeployKeyRequest{}), createDeployKey)
 					m.Combo("/:id").
-						Get(repo.GetDeployKey).
-						Delete(repo.DeleteDeploykey)
+						Get(getDeployKey).
+						Delete(deleteDeploykey)
 				}, reqRepoAdmin())
 
 				m.Group("/issues", func() {
 					m.Combo("").
-						Get(repo.ListIssues).
-						Post(bind(api.CreateIssueOption{}), repo.CreateIssue)
+						Get(listIssues).
+						Post(bind(createIssueRequest{}), createIssue)
 					m.Group("/comments", func() {
-						m.Get("", repo.ListRepoIssueComments)
-						m.Patch("/:id", bind(api.EditIssueCommentOption{}), repo.EditIssueComment)
+						m.Get("", listRepoIssueComments)
+						m.Patch("/:id", bind(editIssueCommentRequest{}), editIssueComment)
 					})
 					m.Group("/:index", func() {
 						m.Combo("").
-							Get(repo.GetIssue).
-							Patch(bind(api.EditIssueOption{}), repo.EditIssue)
+							Get(getIssue).
+							Patch(bind(editIssueRequest{}), editIssue)
 
 						m.Group("/comments", func() {
 							m.Combo("").
-								Get(repo.ListIssueComments).
-								Post(bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
+								Get(listIssueComments).
+								Post(bind(createIssueCommentRequest{}), createIssueComment)
 							m.Combo("/:id").
-								Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
-								Delete(repo.DeleteIssueComment)
+								Patch(bind(editIssueCommentRequest{}), editIssueComment).
+								Delete(deleteIssueComment)
 						})
 
-						m.Get("/labels", repo.ListIssueLabels)
+						m.Get("/labels", listIssueLabels)
 						m.Group("/labels", func() {
 							m.Combo("").
-								Post(bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
-								Put(bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
-								Delete(repo.ClearIssueLabels)
-							m.Delete("/:id", repo.DeleteIssueLabel)
+								Post(bind(issueLabelsRequest{}), addIssueLabels).
+								Put(bind(issueLabelsRequest{}), replaceIssueLabels).
+								Delete(clearIssueLabels)
+							m.Delete("/:id", deleteIssueLabel)
 						}, reqRepoWriter())
 					})
 				}, mustEnableIssues)
 
 				m.Group("/labels", func() {
-					m.Get("", repo.ListLabels)
-					m.Get("/:id", repo.GetLabel)
+					m.Get("", listLabels)
+					m.Get("/:id", getLabel)
 				})
 				m.Group("/labels", func() {
-					m.Post("", bind(api.CreateLabelOption{}), repo.CreateLabel)
+					m.Post("", bind(createLabelRequest{}), createLabel)
 					m.Combo("/:id").
-						Patch(bind(api.EditLabelOption{}), repo.EditLabel).
-						Delete(repo.DeleteLabel)
+						Patch(bind(editLabelRequest{}), editLabel).
+						Delete(deleteLabel)
 				}, reqRepoWriter())
 
 				m.Group("/milestones", func() {
-					m.Get("", repo.ListMilestones)
-					m.Get("/:id", repo.GetMilestone)
+					m.Get("", listMilestones)
+					m.Get("/:id", getMilestone)
 				})
 				m.Group("/milestones", func() {
-					m.Post("", bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
+					m.Post("", bind(createMilestoneRequest{}), createMilestone)
 					m.Combo("/:id").
-						Patch(bind(api.EditMilestoneOption{}), repo.EditMilestone).
-						Delete(repo.DeleteMilestone)
+						Patch(bind(editMilestoneRequest{}), editMilestone).
+						Delete(deleteMilestone)
 				}, reqRepoWriter())
 
-				m.Patch("/issue-tracker", reqRepoWriter(), bind(api.EditIssueTrackerOption{}), repo.IssueTracker)
-				m.Patch("/wiki", reqRepoWriter(), bind(api.EditWikiOption{}), repo.Wiki)
-				m.Post("/mirror-sync", reqRepoWriter(), repo.MirrorSync)
-				m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig)
+				m.Patch("/issue-tracker", reqRepoWriter(), bind(editIssueTrackerRequest{}), issueTracker)
+				m.Patch("/wiki", reqRepoWriter(), bind(editWikiRequest{}), wiki)
+				m.Post("/mirror-sync", reqRepoWriter(), mirrorSync)
+				m.Get("/editorconfig/:filename", context.RepoRef(), getEditorconfig)
 			}, repoAssignment())
 		}, reqToken())
 
-		m.Get("/issues", reqToken(), repo.ListUserIssues)
+		m.Get("/issues", reqToken(), listUserIssues)
 
 		// Organizations
 		m.Combo("/user/orgs", reqToken()).
-			Get(org.ListMyOrgs).
-			Post(bind(api.CreateOrgOption{}), org.CreateMyOrg)
+			Get(listMyOrgs).
+			Post(bind(createOrgRequest{}), createMyOrg)
 
-		m.Get("/users/:username/orgs", org.ListUserOrgs)
+		m.Get("/users/:username/orgs", listUserOrgs)
 		m.Group("/orgs/:orgname", func() {
 			m.Combo("").
-				Get(org.Get).
-				Patch(bind(api.EditOrgOption{}), org.Edit)
-			m.Get("/teams", org.ListTeams)
+				Get(getOrg).
+				Patch(bind(editOrgRequest{}), editOrg)
+			m.Get("/teams", listTeams)
 		}, orgAssignment(true))
 
 		m.Group("/admin", func() {
 			m.Group("/users", func() {
-				m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
+				m.Post("", bind(adminCreateUserRequest{}), adminCreateUser)
 
 				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)
+						Patch(bind(adminEditUserRequest{}), adminEditUser).
+						Delete(adminDeleteUser)
+					m.Post("/keys", bind(createPublicKeyRequest{}), adminCreatePublicKey)
+					m.Post("/orgs", bind(createOrgRequest{}), adminCreateOrg)
+					m.Post("/repos", bind(createRepoRequest{}), adminCreateRepo)
 				})
 			})
 
 			m.Group("/orgs/:orgname", func() {
 				m.Group("/teams", func() {
-					m.Post("", orgAssignment(true), bind(api.CreateTeamOption{}), admin.CreateTeam)
+					m.Post("", orgAssignment(true), bind(adminCreateTeamRequest{}), adminCreateTeam)
 				})
 			})
 
 			m.Group("/teams", func() {
 				m.Group("/:teamid", func() {
-					m.Get("/members", admin.ListTeamMembers)
+					m.Get("/members", adminListTeamMembers)
 					m.Combo("/members/:username").
-						Put(admin.AddTeamMember).
-						Delete(admin.RemoveTeamMember)
+						Put(adminAddTeamMember).
+						Delete(adminRemoveTeamMember)
 					m.Combo("/repos/:reponame").
-						Put(admin.AddTeamRepository).
-						Delete(admin.RemoveTeamRepository)
+						Put(adminAddTeamRepository).
+						Delete(adminRemoveTeamRepository)
 				}, orgAssignment(false, true))
 			})
 		}, reqAdmin())

+ 0 - 135
internal/route/api/v1/convert/convert.go

@@ -1,135 +0,0 @@
-package convert
-
-import (
-	"context"
-	"fmt"
-	"strconv"
-
-	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
-
-	"gogs.io/gogs/internal/database"
-)
-
-func ToEmail(email *database.EmailAddress) *api.Email {
-	return &api.Email{
-		Email:    email.Email,
-		Verified: email.IsActivated,
-		Primary:  email.IsPrimary,
-	}
-}
-
-func ToBranch(b *database.Branch, c *git.Commit) *api.Branch {
-	return &api.Branch{
-		Name:   b.Name,
-		Commit: ToCommit(c),
-	}
-}
-
-type Tag struct {
-	Name   string             `json:"name"`
-	Commit *api.PayloadCommit `json:"commit"`
-}
-
-func ToTag(b *database.Tag, c *git.Commit) *Tag {
-	return &Tag{
-		Name:   b.Name,
-		Commit: ToCommit(c),
-	}
-}
-
-func ToCommit(c *git.Commit) *api.PayloadCommit {
-	authorUsername := ""
-	author, err := database.Handle.Users().GetByEmail(context.TODO(), c.Author.Email)
-	if err == nil {
-		authorUsername = author.Name
-	}
-	committerUsername := ""
-	committer, err := database.Handle.Users().GetByEmail(context.TODO(), 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 *database.PublicKey) *api.PublicKey {
-	return &api.PublicKey{
-		ID:      key.ID,
-		Key:     key.Content,
-		URL:     apiLink + strconv.FormatInt(key.ID, 10),
-		Title:   key.Name,
-		Created: key.Created,
-	}
-}
-
-func ToHook(repoLink string, w *database.Webhook) *api.Hook {
-	config := map[string]string{
-		"url":          w.URL,
-		"content_type": w.ContentType.Name(),
-	}
-	if w.HookTaskType == database.SLACK {
-		s := w.SlackMeta()
-		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 *database.DeployKey) *api.DeployKey {
-	return &api.DeployKey{
-		ID:       key.ID,
-		Key:      key.Content,
-		URL:      apiLink + strconv.FormatInt(key.ID, 10),
-		Title:    key.Name,
-		Created:  key.Created,
-		ReadOnly: true, // All deploy keys are read-only.
-	}
-}
-
-func ToOrganization(org *database.User) *api.Organization {
-	return &api.Organization{
-		ID:          org.ID,
-		AvatarUrl:   org.AvatarURL(),
-		UserName:    org.Name,
-		FullName:    org.FullName,
-		Description: org.Description,
-		Website:     org.Website,
-		Location:    org.Location,
-	}
-}
-
-func ToTeam(team *database.Team) *api.Team {
-	return &api.Team{
-		ID:          team.ID,
-		Name:        team.Name,
-		Description: team.Description,
-		Permission:  team.Authorize.String(),
-	}
-}

+ 0 - 15
internal/route/api/v1/convert/utils.go

@@ -1,15 +0,0 @@
-package convert
-
-import (
-	"gogs.io/gogs/internal/conf"
-)
-
-// ToCorrectPageSize makes sure page size is in allowed range.
-func ToCorrectPageSize(size int) int {
-	if size <= 0 {
-		size = 10
-	} else if size > conf.API.MaxResponseItems {
-		size = conf.API.MaxResponseItems
-	}
-	return size
-}

+ 9 - 5
internal/route/api/v1/misc/markdown.go → internal/route/api/v1/markdown.go

@@ -1,13 +1,17 @@
-package misc
+package v1
 
 import (
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/markup"
 )
 
-func Markdown(c *context.APIContext, form api.MarkdownOption) {
+// markdownRequest represents the request body for rendering markdown.
+type markdownRequest struct {
+	Text    string
+	Context string
+}
+
+func markdown(c *context.APIContext, form markdownRequest) {
 	if form.Text == "" {
 		_, _ = c.Write([]byte(""))
 		return
@@ -16,7 +20,7 @@ func Markdown(c *context.APIContext, form api.MarkdownOption) {
 	_, _ = c.Write(markup.Markdown([]byte(form.Text), form.Context, nil))
 }
 
-func MarkdownRaw(c *context.APIContext) {
+func markdownRaw(c *context.APIContext) {
 	body, err := c.Req.Body().Bytes()
 	if err != nil {
 		c.Error(err, "read body")

+ 33 - 21
internal/route/api/v1/org/org.go → internal/route/api/v1/org.go

@@ -1,17 +1,22 @@
-package org
+package v1
 
 import (
 	"net/http"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
-	"gogs.io/gogs/internal/route/api/v1/convert"
-	"gogs.io/gogs/internal/route/api/v1/user"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func CreateOrgForUser(c *context.APIContext, apiForm api.CreateOrgOption, user *database.User) {
+type createOrgRequest struct {
+	UserName    string `json:"username" binding:"Required"`
+	FullName    string `json:"full_name"`
+	Description string `json:"description"`
+	Website     string `json:"website"`
+	Location    string `json:"location"`
+}
+
+func createOrgForUser(c *context.APIContext, apiForm createOrgRequest, user *database.User) {
 	if c.Written() {
 		return
 	}
@@ -35,10 +40,10 @@ func CreateOrgForUser(c *context.APIContext, apiForm api.CreateOrgOption, user *
 		return
 	}
 
-	c.JSON(201, convert.ToOrganization(org))
+	c.JSON(201, toOrganization(org))
 }
 
-func listUserOrgs(c *context.APIContext, u *database.User, all bool) {
+func listOrgsOfUser(c *context.APIContext, u *database.User, all bool) {
 	orgs, err := database.Handle.Organizations().List(
 		c.Req.Context(),
 		database.ListOrgsOptions{
@@ -51,34 +56,41 @@ func listUserOrgs(c *context.APIContext, u *database.User, all bool) {
 		return
 	}
 
-	apiOrgs := make([]*api.Organization, len(orgs))
+	apiOrgs := make([]*types.Organization, len(orgs))
 	for i := range orgs {
-		apiOrgs[i] = convert.ToOrganization(orgs[i])
+		apiOrgs[i] = toOrganization(orgs[i])
 	}
 	c.JSONSuccess(&apiOrgs)
 }
 
-func ListMyOrgs(c *context.APIContext) {
-	listUserOrgs(c, c.User, true)
+func listMyOrgs(c *context.APIContext) {
+	listOrgsOfUser(c, c.User, true)
 }
 
-func CreateMyOrg(c *context.APIContext, apiForm api.CreateOrgOption) {
-	CreateOrgForUser(c, apiForm, c.User)
+func createMyOrg(c *context.APIContext, apiForm createOrgRequest) {
+	createOrgForUser(c, apiForm, c.User)
 }
 
-func ListUserOrgs(c *context.APIContext) {
-	u := user.GetUserByParams(c)
+func listUserOrgs(c *context.APIContext) {
+	u := getUserByParams(c)
 	if c.Written() {
 		return
 	}
-	listUserOrgs(c, u, false)
+	listOrgsOfUser(c, u, false)
+}
+
+func getOrg(c *context.APIContext) {
+	c.JSONSuccess(toOrganization(c.Org.Organization))
 }
 
-func Get(c *context.APIContext) {
-	c.JSONSuccess(convert.ToOrganization(c.Org.Organization))
+type editOrgRequest struct {
+	FullName    string `json:"full_name"`
+	Description string `json:"description"`
+	Website     string `json:"website"`
+	Location    string `json:"location"`
 }
 
-func Edit(c *context.APIContext, form api.EditOrgOption) {
+func editOrg(c *context.APIContext, form editOrgRequest) {
 	org := c.Org.Organization
 	if !org.IsOwnedBy(c.User.ID) {
 		c.Status(http.StatusForbidden)
@@ -105,5 +117,5 @@ func Edit(c *context.APIContext, form api.EditOrgOption) {
 		c.Error(err, "get organization")
 		return
 	}
-	c.JSONSuccess(convert.ToOrganization(org))
+	c.JSONSuccess(toOrganization(org))
 }

+ 0 - 22
internal/route/api/v1/org/team.go

@@ -1,22 +0,0 @@
-package org
-
-import (
-	api "github.com/gogs/go-gogs-client"
-
-	"gogs.io/gogs/internal/context"
-	"gogs.io/gogs/internal/route/api/v1/convert"
-)
-
-func ListTeams(c *context.APIContext) {
-	org := c.Org.Organization
-	if err := org.GetTeams(); err != nil {
-		c.Error(err, "get teams")
-		return
-	}
-
-	apiTeams := make([]*api.Team, len(org.Teams))
-	for i := range org.Teams {
-		apiTeams[i] = convert.ToTeam(org.Teams[i])
-	}
-	c.JSONSuccess(apiTeams)
-}

+ 20 - 0
internal/route/api/v1/org_team.go

@@ -0,0 +1,20 @@
+package v1
+
+import (
+	"gogs.io/gogs/internal/context"
+	"gogs.io/gogs/internal/route/api/v1/types"
+)
+
+func listTeams(c *context.APIContext) {
+	org := c.Org.Organization
+	if err := org.GetTeams(); err != nil {
+		c.Error(err, "get teams")
+		return
+	}
+
+	apiTeams := make([]*types.OrganizationTeam, len(org.Teams))
+	for i := range org.Teams {
+		apiTeams[i] = toOrganizationTeam(org.Teams[i])
+	}
+	c.JSONSuccess(apiTeams)
+}

+ 2 - 2
internal/route/api/v1/repo/blob.go → internal/route/api/v1/repo_blob.go

@@ -1,4 +1,4 @@
-package repo
+package v1
 
 import (
 	"encoding/base64"
@@ -11,7 +11,7 @@ import (
 	"gogs.io/gogs/internal/repoutil"
 )
 
-func RepoGitBlob(c *context.APIContext) {
+func repoGitBlob(c *context.APIContext) {
 	gitRepo, err := git.Open(repoutil.RepositoryPath(c.Params(":username"), c.Params(":reponame")))
 	if err != nil {
 		c.Error(err, "open repository")

+ 7 - 9
internal/route/api/v1/repo/branch.go → internal/route/api/v1/repo_branch.go

@@ -1,14 +1,12 @@
-package repo
+package v1
 
 import (
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
-	"gogs.io/gogs/internal/route/api/v1/convert"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
 // https://github.com/gogs/go-gogs-client/wiki/Repositories#get-branch
-func GetBranch(c *context.APIContext) {
+func getBranch(c *context.APIContext) {
 	branch, err := c.Repo.Repository.GetBranch(c.Params("*"))
 	if err != nil {
 		c.NotFoundOrError(err, "get branch")
@@ -21,25 +19,25 @@ func GetBranch(c *context.APIContext) {
 		return
 	}
 
-	c.JSONSuccess(convert.ToBranch(branch, commit))
+	c.JSONSuccess(toBranch(branch, commit))
 }
 
 // https://github.com/gogs/go-gogs-client/wiki/Repositories#list-branches
-func ListBranches(c *context.APIContext) {
+func listBranches(c *context.APIContext) {
 	branches, err := c.Repo.Repository.GetBranches()
 	if err != nil {
 		c.Error(err, "get branches")
 		return
 	}
 
-	apiBranches := make([]*api.Branch, len(branches))
+	apiBranches := make([]*types.RepositoryBranch, len(branches))
 	for i := range branches {
 		commit, err := branches[i].GetCommit()
 		if err != nil {
 			c.Error(err, "get commit")
 			return
 		}
-		apiBranches[i] = convert.ToBranch(branches[i], commit)
+		apiBranches[i] = toBranch(branches[i], commit)
 	}
 
 	c.JSONSuccess(&apiBranches)

+ 12 - 9
internal/route/api/v1/repo/collaborators.go → internal/route/api/v1/repo_collaborators.go

@@ -1,29 +1,32 @@
-package repo
+package v1
 
 import (
 	"net/http"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func ListCollaborators(c *context.APIContext) {
+func listCollaborators(c *context.APIContext) {
 	collaborators, err := c.Repo.Repository.GetCollaborators()
 	if err != nil {
 		c.Error(err, "get collaborators")
 		return
 	}
 
-	apiCollaborators := make([]*api.Collaborator, len(collaborators))
+	apiCollaborators := make([]*types.RepositoryCollaborator, len(collaborators))
 	for i := range collaborators {
-		apiCollaborators[i] = collaborators[i].APIFormat()
+		apiCollaborators[i] = toRepositoryCollaborator(collaborators[i])
 	}
 	c.JSONSuccess(&apiCollaborators)
 }
 
-func AddCollaborator(c *context.APIContext, form api.AddCollaboratorOption) {
+type addCollaboratorRequest struct {
+	Permission *string `json:"permission"`
+}
+
+func addCollaborator(c *context.APIContext, form addCollaboratorRequest) {
 	collaborator, err := database.Handle.Users().GetByUsername(c.Req.Context(), c.Params(":collaborator"))
 	if err != nil {
 		if database.IsErrUserNotExist(err) {
@@ -49,7 +52,7 @@ func AddCollaborator(c *context.APIContext, form api.AddCollaboratorOption) {
 	c.NoContent()
 }
 
-func IsCollaborator(c *context.APIContext) {
+func isCollaborator(c *context.APIContext) {
 	collaborator, err := database.Handle.Users().GetByUsername(c.Req.Context(), c.Params(":collaborator"))
 	if err != nil {
 		if database.IsErrUserNotExist(err) {
@@ -67,7 +70,7 @@ func IsCollaborator(c *context.APIContext) {
 	}
 }
 
-func DeleteCollaborator(c *context.APIContext) {
+func deleteCollaborator(c *context.APIContext) {
 	collaborator, err := database.Handle.Users().GetByUsername(c.Req.Context(), c.Params(":collaborator"))
 	if err != nil {
 		if database.IsErrUserNotExist(err) {

+ 24 - 22
internal/route/api/v1/repo/commits.go → internal/route/api/v1/repo_commits.go

@@ -1,4 +1,4 @@
-package repo
+package v1
 
 import (
 	"net/http"
@@ -6,16 +6,18 @@ import (
 	"time"
 
 	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
 	"gogs.io/gogs/internal/gitutil"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-// GetAllCommits returns a slice of commits starting from HEAD.
-func GetAllCommits(c *context.APIContext) {
+const mediaApplicationSHA = "application/vnd.gogs.sha"
+
+// getAllCommits returns a slice of commits starting from HEAD.
+func getAllCommits(c *context.APIContext) {
 	// Get pagesize, set default if it is not specified.
 	pageSize := c.QueryInt("pageSize")
 	if pageSize == 0 {
@@ -29,7 +31,7 @@ func GetAllCommits(c *context.APIContext) {
 	}
 
 	// The response object returned as JSON
-	result := make([]*api.Commit, 0, pageSize)
+	result := make([]*types.Commit, 0, pageSize)
 	commits, err := gitRepo.Log("HEAD", git.LogOptions{MaxCount: pageSize})
 	if err != nil {
 		c.Error(err, "git log")
@@ -47,11 +49,11 @@ func GetAllCommits(c *context.APIContext) {
 	c.JSONSuccess(result)
 }
 
-// GetSingleCommit will return a single Commit object based on the specified SHA.
-func GetSingleCommit(c *context.APIContext) {
-	if strings.Contains(c.Req.Header.Get("Accept"), api.MediaApplicationSHA) {
+// getSingleCommit will return a single Commit object based on the specified SHA.
+func getSingleCommit(c *context.APIContext) {
+	if strings.Contains(c.Req.Header.Get("Accept"), mediaApplicationSHA) {
 		c.SetParams("*", c.Params(":sha"))
-		GetReferenceSHA(c)
+		getReferenceSHA(c)
 		return
 	}
 
@@ -73,7 +75,7 @@ func GetSingleCommit(c *context.APIContext) {
 	c.JSONSuccess(apiCommit)
 }
 
-func GetReferenceSHA(c *context.APIContext) {
+func getReferenceSHA(c *context.APIContext) {
 	gitRepo, err := git.Open(c.Repo.Repository.RepoPath())
 	if err != nil {
 		c.Error(err, "open repository")
@@ -114,14 +116,14 @@ func GetReferenceSHA(c *context.APIContext) {
 }
 
 // gitCommitToApiCommit is a helper function to convert git commit object to API commit.
-func gitCommitToAPICommit(commit *git.Commit, c *context.APIContext) (*api.Commit, error) {
+func gitCommitToAPICommit(commit *git.Commit, c *context.APIContext) (*types.Commit, error) {
 	// Retrieve author and committer information
-	var apiAuthor, apiCommitter *api.User
+	var apiAuthor, apiCommitter *types.User
 	author, err := database.Handle.Users().GetByEmail(c.Req.Context(), commit.Author.Email)
 	if err != nil && !database.IsErrUserNotExist(err) {
 		return nil, err
 	} else if err == nil {
-		apiAuthor = author.APIFormat()
+		apiAuthor = toUser(author)
 	}
 
 	// Save one query if the author is also the committer
@@ -132,40 +134,40 @@ func gitCommitToAPICommit(commit *git.Commit, c *context.APIContext) (*api.Commi
 		if err != nil && !database.IsErrUserNotExist(err) {
 			return nil, err
 		} else if err == nil {
-			apiCommitter = committer.APIFormat()
+			apiCommitter = toUser(committer)
 		}
 	}
 
 	// Retrieve parent(s) of the commit
-	apiParents := make([]*api.CommitMeta, commit.ParentsCount())
+	apiParents := make([]*types.CommitMeta, commit.ParentsCount())
 	for i := 0; i < commit.ParentsCount(); i++ {
 		sha, _ := commit.ParentID(i)
-		apiParents[i] = &api.CommitMeta{
+		apiParents[i] = &types.CommitMeta{
 			URL: c.BaseURL + "/repos/" + c.Repo.Repository.FullName() + "/commits/" + sha.String(),
 			SHA: sha.String(),
 		}
 	}
 
-	return &api.Commit{
-		CommitMeta: &api.CommitMeta{
+	return &types.Commit{
+		CommitMeta: &types.CommitMeta{
 			URL: conf.Server.ExternalURL + c.Link[1:],
 			SHA: commit.ID.String(),
 		},
 		HTMLURL: c.Repo.Repository.HTMLURL() + "/commits/" + commit.ID.String(),
-		RepoCommit: &api.RepoCommit{
+		RepoCommit: &types.RepoCommit{
 			URL: conf.Server.ExternalURL + c.Link[1:],
-			Author: &api.CommitUser{
+			Author: &types.CommitUser{
 				Name:  commit.Author.Name,
 				Email: commit.Author.Email,
 				Date:  commit.Author.When.Format(time.RFC3339),
 			},
-			Committer: &api.CommitUser{
+			Committer: &types.CommitUser{
 				Name:  commit.Committer.Name,
 				Email: commit.Committer.Email,
 				Date:  commit.Committer.When.Format(time.RFC3339),
 			},
 			Message: commit.Summary(),
-			Tree: &api.CommitMeta{
+			Tree: &types.CommitMeta{
 				URL: c.BaseURL + "/repos/" + c.Repo.Repository.FullName() + "/tree/" + commit.ID.String(),
 				SHA: commit.ID.String(),
 			},

+ 5 - 5
internal/route/api/v1/repo/contents.go → internal/route/api/v1/repo_contents.go

@@ -1,4 +1,4 @@
-package repo
+package v1
 
 import (
 	"encoding/base64"
@@ -98,7 +98,7 @@ func toRepoContent(c *context.APIContext, ref, subpath string, commit *git.Commi
 	return content, nil
 }
 
-func GetContents(c *context.APIContext) {
+func getContents(c *context.APIContext) {
 	repoPath := repoutil.RepositoryPath(c.Params(":username"), c.Params(":reponame"))
 	gitRepo, err := git.Open(repoPath)
 	if err != nil {
@@ -168,15 +168,15 @@ func GetContents(c *context.APIContext) {
 	c.JSONSuccess(contents)
 }
 
-// PutContentsRequest is the API message for creating or updating a file.
-type PutContentsRequest struct {
+// putContentsRequest is the API message for creating or updating a file.
+type putContentsRequest struct {
 	Message string `json:"message" binding:"Required"`
 	Content string `json:"content" binding:"Required"`
 	Branch  string `json:"branch"`
 }
 
 // PUT /repos/:username/:reponame/contents/*
-func PutContents(c *context.APIContext, r PutContentsRequest) {
+func putContents(c *context.APIContext, r putContentsRequest) {
 	content, err := base64.StdEncoding.DecodeString(r.Content)
 	if err != nil {
 		c.Error(err, "decoding base64")

+ 7 - 7
internal/route/api/v1/repo/file.go → internal/route/api/v1/repo_file.go

@@ -1,4 +1,4 @@
-package repo
+package v1
 
 import (
 	"github.com/gogs/git-module"
@@ -6,10 +6,10 @@ import (
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
 	"gogs.io/gogs/internal/gitutil"
-	"gogs.io/gogs/internal/route/repo"
+	routerepo "gogs.io/gogs/internal/route/repo"
 )
 
-func GetRawFile(c *context.APIContext) {
+func getRawFile(c *context.APIContext) {
 	if !c.Repo.HasAccess() {
 		c.NotFound()
 		return
@@ -25,12 +25,12 @@ func GetRawFile(c *context.APIContext) {
 		c.NotFoundOrError(gitutil.NewError(err), "get blob")
 		return
 	}
-	if err = repo.ServeBlob(c.Context, blob); err != nil {
+	if err = routerepo.ServeBlob(c.Context, blob); err != nil {
 		c.Error(err, "serve blob")
 	}
 }
 
-func GetArchive(c *context.APIContext) {
+func getArchive(c *context.APIContext) {
 	repoPath := database.RepoPath(c.Params(":username"), c.Params(":reponame"))
 	gitRepo, err := git.Open(repoPath)
 	if err != nil {
@@ -39,10 +39,10 @@ func GetArchive(c *context.APIContext) {
 	}
 	c.Repo.GitRepo = gitRepo
 
-	repo.Download(c.Context)
+	routerepo.Download(c.Context)
 }
 
-func GetEditorconfig(c *context.APIContext) {
+func getEditorconfig(c *context.APIContext) {
 	ec, err := c.Repo.Editorconfig()
 	if err != nil {
 		c.NotFoundOrError(gitutil.NewError(err), "get .editorconfig")

+ 23 - 12
internal/route/api/v1/repo/hook.go → internal/route/api/v1/repo_hook.go

@@ -1,4 +1,4 @@
-package repo
+package v1
 
 import (
 	"encoding/json"
@@ -7,30 +7,35 @@ import (
 
 	"github.com/cockroachdb/errors"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
-	"gogs.io/gogs/internal/route/api/v1/convert"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
 // https://github.com/gogs/go-gogs-client/wiki/Repositories#list-hooks
-func ListHooks(c *context.APIContext) {
+func listHooks(c *context.APIContext) {
 	hooks, err := database.GetWebhooksByRepoID(c.Repo.Repository.ID)
 	if err != nil {
 		c.Errorf(err, "get webhooks by repository ID")
 		return
 	}
 
-	apiHooks := make([]*api.Hook, len(hooks))
+	apiHooks := make([]*types.RepositoryHook, len(hooks))
 	for i := range hooks {
-		apiHooks[i] = convert.ToHook(c.Repo.RepoLink, hooks[i])
+		apiHooks[i] = toRepositoryHook(c.Repo.RepoLink, hooks[i])
 	}
 	c.JSONSuccess(&apiHooks)
 }
 
 // https://github.com/gogs/go-gogs-client/wiki/Repositories#create-a-hook
-func CreateHook(c *context.APIContext, form api.CreateHookOption) {
+type createHookRequest struct {
+	Type   string            `json:"type" binding:"Required"`
+	Config map[string]string `json:"config" binding:"Required"`
+	Events []string          `json:"events"`
+	Active bool              `json:"active"`
+}
+
+func createHook(c *context.APIContext, form createHookRequest) {
 	if !database.IsValidHookTaskType(form.Type) {
 		c.ErrorStatus(http.StatusUnprocessableEntity, errors.New("Invalid hook type."))
 		return
@@ -97,11 +102,17 @@ func CreateHook(c *context.APIContext, form api.CreateHookOption) {
 		return
 	}
 
-	c.JSON(http.StatusCreated, convert.ToHook(c.Repo.RepoLink, w))
+	c.JSON(http.StatusCreated, toRepositoryHook(c.Repo.RepoLink, w))
 }
 
 // https://github.com/gogs/go-gogs-client/wiki/Repositories#edit-a-hook
-func EditHook(c *context.APIContext, form api.EditHookOption) {
+type editHookRequest struct {
+	Config map[string]string `json:"config"`
+	Events []string          `json:"events"`
+	Active *bool             `json:"active"`
+}
+
+func editHook(c *context.APIContext, form editHookRequest) {
 	w, err := database.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
 	if err != nil {
 		c.NotFoundOrError(err, "get webhook of repository by ID")
@@ -166,10 +177,10 @@ func EditHook(c *context.APIContext, form api.EditHookOption) {
 		return
 	}
 
-	c.JSONSuccess(convert.ToHook(c.Repo.RepoLink, w))
+	c.JSONSuccess(toRepositoryHook(c.Repo.RepoLink, w))
 }
 
-func DeleteHook(c *context.APIContext) {
+func deleteHook(c *context.APIContext) {
 	if err := database.DeleteWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
 		c.Errorf(err, "delete webhook of repository by ID")
 		return

+ 35 - 18
internal/route/api/v1/repo/issue.go → internal/route/api/v1/repo_issue.go

@@ -1,18 +1,18 @@
-package repo
+package v1
 
 import (
 	"net/http"
 	"strings"
 
 	"github.com/cockroachdb/errors"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func listIssues(c *context.APIContext, opts *database.IssuesOptions) {
+func queryIssues(c *context.APIContext, opts *database.IssuesOptions) {
 	issues, err := database.Issues(opts)
 	if err != nil {
 		c.Error(err, "list issues")
@@ -26,49 +26,58 @@ func listIssues(c *context.APIContext, opts *database.IssuesOptions) {
 	}
 
 	// FIXME: use IssueList to improve performance.
-	apiIssues := make([]*api.Issue, len(issues))
+	apiIssues := make([]*types.Issue, len(issues))
 	for i := range issues {
 		if err = issues[i].LoadAttributes(); err != nil {
 			c.Error(err, "load attributes")
 			return
 		}
-		apiIssues[i] = issues[i].APIFormat()
+		apiIssues[i] = toIssue(issues[i])
 	}
 
 	c.SetLinkHeader(int(count), conf.UI.IssuePagingNum)
 	c.JSONSuccess(&apiIssues)
 }
 
-func ListUserIssues(c *context.APIContext) {
+func listUserIssues(c *context.APIContext) {
 	opts := database.IssuesOptions{
 		AssigneeID: c.User.ID,
 		Page:       c.QueryInt("page"),
-		IsClosed:   api.StateType(c.Query("state")) == api.STATE_CLOSED,
+		IsClosed:   types.IssueStateType(c.Query("state")) == types.IssueStateClosed,
 	}
 
-	listIssues(c, &opts)
+	queryIssues(c, &opts)
 }
 
-func ListIssues(c *context.APIContext) {
+func listIssues(c *context.APIContext) {
 	opts := database.IssuesOptions{
 		RepoID:   c.Repo.Repository.ID,
 		Page:     c.QueryInt("page"),
-		IsClosed: api.StateType(c.Query("state")) == api.STATE_CLOSED,
+		IsClosed: types.IssueStateType(c.Query("state")) == types.IssueStateClosed,
 	}
 
-	listIssues(c, &opts)
+	queryIssues(c, &opts)
 }
 
-func GetIssue(c *context.APIContext) {
+func getIssue(c *context.APIContext) {
 	issue, err := database.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
 		c.NotFoundOrError(err, "get issue by index")
 		return
 	}
-	c.JSONSuccess(issue.APIFormat())
+	c.JSONSuccess(toIssue(issue))
 }
 
-func CreateIssue(c *context.APIContext, form api.CreateIssueOption) {
+type createIssueRequest struct {
+	Title     string  `json:"title" binding:"Required"`
+	Body      string  `json:"body"`
+	Assignee  string  `json:"assignee"`
+	Milestone int64   `json:"milestone"`
+	Labels    []int64 `json:"labels"`
+	Closed    bool    `json:"closed"`
+}
+
+func createIssue(c *context.APIContext, form createIssueRequest) {
 	issue := &database.Issue{
 		RepoID:   c.Repo.Repository.ID,
 		Title:    form.Title,
@@ -114,10 +123,18 @@ func CreateIssue(c *context.APIContext, form api.CreateIssueOption) {
 		c.Error(err, "get issue by ID")
 		return
 	}
-	c.JSON(http.StatusCreated, issue.APIFormat())
+	c.JSON(http.StatusCreated, toIssue(issue))
+}
+
+type editIssueRequest struct {
+	Title     string  `json:"title"`
+	Body      *string `json:"body"`
+	Assignee  *string `json:"assignee"`
+	Milestone *int64  `json:"milestone"`
+	State     *string `json:"state"`
 }
 
-func EditIssue(c *context.APIContext, form api.EditIssueOption) {
+func editIssue(c *context.APIContext, form editIssueRequest) {
 	issue, err := database.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
 		c.NotFoundOrError(err, "get issue by index")
@@ -173,7 +190,7 @@ func EditIssue(c *context.APIContext, form api.EditIssueOption) {
 		return
 	}
 	if form.State != nil {
-		if err = issue.ChangeStatus(c.User, c.Repo.Repository, api.STATE_CLOSED == api.StateType(*form.State)); err != nil {
+		if err = issue.ChangeStatus(c.User, c.Repo.Repository, types.IssueStateClosed == types.IssueStateType(*form.State)); err != nil {
 			c.Error(err, "change status")
 			return
 		}
@@ -185,5 +202,5 @@ func EditIssue(c *context.APIContext, form api.EditIssueOption) {
 		c.Error(err, "get issue by ID")
 		return
 	}
-	c.JSON(http.StatusCreated, issue.APIFormat())
+	c.JSON(http.StatusCreated, toIssue(issue))
 }

+ 21 - 14
internal/route/api/v1/repo/issue_comment.go → internal/route/api/v1/repo_issue_comment.go

@@ -1,16 +1,15 @@
-package repo
+package v1
 
 import (
 	"net/http"
 	"time"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func ListIssueComments(c *context.APIContext) {
+func listIssueComments(c *context.APIContext) {
 	var since time.Time
 	if len(c.Query("since")) > 0 {
 		var err error
@@ -34,14 +33,14 @@ func ListIssueComments(c *context.APIContext) {
 		return
 	}
 
-	apiComments := make([]*api.Comment, len(comments))
+	apiComments := make([]*types.IssueComment, len(comments))
 	for i := range comments {
-		apiComments[i] = comments[i].APIFormat()
+		apiComments[i] = toIssueComment(comments[i])
 	}
 	c.JSONSuccess(&apiComments)
 }
 
-func ListRepoIssueComments(c *context.APIContext) {
+func listRepoIssueComments(c *context.APIContext) {
 	var since time.Time
 	if len(c.Query("since")) > 0 {
 		var err error
@@ -58,14 +57,18 @@ func ListRepoIssueComments(c *context.APIContext) {
 		return
 	}
 
-	apiComments := make([]*api.Comment, len(comments))
+	apiComments := make([]*types.IssueComment, len(comments))
 	for i := range comments {
-		apiComments[i] = comments[i].APIFormat()
+		apiComments[i] = toIssueComment(comments[i])
 	}
 	c.JSONSuccess(&apiComments)
 }
 
-func CreateIssueComment(c *context.APIContext, form api.CreateIssueCommentOption) {
+type createIssueCommentRequest struct {
+	Body string `json:"body" binding:"Required"`
+}
+
+func createIssueComment(c *context.APIContext, form createIssueCommentRequest) {
 	issue, err := database.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
 		c.Error(err, "get issue by index")
@@ -78,10 +81,14 @@ func CreateIssueComment(c *context.APIContext, form api.CreateIssueCommentOption
 		return
 	}
 
-	c.JSON(http.StatusCreated, comment.APIFormat())
+	c.JSON(http.StatusCreated, toIssueComment(comment))
+}
+
+type editIssueCommentRequest struct {
+	Body string `json:"body" binding:"Required"`
 }
 
-func EditIssueComment(c *context.APIContext, form api.EditIssueCommentOption) {
+func editIssueComment(c *context.APIContext, form editIssueCommentRequest) {
 	comment, err := database.GetCommentByID(c.ParamsInt64(":id"))
 	if err != nil {
 		c.NotFoundOrError(err, "get comment by ID")
@@ -113,10 +120,10 @@ func EditIssueComment(c *context.APIContext, form api.EditIssueCommentOption) {
 		c.Error(err, "update comment")
 		return
 	}
-	c.JSONSuccess(comment.APIFormat())
+	c.JSONSuccess(toIssueComment(comment))
 }
 
-func DeleteIssueComment(c *context.APIContext) {
+func deleteIssueComment(c *context.APIContext) {
 	comment, err := database.GetCommentByID(c.ParamsInt64(":id"))
 	if err != nil {
 		c.NotFoundOrError(err, "get comment by ID")

+ 17 - 14
internal/route/api/v1/repo/issue_label.go → internal/route/api/v1/repo_issue_label.go

@@ -1,29 +1,32 @@
-package repo
+package v1
 
 import (
 	"net/http"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func ListIssueLabels(c *context.APIContext) {
+func listIssueLabels(c *context.APIContext) {
 	issue, err := database.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
 		c.NotFoundOrError(err, "get issue by index")
 		return
 	}
 
-	apiLabels := make([]*api.Label, len(issue.Labels))
+	apiLabels := make([]*types.IssueLabel, len(issue.Labels))
 	for i := range issue.Labels {
-		apiLabels[i] = issue.Labels[i].APIFormat()
+		apiLabels[i] = toIssueLabel(issue.Labels[i])
 	}
 	c.JSONSuccess(&apiLabels)
 }
 
-func AddIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
+type issueLabelsRequest struct {
+	Labels []int64 `json:"labels"`
+}
+
+func addIssueLabels(c *context.APIContext, form issueLabelsRequest) {
 	issue, err := database.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
 		c.NotFoundOrError(err, "get issue by index")
@@ -47,14 +50,14 @@ func AddIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
 		return
 	}
 
-	apiLabels := make([]*api.Label, len(labels))
+	apiLabels := make([]*types.IssueLabel, len(labels))
 	for i := range labels {
-		apiLabels[i] = issue.Labels[i].APIFormat()
+		apiLabels[i] = toIssueLabel(issue.Labels[i])
 	}
 	c.JSONSuccess(&apiLabels)
 }
 
-func DeleteIssueLabel(c *context.APIContext) {
+func deleteIssueLabel(c *context.APIContext) {
 	issue, err := database.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
 		c.NotFoundOrError(err, "get issue by index")
@@ -79,7 +82,7 @@ func DeleteIssueLabel(c *context.APIContext) {
 	c.NoContent()
 }
 
-func ReplaceIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
+func replaceIssueLabels(c *context.APIContext, form issueLabelsRequest) {
 	issue, err := database.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
 		c.NotFoundOrError(err, "get issue by index")
@@ -103,14 +106,14 @@ func ReplaceIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
 		return
 	}
 
-	apiLabels := make([]*api.Label, len(labels))
+	apiLabels := make([]*types.IssueLabel, len(labels))
 	for i := range labels {
-		apiLabels[i] = issue.Labels[i].APIFormat()
+		apiLabels[i] = toIssueLabel(issue.Labels[i])
 	}
 	c.JSONSuccess(&apiLabels)
 }
 
-func ClearIssueLabels(c *context.APIContext) {
+func clearIssueLabels(c *context.APIContext) {
 	issue, err := database.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
 		c.NotFoundOrError(err, "get issue by index")

+ 19 - 15
internal/route/api/v1/repo/key.go → internal/route/api/v1/repo_key.go

@@ -1,15 +1,14 @@
-package repo
+package v1
 
 import (
 	"net/http"
 
 	"github.com/cockroachdb/errors"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
-	"gogs.io/gogs/internal/route/api/v1/convert"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
 func composeDeployKeysAPILink(repoPath string) string {
@@ -17,7 +16,7 @@ func composeDeployKeysAPILink(repoPath string) string {
 }
 
 // https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#list-deploy-keys
-func ListDeployKeys(c *context.APIContext) {
+func listDeployKeys(c *context.APIContext) {
 	keys, err := database.ListDeployKeys(c.Repo.Repository.ID)
 	if err != nil {
 		c.Error(err, "list deploy keys")
@@ -25,20 +24,20 @@ func ListDeployKeys(c *context.APIContext) {
 	}
 
 	apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
-	apiKeys := make([]*api.DeployKey, len(keys))
+	apiKeys := make([]*types.RepositoryDeployKey, len(keys))
 	for i := range keys {
 		if err = keys[i].GetContent(); err != nil {
 			c.Error(err, "get content")
 			return
 		}
-		apiKeys[i] = convert.ToDeployKey(apiLink, keys[i])
+		apiKeys[i] = toDeployKey(apiLink, keys[i])
 	}
 
 	c.JSONSuccess(&apiKeys)
 }
 
 // https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#get-a-deploy-key
-func GetDeployKey(c *context.APIContext) {
+func getDeployKey(c *context.APIContext) {
 	key, err := database.GetDeployKeyByID(c.ParamsInt64(":id"))
 	if err != nil {
 		c.NotFoundOrError(err, "get deploy key by ID")
@@ -56,10 +55,10 @@ func GetDeployKey(c *context.APIContext) {
 	}
 
 	apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
-	c.JSONSuccess(convert.ToDeployKey(apiLink, key))
+	c.JSONSuccess(toDeployKey(apiLink, key))
 }
 
-func HandleCheckKeyStringError(c *context.APIContext, err error) {
+func handleCheckKeyStringError(c *context.APIContext, err error) {
 	if database.IsErrKeyUnableVerify(err) {
 		c.ErrorStatus(http.StatusUnprocessableEntity, errors.New("Unable to verify key content"))
 	} else {
@@ -67,7 +66,7 @@ func HandleCheckKeyStringError(c *context.APIContext, err error) {
 	}
 }
 
-func HandleAddKeyError(c *context.APIContext, err error) {
+func handleAddKeyError(c *context.APIContext, err error) {
 	switch {
 	case database.IsErrKeyAlreadyExist(err):
 		c.ErrorStatus(http.StatusUnprocessableEntity, errors.New("Key content has been used as non-deploy key"))
@@ -79,26 +78,31 @@ func HandleAddKeyError(c *context.APIContext, err error) {
 }
 
 // https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#add-a-new-deploy-key
-func CreateDeployKey(c *context.APIContext, form api.CreateKeyOption) {
+type createDeployKeyRequest struct {
+	Title string `json:"title" binding:"Required"`
+	Key   string `json:"key" binding:"Required"`
+}
+
+func createDeployKey(c *context.APIContext, form createDeployKeyRequest) {
 	content, err := database.CheckPublicKeyString(form.Key)
 	if err != nil {
-		HandleCheckKeyStringError(c, err)
+		handleCheckKeyStringError(c, err)
 		return
 	}
 
 	key, err := database.AddDeployKey(c.Repo.Repository.ID, form.Title, content)
 	if err != nil {
-		HandleAddKeyError(c, err)
+		handleAddKeyError(c, err)
 		return
 	}
 
 	key.Content = content
 	apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
-	c.JSON(http.StatusCreated, convert.ToDeployKey(apiLink, key))
+	c.JSON(http.StatusCreated, toDeployKey(apiLink, key))
 }
 
 // https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#remove-a-deploy-key
-func DeleteDeploykey(c *context.APIContext) {
+func deleteDeploykey(c *context.APIContext) {
 	key, err := database.GetDeployKeyByID(c.ParamsInt64(":id"))
 	if err != nil {
 		c.NotFoundOrError(err, "get deploy key by ID")

+ 22 - 13
internal/route/api/v1/repo/label.go → internal/route/api/v1/repo_label.go

@@ -1,30 +1,29 @@
-package repo
+package v1
 
 import (
 	"net/http"
 	"strconv"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func ListLabels(c *context.APIContext) {
+func listLabels(c *context.APIContext) {
 	labels, err := database.GetLabelsByRepoID(c.Repo.Repository.ID)
 	if err != nil {
 		c.Error(err, "get labels by repository ID")
 		return
 	}
 
-	apiLabels := make([]*api.Label, len(labels))
+	apiLabels := make([]*types.IssueLabel, len(labels))
 	for i := range labels {
-		apiLabels[i] = labels[i].APIFormat()
+		apiLabels[i] = toIssueLabel(labels[i])
 	}
 	c.JSONSuccess(&apiLabels)
 }
 
-func GetLabel(c *context.APIContext) {
+func getLabel(c *context.APIContext) {
 	var label *database.Label
 	var err error
 	idStr := c.Params(":id")
@@ -38,10 +37,15 @@ func GetLabel(c *context.APIContext) {
 		return
 	}
 
-	c.JSONSuccess(label.APIFormat())
+	c.JSONSuccess(toIssueLabel(label))
+}
+
+type createLabelRequest struct {
+	Name  string `json:"name" binding:"Required"`
+	Color string `json:"color" binding:"Required;Size(7)"`
 }
 
-func CreateLabel(c *context.APIContext, form api.CreateLabelOption) {
+func createLabel(c *context.APIContext, form createLabelRequest) {
 	label := &database.Label{
 		Name:   form.Name,
 		Color:  form.Color,
@@ -51,10 +55,15 @@ func CreateLabel(c *context.APIContext, form api.CreateLabelOption) {
 		c.Error(err, "new labels")
 		return
 	}
-	c.JSON(http.StatusCreated, label.APIFormat())
+	c.JSON(http.StatusCreated, toIssueLabel(label))
+}
+
+type editLabelRequest struct {
+	Name  *string `json:"name"`
+	Color *string `json:"color"`
 }
 
-func EditLabel(c *context.APIContext, form api.EditLabelOption) {
+func editLabel(c *context.APIContext, form editLabelRequest) {
 	label, err := database.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
 	if err != nil {
 		c.NotFoundOrError(err, "get label of repository by ID")
@@ -71,10 +80,10 @@ func EditLabel(c *context.APIContext, form api.EditLabelOption) {
 		c.Error(err, "update label")
 		return
 	}
-	c.JSONSuccess(label.APIFormat())
+	c.JSONSuccess(toIssueLabel(label))
 }
 
-func DeleteLabel(c *context.APIContext) {
+func deleteLabel(c *context.APIContext) {
 	if err := database.DeleteLabel(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
 		c.Error(err, "delete label")
 		return

+ 26 - 14
internal/route/api/v1/repo/milestone.go → internal/route/api/v1/repo_milestone.go

@@ -1,39 +1,44 @@
-package repo
+package v1
 
 import (
 	"net/http"
 	"time"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func ListMilestones(c *context.APIContext) {
+func listMilestones(c *context.APIContext) {
 	milestones, err := database.GetMilestonesByRepoID(c.Repo.Repository.ID)
 	if err != nil {
 		c.Error(err, "get milestones by repository ID")
 		return
 	}
 
-	apiMilestones := make([]*api.Milestone, len(milestones))
+	apiMilestones := make([]*types.IssueMilestone, len(milestones))
 	for i := range milestones {
-		apiMilestones[i] = milestones[i].APIFormat()
+		apiMilestones[i] = toIssueMilestone(milestones[i])
 	}
 	c.JSONSuccess(&apiMilestones)
 }
 
-func GetMilestone(c *context.APIContext) {
+func getMilestone(c *context.APIContext) {
 	milestone, err := database.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
 	if err != nil {
 		c.NotFoundOrError(err, "get milestone by repository ID")
 		return
 	}
-	c.JSONSuccess(milestone.APIFormat())
+	c.JSONSuccess(toIssueMilestone(milestone))
+}
+
+type createMilestoneRequest struct {
+	Title       string     `json:"title"`
+	Description string     `json:"description"`
+	Deadline    *time.Time `json:"due_on"`
 }
 
-func CreateMilestone(c *context.APIContext, form api.CreateMilestoneOption) {
+func createMilestone(c *context.APIContext, form createMilestoneRequest) {
 	if form.Deadline == nil {
 		defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local)
 		form.Deadline = &defaultDeadline
@@ -50,10 +55,17 @@ func CreateMilestone(c *context.APIContext, form api.CreateMilestoneOption) {
 		c.Error(err, "new milestone")
 		return
 	}
-	c.JSON(http.StatusCreated, milestone.APIFormat())
+	c.JSON(http.StatusCreated, toIssueMilestone(milestone))
+}
+
+type editMilestoneRequest struct {
+	Title       string     `json:"title"`
+	Description *string    `json:"description"`
+	State       *string    `json:"state"`
+	Deadline    *time.Time `json:"due_on"`
 }
 
-func EditMilestone(c *context.APIContext, form api.EditMilestoneOption) {
+func editMilestone(c *context.APIContext, form editMilestoneRequest) {
 	milestone, err := database.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
 	if err != nil {
 		c.NotFoundOrError(err, "get milestone by repository ID")
@@ -71,7 +83,7 @@ func EditMilestone(c *context.APIContext, form api.EditMilestoneOption) {
 	}
 
 	if form.State != nil {
-		if err = milestone.ChangeStatus(api.STATE_CLOSED == api.StateType(*form.State)); err != nil {
+		if err = milestone.ChangeStatus(types.IssueStateClosed == types.IssueStateType(*form.State)); err != nil {
 			c.Error(err, "change status")
 			return
 		}
@@ -80,10 +92,10 @@ func EditMilestone(c *context.APIContext, form api.EditMilestoneOption) {
 		return
 	}
 
-	c.JSONSuccess(milestone.APIFormat())
+	c.JSONSuccess(toIssueMilestone(milestone))
 }
 
-func DeleteMilestone(c *context.APIContext) {
+func deleteMilestone(c *context.APIContext) {
 	if err := database.DeleteMilestoneOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
 		c.Error(err, "delete milestone of repository by ID")
 		return

+ 64 - 40
internal/route/api/v1/repo/repo.go → internal/route/api/v1/repo_repo.go

@@ -1,25 +1,24 @@
-package repo
+package v1
 
 import (
 	"net/http"
 	"path"
 
 	"github.com/cockroachdb/errors"
-	api "github.com/gogs/go-gogs-client"
 	log "unknwon.dev/clog/v2"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
 	"gogs.io/gogs/internal/form"
-	"gogs.io/gogs/internal/route/api/v1/convert"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func Search(c *context.APIContext) {
+func searchRepos(c *context.APIContext) {
 	opts := &database.SearchRepoOptions{
 		Keyword:  path.Base(c.Query("q")),
 		OwnerID:  c.QueryInt64("uid"),
-		PageSize: convert.ToCorrectPageSize(c.QueryInt("limit")),
+		PageSize: toAllowedPageSize(c.QueryInt("limit")),
 		Page:     c.QueryInt("page"),
 	}
 
@@ -60,9 +59,9 @@ func Search(c *context.APIContext) {
 		return
 	}
 
-	results := make([]*api.Repository, len(repos))
+	results := make([]*types.Repository, len(repos))
 	for i := range repos {
-		results[i] = repos[i].APIFormatLegacy(nil)
+		results[i] = toRepository(repos[i], nil)
 	}
 
 	c.SetLinkHeader(int(count), opts.PageSize)
@@ -72,7 +71,7 @@ func Search(c *context.APIContext) {
 	})
 }
 
-func listUserRepositories(c *context.APIContext, username string) {
+func listReposOfUser(c *context.APIContext, username string) {
 	user, err := database.Handle.Users().GetByUsername(c.Req.Context(), username)
 	if err != nil {
 		c.NotFoundOrError(err, "get user by name")
@@ -104,9 +103,9 @@ func listUserRepositories(c *context.APIContext, username string) {
 
 	// Early return for querying other user's repositories
 	if c.User.ID != user.ID {
-		repos := make([]*api.Repository, len(ownRepos))
+		repos := make([]*types.Repository, len(ownRepos))
 		for i := range ownRepos {
-			repos[i] = ownRepos[i].APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true})
+			repos[i] = toRepository(ownRepos[i], &types.RepositoryPermission{Admin: true, Push: true, Pull: true})
 		}
 		c.JSONSuccess(&repos)
 		return
@@ -128,14 +127,14 @@ func listUserRepositories(c *context.APIContext, username string) {
 	}
 
 	numOwnRepos := len(ownRepos)
-	repos := make([]*api.Repository, 0, numOwnRepos+len(accessibleReposWithAccessMode))
+	repos := make([]*types.Repository, 0, numOwnRepos+len(accessibleReposWithAccessMode))
 	for _, r := range ownRepos {
-		repos = append(repos, r.APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true}))
+		repos = append(repos, toRepository(r, &types.RepositoryPermission{Admin: true, Push: true, Pull: true}))
 	}
 
 	for repo, access := range accessibleReposWithAccessMode {
 		repos = append(repos,
-			repo.APIFormatLegacy(&api.Permission{
+			toRepository(repo, &types.RepositoryPermission{
 				Admin: access >= database.AccessModeAdmin,
 				Push:  access >= database.AccessModeWrite,
 				Pull:  true,
@@ -146,19 +145,29 @@ func listUserRepositories(c *context.APIContext, username string) {
 	c.JSONSuccess(&repos)
 }
 
-func ListMyRepos(c *context.APIContext) {
-	listUserRepositories(c, c.User.Name)
+func listMyRepos(c *context.APIContext) {
+	listReposOfUser(c, c.User.Name)
 }
 
-func ListUserRepositories(c *context.APIContext) {
-	listUserRepositories(c, c.Params(":username"))
+func listUserRepositories(c *context.APIContext) {
+	listReposOfUser(c, c.Params(":username"))
 }
 
-func ListOrgRepositories(c *context.APIContext) {
-	listUserRepositories(c, c.Params(":org"))
+func listOrgRepositories(c *context.APIContext) {
+	listReposOfUser(c, c.Params(":org"))
 }
 
-func CreateUserRepo(c *context.APIContext, owner *database.User, opt api.CreateRepoOption) {
+type createRepoRequest struct {
+	Name        string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"`
+	Description string `json:"description" binding:"MaxSize(255)"`
+	Private     bool   `json:"private"`
+	AutoInit    bool   `json:"auto_init"`
+	Gitignores  string `json:"gitignores"`
+	License     string `json:"license"`
+	Readme      string `json:"readme"`
+}
+
+func createUserRepo(c *context.APIContext, owner *database.User, opt createRepoRequest) {
 	repo, err := database.CreateRepository(c.User, owner, database.CreateRepoOptionsLegacy{
 		Name:        opt.Name,
 		Description: opt.Description,
@@ -183,19 +192,19 @@ func CreateUserRepo(c *context.APIContext, owner *database.User, opt api.CreateR
 		return
 	}
 
-	c.JSON(201, repo.APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true}))
+	c.JSON(201, toRepository(repo, &types.RepositoryPermission{Admin: true, Push: true, Pull: true}))
 }
 
-func Create(c *context.APIContext, opt api.CreateRepoOption) {
+func createRepo(c *context.APIContext, opt createRepoRequest) {
 	// Shouldn't reach this condition, but just in case.
 	if c.User.IsOrganization() {
 		c.ErrorStatus(http.StatusUnprocessableEntity, errors.New("Not allowed to create repository for organization."))
 		return
 	}
-	CreateUserRepo(c, c.User, opt)
+	createUserRepo(c, c.User, opt)
 }
 
-func CreateOrgRepo(c *context.APIContext, opt api.CreateRepoOption) {
+func createOrgRepo(c *context.APIContext, opt createRepoRequest) {
 	org, err := database.GetOrgByName(c.Params(":org"))
 	if err != nil {
 		c.NotFoundOrError(err, "get organization by name")
@@ -206,10 +215,10 @@ func CreateOrgRepo(c *context.APIContext, opt api.CreateRepoOption) {
 		c.ErrorStatus(http.StatusForbidden, errors.New("Given user is not owner of organization."))
 		return
 	}
-	CreateUserRepo(c, org, opt)
+	createUserRepo(c, org, opt)
 }
 
-func Migrate(c *context.APIContext, f form.MigrateRepo) {
+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.
@@ -287,7 +296,7 @@ func Migrate(c *context.APIContext, f form.MigrateRepo) {
 	}
 
 	log.Trace("Repository migrated: %s/%s", ctxUser.Name, f.RepoName)
-	c.JSON(201, repo.APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true}))
+	c.JSON(201, toRepository(repo, &types.RepositoryPermission{Admin: true, Push: true, Pull: true}))
 }
 
 // FIXME: inject in the handler chain
@@ -311,20 +320,20 @@ func parseOwnerAndRepo(c *context.APIContext) (*database.User, *database.Reposit
 	return owner, repo
 }
 
-func Get(c *context.APIContext) {
+func getRepo(c *context.APIContext) {
 	_, repo := parseOwnerAndRepo(c)
 	if c.Written() {
 		return
 	}
 
-	c.JSONSuccess(repo.APIFormatLegacy(&api.Permission{
+	c.JSONSuccess(toRepository(repo, &types.RepositoryPermission{
 		Admin: c.Repo.IsAdmin(),
 		Push:  c.Repo.IsWriter(),
 		Pull:  true,
 	}))
 }
 
-func Delete(c *context.APIContext) {
+func deleteRepo(c *context.APIContext) {
 	owner, repo := parseOwnerAndRepo(c)
 	if c.Written() {
 		return
@@ -344,14 +353,14 @@ func Delete(c *context.APIContext) {
 	c.NoContent()
 }
 
-func ListForks(c *context.APIContext) {
+func listForks(c *context.APIContext) {
 	forks, err := c.Repo.Repository.GetForks()
 	if err != nil {
 		c.Error(err, "get forks")
 		return
 	}
 
-	apiForks := make([]*api.Repository, len(forks))
+	apiForks := make([]*types.Repository, len(forks))
 	for i := range forks {
 		if err := forks[i].GetOwner(); err != nil {
 			c.Error(err, "get owner")
@@ -368,8 +377,8 @@ func ListForks(c *context.APIContext) {
 			},
 		)
 
-		apiForks[i] = forks[i].APIFormatLegacy(
-			&api.Permission{
+		apiForks[i] = toRepository(forks[i],
+			&types.RepositoryPermission{
 				Admin: accessMode >= database.AccessModeAdmin,
 				Push:  accessMode >= database.AccessModeWrite,
 				Pull:  true,
@@ -380,7 +389,15 @@ func ListForks(c *context.APIContext) {
 	c.JSONSuccess(&apiForks)
 }
 
-func IssueTracker(c *context.APIContext, form api.EditIssueTrackerOption) {
+type editIssueTrackerRequest struct {
+	EnableIssues          *bool   `json:"enable_issues"`
+	EnableExternalTracker *bool   `json:"enable_external_tracker"`
+	ExternalTrackerURL    *string `json:"external_tracker_url"`
+	TrackerURLFormat      *string `json:"tracker_url_format"`
+	TrackerIssueStyle     *string `json:"tracker_issue_style"`
+}
+
+func issueTracker(c *context.APIContext, form editIssueTrackerRequest) {
 	_, repo := parseOwnerAndRepo(c)
 	if c.Written() {
 		return
@@ -410,7 +427,14 @@ func IssueTracker(c *context.APIContext, form api.EditIssueTrackerOption) {
 	c.NoContent()
 }
 
-func Wiki(c *context.APIContext, form api.EditWikiOption) {
+type editWikiRequest struct {
+	EnableWiki         *bool   `json:"enable_wiki"`
+	AllowPublicWiki    *bool   `json:"allow_public_wiki"`
+	EnableExternalWiki *bool   `json:"enable_external_wiki"`
+	ExternalWikiURL    *string `json:"external_wiki_url"`
+}
+
+func wiki(c *context.APIContext, form editWikiRequest) {
 	_, repo := parseOwnerAndRepo(c)
 	if c.Written() {
 		return
@@ -436,7 +460,7 @@ func Wiki(c *context.APIContext, form api.EditWikiOption) {
 	c.NoContent()
 }
 
-func MirrorSync(c *context.APIContext) {
+func mirrorSync(c *context.APIContext) {
 	_, repo := parseOwnerAndRepo(c)
 	if c.Written() {
 		return
@@ -449,14 +473,14 @@ func MirrorSync(c *context.APIContext) {
 	c.Status(http.StatusAccepted)
 }
 
-func Releases(c *context.APIContext) {
+func releases(c *context.APIContext) {
 	_, repo := parseOwnerAndRepo(c)
 	releases, err := database.GetReleasesByRepoID(repo.ID)
 	if err != nil {
 		c.Error(err, "get releases by repository ID")
 		return
 	}
-	apiReleases := make([]*api.Release, 0, len(releases))
+	apiReleases := make([]*types.RepositoryRelease, 0, len(releases))
 	for _, r := range releases {
 		publisher, err := database.Handle.Users().GetByID(c.Req.Context(), r.PublisherID)
 		if err != nil {
@@ -466,7 +490,7 @@ func Releases(c *context.APIContext) {
 		r.Publisher = publisher
 	}
 	for _, r := range releases {
-		apiReleases = append(apiReleases, r.APIFormat())
+		apiReleases = append(apiReleases, toRelease(r))
 	}
 
 	c.JSONSuccess(&apiReleases)

+ 4 - 5
internal/route/api/v1/repo/tag.go → internal/route/api/v1/repo_tag.go

@@ -1,25 +1,24 @@
-package repo
+package v1
 
 import (
 	"gogs.io/gogs/internal/context"
-	"gogs.io/gogs/internal/route/api/v1/convert"
 )
 
-func ListTags(c *context.APIContext) {
+func listTags(c *context.APIContext) {
 	tags, err := c.Repo.Repository.GetTags()
 	if err != nil {
 		c.Error(err, "get tags")
 		return
 	}
 
-	apiTags := make([]*convert.Tag, len(tags))
+	apiTags := make([]*tag, len(tags))
 	for i := range tags {
 		commit, err := tags[i].GetCommit()
 		if err != nil {
 			c.Error(err, "get commit")
 			return
 		}
-		apiTags[i] = convert.ToTag(tags[i], commit)
+		apiTags[i] = toTag(tags[i], commit)
 	}
 
 	c.JSONSuccess(&apiTags)

+ 2 - 2
internal/route/api/v1/repo/tree.go → internal/route/api/v1/repo_tree.go

@@ -1,4 +1,4 @@
-package repo
+package v1
 
 import (
 	"fmt"
@@ -9,7 +9,7 @@ import (
 	"gogs.io/gogs/internal/gitutil"
 )
 
-func GetRepoGitTree(c *context.APIContext) {
+func getRepoGitTree(c *context.APIContext) {
 	gitRepo, err := git.Open(c.Repo.Repository.RepoPath())
 	if err != nil {
 		c.Error(err, "open repository")

+ 8 - 0
internal/route/api/v1/tag.go

@@ -0,0 +1,8 @@
+package v1
+
+import "gogs.io/gogs/internal/route/api/v1/types"
+
+type tag struct {
+	Name   string                      `json:"name"`
+	Commit *types.WebhookPayloadCommit `json:"commit"`
+}

+ 29 - 0
internal/route/api/v1/types/commit.go

@@ -0,0 +1,29 @@
+package types
+
+type CommitMeta struct {
+	URL string `json:"url"`
+	SHA string `json:"sha"`
+}
+
+type CommitUser struct {
+	Name  string `json:"name"`
+	Email string `json:"email"`
+	Date  string `json:"date"`
+}
+
+type RepoCommit struct {
+	URL       string      `json:"url"`
+	Author    *CommitUser `json:"author"`
+	Committer *CommitUser `json:"committer"`
+	Message   string      `json:"message"`
+	Tree      *CommitMeta `json:"tree"`
+}
+
+type Commit struct {
+	*CommitMeta
+	HTMLURL    string        `json:"html_url"`
+	RepoCommit *RepoCommit   `json:"commit"`
+	Author     *User         `json:"author"`
+	Committer  *User         `json:"committer"`
+	Parents    []*CommitMeta `json:"parents"`
+}

+ 23 - 0
internal/route/api/v1/types/hook.go

@@ -0,0 +1,23 @@
+package types
+
+import "time"
+
+type RepositoryHook struct {
+	ID      int64             `json:"id"`
+	Type    string            `json:"type"`
+	URL     string            `json:"-"`
+	Config  map[string]string `json:"config"`
+	Events  []string          `json:"events"`
+	Active  bool              `json:"active"`
+	Updated time.Time         `json:"updated_at"`
+	Created time.Time         `json:"created_at"`
+}
+
+type RepositoryDeployKey struct {
+	ID       int64     `json:"id"`
+	Key      string    `json:"key"`
+	URL      string    `json:"url"`
+	Title    string    `json:"title"`
+	Created  time.Time `json:"created_at"`
+	ReadOnly bool      `json:"read_only"`
+}

+ 58 - 0
internal/route/api/v1/types/issue.go

@@ -0,0 +1,58 @@
+package types
+
+import "time"
+
+type IssueStateType string
+
+const (
+	IssueStateOpen   IssueStateType = "open"
+	IssueStateClosed IssueStateType = "closed"
+)
+
+type PullRequestMeta struct {
+	HasMerged bool       `json:"merged"`
+	Merged    *time.Time `json:"merged_at"`
+}
+
+type Issue struct {
+	ID          int64            `json:"id"`
+	Index       int64            `json:"number"`
+	Poster      *User            `json:"user"`
+	Title       string           `json:"title"`
+	Body        string           `json:"body"`
+	Labels      []*IssueLabel    `json:"labels"`
+	Milestone   *IssueMilestone  `json:"milestone"`
+	Assignee    *User            `json:"assignee"`
+	State       IssueStateType   `json:"state"`
+	Comments    int              `json:"comments"`
+	Created     time.Time        `json:"created_at"`
+	Updated     time.Time        `json:"updated_at"`
+	PullRequest *PullRequestMeta `json:"pull_request"`
+}
+
+type IssueLabel struct {
+	ID    int64  `json:"id"`
+	Name  string `json:"name"`
+	Color string `json:"color"`
+	URL   string `json:"url"`
+}
+
+type IssueMilestone struct {
+	ID           int64          `json:"id"`
+	Title        string         `json:"title"`
+	Description  string         `json:"description"`
+	State        IssueStateType `json:"state"`
+	OpenIssues   int            `json:"open_issues"`
+	ClosedIssues int            `json:"closed_issues"`
+	Closed       *time.Time     `json:"closed_at"`
+	Deadline     *time.Time     `json:"due_on"`
+}
+
+type IssueComment struct {
+	ID      int64     `json:"id"`
+	HTMLURL string    `json:"html_url"`
+	Poster  *User     `json:"user"`
+	Body    string    `json:"body"`
+	Created time.Time `json:"created_at"`
+	Updated time.Time `json:"updated_at"`
+}

+ 18 - 0
internal/route/api/v1/types/org.go

@@ -0,0 +1,18 @@
+package types
+
+type Organization struct {
+	ID          int64  `json:"id"`
+	UserName    string `json:"username"`
+	FullName    string `json:"full_name"`
+	AvatarURL   string `json:"avatar_url"`
+	Description string `json:"description"`
+	Website     string `json:"website"`
+	Location    string `json:"location"`
+}
+
+type OrganizationTeam struct {
+	ID          int64  `json:"id"`
+	Name        string `json:"name"`
+	Description string `json:"description"`
+	Permission  string `json:"permission"`
+}

+ 26 - 0
internal/route/api/v1/types/pull_request.go

@@ -0,0 +1,26 @@
+package types
+
+import "time"
+
+type PullRequest struct {
+	ID             int64           `json:"id"`
+	Index          int64           `json:"number"`
+	Poster         *User           `json:"user"`
+	Title          string          `json:"title"`
+	Body           string          `json:"body"`
+	Labels         []*IssueLabel   `json:"labels"`
+	Milestone      *IssueMilestone `json:"milestone"`
+	Assignee       *User           `json:"assignee"`
+	State          IssueStateType  `json:"state"`
+	Comments       int             `json:"comments"`
+	HeadBranch     string          `json:"head_branch"`
+	HeadRepo       *Repository     `json:"head_repo"`
+	BaseBranch     string          `json:"base_branch"`
+	BaseRepo       *Repository     `json:"base_repo"`
+	HTMLURL        string          `json:"html_url"`
+	Mergeable      *bool           `json:"mergeable"`
+	HasMerged      bool            `json:"merged"`
+	Merged         *time.Time      `json:"merged_at"`
+	MergedCommitID *string         `json:"merge_commit_sha"`
+	MergedBy       *User           `json:"merged_by"`
+}

+ 52 - 0
internal/route/api/v1/types/repo.go

@@ -0,0 +1,52 @@
+package types
+
+import "time"
+
+type RepositoryPermission struct {
+	Admin bool `json:"admin"`
+	Push  bool `json:"push"`
+	Pull  bool `json:"pull"`
+}
+
+type Repository struct {
+	ID            int64                 `json:"id"`
+	Owner         *User                 `json:"owner"`
+	Name          string                `json:"name"`
+	FullName      string                `json:"full_name"`
+	Description   string                `json:"description"`
+	Private       bool                  `json:"private"`
+	Fork          bool                  `json:"fork"`
+	Parent        *Repository           `json:"parent"`
+	Empty         bool                  `json:"empty"`
+	Mirror        bool                  `json:"mirror"`
+	Size          int64                 `json:"size"`
+	HTMLURL       string                `json:"html_url"`
+	SSHURL        string                `json:"ssh_url"`
+	CloneURL      string                `json:"clone_url"`
+	Website       string                `json:"website"`
+	Stars         int                   `json:"stars_count"`
+	Forks         int                   `json:"forks_count"`
+	Watchers      int                   `json:"watchers_count"`
+	OpenIssues    int                   `json:"open_issues_count"`
+	DefaultBranch string                `json:"default_branch"`
+	Created       time.Time             `json:"created_at"`
+	Updated       time.Time             `json:"updated_at"`
+	Permissions   *RepositoryPermission `json:"permissions,omitempty"`
+}
+
+type RepositoryBranch struct {
+	Name   string                `json:"name"`
+	Commit *WebhookPayloadCommit `json:"commit"`
+}
+
+type RepositoryRelease struct {
+	ID              int64     `json:"id"`
+	TagName         string    `json:"tag_name"`
+	TargetCommitish string    `json:"target_commitish"`
+	Name            string    `json:"name"`
+	Body            string    `json:"body"`
+	Draft           bool      `json:"draft"`
+	Prerelease      bool      `json:"prerelease"`
+	Author          *User     `json:"author"`
+	Created         time.Time `json:"created_at"`
+}

+ 36 - 0
internal/route/api/v1/types/user.go

@@ -0,0 +1,36 @@
+package types
+
+import "time"
+
+type User struct {
+	ID        int64  `json:"id"`
+	UserName  string `json:"username"`
+	Login     string `json:"login"`
+	FullName  string `json:"full_name"`
+	Email     string `json:"email"`
+	AvatarURL string `json:"avatar_url"`
+}
+
+type UserEmail struct {
+	Email    string `json:"email"`
+	Verified bool   `json:"verified"`
+	Primary  bool   `json:"primary"`
+}
+
+type UserAccessToken struct {
+	Name string `json:"name"`
+	Sha1 string `json:"sha1"`
+}
+
+type UserPublicKey struct {
+	ID      int64     `json:"id"`
+	Key     string    `json:"key"`
+	URL     string    `json:"url,omitempty"`
+	Title   string    `json:"title,omitempty"`
+	Created time.Time `json:"created_at,omitempty"`
+}
+
+type RepositoryCollaborator struct {
+	*User
+	Permissions RepositoryPermission `json:"permissions"`
+}

+ 158 - 0
internal/route/api/v1/types/webhook.go

@@ -0,0 +1,158 @@
+package types
+
+import (
+	"encoding/json"
+	"time"
+)
+
+// WebhookPayloader is implemented by webhook payload types.
+type WebhookPayloader interface {
+	JSONPayload() ([]byte, error)
+}
+
+func jsonPayload(p any) ([]byte, error) {
+	return json.MarshalIndent(p, "", "  ")
+}
+
+type WebhookPayloadUser struct {
+	Name     string `json:"name"`
+	Email    string `json:"email"`
+	UserName string `json:"username"`
+}
+
+type WebhookPayloadCommit struct {
+	ID        string              `json:"id"`
+	Message   string              `json:"message"`
+	URL       string              `json:"url"`
+	Author    *WebhookPayloadUser `json:"author"`
+	Committer *WebhookPayloadUser `json:"committer"`
+	Added     []string            `json:"added"`
+	Removed   []string            `json:"removed"`
+	Modified  []string            `json:"modified"`
+	Timestamp time.Time           `json:"timestamp"`
+}
+
+type WebhookPusherType string
+
+const WebhookPusherTypeUser WebhookPusherType = "user"
+
+type WebhookIssueAction string
+
+const (
+	WebhookIssueOpened       WebhookIssueAction = "opened"
+	WebhookIssueClosed       WebhookIssueAction = "closed"
+	WebhookIssueReopened     WebhookIssueAction = "reopened"
+	WebhookIssueEdited       WebhookIssueAction = "edited"
+	WebhookIssueAssigned     WebhookIssueAction = "assigned"
+	WebhookIssueUnassigned   WebhookIssueAction = "unassigned"
+	WebhookIssueLabelUpdated WebhookIssueAction = "label_updated"
+	WebhookIssueLabelCleared WebhookIssueAction = "label_cleared"
+	WebhookIssueMilestoned   WebhookIssueAction = "milestoned"
+	WebhookIssueDemilestoned WebhookIssueAction = "demilestoned"
+	WebhookIssueSynchronized WebhookIssueAction = "synchronized"
+)
+
+type WebhookIssueCommentAction string
+
+const (
+	WebhookIssueCommentCreated WebhookIssueCommentAction = "created"
+	WebhookIssueCommentEdited  WebhookIssueCommentAction = "edited"
+	WebhookIssueCommentDeleted WebhookIssueCommentAction = "deleted"
+)
+
+type WebhookReleaseAction string
+
+const WebhookReleasePublished WebhookReleaseAction = "published"
+
+type WebhookChangesFromPayload struct {
+	From string `json:"from"`
+}
+
+type WebhookChangesPayload struct {
+	Title *WebhookChangesFromPayload `json:"title,omitempty"`
+	Body  *WebhookChangesFromPayload `json:"body,omitempty"`
+}
+
+type WebhookCreatePayload struct {
+	Ref           string      `json:"ref"`
+	RefType       string      `json:"ref_type"`
+	Sha           string      `json:"sha"`
+	DefaultBranch string      `json:"default_branch"`
+	Repo          *Repository `json:"repository"`
+	Sender        *User       `json:"sender"`
+}
+
+func (p *WebhookCreatePayload) JSONPayload() ([]byte, error) { return jsonPayload(p) }
+
+type WebhookDeletePayload struct {
+	Ref        string            `json:"ref"`
+	RefType    string            `json:"ref_type"`
+	PusherType WebhookPusherType `json:"pusher_type"`
+	Repo       *Repository       `json:"repository"`
+	Sender     *User             `json:"sender"`
+}
+
+func (p *WebhookDeletePayload) JSONPayload() ([]byte, error) { return jsonPayload(p) }
+
+type WebhookForkPayload struct {
+	Forkee *Repository `json:"forkee"`
+	Repo   *Repository `json:"repository"`
+	Sender *User       `json:"sender"`
+}
+
+func (p *WebhookForkPayload) JSONPayload() ([]byte, error) { return jsonPayload(p) }
+
+type WebhookPushPayload struct {
+	Ref        string                  `json:"ref"`
+	Before     string                  `json:"before"`
+	After      string                  `json:"after"`
+	CompareURL string                  `json:"compare_url"`
+	Commits    []*WebhookPayloadCommit `json:"commits"`
+	Repo       *Repository             `json:"repository"`
+	Pusher     *User                   `json:"pusher"`
+	Sender     *User                   `json:"sender"`
+}
+
+func (p *WebhookPushPayload) JSONPayload() ([]byte, error) { return jsonPayload(p) }
+
+type WebhookIssuesPayload struct {
+	Action     WebhookIssueAction     `json:"action"`
+	Index      int64                  `json:"number"`
+	Issue      *Issue                 `json:"issue"`
+	Changes    *WebhookChangesPayload `json:"changes,omitempty"`
+	Repository *Repository            `json:"repository"`
+	Sender     *User                  `json:"sender"`
+}
+
+func (p *WebhookIssuesPayload) JSONPayload() ([]byte, error) { return jsonPayload(p) }
+
+type WebhookIssueCommentPayload struct {
+	Action     WebhookIssueCommentAction `json:"action"`
+	Issue      *Issue                    `json:"issue"`
+	Comment    *IssueComment             `json:"comment"`
+	Changes    *WebhookChangesPayload    `json:"changes,omitempty"`
+	Repository *Repository               `json:"repository"`
+	Sender     *User                     `json:"sender"`
+}
+
+func (p *WebhookIssueCommentPayload) JSONPayload() ([]byte, error) { return jsonPayload(p) }
+
+type WebhookPullRequestPayload struct {
+	Action      WebhookIssueAction     `json:"action"`
+	Index       int64                  `json:"number"`
+	PullRequest *PullRequest           `json:"pull_request"`
+	Changes     *WebhookChangesPayload `json:"changes,omitempty"`
+	Repository  *Repository            `json:"repository"`
+	Sender      *User                  `json:"sender"`
+}
+
+func (p *WebhookPullRequestPayload) JSONPayload() ([]byte, error) { return jsonPayload(p) }
+
+type WebhookReleasePayload struct {
+	Action     WebhookReleaseAction `json:"action"`
+	Release    *RepositoryRelease   `json:"release"`
+	Repository *Repository          `json:"repository"`
+	Sender     *User                `json:"sender"`
+}
+
+func (p *WebhookReleasePayload) JSONPayload() ([]byte, error) { return jsonPayload(p) }

+ 11 - 18
internal/route/api/v1/user/user.go → internal/route/api/v1/user.go

@@ -1,16 +1,14 @@
-package user
+package v1
 
 import (
 	"net/http"
 
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
-	"gogs.io/gogs/internal/markup"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func Search(c *context.APIContext) {
+func searchUsers(c *context.APIContext) {
 	pageSize := c.QueryInt("limit")
 	if pageSize <= 0 {
 		pageSize = 10
@@ -24,16 +22,11 @@ func Search(c *context.APIContext) {
 		return
 	}
 
-	results := make([]*api.User, len(users))
+	results := make([]*types.User, len(users))
 	for i := range users {
-		results[i] = &api.User{
-			ID:        users[i].ID,
-			UserName:  users[i].Name,
-			AvatarUrl: users[i].AvatarURL(),
-			FullName:  markup.Sanitize(users[i].FullName),
-		}
-		if c.IsLogged {
-			results[i].Email = users[i].Email
+		results[i] = toUser(users[i])
+		if !c.IsLogged {
+			results[i].Email = ""
 		}
 	}
 
@@ -43,7 +36,7 @@ func Search(c *context.APIContext) {
 	})
 }
 
-func GetInfo(c *context.APIContext) {
+func getUserProfile(c *context.APIContext) {
 	u, err := database.Handle.Users().GetByUsername(c.Req.Context(), c.Params(":username"))
 	if err != nil {
 		c.NotFoundOrError(err, "get user by name")
@@ -54,9 +47,9 @@ func GetInfo(c *context.APIContext) {
 	if !c.IsLogged {
 		u.Email = ""
 	}
-	c.JSONSuccess(u.APIFormat())
+	c.JSONSuccess(toUser(u))
 }
 
-func GetAuthenticatedUser(c *context.APIContext) {
-	c.JSONSuccess(c.User.APIFormat())
+func getAuthenticatedUser(c *context.APIContext) {
+	c.JSONSuccess(toUser(c.User))
 }

+ 25 - 15
internal/route/api/v1/user/access_tokens.go → internal/route/api/v1/user_access_tokens.go

@@ -1,30 +1,30 @@
-package user
+package v1
 
 import (
 	gocontext "context"
 	"net/http"
 
-	api "github.com/gogs/go-gogs-client"
 	"gopkg.in/macaron.v1"
 
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-// AccessTokensHandler is the handler for users access tokens API endpoints.
-type AccessTokensHandler struct {
+// accessTokensHandler is the handler for users access tokens API endpoints.
+type accessTokensHandler struct {
 	store AccessTokensStore
 }
 
-// NewAccessTokensHandler returns a new AccessTokensHandler for users access
+// newAccessTokensHandler returns a new accessTokensHandler for users access
 // tokens API endpoints.
-func NewAccessTokensHandler(s AccessTokensStore) *AccessTokensHandler {
-	return &AccessTokensHandler{
+func newAccessTokensHandler(s AccessTokensStore) *accessTokensHandler {
+	return &accessTokensHandler{
 		store: s,
 	}
 }
 
-func (h *AccessTokensHandler) List() macaron.Handler {
+func (h *accessTokensHandler) List() macaron.Handler {
 	return func(c *context.APIContext) {
 		tokens, err := h.store.ListAccessTokens(c.Req.Context(), c.User.ID)
 		if err != nil {
@@ -32,16 +32,23 @@ func (h *AccessTokensHandler) List() macaron.Handler {
 			return
 		}
 
-		apiTokens := make([]*api.AccessToken, len(tokens))
+		apiTokens := make([]*types.UserAccessToken, len(tokens))
 		for i := range tokens {
-			apiTokens[i] = &api.AccessToken{Name: tokens[i].Name, Sha1: tokens[i].Sha1}
+			apiTokens[i] = &types.UserAccessToken{
+				Name: tokens[i].Name,
+				Sha1: tokens[i].Sha1,
+			}
 		}
 		c.JSONSuccess(&apiTokens)
 	}
 }
 
-func (h *AccessTokensHandler) Create() macaron.Handler {
-	return func(c *context.APIContext, form api.CreateAccessTokenOption) {
+type createAccessTokenRequest struct {
+	Name string `json:"name" binding:"Required"`
+}
+
+func (h *accessTokensHandler) Create() macaron.Handler {
+	return func(c *context.APIContext, form createAccessTokenRequest) {
 		t, err := h.store.CreateAccessToken(c.Req.Context(), c.User.ID, form.Name)
 		if err != nil {
 			if database.IsErrAccessTokenAlreadyExist(err) {
@@ -51,7 +58,10 @@ func (h *AccessTokensHandler) Create() macaron.Handler {
 			}
 			return
 		}
-		c.JSON(http.StatusCreated, &api.AccessToken{Name: t.Name, Sha1: t.Sha1})
+		c.JSON(http.StatusCreated, &types.UserAccessToken{
+			Name: t.Name,
+			Sha1: t.Sha1,
+		})
 	}
 }
 
@@ -69,9 +79,9 @@ type AccessTokensStore interface {
 
 type accessTokensStore struct{}
 
-// NewAccessTokensStore returns a new AccessTokensStore using the global
+// newAccessTokensStore returns a new AccessTokensStore using the global
 // database handle.
-func NewAccessTokensStore() AccessTokensStore {
+func newAccessTokensStore() AccessTokensStore {
 	return &accessTokensStore{}
 }
 

+ 16 - 15
internal/route/api/v1/user/email.go → internal/route/api/v1/user_email.go

@@ -1,37 +1,40 @@
-package user
+package v1
 
 import (
 	"net/http"
 
 	"github.com/cockroachdb/errors"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
-	"gogs.io/gogs/internal/route/api/v1/convert"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func ListEmails(c *context.APIContext) {
+func listEmails(c *context.APIContext) {
 	emails, err := database.Handle.Users().ListEmails(c.Req.Context(), c.User.ID)
 	if err != nil {
 		c.Error(err, "get email addresses")
 		return
 	}
-	apiEmails := make([]*api.Email, len(emails))
+	apiEmails := make([]*types.UserEmail, len(emails))
 	for i := range emails {
-		apiEmails[i] = convert.ToEmail(emails[i])
+		apiEmails[i] = toUserEmail(emails[i])
 	}
 	c.JSONSuccess(&apiEmails)
 }
 
-func AddEmail(c *context.APIContext, form api.CreateEmailOption) {
+type createEmailRequest struct {
+	Emails []string `json:"emails"`
+}
+
+func addEmail(c *context.APIContext, form createEmailRequest) {
 	if len(form.Emails) == 0 {
 		c.Status(http.StatusUnprocessableEntity)
 		return
 	}
 
-	apiEmails := make([]*api.Email, 0, len(form.Emails))
+	apiEmails := make([]*types.UserEmail, 0, len(form.Emails))
 	for _, email := range form.Emails {
 		err := database.Handle.Users().AddEmail(c.Req.Context(), c.User.ID, email, !conf.Auth.RequireEmailConfirmation)
 		if err != nil {
@@ -43,17 +46,15 @@ func AddEmail(c *context.APIContext, form api.CreateEmailOption) {
 			return
 		}
 
-		apiEmails = append(apiEmails,
-			&api.Email{
-				Email:    email,
-				Verified: !conf.Auth.RequireEmailConfirmation,
-			},
-		)
+		apiEmails = append(apiEmails, &types.UserEmail{
+			Email:    email,
+			Verified: !conf.Auth.RequireEmailConfirmation,
+		})
 	}
 	c.JSON(http.StatusCreated, &apiEmails)
 }
 
-func DeleteEmail(c *context.APIContext, form api.CreateEmailOption) {
+func deleteEmail(c *context.APIContext, form createEmailRequest) {
 	for _, email := range form.Emails {
 		if email == c.User.Email {
 			c.ErrorStatus(http.StatusBadRequest, errors.Errorf("cannot delete primary email %q", email))

+ 19 - 20
internal/route/api/v1/user/follower.go → internal/route/api/v1/user_follower.go

@@ -1,16 +1,15 @@
-package user
+package v1
 
 import (
-	api "github.com/gogs/go-gogs-client"
-
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
 func responseAPIUsers(c *context.APIContext, users []*database.User) {
-	apiUsers := make([]*api.User, len(users))
+	apiUsers := make([]*types.User, len(users))
 	for i := range users {
-		apiUsers[i] = users[i].APIFormat()
+		apiUsers[i] = toUser(users[i])
 	}
 	c.JSONSuccess(&apiUsers)
 }
@@ -24,12 +23,12 @@ func listUserFollowers(c *context.APIContext, u *database.User) {
 	responseAPIUsers(c, users)
 }
 
-func ListMyFollowers(c *context.APIContext) {
+func listMyFollowers(c *context.APIContext) {
 	listUserFollowers(c, c.User)
 }
 
-func ListFollowers(c *context.APIContext) {
-	u := GetUserByParams(c)
+func listFollowers(c *context.APIContext) {
+	u := getUserByParams(c)
 	if c.Written() {
 		return
 	}
@@ -45,12 +44,12 @@ func listUserFollowing(c *context.APIContext, u *database.User) {
 	responseAPIUsers(c, users)
 }
 
-func ListMyFollowing(c *context.APIContext) {
+func listMyFollowing(c *context.APIContext) {
 	listUserFollowing(c, c.User)
 }
 
-func ListFollowing(c *context.APIContext) {
-	u := GetUserByParams(c)
+func listFollowing(c *context.APIContext) {
+	u := getUserByParams(c)
 	if c.Written() {
 		return
 	}
@@ -65,28 +64,28 @@ func checkUserFollowing(c *context.APIContext, u *database.User, followID int64)
 	}
 }
 
-func CheckMyFollowing(c *context.APIContext) {
-	target := GetUserByParams(c)
+func checkMyFollowing(c *context.APIContext) {
+	target := getUserByParams(c)
 	if c.Written() {
 		return
 	}
 	checkUserFollowing(c, c.User, target.ID)
 }
 
-func CheckFollowing(c *context.APIContext) {
-	u := GetUserByParams(c)
+func checkFollowing(c *context.APIContext) {
+	u := getUserByParams(c)
 	if c.Written() {
 		return
 	}
-	target := GetUserByParamsName(c, ":target")
+	target := getUserByParamsName(c, ":target")
 	if c.Written() {
 		return
 	}
 	checkUserFollowing(c, u, target.ID)
 }
 
-func Follow(c *context.APIContext) {
-	target := GetUserByParams(c)
+func follow(c *context.APIContext) {
+	target := getUserByParams(c)
 	if c.Written() {
 		return
 	}
@@ -97,8 +96,8 @@ func Follow(c *context.APIContext) {
 	c.NoContent()
 }
 
-func Unfollow(c *context.APIContext) {
-	target := GetUserByParams(c)
+func unfollow(c *context.APIContext) {
+	target := getUserByParams(c)
 	if c.Written() {
 		return
 	}

+ 27 - 26
internal/route/api/v1/user/key.go → internal/route/api/v1/user_key.go

@@ -1,19 +1,17 @@
-package user
+package v1
 
 import (
 	"net/http"
 
 	"github.com/cockroachdb/errors"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
-	"gogs.io/gogs/internal/route/api/v1/convert"
-	"gogs.io/gogs/internal/route/api/v1/repo"
+	"gogs.io/gogs/internal/route/api/v1/types"
 )
 
-func GetUserByParamsName(c *context.APIContext, name string) *database.User {
+func getUserByParamsName(c *context.APIContext, name string) *database.User {
 	user, err := database.Handle.Users().GetByUsername(c.Req.Context(), c.Params(name))
 	if err != nil {
 		c.NotFoundOrError(err, "get user by name")
@@ -22,16 +20,15 @@ func GetUserByParamsName(c *context.APIContext, name string) *database.User {
 	return user
 }
 
-// GetUserByParams returns user whose name is presented in URL parameter.
-func GetUserByParams(c *context.APIContext) *database.User {
-	return GetUserByParamsName(c, ":username")
+func getUserByParams(c *context.APIContext) *database.User {
+	return getUserByParamsName(c, ":username")
 }
 
 func composePublicKeysAPILink() string {
 	return conf.Server.ExternalURL + "api/v1/user/keys/"
 }
 
-func listPublicKeys(c *context.APIContext, uid int64) {
+func listPublicKeysOfUser(c *context.APIContext, uid int64) {
 	keys, err := database.ListPublicKeys(uid)
 	if err != nil {
 		c.Error(err, "list public keys")
@@ -39,27 +36,27 @@ func listPublicKeys(c *context.APIContext, uid int64) {
 	}
 
 	apiLink := composePublicKeysAPILink()
-	apiKeys := make([]*api.PublicKey, len(keys))
+	apiKeys := make([]*types.UserPublicKey, len(keys))
 	for i := range keys {
-		apiKeys[i] = convert.ToPublicKey(apiLink, keys[i])
+		apiKeys[i] = toUserPublicKey(apiLink, keys[i])
 	}
 
 	c.JSONSuccess(&apiKeys)
 }
 
-func ListMyPublicKeys(c *context.APIContext) {
-	listPublicKeys(c, c.User.ID)
+func listMyPublicKeys(c *context.APIContext) {
+	listPublicKeysOfUser(c, c.User.ID)
 }
 
-func ListPublicKeys(c *context.APIContext) {
-	user := GetUserByParams(c)
+func listPublicKeys(c *context.APIContext) {
+	user := getUserByParams(c)
 	if c.Written() {
 		return
 	}
-	listPublicKeys(c, user.ID)
+	listPublicKeysOfUser(c, user.ID)
 }
 
-func GetPublicKey(c *context.APIContext) {
+func getPublicKey(c *context.APIContext) {
 	key, err := database.GetPublicKeyByID(c.ParamsInt64(":id"))
 	if err != nil {
 		c.NotFoundOrError(err, "get public key by ID")
@@ -67,31 +64,35 @@ func GetPublicKey(c *context.APIContext) {
 	}
 
 	apiLink := composePublicKeysAPILink()
-	c.JSONSuccess(convert.ToPublicKey(apiLink, key))
+	c.JSONSuccess(toUserPublicKey(apiLink, key))
 }
 
-// CreateUserPublicKey creates new public key to given user by ID.
-func CreateUserPublicKey(c *context.APIContext, form api.CreateKeyOption, uid int64) {
+type createPublicKeyRequest struct {
+	Title string `json:"title" binding:"Required"`
+	Key   string `json:"key" binding:"Required"`
+}
+
+func createUserPublicKey(c *context.APIContext, form createPublicKeyRequest, uid int64) {
 	content, err := database.CheckPublicKeyString(form.Key)
 	if err != nil {
-		repo.HandleCheckKeyStringError(c, err)
+		handleCheckKeyStringError(c, err)
 		return
 	}
 
 	key, err := database.AddPublicKey(uid, form.Title, content)
 	if err != nil {
-		repo.HandleAddKeyError(c, err)
+		handleAddKeyError(c, err)
 		return
 	}
 	apiLink := composePublicKeysAPILink()
-	c.JSON(http.StatusCreated, convert.ToPublicKey(apiLink, key))
+	c.JSON(http.StatusCreated, toUserPublicKey(apiLink, key))
 }
 
-func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) {
-	CreateUserPublicKey(c, form, c.User.ID)
+func createPublicKey(c *context.APIContext, form createPublicKeyRequest) {
+	createUserPublicKey(c, form, c.User.ID)
 }
 
-func DeletePublicKey(c *context.APIContext) {
+func deletePublicKey(c *context.APIContext) {
 	if err := database.DeletePublicKey(c.User, c.ParamsInt64(":id")); err != nil {
 		if database.IsErrKeyAccessDenied(err) {
 			c.ErrorStatus(http.StatusForbidden, errors.New("You do not have access to this key."))

+ 3 - 3
internal/route/repo/branch.go

@@ -6,10 +6,10 @@ import (
 	log "unknwon.dev/clog/v2"
 
 	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/database"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 	"gogs.io/gogs/internal/urlutil"
 )
 
@@ -153,10 +153,10 @@ func DeleteBranchPost(c *context.Context) {
 		return
 	}
 
-	if err := database.PrepareWebhooks(c.Repo.Repository, database.HookEventTypeDelete, &api.DeletePayload{
+	if err := database.PrepareWebhooks(c.Repo.Repository, database.HookEventTypeDelete, &apiv1types.WebhookDeletePayload{
 		Ref:        branchName,
 		RefType:    "branch",
-		PusherType: api.PUSHER_TYPE_USER,
+		PusherType: apiv1types.WebhookPusherTypeUser,
 		Repo:       c.Repo.Repository.APIFormatLegacy(nil),
 		Sender:     c.User.APIFormat(),
 	}); err != nil {

+ 5 - 5
internal/route/repo/webhook.go

@@ -10,7 +10,6 @@ import (
 
 	"github.com/cockroachdb/errors"
 	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
 	"gopkg.in/macaron.v1"
 
 	"gogs.io/gogs/internal/conf"
@@ -18,6 +17,7 @@ import (
 	"gogs.io/gogs/internal/database"
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/netutil"
+	apiv1types "gogs.io/gogs/internal/route/api/v1/types"
 )
 
 const (
@@ -513,21 +513,21 @@ func TestWebhook(c *context.Context) {
 	}
 
 	apiUser := c.User.APIFormat()
-	p := &api.PushPayload{
+	p := &apiv1types.WebhookPushPayload{
 		Ref:    git.RefsHeads + c.Repo.Repository.DefaultBranch,
 		Before: commitID,
 		After:  commitID,
-		Commits: []*api.PayloadCommit{
+		Commits: []*apiv1types.WebhookPayloadCommit{
 			{
 				ID:      commitID,
 				Message: commitMessage,
 				URL:     c.Repo.Repository.HTMLURL() + "/commit/" + commitID,
-				Author: &api.PayloadUser{
+				Author: &apiv1types.WebhookPayloadUser{
 					Name:     author.Name,
 					Email:    author.Email,
 					UserName: authorUsername,
 				},
-				Committer: &api.PayloadUser{
+				Committer: &apiv1types.WebhookPayloadUser{
 					Name:     committer.Name,
 					Email:    committer.Email,
 					UserName: committerUsername,