2024-02-11 19:17:19 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
gitea "code.gitea.io/sdk/gitea"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"git.lumeweb.com/LumeWeb/gitea-github-proxy/config"
|
|
|
|
"github.com/google/go-github/v59/github"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
)
|
|
|
|
|
|
|
|
type restApi struct {
|
|
|
|
config *config.Config
|
|
|
|
logger *zap.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func newRestApi(cfg *config.Config, logger *zap.Logger) *restApi {
|
|
|
|
return &restApi{config: cfg, logger: logger}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r restApi) handlerGetPullRequestFiles(w http.ResponseWriter, request *http.Request) {
|
|
|
|
vars := mux.Vars(request)
|
|
|
|
|
|
|
|
owner := vars["owner"]
|
|
|
|
repo := vars["repo"]
|
|
|
|
pullNumber := vars["pull_number"]
|
|
|
|
|
|
|
|
client := r.getClientOrError(w)
|
|
|
|
|
|
|
|
if client == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
parsedPullNumber, err := strconv.ParseInt(pullNumber, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to parse pull number", http.StatusBadRequest)
|
|
|
|
r.logger.Error("Failed to parse pull number", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
files, r2, err := client.ListPullRequestFiles(owner, repo, parsedPullNumber, gitea.ListPullRequestFilesOptions{
|
|
|
|
ListOptions: r.getPagingOptions(request),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to get pull request files", http.StatusInternalServerError)
|
|
|
|
r.logger.Error("Failed to get pull request files", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
githubFiles := make([]*github.CommitFile, len(files))
|
|
|
|
|
|
|
|
for i, file := range files {
|
2024-02-12 06:46:41 +00:00
|
|
|
githubFiles[i] = convertCommitFile(file)
|
2024-02-11 19:17:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
r.sendPagingHeaders(w, r2)
|
|
|
|
r.respond(w, http.StatusOK, githubFiles)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r restApi) getPagingOptions(request *http.Request) gitea.ListOptions {
|
|
|
|
page, _ := strconv.Atoi(request.URL.Query().Get("page"))
|
|
|
|
perPage, _ := strconv.Atoi(request.URL.Query().Get("per_page"))
|
|
|
|
|
|
|
|
return gitea.ListOptions{
|
|
|
|
Page: page,
|
|
|
|
PageSize: perPage,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r restApi) sendPagingHeaders(w http.ResponseWriter, apiResponse *gitea.Response) {
|
|
|
|
links := []string{}
|
|
|
|
|
|
|
|
baseURL := fmt.Sprintf("https://%s", r.config.Domain)
|
|
|
|
|
|
|
|
joinStrings := func(elements []string, separator string) string {
|
|
|
|
if len(elements) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
result := elements[0]
|
|
|
|
for i := 1; i < len(elements); i++ {
|
|
|
|
result += separator + elements[i]
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
joinLinks := func(links []string) string {
|
|
|
|
return fmt.Sprintf("%s", joinStrings(links, ", "))
|
|
|
|
}
|
|
|
|
|
|
|
|
if apiResponse.FirstPage > 0 {
|
|
|
|
links = append(links, fmt.Sprintf(`<%s?page=%d>; rel="first"`, baseURL, apiResponse.FirstPage))
|
|
|
|
}
|
|
|
|
if apiResponse.PrevPage > 0 {
|
|
|
|
links = append(links, fmt.Sprintf(`<%s?page=%d>; rel="prev"`, baseURL, apiResponse.PrevPage))
|
|
|
|
}
|
|
|
|
if apiResponse.NextPage > 0 {
|
|
|
|
links = append(links, fmt.Sprintf(`<%s?page=%d>; rel="next"`, baseURL, apiResponse.NextPage))
|
|
|
|
}
|
|
|
|
if apiResponse.LastPage > 0 {
|
|
|
|
links = append(links, fmt.Sprintf(`<%s?page=%d>; rel="last"`, baseURL, apiResponse.LastPage))
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(links) > 0 {
|
|
|
|
w.Header().Add("Link", fmt.Sprintf("%s", joinLinks(links)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r restApi) respond(w http.ResponseWriter, status int, data interface{}) {
|
|
|
|
jsonData, err := json.Marshal(data)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to marshal response", http.StatusInternalServerError)
|
|
|
|
r.logger.Error("Failed to marshal response", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(status)
|
|
|
|
if data != nil {
|
|
|
|
_, _ = w.Write(jsonData)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r restApi) getClientOrError(w http.ResponseWriter) *gitea.Client {
|
|
|
|
client, err := getClient(ClientParams{
|
|
|
|
Config: r.config,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to get Gitea client", http.StatusInternalServerError)
|
|
|
|
r.logger.Error("Failed to get Gitea client", zap.Error(err))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return client
|
|
|
|
}
|
|
|
|
|
2024-02-12 06:48:49 +00:00
|
|
|
func (r restApi) handlerGetTree(w http.ResponseWriter, request *http.Request) {
|
|
|
|
vars := mux.Vars(request)
|
|
|
|
owner := vars["owner"]
|
|
|
|
repo := vars["repo"]
|
|
|
|
treeSha := vars["tree_sha"]
|
|
|
|
recursive := false
|
|
|
|
|
|
|
|
if request.URL.Query().Has("recursive") {
|
|
|
|
recursive = true
|
|
|
|
}
|
|
|
|
|
|
|
|
client := r.getClientOrError(w)
|
|
|
|
|
|
|
|
if client == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
tree, r2, err := client.GetTrees(owner, repo, treeSha, recursive)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to get tree", http.StatusInternalServerError)
|
|
|
|
r.logger.Error("Failed to get tree", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
treeResponse := convertGitTree(tree)
|
|
|
|
|
|
|
|
r.sendPagingHeaders(w, r2)
|
|
|
|
r.respond(w, http.StatusOK, treeResponse)
|
|
|
|
}
|
|
|
|
|
2024-02-12 08:50:55 +00:00
|
|
|
func (r restApi) handleGetIssueComments(w http.ResponseWriter, request *http.Request) {
|
2024-02-12 08:49:51 +00:00
|
|
|
vars := mux.Vars(request)
|
|
|
|
owner := vars["owner"]
|
|
|
|
repo := vars["repo"]
|
|
|
|
issueNumber := vars["issue_number"]
|
|
|
|
|
|
|
|
client := r.getClientOrError(w)
|
|
|
|
|
|
|
|
if client == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
issueNumberInt, err := strconv.Atoi(issueNumber)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to parse issue number", http.StatusBadRequest)
|
|
|
|
r.logger.Error("Failed to parse issue number", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
comments, r2, err := client.ListIssueComments(owner, repo, int64(issueNumberInt), gitea.ListIssueCommentOptions{ListOptions: r.getPagingOptions(request)})
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to get issue comments", http.StatusInternalServerError)
|
|
|
|
r.logger.Error("Failed to get issue comments", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ghComments := make([]*github.IssueComment, len(comments))
|
|
|
|
|
|
|
|
for i, comment := range comments {
|
|
|
|
reactions, _, _ := client.GetIssueCommentReactions(owner, repo, comment.ID)
|
|
|
|
ghComments[i] = convertIssueComment(comment, reactions)
|
|
|
|
}
|
|
|
|
|
|
|
|
r.sendPagingHeaders(w, r2)
|
|
|
|
r.respond(w, http.StatusOK, ghComments)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-02-12 08:15:23 +00:00
|
|
|
func (r restApi) handlerCreateIssueComment(w http.ResponseWriter, request *http.Request) {
|
2024-02-12 07:50:23 +00:00
|
|
|
vars := mux.Vars(request)
|
|
|
|
owner := vars["owner"]
|
|
|
|
repo := vars["repo"]
|
|
|
|
issueNumber := vars["issue_number"]
|
|
|
|
|
2024-02-12 08:15:23 +00:00
|
|
|
client := r.getClientOrError(w)
|
2024-02-12 07:50:23 +00:00
|
|
|
|
|
|
|
if client == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-12 08:15:23 +00:00
|
|
|
ghComment := github.IssueComment{}
|
|
|
|
|
|
|
|
if err := r.parseJsonBody(request, &ghComment); err != nil {
|
|
|
|
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
|
|
|
|
r.logger.Error("Failed to parse request body", zap.Error(err))
|
2024-02-12 07:50:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
issueNumberInt, err := strconv.Atoi(issueNumber)
|
|
|
|
if err != nil {
|
2024-02-12 08:15:23 +00:00
|
|
|
http.Error(w, "Failed to parse issue number", http.StatusBadRequest)
|
2024-02-12 07:50:23 +00:00
|
|
|
r.logger.Error("Failed to parse issue number", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
comment := gitea.CreateIssueCommentOption{
|
2024-02-12 08:15:23 +00:00
|
|
|
Body: ghComment.GetBody(),
|
2024-02-12 07:50:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
commentResponse, _, err := client.CreateIssueComment(owner, repo, int64(issueNumberInt), comment)
|
|
|
|
if err != nil {
|
2024-02-12 08:15:23 +00:00
|
|
|
http.Error(w, "Failed to create comment", http.StatusInternalServerError)
|
2024-02-12 07:50:23 +00:00
|
|
|
r.logger.Error("Failed to create comment", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-12 08:15:23 +00:00
|
|
|
r.respond(w, http.StatusCreated, commentResponse)
|
2024-02-12 07:50:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r restApi) handlerUpdateIssueComment(w http.ResponseWriter, request *http.Request) {
|
|
|
|
vars := mux.Vars(request)
|
|
|
|
owner := vars["owner"]
|
|
|
|
repo := vars["repo"]
|
|
|
|
commentID := vars["comment_id"]
|
|
|
|
|
|
|
|
client := r.getClientOrError(w)
|
|
|
|
|
|
|
|
if client == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-12 08:15:23 +00:00
|
|
|
ghComment := github.IssueComment{}
|
|
|
|
|
|
|
|
if err := r.parseJsonBody(request, &ghComment); err != nil {
|
|
|
|
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
|
|
|
|
r.logger.Error("Failed to parse request body", zap.Error(err))
|
2024-02-12 07:50:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
commentIDInt, err := strconv.Atoi(commentID)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to parse comment ID", http.StatusBadRequest)
|
|
|
|
r.logger.Error("Failed to parse comment ID", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
comment := gitea.EditIssueCommentOption{
|
2024-02-12 09:42:34 +00:00
|
|
|
Body: ghComment.GetBody(),
|
2024-02-12 07:50:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
commentResponse, _, err := client.EditIssueComment(owner, repo, int64(commentIDInt), comment)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to update comment", http.StatusInternalServerError)
|
|
|
|
r.logger.Error("Failed to update comment", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.respond(w, http.StatusOK, commentResponse)
|
|
|
|
}
|
|
|
|
|
2024-02-12 20:54:34 +00:00
|
|
|
func (r restApi) handlerCreateRelease(w http.ResponseWriter, request *http.Request) {
|
|
|
|
vars := mux.Vars(request)
|
|
|
|
owner := vars["owner"]
|
|
|
|
repo := vars["repo"]
|
|
|
|
|
|
|
|
client := r.getClientOrError(w)
|
|
|
|
|
|
|
|
if client == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ghRelease := github.RepositoryRelease{}
|
|
|
|
|
|
|
|
if err := r.parseJsonBody(request, &ghRelease); err != nil {
|
|
|
|
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
|
|
|
|
r.logger.Error("Failed to parse request body", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
release := gitea.CreateReleaseOption{
|
|
|
|
TagName: ghRelease.GetTagName(),
|
|
|
|
Target: ghRelease.GetTargetCommitish(),
|
|
|
|
Title: ghRelease.GetName(),
|
|
|
|
Note: ghRelease.GetBody(),
|
|
|
|
IsDraft: ghRelease.GetDraft(),
|
|
|
|
IsPrerelease: ghRelease.GetPrerelease(),
|
|
|
|
}
|
|
|
|
|
|
|
|
releaseResponse, r2, err := client.CreateRelease(owner, repo, release)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to create release", http.StatusInternalServerError)
|
|
|
|
r.logger.Error("Failed to create release", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.sendPagingHeaders(w, r2)
|
|
|
|
r.respond(w, http.StatusCreated, releaseResponse)
|
|
|
|
}
|
|
|
|
|
2024-02-12 21:25:43 +00:00
|
|
|
func (r restApi) handlerCreatePullRequest(w http.ResponseWriter, request *http.Request) {
|
|
|
|
vars := mux.Vars(request)
|
|
|
|
owner := vars["owner"]
|
|
|
|
repo := vars["repo"]
|
|
|
|
|
|
|
|
client := r.getClientOrError(w)
|
|
|
|
|
|
|
|
if client == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ghPullRequest := github.NewPullRequest{}
|
|
|
|
|
|
|
|
if err := r.parseJsonBody(request, &ghPullRequest); err != nil {
|
|
|
|
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
|
|
|
|
r.logger.Error("Failed to parse request body", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
pullRequest := gitea.CreatePullRequestOption{
|
|
|
|
Title: ghPullRequest.GetTitle(),
|
|
|
|
Head: ghPullRequest.GetHead(),
|
|
|
|
Base: ghPullRequest.GetBase(),
|
|
|
|
Body: ghPullRequest.GetBody(),
|
|
|
|
}
|
|
|
|
|
|
|
|
pullRequestResponse, r2, err := client.CreatePullRequest(owner, repo, pullRequest)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to create pull request", http.StatusInternalServerError)
|
|
|
|
r.logger.Error("Failed to create pull request", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.sendPagingHeaders(w, r2)
|
|
|
|
r.respond(w, http.StatusCreated, pullRequestResponse)
|
|
|
|
}
|
|
|
|
|
2024-02-12 08:15:23 +00:00
|
|
|
func (r restApi) parseJsonBody(request *http.Request, obj interface{}) error {
|
|
|
|
if obj == nil {
|
|
|
|
obj = make(map[string]interface{})
|
|
|
|
}
|
|
|
|
|
|
|
|
decoder := json.NewDecoder(request.Body)
|
|
|
|
return decoder.Decode(obj)
|
|
|
|
}
|
|
|
|
|
2024-02-11 19:17:19 +00:00
|
|
|
func setupRestRoutes(params RouteParams) {
|
|
|
|
logger := params.Logger
|
|
|
|
cfg := params.Config
|
|
|
|
r := params.R
|
|
|
|
|
|
|
|
restApi := newRestApi(cfg, logger)
|
2024-02-12 05:01:22 +00:00
|
|
|
|
|
|
|
setupRoutes := func(r *mux.Router) {
|
|
|
|
r.HandleFunc("/repos/{owner}/{repo}/pulls/{pull_number}/files", restApi.handlerGetPullRequestFiles).Methods("GET")
|
2024-02-12 06:48:49 +00:00
|
|
|
r.HandleFunc("/repos/{owner}/{repo}/git/trees/{tree_sha}", restApi.handlerGetTree).Methods("GET")
|
2024-02-12 08:49:51 +00:00
|
|
|
|
|
|
|
// Comment routes
|
2024-02-12 08:50:55 +00:00
|
|
|
r.HandleFunc("/repos/{owner}/{repo}/issues/{issue_number}/comments", restApi.handleGetIssueComments).Methods("GET")
|
2024-02-12 07:50:23 +00:00
|
|
|
r.HandleFunc("/repos/{owner}/{repo}/issues/{issue_number}/comments", restApi.handlerCreateIssueComment).Methods("POST")
|
2024-02-12 09:41:31 +00:00
|
|
|
r.HandleFunc("/repos/{owner}/{repo}/issues/comments/{comment_id}", restApi.handlerUpdateIssueComment).Methods("PATCH")
|
2024-02-12 20:54:34 +00:00
|
|
|
|
|
|
|
// Repo Release routes
|
|
|
|
r.HandleFunc("/repos/{owner}/{repo}/releases", restApi.handlerCreateRelease).Methods("POST")
|
2024-02-12 21:25:43 +00:00
|
|
|
|
|
|
|
// Pull Request routes
|
|
|
|
r.HandleFunc("/repos/{owner}/{repo}/pulls", restApi.handlerCreatePullRequest).Methods("POST")
|
2024-02-12 05:01:22 +00:00
|
|
|
}
|
|
|
|
|
2024-02-11 19:17:19 +00:00
|
|
|
restRouter := r.PathPrefix("/api").Subrouter()
|
2024-02-12 04:55:02 +00:00
|
|
|
restRouter.Use(githubRestVerifyMiddleware(params.Db))
|
|
|
|
restRouter.Use(githubRestRequireAuthMiddleware(params.Config))
|
2024-02-11 19:17:19 +00:00
|
|
|
|
2024-02-12 05:01:22 +00:00
|
|
|
setupRoutes(restRouter)
|
|
|
|
setupRoutes(restRouter.PathPrefix("/v3").Subrouter())
|
2024-02-11 19:17:19 +00:00
|
|
|
}
|