Skip to content

Commit c480e8b

Browse files
authored
feat: add fetching branches for repository during self hosting (#332)
* feat: add fetching branches for repository during self hosting a new application - less error prone and improved accessibility * test: add unit tests for get repository branches * fix: address review comments
1 parent ac17507 commit c480e8b

File tree

13 files changed

+604
-12
lines changed

13 files changed

+604
-12
lines changed

api/api/versions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{
44
"version": "v1",
55
"status": "active",
6-
"release_date": "2025-08-14T18:50:02.928075+05:30",
6+
"release_date": "2025-08-15T19:28:35.912028+05:30",
77
"end_of_life": "0001-01-01T00:00:00Z",
88
"changes": [
99
"Initial API version"

api/doc/openapi.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package controller
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"strings"
7+
8+
"github.com/go-fuego/fuego"
9+
"github.com/raghavyuva/nixopus-api/internal/features/logger"
10+
"github.com/raghavyuva/nixopus-api/internal/utils"
11+
12+
shared_types "github.com/raghavyuva/nixopus-api/internal/types"
13+
)
14+
15+
type GetGithubRepositoryBranchesRequest struct {
16+
RepositoryName string `json:"repository_name" validate:"required"`
17+
}
18+
19+
func (c *GithubConnectorController) GetGithubRepositoryBranches(f fuego.ContextWithBody[GetGithubRepositoryBranchesRequest]) (*shared_types.Response, error) {
20+
w, r := f.Response(), f.Request()
21+
user := utils.GetUser(w, r)
22+
23+
if user == nil {
24+
return nil, fuego.HTTPError{
25+
Err: nil,
26+
Status: http.StatusUnauthorized,
27+
}
28+
}
29+
30+
body, err := f.Body()
31+
if err != nil {
32+
return nil, fuego.HTTPError{
33+
Err: err,
34+
Status: http.StatusBadRequest,
35+
}
36+
}
37+
38+
if strings.TrimSpace(body.RepositoryName) == "" {
39+
return nil, fuego.HTTPError{
40+
Err: fmt.Errorf("repository_name is required"),
41+
Status: http.StatusBadRequest,
42+
}
43+
}
44+
45+
branches, err := c.service.GetGithubRepositoryBranches(user.ID.String(), body.RepositoryName)
46+
if err != nil {
47+
c.logger.Log(logger.Error, err.Error(), "")
48+
return nil, fuego.HTTPError{
49+
Err: err,
50+
Status: http.StatusInternalServerError,
51+
}
52+
}
53+
54+
return &shared_types.Response{
55+
Status: "success",
56+
Message: "Branches fetched successfully",
57+
Data: branches,
58+
}, nil
59+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package service
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
9+
"github.com/raghavyuva/nixopus-api/internal/features/logger"
10+
shared_types "github.com/raghavyuva/nixopus-api/internal/types"
11+
)
12+
13+
var githubAPIBaseURL = "https://api.github.com"
14+
15+
func SetGithubAPIBaseURL(url string) {
16+
githubAPIBaseURL = url
17+
}
18+
19+
func (c *GithubConnectorService) GetGithubRepositoryBranches(user_id string, repository_name string) ([]shared_types.GithubRepositoryBranch, error) {
20+
connectors, err := c.storage.GetAllConnectors(user_id)
21+
if err != nil {
22+
c.logger.Log(logger.Error, err.Error(), "")
23+
return nil, err
24+
}
25+
26+
if len(connectors) == 0 {
27+
c.logger.Log(logger.Error, "No connectors found for user", user_id)
28+
return []shared_types.GithubRepositoryBranch{}, nil
29+
}
30+
31+
installation_id := connectors[0].InstallationID
32+
33+
jwt := GenerateJwt(&connectors[0])
34+
if jwt == "" {
35+
c.logger.Log(logger.Error, "Failed to generate app JWT", "")
36+
return nil, fmt.Errorf("failed to generate app JWT")
37+
}
38+
39+
accessToken, err := c.getInstallationToken(jwt, installation_id)
40+
if err != nil {
41+
c.logger.Log(logger.Error, fmt.Sprintf("Failed to get installation token: %s", err.Error()), "")
42+
return nil, err
43+
}
44+
45+
client := &http.Client{}
46+
req, err := http.NewRequest("GET", fmt.Sprintf("%s/repos/%s/branches", githubAPIBaseURL, repository_name), nil)
47+
if err != nil {
48+
c.logger.Log(logger.Error, err.Error(), "")
49+
return nil, err
50+
}
51+
52+
req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
53+
req.Header.Set("Accept", "application/vnd.github.v3+json")
54+
req.Header.Set("User-Agent", "nixopus")
55+
56+
resp, err := client.Do(req)
57+
if err != nil {
58+
c.logger.Log(logger.Error, err.Error(), "")
59+
return nil, err
60+
}
61+
defer resp.Body.Close()
62+
63+
if resp.StatusCode != http.StatusOK {
64+
bodyBytes, _ := io.ReadAll(resp.Body)
65+
c.logger.Log(logger.Error, fmt.Sprintf("GitHub API error: %s - %s", resp.Status, string(bodyBytes)), "")
66+
return nil, fmt.Errorf("GitHub API error: %s", resp.Status)
67+
}
68+
69+
var branches []shared_types.GithubRepositoryBranch
70+
71+
err = json.NewDecoder(resp.Body).Decode(&branches)
72+
if err != nil {
73+
c.logger.Log(logger.Error, err.Error(), "")
74+
return nil, err
75+
}
76+
77+
return branches, nil
78+
}

api/internal/features/github-connector/service/installation_token.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
func (c *GithubConnectorService) getInstallationToken(jwt string, installation_id string) (string, error) {
16-
url := fmt.Sprintf("https://api.github.com/app/installations/%s/access_tokens", installation_id)
16+
url := fmt.Sprintf("%s/app/installations/%s/access_tokens", githubAPIBaseURL, installation_id)
1717

1818
client := &http.Client{}
1919
req, err := http.NewRequest("POST", url, nil)

0 commit comments

Comments
 (0)