Skip to content

Commit 17b538d

Browse files
committed
feat: Introduce contenedor DI y registros de proveedores (#47)
1 parent 0006473 commit 17b538d

37 files changed

Lines changed: 2031 additions & 368 deletions

cmd/main.go

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"log"
7-
"net/http"
87
"os"
98

109
"github.com/Tomas-vilte/MateCommit/internal/cli/command/config"
@@ -14,14 +13,13 @@ import (
1413
"github.com/Tomas-vilte/MateCommit/internal/cli/command/suggests_commits"
1514
"github.com/Tomas-vilte/MateCommit/internal/cli/registry"
1615
cfg "github.com/Tomas-vilte/MateCommit/internal/config"
17-
"github.com/Tomas-vilte/MateCommit/internal/domain/ports"
1816
"github.com/Tomas-vilte/MateCommit/internal/i18n"
1917
"github.com/Tomas-vilte/MateCommit/internal/infrastructure/ai/gemini"
18+
"github.com/Tomas-vilte/MateCommit/internal/infrastructure/di"
2019
"github.com/Tomas-vilte/MateCommit/internal/infrastructure/factory"
2120
"github.com/Tomas-vilte/MateCommit/internal/infrastructure/git"
2221
"github.com/Tomas-vilte/MateCommit/internal/infrastructure/tickets/jira"
2322
"github.com/Tomas-vilte/MateCommit/internal/infrastructure/vcs/github"
24-
"github.com/Tomas-vilte/MateCommit/internal/services"
2523
"github.com/urfave/cli/v3"
2624
)
2725

@@ -57,46 +55,39 @@ func initializeApp() (*cli.Command, error) {
5755
return nil, err
5856
}
5957

60-
gitService := git.NewGitService(translations)
61-
aiProvider, err := gemini.NewGeminiService(context.Background(), cfgApp, translations)
62-
if err != nil {
63-
log.Printf("Warning: %v", err)
64-
log.Println("La IA no está configurada. Podés configurarla con 'matecommit config init'")
65-
aiProvider = nil
66-
}
58+
container := di.NewContainer(cfgApp, translations)
6759

68-
aiSummarizer, err := gemini.NewGeminiPRSummarizer(context.Background(), cfgApp, translations)
69-
if err != nil {
70-
log.Printf("Warning: %v", err)
71-
log.Println("El resumidor de PRs está deshabilitado hasta configurar la IA (Gemini).")
72-
aiSummarizer = nil
60+
if err := container.RegisterAIProvider("gemini", gemini.NewGeminiProviderFactory()); err != nil {
61+
log.Printf("Warning: failed to register Gemini provider: %v", err)
7362
}
7463

75-
ticketService := jira.NewJiraService(cfgApp, &http.Client{})
76-
77-
// Inicializar VCS Client si es posible
78-
var vcsClient ports.VCSClient
79-
repoOwner, repoName, provider, err := gitService.GetRepoInfo(context.Background())
80-
if err == nil {
81-
if vcsConfig, ok := cfgApp.VCSConfigs[provider]; ok && provider == "github" {
82-
vcsClient = github.NewGitHubClient(repoOwner, repoName, vcsConfig.Token, translations)
83-
} else if cfgApp.ActiveVCSProvider != "" {
84-
if vcsConfig, ok := cfgApp.VCSConfigs[cfgApp.ActiveVCSProvider]; ok && cfgApp.ActiveVCSProvider == "github" {
85-
vcsClient = github.NewGitHubClient(repoOwner, repoName, vcsConfig.Token, translations)
86-
}
87-
}
64+
if err := container.RegisterVCSProvider("github", github.NewGitHubProviderFactory()); err != nil {
65+
log.Printf("Warning: failed to register GitHub provider: %v", err)
8866
}
8967

90-
commitService := services.NewCommitService(gitService, aiProvider, ticketService, nil, cfgApp, translations)
68+
if err := container.RegisterTicketProvider("jira", jira.NewJiraProviderFactory()); err != nil {
69+
log.Printf("Warning: failed to register Jira provider: %v", err)
70+
}
9171

92-
commitHandler := handler.NewSuggestionHandler(gitService, vcsClient, translations)
72+
gitService := git.NewGitService(translations)
73+
container.SetGitService(gitService)
9374

94-
registerCommand := registry.NewRegistry(cfgApp, translations)
75+
ctx := context.Background()
76+
commitService, err := container.GetCommitService(ctx)
77+
if err != nil {
78+
log.Printf("Warning: commit service initialization failed: %v", err)
79+
log.Println("La IA no está configurada. Podés configurarla con 'matecommit config init'")
80+
}
9581

96-
prServiceFactory := factory.NewPrServiceFactory(cfgApp, translations, aiSummarizer, gitService)
82+
var vcsClient = container.GetVCSRegistry()
83+
vcsClientInstance, _ := vcsClient.CreateClientFromConfig(ctx, gitService, cfgApp, translations)
84+
commitHandler := handler.NewSuggestionHandler(gitService, vcsClientInstance, translations)
9785

86+
prServiceFactory := factory.NewPrServiceFactory(cfgApp, translations, nil, gitService)
9887
prCommand := pull_requests.NewSummarizeCommand(prServiceFactory)
9988

89+
registerCommand := registry.NewRegistry(cfgApp, translations)
90+
10091
if err := registerCommand.Register("suggest", suggests_commits.NewSuggestCommandFactory(commitService, commitHandler)); err != nil {
10192
log.Fatalf("Error al registrar el comando 'suggest': %v", err)
10293
}

internal/cli/command/config/doctor.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ func (d *DoctorCommand) runHealthCheck(ctx context.Context, t *i18n.Translations
9595
fmt.Println()
9696
ui.PrintInfo(t.GetMessage("doctor.available_commands", 0, nil))
9797

98-
hasGemini := cfg.GeminiAPIKey != ""
98+
hasGemini := false
99+
if providerCfg, exists := cfg.AIProviders["gemini"]; exists && providerCfg.APIKey != "" {
100+
hasGemini = true
101+
}
99102
hasGitHub := false
100103
if cfg.VCSConfigs != nil {
101104
if githubConfig, ok := cfg.VCSConfigs["github"]; ok && githubConfig.Token != "" {
@@ -191,7 +194,8 @@ func (d *DoctorCommand) checkGitInstalled(ctx context.Context, t *i18n.Translati
191194
}
192195

193196
func (d *DoctorCommand) checkGeminiAPIKey(ctx context.Context, t *i18n.Translations, cfg *config.Config) checkResult {
194-
if cfg.GeminiAPIKey == "" {
197+
providerCfg, exists := cfg.AIProviders["gemini"]
198+
if !exists || providerCfg.APIKey == "" {
195199
return checkResult{
196200
status: checkStatusWarning,
197201
message: t.GetMessage("doctor.gemini_not_configured", 0, nil),

internal/cli/command/config/init.go

Lines changed: 95 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,52 @@ import (
1818

1919
func (c *ConfigCommandFactory) newInitCommand(t *i18n.Translations, cfg *config.Config) *cli.Command {
2020
return &cli.Command{
21-
Name: "init",
22-
Usage: t.GetMessage("config_init_usage", 0, nil),
21+
Name: "init",
22+
Usage: t.GetMessage("config_init_usage", 0, nil),
23+
Flags: []cli.Flag{
24+
&cli.BoolFlag{
25+
Name: "quick",
26+
Aliases: []string{"q"},
27+
Usage: t.GetMessage("config_init_quick_flag", 0, nil),
28+
},
29+
&cli.BoolFlag{
30+
Name: "full",
31+
Usage: t.GetMessage("config_init_full_flag", 0, nil),
32+
},
33+
},
2334
Action: initConfigAction(cfg, t),
2435
}
2536
}
2637

2738
func initConfigAction(cfg *config.Config, t *i18n.Translations) cli.ActionFunc {
2839
return func(ctx context.Context, command *cli.Command) error {
2940
reader := bufio.NewReader(os.Stdin)
30-
return runInitProcess(ctx, command, reader, cfg, t)
41+
42+
if command.Bool("quick") {
43+
return runQuickSetup(ctx, reader, cfg, t)
44+
}
45+
46+
if command.Bool("full") {
47+
return runFullSetup(ctx, command, reader, cfg, t)
48+
}
49+
50+
fmt.Println(t.GetMessage("setup_mode.choose_mode", 0, nil))
51+
fmt.Println(t.GetMessage("setup_mode.quick_option", 0, nil))
52+
fmt.Println(t.GetMessage("setup_mode.full_option", 0, nil))
53+
fmt.Print(t.GetMessage("setup_mode.prompt_selection", 0, nil))
54+
55+
choice, _ := reader.ReadString('\n')
56+
choice = strings.TrimSpace(choice)
57+
58+
if choice == "" || choice == "1" {
59+
return runQuickSetup(ctx, reader, cfg, t)
60+
}
61+
62+
return runFullSetup(ctx, command, reader, cfg, t)
3163
}
3264
}
3365

34-
func runInitProcess(ctx context.Context, command *cli.Command, reader *bufio.Reader, cfg *config.Config, t *i18n.Translations) error {
66+
func runFullSetup(ctx context.Context, command *cli.Command, reader *bufio.Reader, cfg *config.Config, t *i18n.Translations) error {
3567
if err := configureWelcome(ctx, reader, cfg, t); err != nil {
3668
return err
3769
}
@@ -46,7 +78,7 @@ func runInitProcess(ctx context.Context, command *cli.Command, reader *bufio.Rea
4678
}
4779
if err := config.SaveConfig(cfg); err != nil {
4880
fmt.Println(t.GetMessage("config_save.error_saving_config", 0, map[string]interface{}{"Error": err.Error()}))
49-
return fmt.Errorf("error saving configuration: %w", err)
81+
return fmt.Errorf("error guardando configuracion: %w", err)
5082
}
5183

5284
printConfigSummary(cfg, t)
@@ -55,11 +87,11 @@ func runInitProcess(ctx context.Context, command *cli.Command, reader *bufio.Rea
5587
fmt.Print(t.GetMessage("init.prompt_run_again", 0, nil))
5688
runAgain, err := reader.ReadString('\n')
5789
if err != nil {
58-
return fmt.Errorf("error reading input: %w", err)
90+
return fmt.Errorf("error leyendo input: %w", err)
5991
}
6092

6193
if isYes(runAgain) {
62-
return runInitProcess(ctx, command, reader, cfg, t)
94+
return runFullSetup(ctx, command, reader, cfg, t)
6395
}
6496

6597
return nil
@@ -77,14 +109,20 @@ func configureWelcome(ctx context.Context, reader *bufio.Reader, cfg *config.Con
77109
fmt.Println(t.GetMessage("init.welcome", 0, nil))
78110
fmt.Println(t.GetMessage("init.ai_intro", 0, map[string]interface{}{"Providers": aiProvidersStr}))
79111

80-
ui.PrintInfo(t.GetMessage("config.api_key_instructions", 0, nil))
81-
ui.PrintInfo(t.GetMessage("config.get_key_at", 0, nil) + " https://makersuite.google.com/app/apikey")
112+
ui.PrintInfo(t.GetMessage("config.api_key_instructions", 0, map[string]interface{}{
113+
"Provider": "Gemini",
114+
}))
115+
ui.PrintInfo(t.GetMessage("config.get_key_at", 0, map[string]interface{}{
116+
"URL": "https://makersuite.google.com/app/apikey",
117+
}))
82118
fmt.Println()
83119

84-
fmt.Print(t.GetMessage("init.prompt_gemini_api_key", 0, nil))
120+
fmt.Print(t.GetMessage("init.prompt_ai_api_key", 0, map[string]interface{}{
121+
"Provider": "Gemini",
122+
}))
85123
apiKey, err := reader.ReadString('\n')
86124
if err != nil {
87-
return fmt.Errorf("error reading API key: %w", err)
125+
return fmt.Errorf("error leyendo API_KEY: %w", err)
88126
}
89127
apiKey = strings.TrimSpace(apiKey)
90128

@@ -98,25 +136,36 @@ func configureWelcome(ctx context.Context, reader *bufio.Reader, cfg *config.Con
98136
}
99137
}
100138
}
101-
cfg.GeminiAPIKey = apiKey
102139

103140
fmt.Println(t.GetMessage("init.model_hint_supported", 0, map[string]interface{}{"Models": geminiModelsStr}))
104141
fmt.Print(t.GetMessage("init.prompt_model_with_default", 0, map[string]interface{}{"Default": geminiDefault}))
105142
modelInput, err := reader.ReadString('\n')
106143
if err != nil {
107-
return fmt.Errorf("error reading model: %w", err)
144+
return fmt.Errorf("error leyendo modelo: %w", err)
108145
}
109146
modelInput = strings.TrimSpace(modelInput)
110147

148+
selectedModel := geminiDefault
149+
if modelInput != "" {
150+
selectedModel = modelInput
151+
}
152+
153+
if cfg.AIProviders == nil {
154+
cfg.AIProviders = make(map[string]config.AIProviderConfig)
155+
}
156+
157+
cfg.AIProviders["gemini"] = config.AIProviderConfig{
158+
APIKey: apiKey,
159+
Model: selectedModel,
160+
Temperature: 0.3,
161+
MaxTokens: 10000,
162+
}
163+
111164
cfg.AIConfig.ActiveAI = config.AIGemini
112165
if cfg.AIConfig.Models == nil {
113166
cfg.AIConfig.Models = make(map[config.AI]config.Model)
114167
}
115-
if modelInput == "" {
116-
cfg.AIConfig.Models[config.AIGemini] = config.Model(geminiDefault)
117-
} else {
118-
cfg.AIConfig.Models[config.AIGemini] = config.Model(modelInput)
119-
}
168+
cfg.AIConfig.Models[config.AIGemini] = config.Model(selectedModel)
120169

121170
return nil
122171
}
@@ -152,7 +201,7 @@ func configureVCS(reader *bufio.Reader, cfg *config.Config, t *i18n.Translations
152201

153202
ansVCS, err := reader.ReadString('\n')
154203
if err != nil {
155-
return fmt.Errorf("error reading VCS answer: %w", err)
204+
return fmt.Errorf("error al leer la respuesta de VCS: %w", err)
156205
}
157206
ansVCS = strings.TrimSpace(strings.ToLower(ansVCS))
158207

@@ -161,7 +210,7 @@ func configureVCS(reader *bufio.Reader, cfg *config.Config, t *i18n.Translations
161210
fmt.Print(t.GetMessage("init.prompt_github_token_blank_skip", 0, nil))
162211
token, err := reader.ReadString('\n')
163212
if err != nil {
164-
return fmt.Errorf("error reading GitHub token: %w", err)
213+
return fmt.Errorf("error al leer el token de GitHub: %w", err)
165214
}
166215
token = strings.TrimSpace(token)
167216

@@ -190,7 +239,7 @@ func configureTickets(reader *bufio.Reader, cfg *config.Config, t *i18n.Translat
190239

191240
ansJira, err := reader.ReadString('\n')
192241
if err != nil {
193-
return fmt.Errorf("error reading Jira answer: %w", err)
242+
return fmt.Errorf("error al leer la respuesta de Jira: %w", err)
194243
}
195244
ansJira = strings.TrimSpace(strings.ToLower(ansJira))
196245

@@ -205,7 +254,7 @@ func configureTickets(reader *bufio.Reader, cfg *config.Config, t *i18n.Translat
205254
fmt.Print(t.GetMessage("init.prompt_jira_base_url_blank_cancel", 0, nil))
206255
jiraURL, err := reader.ReadString('\n')
207256
if err != nil {
208-
return fmt.Errorf("error reading Jira URL: %w", err)
257+
return fmt.Errorf("error al leer la URL de Jira: %w", err)
209258
}
210259
jiraURL = strings.TrimSpace(jiraURL)
211260

@@ -218,12 +267,11 @@ func configureTickets(reader *bufio.Reader, cfg *config.Config, t *i18n.Translat
218267
if !isValidURL(jiraURL) {
219268
fmt.Println(t.GetMessage("init.warning_invalid_url", 0, nil))
220269
}
221-
cfg.JiraConfig.BaseURL = jiraURL
222270

223271
fmt.Print(t.GetMessage("init.prompt_jira_email_blank_cancel", 0, nil))
224272
jiraEmail, err := reader.ReadString('\n')
225273
if err != nil {
226-
return fmt.Errorf("error reading Jira email: %w", err)
274+
return fmt.Errorf("error al leer el correo electrónico de Jira: %w", err)
227275
}
228276
jiraEmail = strings.TrimSpace(jiraEmail)
229277

@@ -236,12 +284,11 @@ func configureTickets(reader *bufio.Reader, cfg *config.Config, t *i18n.Translat
236284
if !isValidEmail(jiraEmail) {
237285
fmt.Println(t.GetMessage("init.warning_invalid_email", 0, nil))
238286
}
239-
cfg.JiraConfig.Email = jiraEmail
240287

241288
fmt.Print(t.GetMessage("init.prompt_jira_api_token_blank_cancel", 0, nil))
242289
jiraToken, err := reader.ReadString('\n')
243290
if err != nil {
244-
return fmt.Errorf("error reading Jira token: %w", err)
291+
return fmt.Errorf("error al leer el token de Jira: %w", err)
245292
}
246293
jiraToken = strings.TrimSpace(jiraToken)
247294

@@ -250,7 +297,16 @@ func configureTickets(reader *bufio.Reader, cfg *config.Config, t *i18n.Translat
250297
disableTickets(cfg)
251298
return nil
252299
}
253-
cfg.JiraConfig.APIKey = jiraToken
300+
301+
if cfg.TicketProviders == nil {
302+
cfg.TicketProviders = make(map[string]config.TicketProviderConfig)
303+
}
304+
305+
cfg.TicketProviders["jira"] = config.TicketProviderConfig{
306+
APIKey: jiraToken,
307+
BaseURL: jiraURL,
308+
Email: jiraEmail,
309+
}
254310

255311
return nil
256312
}
@@ -272,7 +328,7 @@ func printConfigSummary(cfg *config.Config, t *i18n.Translations) {
272328
}
273329

274330
apiMask := "❌"
275-
if cfg.GeminiAPIKey != "" {
331+
if providerCfg, exists := cfg.AIProviders["gemini"]; exists && providerCfg.APIKey != "" {
276332
apiMask = "✅"
277333
}
278334
fmt.Println(t.GetMessage("init.summary_api", 0, map[string]interface{}{"AI": "gemini", "Configured": apiMask}))
@@ -285,7 +341,8 @@ func printConfigSummary(cfg *config.Config, t *i18n.Translations) {
285341

286342
if cfg.UseTicket && cfg.ActiveTicketService == "jira" {
287343
fmt.Println(t.GetMessage("config_models.ticket_service_enabled", 0, map[string]interface{}{"Service": "jira"}))
288-
fmt.Println(t.GetMessage("config_models.jira_config_label", 0, map[string]interface{}{"BaseURL": cfg.JiraConfig.BaseURL, "Email": cfg.JiraConfig.Email}))
344+
jiraCfg := cfg.TicketProviders["jira"]
345+
fmt.Println(t.GetMessage("config_models.jira_config_label", 0, map[string]interface{}{"BaseURL": jiraCfg.BaseURL, "Email": jiraCfg.Email}))
289346
} else {
290347
fmt.Println(t.GetMessage("config_models.ticket_service_disabled", 0, nil))
291348
}
@@ -303,8 +360,15 @@ func validateGeminiAPIKey(ctx context.Context, apiKey string, t *i18n.Translatio
303360
spinner.Start()
304361

305362
testCfg := &config.Config{
306-
GeminiAPIKey: apiKey,
307-
Language: "en",
363+
Language: "en",
364+
AIProviders: map[string]config.AIProviderConfig{
365+
"gemini": {
366+
APIKey: apiKey,
367+
Model: string(config.ModelGeminiV25Flash),
368+
Temperature: 0.3,
369+
MaxTokens: 10000,
370+
},
371+
},
308372
AIConfig: config.AIConfig{
309373
ActiveAI: config.AIGemini,
310374
Models: map[config.AI]config.Model{

0 commit comments

Comments
 (0)