Skip to content

Commit e301e0b

Browse files
committed
Add github integration
1 parent fa5cc8b commit e301e0b

15 files changed

+500
-5
lines changed

app/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func LoadConfig() (*koanf.Koanf, error) {
4848
return config, err
4949
}
5050

51-
// Get returns the value for a given key.
51+
// Deprecated: This is a misuse of the config package.
5252
func Get(key string) interface{} {
5353
return config.Get(key)
5454
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package config
2+
3+
import "github.com/knadh/koanf/v2"
4+
5+
type GithubIntegrationConfig struct {
6+
config *koanf.Koanf
7+
}
8+
9+
func (gic *GithubIntegrationConfig) GetClientID() string {
10+
return config.String("github.integration.client.id")
11+
}
12+
13+
func (gic *GithubIntegrationConfig) GetClientSecret() string {
14+
return config.String("github.integration.client.secret")
15+
}
16+
17+
func (gic *GithubIntegrationConfig) GetRedirectURL() string {
18+
return config.String("github.integration.client.redirecturl")
19+
}
20+
21+
func NewGithubIntegrationConfig(config *koanf.Koanf) *GithubIntegrationConfig {
22+
return &GithubIntegrationConfig{config}
23+
}

app/config/logger.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import "go.uber.org/zap"
55
var Logger *zap.Logger
66

77
func InitLogger() {
8-
var err error
9-
Logger, err = zap.NewProduction()
10-
if err != nil {
11-
panic(err)
8+
if AppEnv() == "development" {
9+
Logger, _ = zap.NewDevelopment(zap.IncreaseLevel(zap.DebugLevel))
10+
} else {
11+
Logger, _ = zap.NewProduction()
1212
}
1313
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package controllers
2+
3+
import (
4+
"ai-developer/app/services/integrations"
5+
"github.com/gin-gonic/gin"
6+
"go.uber.org/zap"
7+
"net/http"
8+
)
9+
10+
type GithubIntegrationController struct {
11+
githubIntegrationService *integrations.GithubIntegrationService
12+
logger *zap.Logger
13+
}
14+
15+
func (gic *GithubIntegrationController) Authorize(c *gin.Context) {
16+
userId, _ := c.Get("user_id")
17+
gic.logger.Debug(
18+
"Authorizing github integration",
19+
zap.Any("user_id", userId),
20+
)
21+
authCodeUrl := gic.githubIntegrationService.GetRedirectUrl(uint64(userId.(int)))
22+
c.Redirect(http.StatusTemporaryRedirect, authCodeUrl)
23+
}
24+
25+
func (gic *GithubIntegrationController) CheckIfIntegrationExists(c *gin.Context) {
26+
userId, _ := c.Get("user_id")
27+
hasIntegration, err := gic.githubIntegrationService.HasGithubIntegration(uint64(userId.(int)))
28+
if err != nil {
29+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
30+
return
31+
}
32+
c.JSON(http.StatusOK, gin.H{"integrated": hasIntegration})
33+
}
34+
35+
func (gic *GithubIntegrationController) GetRepositories(c *gin.Context) {
36+
userId, _ := c.Get("user_id")
37+
repositories, err := gic.githubIntegrationService.GetRepositories(uint64(userId.(int)))
38+
if err != nil {
39+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
40+
return
41+
}
42+
response := make([]map[string]interface{}, 0)
43+
for _, repo := range repositories {
44+
response = append(response, map[string]interface{}{
45+
"id": repo.GetID(),
46+
"name": repo.GetName(),
47+
"url": repo.GetURL(),
48+
"full_name": repo.GetFullName(),
49+
})
50+
}
51+
c.JSON(http.StatusOK, gin.H{"repositories": response})
52+
}
53+
54+
func (gic *GithubIntegrationController) HandleCallback(c *gin.Context) {
55+
code := c.Query("code")
56+
state := c.Query("state")
57+
58+
gic.logger.Debug(
59+
"Handling github integration callback",
60+
zap.String("code", code),
61+
zap.String("state", state),
62+
)
63+
64+
err := gic.githubIntegrationService.GenerateAndSaveAccessToken(code, state)
65+
if err != nil {
66+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
67+
return
68+
} else {
69+
c.JSON(http.StatusOK, gin.H{"message": "Integration successful"})
70+
return
71+
}
72+
}
73+
74+
func NewGithubIntegrationController(
75+
githubIntegrationService *integrations.GithubIntegrationService,
76+
logger *zap.Logger,
77+
) *GithubIntegrationController {
78+
return &GithubIntegrationController{
79+
githubIntegrationService: githubIntegrationService,
80+
logger: logger,
81+
}
82+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE IF EXISTS integrations;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
CREATE TABLE IF NOT EXISTS integrations
2+
(
3+
id SERIAL PRIMARY KEY,
4+
created_at TIMESTAMP NOT NULL,
5+
updated_at TIMESTAMP NOT NULL,
6+
deleted_at TIMESTAMP,
7+
user_id BIGINT NOT NULL,
8+
integration_type VARCHAR(255) NOT NULL,
9+
access_token VARCHAR(255) NOT NULL,
10+
refresh_token VARCHAR(255),
11+
metadata JSONB
12+
);
13+
14+
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_integration ON integrations (user_id, integration_type);
15+
CREATE INDEX IF NOT EXISTS idx_user ON integrations (user_id);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package integrations
2+
3+
type GithubIntegrationDetails struct {
4+
UserId uint64
5+
GithubUserId string
6+
AccessToken string
7+
RefreshToken *string
8+
}

app/models/integration.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package models
2+
3+
import (
4+
"ai-developer/app/models/types"
5+
"gorm.io/gorm"
6+
)
7+
8+
type Integration struct {
9+
*gorm.Model
10+
ID uint `gorm:"primaryKey, autoIncrement"`
11+
12+
UserId uint64 `gorm:"column:user_id;not null"`
13+
User User `gorm:"foreignKey:UserId;uniqueIndex:idx_user_integration"`
14+
15+
IntegrationType string `gorm:"column:integration_type;type:varchar(255);not null;uniqueIndex:idx_user_integration"`
16+
17+
AccessToken string `gorm:"type:varchar(255);not null"`
18+
RefreshToken *string `gorm:"type:varchar(255);null"`
19+
20+
Metadata *types.JSONMap `gorm:"type:json;null"`
21+
}

app/repositories/integrations.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package repositories
2+
3+
import (
4+
"ai-developer/app/models"
5+
"ai-developer/app/models/types"
6+
"errors"
7+
"go.uber.org/zap"
8+
"gorm.io/gorm"
9+
)
10+
11+
type IntegrationsRepository struct {
12+
db *gorm.DB
13+
logger *zap.Logger
14+
}
15+
16+
func (ir *IntegrationsRepository) FindIntegrationIdByUserIdAndType(userId uint64, integrationType string) (integration *models.Integration, err error) {
17+
err = ir.db.Model(models.Integration{
18+
UserId: userId,
19+
IntegrationType: integrationType,
20+
}).First(&integration).Error
21+
if err != nil {
22+
return nil, err
23+
}
24+
return
25+
}
26+
27+
func (ir *IntegrationsRepository) AddOrUpdateIntegration(
28+
userId uint64,
29+
integrationType string,
30+
accessToken string,
31+
refreshToken *string,
32+
metadata *types.JSONMap,
33+
) (err error) {
34+
integration, err := ir.FindIntegrationIdByUserIdAndType(userId, integrationType)
35+
if !errors.Is(err, gorm.ErrRecordNotFound) {
36+
return err
37+
}
38+
39+
if integration != nil {
40+
ir.logger.Info(
41+
"Updating integration",
42+
zap.Uint64("userId", integration.UserId),
43+
zap.String("integrationType", integration.IntegrationType),
44+
)
45+
integration.AccessToken = accessToken
46+
integration.RefreshToken = refreshToken
47+
integration.Metadata = metadata
48+
return ir.db.Save(integration).Error
49+
} else {
50+
integration = &models.Integration{
51+
UserId: userId,
52+
IntegrationType: integrationType,
53+
AccessToken: accessToken,
54+
RefreshToken: refreshToken,
55+
Metadata: metadata,
56+
}
57+
ir.logger.Info(
58+
"Adding new integration",
59+
zap.Uint64("userId", userId),
60+
zap.String("integrationType", integrationType),
61+
)
62+
return ir.db.Create(integration).Error
63+
}
64+
}
65+
66+
func NewIntegrationsRepository(db *gorm.DB, logger *zap.Logger) *IntegrationsRepository {
67+
return &IntegrationsRepository{
68+
db: db,
69+
logger: logger.Named("IntegrationsRepository"),
70+
}
71+
}

0 commit comments

Comments
 (0)