Skip to content

Commit 889539f

Browse files
authored
api support for repos (#1887)
* support for repos url and migrations
1 parent f5f7f58 commit 889539f

File tree

8 files changed

+214
-18
lines changed

8 files changed

+214
-18
lines changed

backend/bootstrap/main.go

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -206,20 +206,6 @@ func Bootstrap(templates embed.FS, diggerController controllers.DiggerController
206206
admin.POST("/tokens/issue-access-token", controllers.IssueAccessTokenForOrg)
207207

208208
r.Use(middleware.CORSMiddleware())
209-
projectsApiGroup := r.Group("/api/projects")
210-
projectsApiGroup.Use(middleware.GetApiMiddleware())
211-
projectsApiGroup.GET("/", controllers.FindProjectsForOrg)
212-
projectsApiGroup.GET("/:project_id", controllers.ProjectDetails)
213-
projectsApiGroup.GET("/:project_id/runs", controllers.RunsForProject)
214-
215-
activityApiGroup := r.Group("/api/activity")
216-
activityApiGroup.Use(middleware.GetApiMiddleware())
217-
activityApiGroup.GET("/", controllers.GetActivity)
218-
219-
runsApiGroup := r.Group("/api/runs")
220-
runsApiGroup.Use(middleware.CORSMiddleware(), middleware.GetApiMiddleware())
221-
runsApiGroup.GET("/:run_id", controllers.RunDetails)
222-
runsApiGroup.POST("/:run_id/approve", controllers.ApproveRun)
223209

224210
// internal endpoints not meant to be exposed to public and protected behind webhook secret
225211
if enableInternal := os.Getenv("DIGGER_ENABLE_INTERNAL_ENDPOINTS"); enableInternal == "true" {
@@ -228,6 +214,17 @@ func Bootstrap(templates embed.FS, diggerController controllers.DiggerController
228214
r.POST("_internal/api/upsert_org", middleware.InternalApiAuth(), diggerController.UpsertOrgInternal)
229215
}
230216

217+
if enableApi := os.Getenv("DIGGER_ENABLE_API_ENDPOINTS"); enableApi == "true" {
218+
apiGroup := r.Group("/api")
219+
apiGroup.Use(middleware.HeadersApiAuth())
220+
221+
reposApiGroup := apiGroup.Group("/repos")
222+
reposApiGroup.GET("/", controllers.ListReposApi)
223+
224+
githubApiGroup := apiGroup.Group("/github")
225+
githubApiGroup.POST("/link", controllers.LinkGithubInstallationToOrgApi)
226+
}
227+
231228
fronteggWebhookProcessor.POST("/create-org-from-frontegg", controllers.CreateFronteggOrgFromWebhook)
232229

233230
return r

backend/controllers/github_api.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package controllers
2+
3+
import (
4+
"errors"
5+
"github.com/diggerhq/digger/backend/middleware"
6+
"github.com/diggerhq/digger/backend/models"
7+
"github.com/gin-gonic/gin"
8+
"gorm.io/gorm"
9+
"log"
10+
"net/http"
11+
"strconv"
12+
)
13+
14+
func LinkGithubInstallationToOrgApi(c *gin.Context) {
15+
type LinkInstallationRequest struct {
16+
InstallationId string `json:"installation_id"`
17+
}
18+
19+
var request LinkInstallationRequest
20+
err := c.BindJSON(&request)
21+
if err != nil {
22+
log.Printf("Error binding JSON: %v", err)
23+
c.JSON(http.StatusBadRequest, gin.H{"status": "Invalid request format"})
24+
return
25+
}
26+
27+
organisationId := c.GetString(middleware.ORGANISATION_ID_KEY)
28+
organisationSource := c.GetString(middleware.ORGANISATION_SOURCE_KEY)
29+
30+
var org models.Organisation
31+
err = models.DB.GormDB.Where("external_id = ? AND external_source = ?", organisationId, organisationSource).First(&org).Error
32+
if err != nil {
33+
if errors.Is(err, gorm.ErrRecordNotFound) {
34+
log.Printf("could not find organisation: %v", err)
35+
c.JSON(http.StatusNotFound, gin.H{"status": "Could not find organisation: " + organisationId})
36+
} else {
37+
log.Printf("database error while finding organisation: %v", err)
38+
c.JSON(http.StatusInternalServerError, gin.H{"status": "Internal server error"})
39+
}
40+
return
41+
}
42+
43+
installationId, err := strconv.ParseInt(request.InstallationId, 10, 64)
44+
if err != nil {
45+
log.Printf("Failed to convert InstallationId to int64: %v", err)
46+
c.JSON(http.StatusBadRequest, gin.H{"status": "installationID should be a valid integer"})
47+
return
48+
}
49+
50+
link, err := models.DB.GetGithubAppInstallationLink(installationId)
51+
52+
if err != nil {
53+
log.Printf("could not get installation link: %v", err)
54+
c.JSON(http.StatusInternalServerError, gin.H{"status": "Could not get installation link"})
55+
return
56+
}
57+
58+
// if there is an existing link it should already belong to existing org id
59+
if link != nil {
60+
if link.OrganisationId == org.ID {
61+
c.JSON(200, gin.H{"status": "already linked to this org"})
62+
return
63+
} else {
64+
log.Printf("installation id %v is already linked to %v", installationId, org.ExternalId)
65+
c.JSON(http.StatusBadRequest, gin.H{"status": "installationID already linked to another org and cant be linked to this one unless the app is uninstalled"})
66+
return
67+
}
68+
}
69+
70+
_, err = models.DB.CreateGithubInstallationLink(&org, installationId)
71+
if err != nil {
72+
log.Printf("failed to create installation link: %v", err)
73+
c.JSON(http.StatusInternalServerError, gin.H{"status": "failed to create installation link"})
74+
return
75+
}
76+
77+
// Return status 200
78+
c.JSON(http.StatusOK, gin.H{"status": "Successfully created Github installation link"})
79+
return
80+
}

backend/controllers/repos.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package controllers
2+
3+
import (
4+
"errors"
5+
"github.com/diggerhq/digger/backend/middleware"
6+
"github.com/diggerhq/digger/backend/models"
7+
"github.com/gin-gonic/gin"
8+
"gorm.io/gorm"
9+
"net/http"
10+
)
11+
12+
func ListReposApi(c *gin.Context) {
13+
// assume all exists as validated in middleware
14+
organisationId := c.GetString(middleware.ORGANISATION_ID_KEY)
15+
organisationSource := c.GetString(middleware.ORGANISATION_SOURCE_KEY)
16+
17+
var org models.Organisation
18+
err := models.DB.GormDB.Where("external_id = ? AND external_source = ?", organisationId, organisationSource).First(&org).Error
19+
if err != nil {
20+
if errors.Is(err, gorm.ErrRecordNotFound) {
21+
c.String(http.StatusNotFound, "Could not find organisation: "+organisationId)
22+
}
23+
return
24+
}
25+
26+
var repos []models.Repo
27+
28+
err = models.DB.GormDB.Preload("Organisation").
29+
Joins("LEFT JOIN organisations ON repos.organisation_id = organisations.id").
30+
Where("repos.organisation_id = ?", org.ID).
31+
Order("name").
32+
Find(&repos).Error
33+
34+
if err != nil {
35+
c.String(http.StatusInternalServerError, "Unknown error occurred while fetching database")
36+
return
37+
}
38+
39+
marshalledRepos := make([]interface{}, 0)
40+
41+
for _, r := range repos {
42+
marshalled := r.MapToJsonStruct()
43+
marshalledRepos = append(marshalledRepos, marshalled)
44+
}
45+
46+
response := make(map[string]interface{})
47+
response["result"] = marshalledRepos
48+
49+
c.JSON(http.StatusOK, response)
50+
}

backend/middleware/headers.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package middleware
2+
3+
import (
4+
"github.com/gin-gonic/gin"
5+
"net/http"
6+
)
7+
8+
func HeadersApiAuth() gin.HandlerFunc {
9+
return func(c *gin.Context) {
10+
orgId := c.Request.Header.Get("DIGGER_ORG_ID")
11+
orgSource := c.Request.Header.Get("DIGGER_ORG_SOURCE")
12+
userId := c.Request.Header.Get("DIGGER_USER_ID")
13+
14+
if orgId == "" {
15+
c.String(http.StatusBadRequest, "Missing parameter: DIGGER_ORG_ID")
16+
c.Abort()
17+
return
18+
}
19+
20+
if orgSource == "" {
21+
c.String(http.StatusBadRequest, "Missing parameter: DIGGER_ORG_SOURCE")
22+
c.Abort()
23+
return
24+
}
25+
26+
if userId == "" {
27+
c.String(http.StatusBadRequest, "Missing parameter: DIGGER_USER_ID")
28+
c.Abort()
29+
return
30+
}
31+
32+
c.Set(ORGANISATION_ID_KEY, orgId)
33+
c.Set(ORGANISATION_SOURCE_KEY, orgSource)
34+
c.Set(USER_ID_KEY, userId)
35+
36+
c.Next()
37+
}
38+
}

backend/middleware/jwt.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,5 +308,7 @@ func CORSMiddleware() gin.HandlerFunc {
308308
}
309309

310310
const ORGANISATION_ID_KEY = "organisation_ID"
311+
const ORGANISATION_SOURCE_KEY = "organisation_Source"
312+
const USER_ID_KEY = "user_ID"
311313
const ACCESS_LEVEL_KEY = "access_level"
312314
const JOB_TOKEN_KEY = "job_token"

backend/migrations/20250224152926.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Modify "repos" table
2+
ALTER TABLE "public"."repos" ADD COLUMN "vcs" text NULL DEFAULT 'github';

backend/migrations/atlas.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
h1:QQtQnP46bNFA5g3iD9R9tIJmWXQAue5yU7JTWucDhro=
1+
h1:5Ds30GVrcb/6GVoexZ0yD9I3TD4q/Udz6UfOgUkrpks=
22
20231227132525.sql h1:43xn7XC0GoJsCnXIMczGXWis9d504FAWi4F1gViTIcw=
33
20240115170600.sql h1:IW8fF/8vc40+eWqP/xDK+R4K9jHJ9QBSGO6rN9LtfSA=
44
20240116123649.sql h1:R1JlUIgxxF6Cyob9HdtMqiKmx/BfnsctTl5rvOqssQw=
@@ -41,3 +41,4 @@ h1:QQtQnP46bNFA5g3iD9R9tIJmWXQAue5yU7JTWucDhro=
4141
20250220173053.sql h1:Ry/j04tOPQceff4o/uPM0I1r5ltMcP2vuJc6/SQBcac=
4242
20250220173439.sql h1:Dho4Gw361D8kuL5pB56hUj5hZM1NPTbMYVBjBW54DkE=
4343
20250221044813.sql h1:PPcVcoMaMY5DXyoptQYeBOEwofrzIfyKcWRd3S12I2U=
44+
20250224152926.sql h1:EjoFpfeoCpk/SjSo2i7sajKCR3t7YCn+1ZgGJrT0L9Y=

backend/models/orgs.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package models
22

33
import (
4-
"gorm.io/gorm"
54
"time"
5+
6+
"gorm.io/gorm"
67
)
78

89
type Organisation struct {
@@ -19,7 +20,8 @@ type Repo struct {
1920
RepoOrganisation string
2021
RepoName string
2122
RepoUrl string
22-
OrganisationID uint `gorm:"uniqueIndex:idx_org_repo"`
23+
VCS DiggerVCSType `gorm:"default:'github'"`
24+
OrganisationID uint `gorm:"uniqueIndex:idx_org_repo"`
2325
Organisation *Organisation
2426
DiggerConfig string
2527
}
@@ -122,7 +124,31 @@ func (p *Project) MapToJsonStruct() interface{} {
122124
IsGenerated: p.IsGenerated,
123125
IsInMainBranch: p.IsInMainBranch,
124126
}
125-
127+
}
128+
func (r *Repo) MapToJsonStruct() interface{} {
129+
OrganisationName := func() string {
130+
if r.Organisation == nil {
131+
return ""
132+
}
133+
return r.Organisation.Name
134+
}
135+
return struct {
136+
Id uint `json:"id"`
137+
Name string `json:"name"`
138+
RepoFullName string `json:"repo_full_name"`
139+
RepoUrl string `json:"repo_url"`
140+
VCS string `json:"vcs"`
141+
OrganisationID uint `json:"organisation_id"`
142+
OrganisationName string `json:"organisation_name"`
143+
}{
144+
Id: r.ID,
145+
Name: r.RepoName,
146+
RepoFullName: r.RepoFullName,
147+
RepoUrl: r.RepoUrl,
148+
VCS: string(r.VCS),
149+
OrganisationID: r.OrganisationID,
150+
OrganisationName: OrganisationName(),
151+
}
126152
}
127153

128154
type Token struct {

0 commit comments

Comments
 (0)