From b481a5bec2926c320f994d596e947a6fcd4b35d5 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 15:54:02 +0900 Subject: [PATCH 01/26] Add config file --- backend-go/config.yaml | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/backend-go/config.yaml b/backend-go/config.yaml index e69de29b..6b6ff324 100644 --- a/backend-go/config.yaml +++ b/backend-go/config.yaml @@ -0,0 +1,45 @@ +Server: + Port: 3001 + +OAuth: + Github: + ClientID: "your_client_id" + ClientSecret: "your_client_secret" + CallbackURL: "http://localhost:3000/auth/login/github" + AuthorizationURL: "https://github.com/login/oauth/authorize" + TokenURL: "https://github.com/login/oauth/access_token" + UserProfileURL: "https://api.github.com/user" + +FrontendBaseURL: "http://localhost:5173" + +JWT: + AccessTokenSecret: "your_access_token_secret" + AccessTokenExpirationTime: "1d" + RefreshTokenSecret: "your_refresh_token_secret" + RefreshTokenExpirationTime: "7d" + +Yorkie: + APIAddr: "http://localhost:8080" + ProjectName: "default" + ProjectSecretKey: "" + +Mongo: + ConnectionTimeout: "10s" + ConnectionURI: "mongodb://localhost:27017/codepair" + PingTimeout: "5s" + DatabaseName: "codepair" + +Storage: + Provider: "minio" # or "s3" + + Minio: + Bucket: "default-storage" + Endpoint: "http://localhost:9000" + AccessKey: "minioadmin" + SecretKey: "minioadmin" + + S3: + Bucket: "default-storage" + Region: "us-east-1" + AccessKey: "aws_access_key" + SecretKey: "aws_secret_key" From bed0469a554f3427457c42ae42e60ff1f3ac1ab5 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 18:14:03 +0900 Subject: [PATCH 02/26] Add config.go files --- backend-go/internal/config/jwt.go | 46 ++++++++++ backend-go/internal/config/mongo.go | 46 ++++++++++ backend-go/internal/config/oauth.go | 65 +++++++++++++++ backend-go/internal/config/server.go | 19 ++++- backend-go/internal/config/storage.go | 116 ++++++++++++++++++++++++++ backend-go/internal/config/yorkie.go | 34 ++++++++ 6 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 backend-go/internal/config/jwt.go create mode 100644 backend-go/internal/config/mongo.go create mode 100644 backend-go/internal/config/oauth.go create mode 100644 backend-go/internal/config/storage.go create mode 100644 backend-go/internal/config/yorkie.go diff --git a/backend-go/internal/config/jwt.go b/backend-go/internal/config/jwt.go new file mode 100644 index 00000000..2d2de759 --- /dev/null +++ b/backend-go/internal/config/jwt.go @@ -0,0 +1,46 @@ +package config + +import ( + "fmt" + "time" +) + +const ( + DefaultAccessTokenSecret = "your_access_token_secret" + DefaultAccessTokenExpirationTime = 24 * time.Hour + DefaultRefreshTokenSecret = "your_refresh_token_secret" + DefaultRefreshTokenExpirationTime = 168 * time.Hour +) + +type JWT struct { + AccessTokenSecret string + AccessTokenExpirationTime time.Duration `yaml:"AccessTokenExpirationTime"` + RefreshTokenSecret string `yaml:"RefreshTokenSecret"` + RefreshTokenExpirationTime time.Duration `yaml:"RefreshTokenExpirationTime" ` +} + +func (j *JWT) ensureDefaultValue() { + if j.AccessTokenSecret == "" { + j.AccessTokenSecret = DefaultAccessTokenSecret + } + if j.AccessTokenExpirationTime == 0 { + j.AccessTokenExpirationTime = DefaultAccessTokenExpirationTime + } + if j.RefreshTokenSecret == "" { + j.RefreshTokenSecret = DefaultRefreshTokenSecret + } + if j.RefreshTokenExpirationTime == 0 { + j.RefreshTokenExpirationTime = DefaultRefreshTokenExpirationTime + } +} + +func (j *JWT) validate() error { + if j.AccessTokenSecret == "" { + return fmt.Errorf("access token secret cannot be empty") + } + if j.RefreshTokenSecret == "" { + return fmt.Errorf("refresh token secret cannot be empty") + } + + return nil +} diff --git a/backend-go/internal/config/mongo.go b/backend-go/internal/config/mongo.go new file mode 100644 index 00000000..2b1d3fdd --- /dev/null +++ b/backend-go/internal/config/mongo.go @@ -0,0 +1,46 @@ +package config + +import ( + "fmt" + "time" +) + +const ( + DefaultMongoConnectionURI = "mongodb://localhost:27017/codepair" + DefaultMongoDatabaseName = "codepair" + DefaultConnectionTimeout = 10 * time.Second + DefaultPingTimeout = 5 * time.Second +) + +type Mongo struct { + ConnectionTimeout time.Duration `yaml:"ConnectionTimeout"` + ConnectionURI string `yaml:"ConnectionURI"` + PingTimeout time.Duration `yaml:"PingTimeout"` + DatabaseName string `yaml:"DatabaseName"` +} + +func (m *Mongo) ensureDefaultValue() { + if m.ConnectionTimeout == 0 { + m.ConnectionTimeout = DefaultConnectionTimeout + } + if m.ConnectionURI == "" { + m.ConnectionURI = DefaultMongoConnectionURI + } + if m.PingTimeout == 0 { + m.PingTimeout = DefaultPingTimeout + } + if m.DatabaseName == "" { + m.DatabaseName = DefaultMongoDatabaseName + } +} + +func (m *Mongo) validate() error { + if m.ConnectionURI == "" { + return fmt.Errorf("mongo connection URI cannot be empty") + } + if m.DatabaseName == "" { + return fmt.Errorf("mongo database name cannot be empty") + } + + return nil +} diff --git a/backend-go/internal/config/oauth.go b/backend-go/internal/config/oauth.go new file mode 100644 index 00000000..62ac5b81 --- /dev/null +++ b/backend-go/internal/config/oauth.go @@ -0,0 +1,65 @@ +package config + +import ( + "fmt" +) + +const ( + DefaultGitHubAuthorizationURL = "https://github.com/login/oauth/authorize" + DefaultGitHubTokenURL = "https://github.com/login/oauth/access_token" + DefaultGitHubUserProfileURL = "https://api.github.com/user" + DefaultGitHubCallbackURL = "http://localhost:3000/auth/login/github" +) + +type OAuth struct { + Github *GitHubOAuth `yaml:"Github"` +} + +type GitHubOAuth struct { + ClientID string `yaml:"ClientID"` + ClientSecret string `yaml:"ClientSecret"` + CallbackURL string + AuthorizationURL string `yaml:"AuthorizationURL"` + TokenURL string + UserProfileURL string `yaml:"UserProfileURL"` +} + +func (o *OAuth) ensureDefaultValue() { + if o.Github == nil { + o.Github = &GitHubOAuth{} + } + o.Github.ensureDefaultValue() +} + +func (o *OAuth) validate() error { + if err := o.Github.validate(); err != nil { + return err + } + return nil +} + +func (g *GitHubOAuth) ensureDefaultValue() { + if g.CallbackURL == "" { + g.CallbackURL = DefaultGitHubCallbackURL + } + if g.AuthorizationURL == "" { + g.AuthorizationURL = DefaultGitHubAuthorizationURL + } + if g.TokenURL == "" { + g.TokenURL = DefaultGitHubTokenURL + } + if g.UserProfileURL == "" { + g.UserProfileURL = DefaultGitHubUserProfileURL + } +} + +func (g *GitHubOAuth) validate() error { + if g.ClientID == "" { + return fmt.Errorf("github client_id cannot be empty") + } + if g.ClientSecret == "" { + return fmt.Errorf("github client_secret cannot be empty") + } + + return nil +} diff --git a/backend-go/internal/config/server.go b/backend-go/internal/config/server.go index 9316bc81..ba41153d 100644 --- a/backend-go/internal/config/server.go +++ b/backend-go/internal/config/server.go @@ -1,9 +1,26 @@ package config +import "fmt" + const ( DefaultServerPort = 3001 ) +// Server holds your server configuration. type Server struct { - Port int + Port int `yaml:"Port"` +} + +// ensureDefaultValue sets a default if Port is not provided. +func (s *Server) ensureDefaultValue() { + if s.Port == 0 { + s.Port = DefaultServerPort + } +} + +func (s *Server) validate() error { + if s.Port < 1 || 65535 < s.Port { + return fmt.Errorf("must be between 1 and 65535, given %d", s.Port) + } + return nil } diff --git a/backend-go/internal/config/storage.go b/backend-go/internal/config/storage.go new file mode 100644 index 00000000..4e166137 --- /dev/null +++ b/backend-go/internal/config/storage.go @@ -0,0 +1,116 @@ +package config + +import ( + "fmt" +) + +const ( + DefaultStorageProvider = "minio" + + DefaultMinioBucket = "default-minio-bucket" + DefaultMinioEndpoint = "localhost:9000" + DefaultMinioAccessKey = "minioadmin" + DefaultMinioSecretKey = "minioadmin" + + DefaultS3Bucket = "default-s3-bucket" + DefaultS3Region = "us-west-2" + DefaultS3AccessKey = "" + DefaultS3SecretKey = "" +) + +type Storage struct { + Provider string `yaml:"provider"` + S3 *S3 `yaml:"s3"` + Minio *Minio `yaml:"minio"` +} + +type S3 struct { + Bucket string `yaml:"bucket"` + Region string `yaml:"region"` + AccessKey string `yaml:"access_key"` + SecretKey string `yaml:"secret_key"` +} + +type Minio struct { + Bucket string `yaml:"bucket"` + Endpoint string `yaml:"endpoint"` + AccessKey string `yaml:"access_key"` + SecretKey string `yaml:"secret_key"` +} + +func (s *Storage) ensureDefaultValue() { + if s.Provider == "" { + s.Provider = DefaultStorageProvider + } + if s.Minio != nil { + s.Minio.EnsureDefaultValue() + } + if s.S3 != nil { + s.S3.EnsureDefaultValue() + } +} + +func (s *Storage) validate() error { + if s.Provider != "minio" && s.Provider != "s3" { + return fmt.Errorf("invalid storage provider: %s (only 'minio' or 's3' are supported)", s.Provider) + } + + switch s.Provider { + case "minio": + if s.Minio == nil { + return fmt.Errorf("storage provider is minio but Minio config is nil") + } + return s.Minio.validate() + case "s3": + if s.S3 == nil { + return fmt.Errorf("storage provider is s3 but S3 config is nil") + } + return s.S3.validate() + } + + return nil +} + +func (m *Minio) EnsureDefaultValue() { + if m.Bucket == "" { + m.Bucket = DefaultMinioBucket + } + if m.Endpoint == "" { + m.Endpoint = DefaultMinioEndpoint + } + if m.AccessKey == "" { + m.AccessKey = DefaultMinioAccessKey + } + if m.SecretKey == "" { + m.SecretKey = DefaultMinioSecretKey + } +} + +func (m *Minio) validate() error { + if m.Bucket == "" || m.Endpoint == "" { + return fmt.Errorf("minio requires Bucket and Endpoint to be set") + } + return nil +} + +func (s3 *S3) EnsureDefaultValue() { + if s3.Bucket == "" { + s3.Bucket = DefaultS3Bucket + } + if s3.Region == "" { + s3.Region = DefaultS3Region + } + if s3.AccessKey == "" { + s3.AccessKey = DefaultS3AccessKey + } + if s3.SecretKey == "" { + s3.SecretKey = DefaultS3SecretKey + } +} + +func (s3 *S3) validate() error { + if s3.Bucket == "" || s3.Region == "" { + return fmt.Errorf("s3 requires Bucket and Region to be set") + } + return nil +} diff --git a/backend-go/internal/config/yorkie.go b/backend-go/internal/config/yorkie.go new file mode 100644 index 00000000..c332686a --- /dev/null +++ b/backend-go/internal/config/yorkie.go @@ -0,0 +1,34 @@ +package config + +import "fmt" + +const ( + DefaultYorkieAPIAddr = "http://localhost:8080" + DefaultYorkieProjectName = "default" + DefaultYorkieProjectSecret = "" +) + +type Yorkie struct { + APIAddr string `yaml:"APIAddr"` + ProjectName string `yaml:"ProjectName"` + ProjectSecretKey string `yaml:"ProjectSecretKey"` +} + +func (y *Yorkie) ensureDefaultValue() { + if y.APIAddr == "" { + y.APIAddr = DefaultYorkieAPIAddr + } + if y.ProjectName == "" { + y.ProjectName = DefaultYorkieProjectName + } + if y.ProjectSecretKey == "" { + y.ProjectSecretKey = DefaultYorkieProjectSecret + } +} + +func (y *Yorkie) validate() error { + if y.APIAddr == "" { + return fmt.Errorf("yorkie APIAddr cannot be empty") + } + return nil +} From c0ab8aa573faacb61739552a501b1e105e07f12c Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 18:14:45 +0900 Subject: [PATCH 03/26] Add env map for viper parsing --- backend-go/internal/config/env.go | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 backend-go/internal/config/env.go diff --git a/backend-go/internal/config/env.go b/backend-go/internal/config/env.go new file mode 100644 index 00000000..afa2952f --- /dev/null +++ b/backend-go/internal/config/env.go @@ -0,0 +1,37 @@ +package config + +var EnvVarMap = map[string]string{ + "server.port": "PORT", + + "oauth.github.ClientId": "OAUTH_GITHUB_CLIENT_ID", + "oauth.github.ClientSecret": "OAUTH_GITHUB_CLIENT_SECRET", + "oauth.github.CallbackUrl": "OAUTH_GITHUB_CALLBACK_URL", + "oauth.github.AuthorizationUrl": "OAUTH_GITHUB_AUTHORIZATION_URL", + "oauth.github.TokenUrl": "OAUTH_GITHUB_TOKEN_URL", + "oauth.github.UserProfileUrl": "OAUTH_GITHUB_USER_PROFILE_URL", + "auth.FrontendBaseUrl": "AUTH_FRONTEND_BASE_URL", + + "jwt.AccessTokenSecret": "JWT_ACCESS_TOKEN_SECRET", + "jwt.AccessTokenExpirationTime": "JWT_ACCESS_TOKEN_EXPIRATION_TIME", + "jwt.RefreshTokenSecret": "JWT_REFRESH_TOKEN_SECRET", + "jwt.RefreshTokenExpirationTime": "JWT_REFRESH_TOKEN_EXPIRATION_TIME", + + "yorkie.ApiAddr": "YORKIE_API_ADDR", + "yorkie.ProjectName": "YORKIE_PROJECT_NAME", + "yorkie.ProjectSecretKey": "YORKIE_PROJECT_SECRET_KEY", + + "mongo.ConnectionTimeout": "MONGO_CONNECTION_TIMEOUT", + "mongo.ConnectionURI": "MONGO_CONNECTION_URI", + "mongo.PingTimeout": "MONGO_PING_TIMEOUT", + "mongo.DatabaseName": "MONGO_DATABASE_NAME", + + "storage.Provider": "STORAGE_PROVIDER", + "storage.minio.Bucket": "STORAGE_MINIO_BUCKET", + "storage.minio.Endpoint": "STORAGE_MINIO_ENDPOINT", + "storage.minio.AccessKey": "STORAGE_MINIO_ACCESS_KEY", + "storage.minio.SecretKey": "STORAGE_MINIO_SECRET_KEY", + "storage.s3.Bucket": "STORAGE_S3_BUCKET", + "storage.s3.Region": "STORAGE_S3_REGION", + "storage.s3.AccessKey": "STORAGE_S3_ACCESS_KEY", + "storage.s3.SecretKey": "STORAGE_S3_SECRET_KEY", +} From ed8bf2c0d5d1753db1a551e862d07b072847294d Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 18:15:54 +0900 Subject: [PATCH 04/26] implement viper parsing --- backend-go/cmd/codepair/main.go | 43 +++++++++- backend-go/internal/config/config.go | 117 +++++++++++++++++++++++++-- 2 files changed, 153 insertions(+), 7 deletions(-) diff --git a/backend-go/cmd/codepair/main.go b/backend-go/cmd/codepair/main.go index b65106d9..d493d90d 100644 --- a/backend-go/cmd/codepair/main.go +++ b/backend-go/cmd/codepair/main.go @@ -1,17 +1,58 @@ package main import ( + "flag" + "fmt" "github.com/labstack/echo/v4" + "github.com/labstack/gommon/log" "github.com/yorkie-team/codepair/backend/internal/config" "github.com/yorkie-team/codepair/backend/internal/server" ) func main() { + configPath, loglevel := parseFlag() + e := echo.New() - conf := config.LoadConfig() + if err := setLogLevel(e, loglevel); err != nil { + e.Logger.Fatal(err) + } + + conf, err := config.LoadConfig(configPath, e.Logger) + if err != nil { + e.Logger.Fatal(err) + } + cp := server.New(e, conf) if err := cp.Start(); err != nil { e.Logger.Fatal(err) } } + +func parseFlag() (string, string) { + var configPath string + var loglevel string + flag.StringVar(&configPath, "config", "", "Path to the configuration file") + flag.StringVar(&loglevel, "loglevel", "error", "Log level") + flag.Parse() + + return configPath, loglevel +} + +func setLogLevel(e *echo.Echo, level string) error { + switch level { + case "debug": + e.Logger.SetLevel(log.DEBUG) + case "info": + e.Logger.SetLevel(log.INFO) + case "warn": + e.Logger.SetLevel(log.WARN) + case "error": + e.Logger.SetLevel(log.ERROR) + case "off": + e.Logger.SetLevel(log.OFF) + default: + return fmt.Errorf("invalid log level %s", level) + } + return nil +} diff --git a/backend-go/internal/config/config.go b/backend-go/internal/config/config.go index d32ecf4a..9fb69cb9 100644 --- a/backend-go/internal/config/config.go +++ b/backend-go/internal/config/config.go @@ -1,13 +1,118 @@ package config +import ( + "errors" + "fmt" + "github.com/labstack/echo/v4" + + "github.com/spf13/viper" +) + +// Config holds the application configuration. type Config struct { - Server *Server + Server Server `yaml:"Server"` + OAuth OAuth `yaml:"OAuth"` + JWT JWT `yaml:"JWT"` + Yorkie Yorkie `yaml:"Yorkie"` + Mongo Mongo `yaml:"Mongo"` + Storage Storage `yaml:"Storage"` +} + +// LoadConfig loads configuration settings from a file (if provided) and from environment variables. +// It returns the populated Config, a status message describing which sources were used, and an error if any. +func LoadConfig(filePath string, logger echo.Logger) (*Config, error) { + if err := bindEnvironmentVariables(logger); err != nil { + return nil, err + } + + if err := readConfigFile(filePath); err != nil { + return nil, err + } + + cfg := &Config{} + if err := viper.Unmarshal(cfg); err != nil { + return nil, fmt.Errorf("unable to decode configuration into struct: %w", err) + } + + cfg.ensureDefaultValue() + + if err := cfg.validate(); err != nil { + return nil, fmt.Errorf("failed to validate config: %w", err) + } + + return cfg, nil +} + +// bindEnvironmentVariables binds each configuration key to its corresponding environment variable. +func bindEnvironmentVariables(logger echo.Logger) error { + for key, env := range EnvVarMap { + if err := viper.BindEnv(key, env); err != nil { + return fmt.Errorf("failed to bind environment variable %s to key %s: %w", env, key, err) + } + } + + isSet := false + for key, _ := range EnvVarMap { + if ok := viper.IsSet(key); ok { + isSet = true + } + } + if !isSet { + logger.Info("no env vars are used") + } + + return nil } -func LoadConfig() *Config { - return &Config{ - Server: &Server{ - Port: DefaultServerPort, - }, +func readConfigFile(filePath string) error { + if filePath != "" { + viper.SetConfigFile(filePath) + } else { + viper.SetConfigName("config") + viper.SetConfigType("yaml") + viper.AddConfigPath(".") + } + + if err := viper.ReadInConfig(); err != nil { + var nf viper.ConfigFileNotFoundError + if filePath != "" && errors.As(err, &nf) { + return fmt.Errorf("file path given but not found: %w", err) + } else { + return fmt.Errorf("failed to read config file: %w", err) + } } + + return nil +} + +func (c *Config) ensureDefaultValue() { + c.Server.ensureDefaultValue() + c.OAuth.ensureDefaultValue() + c.JWT.ensureDefaultValue() + c.Yorkie.ensureDefaultValue() + c.Mongo.ensureDefaultValue() + c.Storage.ensureDefaultValue() +} + +func (c *Config) validate() error { + if err := c.Server.validate(); err != nil { + return fmt.Errorf("server config invalid: %w", err) + } + if err := c.OAuth.validate(); err != nil { + return fmt.Errorf("oauth config invalid: %w", err) + } + if err := c.JWT.validate(); err != nil { + return fmt.Errorf("jwt config invalid: %w", err) + } + if err := c.Yorkie.validate(); err != nil { + return fmt.Errorf("yorkie config invalid: %w", err) + } + if err := c.Mongo.validate(); err != nil { + return fmt.Errorf("mongo config invalid: %w", err) + } + if err := c.Storage.validate(); err != nil { + return fmt.Errorf("storage config invalid: %w", err) + } + + return nil } From 6ce643f20cf25e2388e239f9c80a4f994d080ce3 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 18:17:35 +0900 Subject: [PATCH 05/26] add empty test --- backend-go/internal/config/config_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 backend-go/internal/config/config_test.go diff --git a/backend-go/internal/config/config_test.go b/backend-go/internal/config/config_test.go new file mode 100644 index 00000000..b00d0893 --- /dev/null +++ b/backend-go/internal/config/config_test.go @@ -0,0 +1,21 @@ +package config + +import "testing" + +func TestInitConfig(t *testing.T) { + + t.Run("Config with only environment variables test", func(t *testing.T) { + }) + + t.Run("Config with only yaml file test", func(t *testing.T) { + }) + + t.Run("Config with only Default values test", func(t *testing.T) { + }) + + t.Run("Environment override config file test", func(t *testing.T) { + }) + + t.Run("Config file override default values test", func(t *testing.T) { + }) +} From b035f49167e30ddd18e60432112ab613cbea9b51 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 18:18:18 +0900 Subject: [PATCH 06/26] fix time `1d` -> `24h` - viper cannot parse day --- backend-go/config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend-go/config.yaml b/backend-go/config.yaml index 6b6ff324..9c66fb3b 100644 --- a/backend-go/config.yaml +++ b/backend-go/config.yaml @@ -14,9 +14,9 @@ FrontendBaseURL: "http://localhost:5173" JWT: AccessTokenSecret: "your_access_token_secret" - AccessTokenExpirationTime: "1d" + AccessTokenExpirationTime: "24h" RefreshTokenSecret: "your_refresh_token_secret" - RefreshTokenExpirationTime: "7d" + RefreshTokenExpirationTime: "168h" Yorkie: APIAddr: "http://localhost:8080" From ef8c399e1652b324a6d7006ba355b14659a52a04 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 18:25:04 +0900 Subject: [PATCH 07/26] Replace `yaml` structure tag to `mapstructure` - viper use `mapstructure` and `yaml` is not needed --- backend-go/internal/config/config.go | 12 ++++++------ backend-go/internal/config/jwt.go | 8 ++++---- backend-go/internal/config/mongo.go | 8 ++++---- backend-go/internal/config/oauth.go | 14 +++++++------- backend-go/internal/config/server.go | 2 +- backend-go/internal/config/storage.go | 22 +++++++++++----------- backend-go/internal/config/yorkie.go | 6 +++--- 7 files changed, 36 insertions(+), 36 deletions(-) diff --git a/backend-go/internal/config/config.go b/backend-go/internal/config/config.go index 9fb69cb9..c66f9065 100644 --- a/backend-go/internal/config/config.go +++ b/backend-go/internal/config/config.go @@ -10,12 +10,12 @@ import ( // Config holds the application configuration. type Config struct { - Server Server `yaml:"Server"` - OAuth OAuth `yaml:"OAuth"` - JWT JWT `yaml:"JWT"` - Yorkie Yorkie `yaml:"Yorkie"` - Mongo Mongo `yaml:"Mongo"` - Storage Storage `yaml:"Storage"` + Server Server `mapstructure:"Server"` + OAuth OAuth `mapstructure:"OAuth"` + JWT JWT `mapstructure:"JWT"` + Yorkie Yorkie `mapstructure:"Yorkie"` + Mongo Mongo `mapstructure:"Mongo"` + Storage Storage `mapstructure:"Storage"` } // LoadConfig loads configuration settings from a file (if provided) and from environment variables. diff --git a/backend-go/internal/config/jwt.go b/backend-go/internal/config/jwt.go index 2d2de759..99943392 100644 --- a/backend-go/internal/config/jwt.go +++ b/backend-go/internal/config/jwt.go @@ -13,10 +13,10 @@ const ( ) type JWT struct { - AccessTokenSecret string - AccessTokenExpirationTime time.Duration `yaml:"AccessTokenExpirationTime"` - RefreshTokenSecret string `yaml:"RefreshTokenSecret"` - RefreshTokenExpirationTime time.Duration `yaml:"RefreshTokenExpirationTime" ` + AccessTokenSecret string `mapstructure:"AccessTokenSecret"` + AccessTokenExpirationTime time.Duration `mapstructure:"AccessTokenExpirationTime"` + RefreshTokenSecret string `mapstructure:"RefreshTokenSecret"` + RefreshTokenExpirationTime time.Duration `mapstructure:"RefreshTokenExpirationTime"` } func (j *JWT) ensureDefaultValue() { diff --git a/backend-go/internal/config/mongo.go b/backend-go/internal/config/mongo.go index 2b1d3fdd..df72b51a 100644 --- a/backend-go/internal/config/mongo.go +++ b/backend-go/internal/config/mongo.go @@ -13,10 +13,10 @@ const ( ) type Mongo struct { - ConnectionTimeout time.Duration `yaml:"ConnectionTimeout"` - ConnectionURI string `yaml:"ConnectionURI"` - PingTimeout time.Duration `yaml:"PingTimeout"` - DatabaseName string `yaml:"DatabaseName"` + ConnectionTimeout time.Duration `mapstructure:"ConnectionTimeout"` + ConnectionURI string `mapstructure:"ConnectionURI"` + PingTimeout time.Duration `mapstructure:"PingTimeout"` + DatabaseName string `mapstructure:"DatabaseName"` } func (m *Mongo) ensureDefaultValue() { diff --git a/backend-go/internal/config/oauth.go b/backend-go/internal/config/oauth.go index 62ac5b81..12962c46 100644 --- a/backend-go/internal/config/oauth.go +++ b/backend-go/internal/config/oauth.go @@ -12,16 +12,16 @@ const ( ) type OAuth struct { - Github *GitHubOAuth `yaml:"Github"` + Github *GitHubOAuth `mapstructure:"Github"` } type GitHubOAuth struct { - ClientID string `yaml:"ClientID"` - ClientSecret string `yaml:"ClientSecret"` - CallbackURL string - AuthorizationURL string `yaml:"AuthorizationURL"` - TokenURL string - UserProfileURL string `yaml:"UserProfileURL"` + ClientID string `mapstructure:"ClientID"` + ClientSecret string `mapstructure:"ClientSecret"` + CallbackURL string `mapstructure:"CallbackURL"` + AuthorizationURL string `mapstructure:"AuthorizationURL"` + TokenURL string `mapstructure:"TokenURL"` + UserProfileURL string `mapstructure:"UserProfileURL"` } func (o *OAuth) ensureDefaultValue() { diff --git a/backend-go/internal/config/server.go b/backend-go/internal/config/server.go index ba41153d..bc0a1e5d 100644 --- a/backend-go/internal/config/server.go +++ b/backend-go/internal/config/server.go @@ -8,7 +8,7 @@ const ( // Server holds your server configuration. type Server struct { - Port int `yaml:"Port"` + Port int `mapstructure:"Port"` } // ensureDefaultValue sets a default if Port is not provided. diff --git a/backend-go/internal/config/storage.go b/backend-go/internal/config/storage.go index 4e166137..168d890d 100644 --- a/backend-go/internal/config/storage.go +++ b/backend-go/internal/config/storage.go @@ -19,23 +19,23 @@ const ( ) type Storage struct { - Provider string `yaml:"provider"` - S3 *S3 `yaml:"s3"` - Minio *Minio `yaml:"minio"` + Provider string `mapstructure:"provider"` + S3 *S3 `mapstructure:"s3"` + Minio *Minio `mapstructure:"minio"` } type S3 struct { - Bucket string `yaml:"bucket"` - Region string `yaml:"region"` - AccessKey string `yaml:"access_key"` - SecretKey string `yaml:"secret_key"` + Bucket string `mapstructure:"bucket"` + Region string `mapstructure:"region"` + AccessKey string `mapstructure:"access_key"` + SecretKey string `mapstructure:"secret_key"` } type Minio struct { - Bucket string `yaml:"bucket"` - Endpoint string `yaml:"endpoint"` - AccessKey string `yaml:"access_key"` - SecretKey string `yaml:"secret_key"` + Bucket string `mapstructure:"bucket"` + Endpoint string `mapstructure:"endpoint"` + AccessKey string `mapstructure:"access_key"` + SecretKey string `mapstructure:"secret_key"` } func (s *Storage) ensureDefaultValue() { diff --git a/backend-go/internal/config/yorkie.go b/backend-go/internal/config/yorkie.go index c332686a..fdc5729c 100644 --- a/backend-go/internal/config/yorkie.go +++ b/backend-go/internal/config/yorkie.go @@ -9,9 +9,9 @@ const ( ) type Yorkie struct { - APIAddr string `yaml:"APIAddr"` - ProjectName string `yaml:"ProjectName"` - ProjectSecretKey string `yaml:"ProjectSecretKey"` + APIAddr string `mapstructure:"APIAddr"` + ProjectName string `mapstructure:"ProjectName"` + ProjectSecretKey string `mapstructure:"ProjectSecretKey"` } func (y *Yorkie) ensureDefaultValue() { From f0268a94a0e92ca1f535eaa24db326bdf744b16c Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 19:39:09 +0900 Subject: [PATCH 08/26] Remove logger in config --- backend-go/cmd/codepair/main.go | 2 +- backend-go/internal/config/config.go | 18 +++--------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/backend-go/cmd/codepair/main.go b/backend-go/cmd/codepair/main.go index d493d90d..57839bfa 100644 --- a/backend-go/cmd/codepair/main.go +++ b/backend-go/cmd/codepair/main.go @@ -17,7 +17,7 @@ func main() { e.Logger.Fatal(err) } - conf, err := config.LoadConfig(configPath, e.Logger) + conf, err := config.LoadConfig(configPath) if err != nil { e.Logger.Fatal(err) } diff --git a/backend-go/internal/config/config.go b/backend-go/internal/config/config.go index c66f9065..2417402b 100644 --- a/backend-go/internal/config/config.go +++ b/backend-go/internal/config/config.go @@ -3,8 +3,6 @@ package config import ( "errors" "fmt" - "github.com/labstack/echo/v4" - "github.com/spf13/viper" ) @@ -20,8 +18,8 @@ type Config struct { // LoadConfig loads configuration settings from a file (if provided) and from environment variables. // It returns the populated Config, a status message describing which sources were used, and an error if any. -func LoadConfig(filePath string, logger echo.Logger) (*Config, error) { - if err := bindEnvironmentVariables(logger); err != nil { +func LoadConfig(filePath string) (*Config, error) { + if err := bindEnvironmentVariables(); err != nil { return nil, err } @@ -44,23 +42,13 @@ func LoadConfig(filePath string, logger echo.Logger) (*Config, error) { } // bindEnvironmentVariables binds each configuration key to its corresponding environment variable. -func bindEnvironmentVariables(logger echo.Logger) error { +func bindEnvironmentVariables() error { for key, env := range EnvVarMap { if err := viper.BindEnv(key, env); err != nil { return fmt.Errorf("failed to bind environment variable %s to key %s: %w", env, key, err) } } - isSet := false - for key, _ := range EnvVarMap { - if ok := viper.IsSet(key); ok { - isSet = true - } - } - if !isSet { - logger.Info("no env vars are used") - } - return nil } From eecb5612f1322ee5ab7a3c7b3230351be439d7ec Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 20:49:22 +0900 Subject: [PATCH 09/26] Fix error condition - return nil when config path is not given and config file not found --- backend-go/internal/config/config.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend-go/internal/config/config.go b/backend-go/internal/config/config.go index 2417402b..421023c8 100644 --- a/backend-go/internal/config/config.go +++ b/backend-go/internal/config/config.go @@ -63,11 +63,13 @@ func readConfigFile(filePath string) error { if err := viper.ReadInConfig(); err != nil { var nf viper.ConfigFileNotFoundError - if filePath != "" && errors.As(err, &nf) { - return fmt.Errorf("file path given but not found: %w", err) - } else { - return fmt.Errorf("failed to read config file: %w", err) + if errors.As(err, &nf) { + if filePath != "" { + return fmt.Errorf("file path given but not found: %w", err) + } + return nil } + return err } return nil From 8d8b24eeed05d0fb6ee1b8d1ec0f868109cc00d7 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 20:49:57 +0900 Subject: [PATCH 10/26] Fix port env var name - `PORT` -> `SERVER_PORT` --- backend-go/internal/config/env.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend-go/internal/config/env.go b/backend-go/internal/config/env.go index afa2952f..585782e4 100644 --- a/backend-go/internal/config/env.go +++ b/backend-go/internal/config/env.go @@ -1,7 +1,7 @@ package config var EnvVarMap = map[string]string{ - "server.port": "PORT", + "server.port": "SERVER_PORT", "oauth.github.ClientId": "OAUTH_GITHUB_CLIENT_ID", "oauth.github.ClientSecret": "OAUTH_GITHUB_CLIENT_SECRET", From 8f8a8ac9ed37a314f195811a0fbb90b3d283dc20 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 20:50:27 +0900 Subject: [PATCH 11/26] Remove default value for S3 --- backend-go/internal/config/storage.go | 38 ++++++--------------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/backend-go/internal/config/storage.go b/backend-go/internal/config/storage.go index 168d890d..9970b4e9 100644 --- a/backend-go/internal/config/storage.go +++ b/backend-go/internal/config/storage.go @@ -11,11 +11,6 @@ const ( DefaultMinioEndpoint = "localhost:9000" DefaultMinioAccessKey = "minioadmin" DefaultMinioSecretKey = "minioadmin" - - DefaultS3Bucket = "default-s3-bucket" - DefaultS3Region = "us-west-2" - DefaultS3AccessKey = "" - DefaultS3SecretKey = "" ) type Storage struct { @@ -27,27 +22,25 @@ type Storage struct { type S3 struct { Bucket string `mapstructure:"bucket"` Region string `mapstructure:"region"` - AccessKey string `mapstructure:"access_key"` - SecretKey string `mapstructure:"secret_key"` + AccessKey string `mapstructure:"accessKey"` + SecretKey string `mapstructure:"secretKey"` } type Minio struct { Bucket string `mapstructure:"bucket"` Endpoint string `mapstructure:"endpoint"` - AccessKey string `mapstructure:"access_key"` - SecretKey string `mapstructure:"secret_key"` + AccessKey string `mapstructure:"accessKey"` + SecretKey string `mapstructure:"secretKey"` } func (s *Storage) ensureDefaultValue() { if s.Provider == "" { s.Provider = DefaultStorageProvider } - if s.Minio != nil { - s.Minio.EnsureDefaultValue() - } - if s.S3 != nil { - s.S3.EnsureDefaultValue() + if s.Minio == nil { + s.Minio = &Minio{} } + s.Minio.EnsureDefaultValue() } func (s *Storage) validate() error { @@ -93,23 +86,8 @@ func (m *Minio) validate() error { return nil } -func (s3 *S3) EnsureDefaultValue() { - if s3.Bucket == "" { - s3.Bucket = DefaultS3Bucket - } - if s3.Region == "" { - s3.Region = DefaultS3Region - } - if s3.AccessKey == "" { - s3.AccessKey = DefaultS3AccessKey - } - if s3.SecretKey == "" { - s3.SecretKey = DefaultS3SecretKey - } -} - func (s3 *S3) validate() error { - if s3.Bucket == "" || s3.Region == "" { + if s3.Bucket == "" || s3.Region == "" || s3.AccessKey == "" || s3.SecretKey == "" { return fmt.Errorf("s3 requires Bucket and Region to be set") } return nil From 80406f6e6d9b1f7220ef358453375f436544277f Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 20:50:59 +0900 Subject: [PATCH 12/26] Implement `LoadConfig` test --- backend-go/internal/config/config_test.go | 268 +++++++++++++++++++++- 1 file changed, 257 insertions(+), 11 deletions(-) diff --git a/backend-go/internal/config/config_test.go b/backend-go/internal/config/config_test.go index b00d0893..42b07a38 100644 --- a/backend-go/internal/config/config_test.go +++ b/backend-go/internal/config/config_test.go @@ -1,21 +1,267 @@ -package config +package config_test -import "testing" +import ( + "os" + "path/filepath" + "testing" + "time" -func TestInitConfig(t *testing.T) { + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - t.Run("Config with only environment variables test", func(t *testing.T) { - }) + "github.com/yorkie-team/codepair/backend/internal/config" +) - t.Run("Config with only yaml file test", func(t *testing.T) { - }) +func TestConfigWithEnvVars(t *testing.T) { + t.Setenv("SERVER_PORT", "3002") - t.Run("Config with only Default values test", func(t *testing.T) { - }) + t.Setenv("OAUTH_GITHUB_CLIENT_ID", "test_value") + t.Setenv("OAUTH_GITHUB_CLIENT_SECRET", "test_value") + t.Setenv("OAUTH_GITHUB_CALLBACK_URL", "test_value") + t.Setenv("OAUTH_GITHUB_AUTHORIZATION_URL", "test_value") + t.Setenv("OAUTH_GITHUB_TOKEN_URL", "test_value") + t.Setenv("OAUTH_GITHUB_USER_PROFILE_URL", "test_value") + + t.Setenv("AUTH_FRONTEND_BASE_URL", "test_value") + + t.Setenv("JWT_ACCESS_TOKEN_SECRET", "test_value") + t.Setenv("JWT_ACCESS_TOKEN_EXPIRATION_TIME", "48h") + t.Setenv("JWT_REFRESH_TOKEN_SECRET", "test_value") + t.Setenv("JWT_REFRESH_TOKEN_EXPIRATION_TIME", "72h") + + t.Setenv("YORKIE_API_ADDR", "test_value") + t.Setenv("YORKIE_PROJECT_NAME", "test_value") + t.Setenv("YORKIE_PROJECT_SECRET_KEY", "test_value") + + t.Setenv("MONGO_CONNECTION_TIMEOUT", "10s") + t.Setenv("MONGO_CONNECTION_URI", "test_value") + t.Setenv("MONGO_PING_TIMEOUT", "3s") + t.Setenv("MONGO_DATABASE_NAME", "test_value") + + t.Setenv("STORAGE_PROVIDER", "minio") + t.Setenv("STORAGE_MINIO_BUCKET", "test_value") + t.Setenv("STORAGE_MINIO_ENDPOINT", "test_value") + t.Setenv("STORAGE_MINIO_ACCESS_KEY", "test_value") + t.Setenv("STORAGE_MINIO_SECRET_KEY", "test_value") + + t.Setenv("STORAGE_S3_BUCKET", "test_value") + t.Setenv("STORAGE_S3_REGION", "test_value") + t.Setenv("STORAGE_S3_ACCESS_KEY", "test_value") + t.Setenv("STORAGE_S3_SECRET_KEY", "test_value") + + cfg, err := config.LoadConfig("") + require.NoError(t, err, "LoadConfig should not fail with valid environment variables") + + assert.Equal(t, 3002, cfg.Server.Port, "server.port should reflect 'PORT' env var") + + require.NotNil(t, cfg.OAuth.Github, "GitHub OAuth config should not be nil") + assert.Equal(t, "test_value", cfg.OAuth.Github.ClientID) + assert.Equal(t, "test_value", cfg.OAuth.Github.ClientSecret) + assert.Equal(t, "test_value", cfg.OAuth.Github.CallbackURL) + assert.Equal(t, "test_value", cfg.OAuth.Github.AuthorizationURL) + assert.Equal(t, "test_value", cfg.OAuth.Github.TokenURL) + assert.Equal(t, "test_value", cfg.OAuth.Github.UserProfileURL) - t.Run("Environment override config file test", func(t *testing.T) { + assert.Equal(t, "test_value", cfg.JWT.AccessTokenSecret) + assert.Equal(t, 48*time.Hour, cfg.JWT.AccessTokenExpirationTime) + assert.Equal(t, "test_value", cfg.JWT.RefreshTokenSecret) + assert.Equal(t, 72*time.Hour, cfg.JWT.RefreshTokenExpirationTime) + + assert.Equal(t, "test_value", cfg.Yorkie.APIAddr) + assert.Equal(t, "test_value", cfg.Yorkie.ProjectName) + assert.Equal(t, "test_value", cfg.Yorkie.ProjectSecretKey) + + assert.Equal(t, 10*time.Second, cfg.Mongo.ConnectionTimeout) + assert.Equal(t, "test_value", cfg.Mongo.ConnectionURI) + assert.Equal(t, 3*time.Second, cfg.Mongo.PingTimeout) + assert.Equal(t, "test_value", cfg.Mongo.DatabaseName) + + assert.Equal(t, "minio", cfg.Storage.Provider, "storage.provider must be 'minio' or 's3'") + + require.NotNil(t, cfg.Storage.Minio, "Storage.Minio must not be nil if provider=minio") + assert.Equal(t, "test_value", cfg.Storage.Minio.Bucket) + assert.Equal(t, "test_value", cfg.Storage.Minio.Endpoint) + assert.Equal(t, "test_value", cfg.Storage.Minio.AccessKey) + assert.Equal(t, "test_value", cfg.Storage.Minio.SecretKey) + + require.NotNil(t, cfg.Storage.S3, "Storage.S3 struct can still exist") + assert.Equal(t, "test_value", cfg.Storage.S3.Bucket) + assert.Equal(t, "test_value", cfg.Storage.S3.Region) + assert.Equal(t, "test_value", cfg.Storage.S3.AccessKey) + assert.Equal(t, "test_value", cfg.Storage.S3.SecretKey) +} + +func TestLoadConfigFromFile(t *testing.T) { + t.Run("invalid file path test", func(t *testing.T) { + _, err := config.LoadConfig("invalidPath") + assert.Error(t, err) }) - t.Run("Config file override default values test", func(t *testing.T) { + t.Run("load config from file test", func(t *testing.T) { + const sampleYAML = ` +Server: + Port: 3001 + +OAuth: + Github: + ClientID: "config_client_id" + ClientSecret: "config_client_secret" + CallbackURL: "http://config.example.com/auth/login/github" + AuthorizationURL: "https://config.example.com/login/oauth/authorize" + TokenURL: "https://config.example.com/login/oauth/access_token" + UserProfileURL: "https://config.example.com/api/user" + +FrontendBaseURL: "http://config-frontend:5173" + +JWT: + AccessTokenSecret: "config_access_token_secret" + AccessTokenExpirationTime: "24h" + RefreshTokenSecret: "config_refresh_token_secret" + RefreshTokenExpirationTime: "168h" + +Yorkie: + APIAddr: "http://config-yorkie:8080" + ProjectName: "config_project" + ProjectSecretKey: "config_project_secret" + +Mongo: + ConnectionTimeout: "10s" + ConnectionURI: "mongodb://config-mongo:27017/codepair" + PingTimeout: "5s" + DatabaseName: "config_codepair" + +Storage: + Provider: "s3" + + Minio: + Bucket: "config-storage" + Endpoint: "http://config-minio:9000" + AccessKey: "config_minioadmin" + SecretKey: "config_minioadmin" + + S3: + Bucket: "default-storage" + Region: "us-east-1" + AccessKey: "aws_access_key" + SecretKey: "aws_secret_key" +` + + tmpDir := t.TempDir() + filePath := filepath.Join(tmpDir, "config.yaml") + + err := os.WriteFile(filePath, []byte(sampleYAML), 0644) + require.NoError(t, err, "Should be able to create a temporary config.yaml file") + + // 2) Call LoadConfig with the file path + cfg, err := config.LoadConfig(filePath) + require.NoError(t, err, "LoadConfig should not fail with a valid config.yaml file") + + // --- Server --- + assert.Equal(t, 3001, cfg.Server.Port, "Server.Port should be 3001") + + // --- OAuth (GitHub) --- + require.NotNil(t, cfg.OAuth.Github, "OAuth.Github should not be nil") + assert.Equal(t, "config_client_id", cfg.OAuth.Github.ClientID, "OAuth.Github.ClientID should match") + assert.Equal(t, "config_client_secret", cfg.OAuth.Github.ClientSecret, "OAuth.Github.ClientSecret should match") + assert.Equal(t, "http://config.example.com/auth/login/github", cfg.OAuth.Github.CallbackURL, "OAuth.Github.CallbackURL should match") + assert.Equal(t, "https://config.example.com/login/oauth/authorize", cfg.OAuth.Github.AuthorizationURL, "OAuth.Github.AuthorizationURL should match") + assert.Equal(t, "https://config.example.com/login/oauth/access_token", cfg.OAuth.Github.TokenURL, "OAuth.Github.TokenURL should match") + assert.Equal(t, "https://config.example.com/api/user", cfg.OAuth.Github.UserProfileURL, "OAuth.Github.UserProfileURL should match") + + // --- JWT --- + assert.Equal(t, "config_access_token_secret", cfg.JWT.AccessTokenSecret, "JWT.AccessTokenSecret should match") + assert.Equal(t, 24*time.Hour, cfg.JWT.AccessTokenExpirationTime, "JWT.AccessTokenExpirationTime should be 24h") + assert.Equal(t, "config_refresh_token_secret", cfg.JWT.RefreshTokenSecret, "JWT.RefreshTokenSecret should match") + assert.Equal(t, 168*time.Hour, cfg.JWT.RefreshTokenExpirationTime, "JWT.RefreshTokenExpirationTime should be 168h") + + // --- Yorkie --- + // The YAML key is APIAddr, but it maps to ApiAddr in the Config struct based on the mapstructure tag. + assert.Equal(t, "http://config-yorkie:8080", cfg.Yorkie.APIAddr, "Yorkie.ApiAddr should match") + assert.Equal(t, "config_project", cfg.Yorkie.ProjectName, "Yorkie.ProjectName should match") + assert.Equal(t, "config_project_secret", cfg.Yorkie.ProjectSecretKey, "Yorkie.ProjectSecretKey should match") + + // --- Mongo --- + assert.Equal(t, 10*time.Second, cfg.Mongo.ConnectionTimeout, "Mongo.ConnectionTimeout should be 10s") + assert.Equal(t, "mongodb://config-mongo:27017/codepair", cfg.Mongo.ConnectionURI, "Mongo.ConnectionURI should match") + assert.Equal(t, 5*time.Second, cfg.Mongo.PingTimeout, "Mongo.PingTimeout should be 5s") + assert.Equal(t, "config_codepair", cfg.Mongo.DatabaseName, "Mongo.DatabaseName should match") + + // --- Storage --- + assert.Equal(t, "s3", cfg.Storage.Provider, "Storage.Provider should be 's3'") + + // Minio + require.NotNil(t, cfg.Storage.Minio, "Storage.Minio should not be nil if the provider is 'minio'") + assert.Equal(t, "config-storage", cfg.Storage.Minio.Bucket, "Storage.Minio.Bucket should match") + assert.Equal(t, "http://config-minio:9000", cfg.Storage.Minio.Endpoint, "Storage.Minio.Endpoint should match") + assert.Equal(t, "config_minioadmin", cfg.Storage.Minio.AccessKey, "Storage.Minio.AccessKey should match") + assert.Equal(t, "config_minioadmin", cfg.Storage.Minio.SecretKey, "Storage.Minio.SecretKey should match") + + // S3 (Values should be applied as defined in YAML) + require.NotNil(t, cfg.Storage.S3, "Storage.S3 struct should not be nil") + assert.Equal(t, "default-storage", cfg.Storage.S3.Bucket, "Storage.S3.Bucket should match") + assert.Equal(t, "us-east-1", cfg.Storage.S3.Region, "Storage.S3.Region should match") + assert.Equal(t, "aws_access_key", cfg.Storage.S3.AccessKey, "Storage.S3.AccessKey should match") + assert.Equal(t, "aws_secret_key", cfg.Storage.S3.SecretKey, "Storage.S3.SecretKey should match") }) } + +func TestConfigWithDefaultValues(t *testing.T) { + // minimalYAML contains only the required values; all other fields should be set to their defaults. + const minimalYAML = ` +OAuth: + Github: + ClientID: "provided_client_id" + ClientSecret: "provided_client_secret" +` + + // Write the minimal YAML content to a temporary file. + tmpDir := t.TempDir() + filePath := filepath.Join(tmpDir, "minimal_config.yaml") + err := os.WriteFile(filePath, []byte(minimalYAML), 0644) + require.NoError(t, err, "Should be able to create a temporary minimal config file") + + // Call LoadConfig with the minimal config file. + cfg, err := config.LoadConfig(filePath) + require.NoError(t, err, "LoadConfig should succeed with a minimal config file") + + // --- Server --- + // Since no Server section was provided, ensureDefaultValue should set the default port. + assert.Equal(t, config.DefaultServerPort, cfg.Server.Port, "Server.Port should default to DefaultServerPort") + + // --- OAuth (GitHub) --- + require.NotNil(t, cfg.OAuth.Github, "OAuth.Github should not be nil") + // Provided values. + assert.Equal(t, "provided_client_id", cfg.OAuth.Github.ClientID, "OAuth.Github.ClientID should match provided value") + assert.Equal(t, "provided_client_secret", cfg.OAuth.Github.ClientSecret, "OAuth.Github.ClientSecret should match provided value") + // Default values should be applied for these fields. + assert.Equal(t, config.DefaultGitHubCallbackURL, cfg.OAuth.Github.CallbackURL, "OAuth.Github.CallbackURL should default") + assert.Equal(t, config.DefaultGitHubAuthorizationURL, cfg.OAuth.Github.AuthorizationURL, "OAuth.Github.AuthorizationURL should default") + assert.Equal(t, config.DefaultGitHubTokenURL, cfg.OAuth.Github.TokenURL, "OAuth.Github.TokenURL should default") + assert.Equal(t, config.DefaultGitHubUserProfileURL, cfg.OAuth.Github.UserProfileURL, "OAuth.Github.UserProfileURL should default") + + // --- JWT --- + // Since no JWT section was provided, defaults should be applied. + assert.Equal(t, config.DefaultAccessTokenSecret, cfg.JWT.AccessTokenSecret, "JWT.AccessTokenSecret should default") + assert.Equal(t, config.DefaultAccessTokenExpirationTime, cfg.JWT.AccessTokenExpirationTime, "JWT.AccessTokenExpirationTime should default") + assert.Equal(t, config.DefaultRefreshTokenSecret, cfg.JWT.RefreshTokenSecret, "JWT.RefreshTokenSecret should default") + assert.Equal(t, config.DefaultRefreshTokenExpirationTime, cfg.JWT.RefreshTokenExpirationTime, "JWT.RefreshTokenExpirationTime should default") + + // --- Mongo --- + // With no Mongo section provided, the defaults should be used. + assert.Equal(t, config.DefaultConnectionTimeout, cfg.Mongo.ConnectionTimeout, "Mongo.ConnectionTimeout should default") + assert.Equal(t, config.DefaultMongoConnectionURI, cfg.Mongo.ConnectionURI, "Mongo.ConnectionURI should default") + assert.Equal(t, config.DefaultPingTimeout, cfg.Mongo.PingTimeout, "Mongo.PingTimeout should default") + assert.Equal(t, config.DefaultMongoDatabaseName, cfg.Mongo.DatabaseName, "Mongo.DatabaseName should default") + + // --- Storage --- + // Because no Storage.Provider was provided, ensureDefaultValue should set it to the default provider. + assert.Equal(t, config.DefaultStorageProvider, cfg.Storage.Provider, "Storage.Provider should default to DefaultStorageProvider") + // For provider "minio", an empty block was provided so that Minio is not nil. + require.NotNil(t, cfg.Storage.Minio, "Storage.Minio should not be nil when provider is 'minio'") + assert.Equal(t, config.DefaultMinioBucket, cfg.Storage.Minio.Bucket, "Storage.Minio.Bucket should default") + assert.Equal(t, config.DefaultMinioEndpoint, cfg.Storage.Minio.Endpoint, "Storage.Minio.Endpoint should default") + assert.Equal(t, config.DefaultMinioAccessKey, cfg.Storage.Minio.AccessKey, "Storage.Minio.AccessKey should default") + assert.Equal(t, config.DefaultMinioSecretKey, cfg.Storage.Minio.SecretKey, "Storage.Minio.SecretKey should default") + // S3 should remain nil if not provided. + assert.Nil(t, cfg.Storage.S3, "Storage.S3 should be nil by default") +} From 36964b9fb4a4e7c242777f739fd655cf6193ef8a Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 20:53:41 +0900 Subject: [PATCH 13/26] Refactor test code --- backend-go/internal/config/config_test.go | 239 +++++++++++----------- 1 file changed, 124 insertions(+), 115 deletions(-) diff --git a/backend-go/internal/config/config_test.go b/backend-go/internal/config/config_test.go index 42b07a38..5283505e 100644 --- a/backend-go/internal/config/config_test.go +++ b/backend-go/internal/config/config_test.go @@ -12,48 +12,70 @@ import ( "github.com/yorkie-team/codepair/backend/internal/config" ) +// setEnvVars is a helper to set multiple environment variables. +func setEnvVars(t *testing.T, envs map[string]string) { + for key, value := range envs { + t.Setenv(key, value) + } +} + +// writeTempConfigFile writes the given content to a temporary file and returns its path. +func writeTempConfigFile(t *testing.T, filename, content string) string { + tmpDir := t.TempDir() + filePath := filepath.Join(tmpDir, filename) + err := os.WriteFile(filePath, []byte(content), 0644) + require.NoError(t, err, "Should be able to create a temporary config file") + return filePath +} + func TestConfigWithEnvVars(t *testing.T) { - t.Setenv("SERVER_PORT", "3002") - - t.Setenv("OAUTH_GITHUB_CLIENT_ID", "test_value") - t.Setenv("OAUTH_GITHUB_CLIENT_SECRET", "test_value") - t.Setenv("OAUTH_GITHUB_CALLBACK_URL", "test_value") - t.Setenv("OAUTH_GITHUB_AUTHORIZATION_URL", "test_value") - t.Setenv("OAUTH_GITHUB_TOKEN_URL", "test_value") - t.Setenv("OAUTH_GITHUB_USER_PROFILE_URL", "test_value") - - t.Setenv("AUTH_FRONTEND_BASE_URL", "test_value") - - t.Setenv("JWT_ACCESS_TOKEN_SECRET", "test_value") - t.Setenv("JWT_ACCESS_TOKEN_EXPIRATION_TIME", "48h") - t.Setenv("JWT_REFRESH_TOKEN_SECRET", "test_value") - t.Setenv("JWT_REFRESH_TOKEN_EXPIRATION_TIME", "72h") - - t.Setenv("YORKIE_API_ADDR", "test_value") - t.Setenv("YORKIE_PROJECT_NAME", "test_value") - t.Setenv("YORKIE_PROJECT_SECRET_KEY", "test_value") - - t.Setenv("MONGO_CONNECTION_TIMEOUT", "10s") - t.Setenv("MONGO_CONNECTION_URI", "test_value") - t.Setenv("MONGO_PING_TIMEOUT", "3s") - t.Setenv("MONGO_DATABASE_NAME", "test_value") - - t.Setenv("STORAGE_PROVIDER", "minio") - t.Setenv("STORAGE_MINIO_BUCKET", "test_value") - t.Setenv("STORAGE_MINIO_ENDPOINT", "test_value") - t.Setenv("STORAGE_MINIO_ACCESS_KEY", "test_value") - t.Setenv("STORAGE_MINIO_SECRET_KEY", "test_value") - - t.Setenv("STORAGE_S3_BUCKET", "test_value") - t.Setenv("STORAGE_S3_REGION", "test_value") - t.Setenv("STORAGE_S3_ACCESS_KEY", "test_value") - t.Setenv("STORAGE_S3_SECRET_KEY", "test_value") + // Define environment variables in a map. + envs := map[string]string{ + "SERVER_PORT": "3002", + + "OAUTH_GITHUB_CLIENT_ID": "test_value", + "OAUTH_GITHUB_CLIENT_SECRET": "test_value", + "OAUTH_GITHUB_CALLBACK_URL": "test_value", + "OAUTH_GITHUB_AUTHORIZATION_URL": "test_value", + "OAUTH_GITHUB_TOKEN_URL": "test_value", + "OAUTH_GITHUB_USER_PROFILE_URL": "test_value", + + "AUTH_FRONTEND_BASE_URL": "test_value", + + "JWT_ACCESS_TOKEN_SECRET": "test_value", + "JWT_ACCESS_TOKEN_EXPIRATION_TIME": "48h", + "JWT_REFRESH_TOKEN_SECRET": "test_value", + "JWT_REFRESH_TOKEN_EXPIRATION_TIME": "72h", + + "YORKIE_API_ADDR": "test_value", + "YORKIE_PROJECT_NAME": "test_value", + "YORKIE_PROJECT_SECRET_KEY": "test_value", + + "MONGO_CONNECTION_TIMEOUT": "10s", + "MONGO_CONNECTION_URI": "test_value", + "MONGO_PING_TIMEOUT": "3s", + "MONGO_DATABASE_NAME": "test_value", + + "STORAGE_PROVIDER": "minio", + "STORAGE_MINIO_BUCKET": "test_value", + "STORAGE_MINIO_ENDPOINT": "test_value", + "STORAGE_MINIO_ACCESS_KEY": "test_value", + "STORAGE_MINIO_SECRET_KEY": "test_value", + + "STORAGE_S3_BUCKET": "test_value", + "STORAGE_S3_REGION": "test_value", + "STORAGE_S3_ACCESS_KEY": "test_value", + "STORAGE_S3_SECRET_KEY": "test_value", + } + setEnvVars(t, envs) cfg, err := config.LoadConfig("") require.NoError(t, err, "LoadConfig should not fail with valid environment variables") - assert.Equal(t, 3002, cfg.Server.Port, "server.port should reflect 'PORT' env var") + // --- Server --- + assert.Equal(t, 3002, cfg.Server.Port, "Server.Port should reflect 'SERVER_PORT' env var") + // --- OAuth (GitHub) --- require.NotNil(t, cfg.OAuth.Github, "GitHub OAuth config should not be nil") assert.Equal(t, "test_value", cfg.OAuth.Github.ClientID) assert.Equal(t, "test_value", cfg.OAuth.Github.ClientSecret) @@ -62,28 +84,34 @@ func TestConfigWithEnvVars(t *testing.T) { assert.Equal(t, "test_value", cfg.OAuth.Github.TokenURL) assert.Equal(t, "test_value", cfg.OAuth.Github.UserProfileURL) + // --- JWT --- assert.Equal(t, "test_value", cfg.JWT.AccessTokenSecret) assert.Equal(t, 48*time.Hour, cfg.JWT.AccessTokenExpirationTime) assert.Equal(t, "test_value", cfg.JWT.RefreshTokenSecret) assert.Equal(t, 72*time.Hour, cfg.JWT.RefreshTokenExpirationTime) + // --- Yorkie --- assert.Equal(t, "test_value", cfg.Yorkie.APIAddr) assert.Equal(t, "test_value", cfg.Yorkie.ProjectName) assert.Equal(t, "test_value", cfg.Yorkie.ProjectSecretKey) + // --- Mongo --- assert.Equal(t, 10*time.Second, cfg.Mongo.ConnectionTimeout) assert.Equal(t, "test_value", cfg.Mongo.ConnectionURI) assert.Equal(t, 3*time.Second, cfg.Mongo.PingTimeout) assert.Equal(t, "test_value", cfg.Mongo.DatabaseName) - assert.Equal(t, "minio", cfg.Storage.Provider, "storage.provider must be 'minio' or 's3'") + // --- Storage --- + assert.Equal(t, "minio", cfg.Storage.Provider, "Storage.Provider must be 'minio' or 's3'") + // For Minio: ensure the block is not nil and values match. require.NotNil(t, cfg.Storage.Minio, "Storage.Minio must not be nil if provider=minio") assert.Equal(t, "test_value", cfg.Storage.Minio.Bucket) assert.Equal(t, "test_value", cfg.Storage.Minio.Endpoint) assert.Equal(t, "test_value", cfg.Storage.Minio.AccessKey) assert.Equal(t, "test_value", cfg.Storage.Minio.SecretKey) + // For S3: the struct exists though values may be default or provided. require.NotNil(t, cfg.Storage.S3, "Storage.S3 struct can still exist") assert.Equal(t, "test_value", cfg.Storage.S3.Bucket) assert.Equal(t, "test_value", cfg.Storage.S3.Region) @@ -92,12 +120,12 @@ func TestConfigWithEnvVars(t *testing.T) { } func TestLoadConfigFromFile(t *testing.T) { - t.Run("invalid file path test", func(t *testing.T) { + t.Run("invalid file path", func(t *testing.T) { _, err := config.LoadConfig("invalidPath") assert.Error(t, err) }) - t.Run("load config from file test", func(t *testing.T) { + t.Run("load config from file", func(t *testing.T) { const sampleYAML = ` Server: Port: 3001 @@ -145,14 +173,7 @@ Storage: AccessKey: "aws_access_key" SecretKey: "aws_secret_key" ` - - tmpDir := t.TempDir() - filePath := filepath.Join(tmpDir, "config.yaml") - - err := os.WriteFile(filePath, []byte(sampleYAML), 0644) - require.NoError(t, err, "Should be able to create a temporary config.yaml file") - - // 2) Call LoadConfig with the file path + filePath := writeTempConfigFile(t, "config.yaml", sampleYAML) cfg, err := config.LoadConfig(filePath) require.NoError(t, err, "LoadConfig should not fail with a valid config.yaml file") @@ -161,52 +182,50 @@ Storage: // --- OAuth (GitHub) --- require.NotNil(t, cfg.OAuth.Github, "OAuth.Github should not be nil") - assert.Equal(t, "config_client_id", cfg.OAuth.Github.ClientID, "OAuth.Github.ClientID should match") - assert.Equal(t, "config_client_secret", cfg.OAuth.Github.ClientSecret, "OAuth.Github.ClientSecret should match") - assert.Equal(t, "http://config.example.com/auth/login/github", cfg.OAuth.Github.CallbackURL, "OAuth.Github.CallbackURL should match") - assert.Equal(t, "https://config.example.com/login/oauth/authorize", cfg.OAuth.Github.AuthorizationURL, "OAuth.Github.AuthorizationURL should match") - assert.Equal(t, "https://config.example.com/login/oauth/access_token", cfg.OAuth.Github.TokenURL, "OAuth.Github.TokenURL should match") - assert.Equal(t, "https://config.example.com/api/user", cfg.OAuth.Github.UserProfileURL, "OAuth.Github.UserProfileURL should match") + assert.Equal(t, "config_client_id", cfg.OAuth.Github.ClientID) + assert.Equal(t, "config_client_secret", cfg.OAuth.Github.ClientSecret) + assert.Equal(t, "http://config.example.com/auth/login/github", cfg.OAuth.Github.CallbackURL) + assert.Equal(t, "https://config.example.com/login/oauth/authorize", cfg.OAuth.Github.AuthorizationURL) + assert.Equal(t, "https://config.example.com/login/oauth/access_token", cfg.OAuth.Github.TokenURL) + assert.Equal(t, "https://config.example.com/api/user", cfg.OAuth.Github.UserProfileURL) // --- JWT --- - assert.Equal(t, "config_access_token_secret", cfg.JWT.AccessTokenSecret, "JWT.AccessTokenSecret should match") - assert.Equal(t, 24*time.Hour, cfg.JWT.AccessTokenExpirationTime, "JWT.AccessTokenExpirationTime should be 24h") - assert.Equal(t, "config_refresh_token_secret", cfg.JWT.RefreshTokenSecret, "JWT.RefreshTokenSecret should match") - assert.Equal(t, 168*time.Hour, cfg.JWT.RefreshTokenExpirationTime, "JWT.RefreshTokenExpirationTime should be 168h") + assert.Equal(t, "config_access_token_secret", cfg.JWT.AccessTokenSecret) + assert.Equal(t, 24*time.Hour, cfg.JWT.AccessTokenExpirationTime) + assert.Equal(t, "config_refresh_token_secret", cfg.JWT.RefreshTokenSecret) + assert.Equal(t, 168*time.Hour, cfg.JWT.RefreshTokenExpirationTime) // --- Yorkie --- - // The YAML key is APIAddr, but it maps to ApiAddr in the Config struct based on the mapstructure tag. - assert.Equal(t, "http://config-yorkie:8080", cfg.Yorkie.APIAddr, "Yorkie.ApiAddr should match") - assert.Equal(t, "config_project", cfg.Yorkie.ProjectName, "Yorkie.ProjectName should match") - assert.Equal(t, "config_project_secret", cfg.Yorkie.ProjectSecretKey, "Yorkie.ProjectSecretKey should match") + assert.Equal(t, "http://config-yorkie:8080", cfg.Yorkie.APIAddr) + assert.Equal(t, "config_project", cfg.Yorkie.ProjectName) + assert.Equal(t, "config_project_secret", cfg.Yorkie.ProjectSecretKey) // --- Mongo --- - assert.Equal(t, 10*time.Second, cfg.Mongo.ConnectionTimeout, "Mongo.ConnectionTimeout should be 10s") - assert.Equal(t, "mongodb://config-mongo:27017/codepair", cfg.Mongo.ConnectionURI, "Mongo.ConnectionURI should match") - assert.Equal(t, 5*time.Second, cfg.Mongo.PingTimeout, "Mongo.PingTimeout should be 5s") - assert.Equal(t, "config_codepair", cfg.Mongo.DatabaseName, "Mongo.DatabaseName should match") + assert.Equal(t, 10*time.Second, cfg.Mongo.ConnectionTimeout) + assert.Equal(t, "mongodb://config-mongo:27017/codepair", cfg.Mongo.ConnectionURI) + assert.Equal(t, 5*time.Second, cfg.Mongo.PingTimeout) + assert.Equal(t, "config_codepair", cfg.Mongo.DatabaseName) // --- Storage --- assert.Equal(t, "s3", cfg.Storage.Provider, "Storage.Provider should be 's3'") // Minio require.NotNil(t, cfg.Storage.Minio, "Storage.Minio should not be nil if the provider is 'minio'") - assert.Equal(t, "config-storage", cfg.Storage.Minio.Bucket, "Storage.Minio.Bucket should match") - assert.Equal(t, "http://config-minio:9000", cfg.Storage.Minio.Endpoint, "Storage.Minio.Endpoint should match") - assert.Equal(t, "config_minioadmin", cfg.Storage.Minio.AccessKey, "Storage.Minio.AccessKey should match") - assert.Equal(t, "config_minioadmin", cfg.Storage.Minio.SecretKey, "Storage.Minio.SecretKey should match") + assert.Equal(t, "config-storage", cfg.Storage.Minio.Bucket) + assert.Equal(t, "http://config-minio:9000", cfg.Storage.Minio.Endpoint) + assert.Equal(t, "config_minioadmin", cfg.Storage.Minio.AccessKey) + assert.Equal(t, "config_minioadmin", cfg.Storage.Minio.SecretKey) - // S3 (Values should be applied as defined in YAML) + // S3 require.NotNil(t, cfg.Storage.S3, "Storage.S3 struct should not be nil") - assert.Equal(t, "default-storage", cfg.Storage.S3.Bucket, "Storage.S3.Bucket should match") - assert.Equal(t, "us-east-1", cfg.Storage.S3.Region, "Storage.S3.Region should match") - assert.Equal(t, "aws_access_key", cfg.Storage.S3.AccessKey, "Storage.S3.AccessKey should match") - assert.Equal(t, "aws_secret_key", cfg.Storage.S3.SecretKey, "Storage.S3.SecretKey should match") + assert.Equal(t, "default-storage", cfg.Storage.S3.Bucket) + assert.Equal(t, "us-east-1", cfg.Storage.S3.Region) + assert.Equal(t, "aws_access_key", cfg.Storage.S3.AccessKey) + assert.Equal(t, "aws_secret_key", cfg.Storage.S3.SecretKey) }) } func TestConfigWithDefaultValues(t *testing.T) { - // minimalYAML contains only the required values; all other fields should be set to their defaults. const minimalYAML = ` OAuth: Github: @@ -214,54 +233,44 @@ OAuth: ClientSecret: "provided_client_secret" ` - // Write the minimal YAML content to a temporary file. - tmpDir := t.TempDir() - filePath := filepath.Join(tmpDir, "minimal_config.yaml") - err := os.WriteFile(filePath, []byte(minimalYAML), 0644) - require.NoError(t, err, "Should be able to create a temporary minimal config file") - - // Call LoadConfig with the minimal config file. + filePath := writeTempConfigFile(t, "minimal_config.yaml", minimalYAML) cfg, err := config.LoadConfig(filePath) require.NoError(t, err, "LoadConfig should succeed with a minimal config file") - // --- Server --- - // Since no Server section was provided, ensureDefaultValue should set the default port. + // --- Server defaults --- assert.Equal(t, config.DefaultServerPort, cfg.Server.Port, "Server.Port should default to DefaultServerPort") - // --- OAuth (GitHub) --- + // --- OAuth (GitHub) defaults and provided values --- require.NotNil(t, cfg.OAuth.Github, "OAuth.Github should not be nil") // Provided values. - assert.Equal(t, "provided_client_id", cfg.OAuth.Github.ClientID, "OAuth.Github.ClientID should match provided value") - assert.Equal(t, "provided_client_secret", cfg.OAuth.Github.ClientSecret, "OAuth.Github.ClientSecret should match provided value") - // Default values should be applied for these fields. - assert.Equal(t, config.DefaultGitHubCallbackURL, cfg.OAuth.Github.CallbackURL, "OAuth.Github.CallbackURL should default") - assert.Equal(t, config.DefaultGitHubAuthorizationURL, cfg.OAuth.Github.AuthorizationURL, "OAuth.Github.AuthorizationURL should default") - assert.Equal(t, config.DefaultGitHubTokenURL, cfg.OAuth.Github.TokenURL, "OAuth.Github.TokenURL should default") - assert.Equal(t, config.DefaultGitHubUserProfileURL, cfg.OAuth.Github.UserProfileURL, "OAuth.Github.UserProfileURL should default") - - // --- JWT --- - // Since no JWT section was provided, defaults should be applied. - assert.Equal(t, config.DefaultAccessTokenSecret, cfg.JWT.AccessTokenSecret, "JWT.AccessTokenSecret should default") - assert.Equal(t, config.DefaultAccessTokenExpirationTime, cfg.JWT.AccessTokenExpirationTime, "JWT.AccessTokenExpirationTime should default") - assert.Equal(t, config.DefaultRefreshTokenSecret, cfg.JWT.RefreshTokenSecret, "JWT.RefreshTokenSecret should default") - assert.Equal(t, config.DefaultRefreshTokenExpirationTime, cfg.JWT.RefreshTokenExpirationTime, "JWT.RefreshTokenExpirationTime should default") - - // --- Mongo --- - // With no Mongo section provided, the defaults should be used. - assert.Equal(t, config.DefaultConnectionTimeout, cfg.Mongo.ConnectionTimeout, "Mongo.ConnectionTimeout should default") - assert.Equal(t, config.DefaultMongoConnectionURI, cfg.Mongo.ConnectionURI, "Mongo.ConnectionURI should default") - assert.Equal(t, config.DefaultPingTimeout, cfg.Mongo.PingTimeout, "Mongo.PingTimeout should default") - assert.Equal(t, config.DefaultMongoDatabaseName, cfg.Mongo.DatabaseName, "Mongo.DatabaseName should default") - - // --- Storage --- - // Because no Storage.Provider was provided, ensureDefaultValue should set it to the default provider. + assert.Equal(t, "provided_client_id", cfg.OAuth.Github.ClientID) + assert.Equal(t, "provided_client_secret", cfg.OAuth.Github.ClientSecret) + // Default values. + assert.Equal(t, config.DefaultGitHubCallbackURL, cfg.OAuth.Github.CallbackURL) + assert.Equal(t, config.DefaultGitHubAuthorizationURL, cfg.OAuth.Github.AuthorizationURL) + assert.Equal(t, config.DefaultGitHubTokenURL, cfg.OAuth.Github.TokenURL) + assert.Equal(t, config.DefaultGitHubUserProfileURL, cfg.OAuth.Github.UserProfileURL) + + // --- JWT defaults --- + assert.Equal(t, config.DefaultAccessTokenSecret, cfg.JWT.AccessTokenSecret) + assert.Equal(t, config.DefaultAccessTokenExpirationTime, cfg.JWT.AccessTokenExpirationTime) + assert.Equal(t, config.DefaultRefreshTokenSecret, cfg.JWT.RefreshTokenSecret) + assert.Equal(t, config.DefaultRefreshTokenExpirationTime, cfg.JWT.RefreshTokenExpirationTime) + + // --- Mongo defaults --- + assert.Equal(t, config.DefaultConnectionTimeout, cfg.Mongo.ConnectionTimeout) + assert.Equal(t, config.DefaultMongoConnectionURI, cfg.Mongo.ConnectionURI) + assert.Equal(t, config.DefaultPingTimeout, cfg.Mongo.PingTimeout) + assert.Equal(t, config.DefaultMongoDatabaseName, cfg.Mongo.DatabaseName) + + // --- Storage defaults --- assert.Equal(t, config.DefaultStorageProvider, cfg.Storage.Provider, "Storage.Provider should default to DefaultStorageProvider") - // For provider "minio", an empty block was provided so that Minio is not nil. + // When provider is minio, ensure the Minio block is set with defaults. require.NotNil(t, cfg.Storage.Minio, "Storage.Minio should not be nil when provider is 'minio'") - assert.Equal(t, config.DefaultMinioBucket, cfg.Storage.Minio.Bucket, "Storage.Minio.Bucket should default") - assert.Equal(t, config.DefaultMinioEndpoint, cfg.Storage.Minio.Endpoint, "Storage.Minio.Endpoint should default") - assert.Equal(t, config.DefaultMinioAccessKey, cfg.Storage.Minio.AccessKey, "Storage.Minio.AccessKey should default") - assert.Equal(t, config.DefaultMinioSecretKey, cfg.Storage.Minio.SecretKey, "Storage.Minio.SecretKey should default") - // S3 should remain nil if not provided. + assert.Equal(t, config.DefaultMinioBucket, cfg.Storage.Minio.Bucket) + assert.Equal(t, config.DefaultMinioEndpoint, cfg.Storage.Minio.Endpoint) + assert.Equal(t, config.DefaultMinioAccessKey, cfg.Storage.Minio.AccessKey) + assert.Equal(t, config.DefaultMinioSecretKey, cfg.Storage.Minio.SecretKey) + // S3 should be nil if not provided. assert.Nil(t, cfg.Storage.S3, "Storage.S3 should be nil by default") } From 232b463e6d0e91830c355cf72efbdcdc5b59bd71 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 2 Feb 2025 21:15:50 +0900 Subject: [PATCH 14/26] fix path --- backend-go/internal/config/config_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend-go/internal/config/config_test.go b/backend-go/internal/config/config_test.go index 5283505e..13d95648 100644 --- a/backend-go/internal/config/config_test.go +++ b/backend-go/internal/config/config_test.go @@ -134,7 +134,7 @@ OAuth: Github: ClientID: "config_client_id" ClientSecret: "config_client_secret" - CallbackURL: "http://config.example.com/auth/login/github" + CallbackURL: "https://config.example.com/auth/login/github" AuthorizationURL: "https://config.example.com/login/oauth/authorize" TokenURL: "https://config.example.com/login/oauth/access_token" UserProfileURL: "https://config.example.com/api/user" @@ -184,7 +184,7 @@ Storage: require.NotNil(t, cfg.OAuth.Github, "OAuth.Github should not be nil") assert.Equal(t, "config_client_id", cfg.OAuth.Github.ClientID) assert.Equal(t, "config_client_secret", cfg.OAuth.Github.ClientSecret) - assert.Equal(t, "http://config.example.com/auth/login/github", cfg.OAuth.Github.CallbackURL) + assert.Equal(t, "https://config.example.com/auth/login/github", cfg.OAuth.Github.CallbackURL) assert.Equal(t, "https://config.example.com/login/oauth/authorize", cfg.OAuth.Github.AuthorizationURL) assert.Equal(t, "https://config.example.com/login/oauth/access_token", cfg.OAuth.Github.TokenURL) assert.Equal(t, "https://config.example.com/api/user", cfg.OAuth.Github.UserProfileURL) From a918969d15440d8835fc765b945394c6d1effe2d Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 3 Feb 2025 11:54:07 +0900 Subject: [PATCH 15/26] fix test comments --- backend-go/internal/config/config_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend-go/internal/config/config_test.go b/backend-go/internal/config/config_test.go index 13d95648..1e7513a1 100644 --- a/backend-go/internal/config/config_test.go +++ b/backend-go/internal/config/config_test.go @@ -23,7 +23,7 @@ func setEnvVars(t *testing.T, envs map[string]string) { func writeTempConfigFile(t *testing.T, filename, content string) string { tmpDir := t.TempDir() filePath := filepath.Join(tmpDir, filename) - err := os.WriteFile(filePath, []byte(content), 0644) + err := os.WriteFile(filePath, []byte(content), 0600) require.NoError(t, err, "Should be able to create a temporary config file") return filePath } @@ -104,6 +104,7 @@ func TestConfigWithEnvVars(t *testing.T) { // --- Storage --- assert.Equal(t, "minio", cfg.Storage.Provider, "Storage.Provider must be 'minio' or 's3'") + // --- Minio --- // For Minio: ensure the block is not nil and values match. require.NotNil(t, cfg.Storage.Minio, "Storage.Minio must not be nil if provider=minio") assert.Equal(t, "test_value", cfg.Storage.Minio.Bucket) @@ -111,6 +112,7 @@ func TestConfigWithEnvVars(t *testing.T) { assert.Equal(t, "test_value", cfg.Storage.Minio.AccessKey) assert.Equal(t, "test_value", cfg.Storage.Minio.SecretKey) + // --- S3 --- // For S3: the struct exists though values may be default or provided. require.NotNil(t, cfg.Storage.S3, "Storage.S3 struct can still exist") assert.Equal(t, "test_value", cfg.Storage.S3.Bucket) @@ -264,7 +266,7 @@ OAuth: assert.Equal(t, config.DefaultMongoDatabaseName, cfg.Mongo.DatabaseName) // --- Storage defaults --- - assert.Equal(t, config.DefaultStorageProvider, cfg.Storage.Provider, "Storage.Provider should default to DefaultStorageProvider") + assert.Equal(t, config.DefaultStorageProvider, cfg.Storage.Provider, "Default storage provider is minio") // When provider is minio, ensure the Minio block is set with defaults. require.NotNil(t, cfg.Storage.Minio, "Storage.Minio should not be nil when provider is 'minio'") assert.Equal(t, config.DefaultMinioBucket, cfg.Storage.Minio.Bucket) From 9b1220d4d4aa324cc8853656006250e38f614915 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 3 Feb 2025 11:55:07 +0900 Subject: [PATCH 16/26] lint --- backend-go/cmd/codepair/main.go | 1 + backend-go/internal/config/config.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/backend-go/cmd/codepair/main.go b/backend-go/cmd/codepair/main.go index 57839bfa..18d612e0 100644 --- a/backend-go/cmd/codepair/main.go +++ b/backend-go/cmd/codepair/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "github.com/labstack/echo/v4" "github.com/labstack/gommon/log" "github.com/yorkie-team/codepair/backend/internal/config" diff --git a/backend-go/internal/config/config.go b/backend-go/internal/config/config.go index 421023c8..11bb36e0 100644 --- a/backend-go/internal/config/config.go +++ b/backend-go/internal/config/config.go @@ -3,6 +3,7 @@ package config import ( "errors" "fmt" + "github.com/spf13/viper" ) @@ -69,7 +70,7 @@ func readConfigFile(filePath string) error { } return nil } - return err + return fmt.Errorf("failed to read config file: %w", err) } return nil From ec9d3e1853275f67e8cfa47e53ae0f603ce3c018 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 3 Feb 2025 13:09:15 +0900 Subject: [PATCH 17/26] go mod tidy --- backend-go/go.mod | 27 ++++++++++++++++++-- backend-go/go.sum | 64 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/backend-go/go.mod b/backend-go/go.mod index 5b7f61ba..f9e28428 100644 --- a/backend-go/go.mod +++ b/backend-go/go.mod @@ -2,16 +2,39 @@ module github.com/yorkie-team/codepair/backend go 1.21 -require github.com/labstack/echo/v4 v4.13.3 +require ( + github.com/labstack/echo/v4 v4.13.3 + github.com/labstack/gommon v0.4.2 + github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.10.0 +) require ( - github.com/labstack/gommon v0.4.2 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.31.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/backend-go/go.sum b/backend-go/go.sum index a37c9e39..21e407ee 100644 --- a/backend-go/go.sum +++ b/backend-go/go.sum @@ -1,24 +1,78 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -27,5 +81,11 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From f50da7fe829ed165f0b46ef65f500d183bf97276 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 3 Feb 2025 13:50:05 +0900 Subject: [PATCH 18/26] apply code rabbit's comments --- backend-go/config.yaml | 10 ++++++---- backend-go/internal/config/config_test.go | 5 ----- backend-go/internal/config/jwt.go | 15 ++++++--------- backend-go/internal/config/oauth.go | 6 +----- backend-go/internal/config/storage.go | 12 ++---------- 5 files changed, 15 insertions(+), 33 deletions(-) diff --git a/backend-go/config.yaml b/backend-go/config.yaml index 9c66fb3b..3e89c6b7 100644 --- a/backend-go/config.yaml +++ b/backend-go/config.yaml @@ -3,8 +3,9 @@ Server: OAuth: Github: - ClientID: "your_client_id" - ClientSecret: "your_client_secret" + # WARNING: Replace these values with your GitHub OAuth credentials + ClientID: "" + ClientSecret: "" CallbackURL: "http://localhost:3000/auth/login/github" AuthorizationURL: "https://github.com/login/oauth/authorize" TokenURL: "https://github.com/login/oauth/access_token" @@ -13,9 +14,10 @@ OAuth: FrontendBaseURL: "http://localhost:5173" JWT: - AccessTokenSecret: "your_access_token_secret" + # WARNING: Use strong, unique secrets in production + AccessTokenSecret: "" AccessTokenExpirationTime: "24h" - RefreshTokenSecret: "your_refresh_token_secret" + RefreshTokenSecret: "" RefreshTokenExpirationTime: "168h" Yorkie: diff --git a/backend-go/internal/config/config_test.go b/backend-go/internal/config/config_test.go index 1e7513a1..08239cfa 100644 --- a/backend-go/internal/config/config_test.go +++ b/backend-go/internal/config/config_test.go @@ -250,13 +250,10 @@ OAuth: // Default values. assert.Equal(t, config.DefaultGitHubCallbackURL, cfg.OAuth.Github.CallbackURL) assert.Equal(t, config.DefaultGitHubAuthorizationURL, cfg.OAuth.Github.AuthorizationURL) - assert.Equal(t, config.DefaultGitHubTokenURL, cfg.OAuth.Github.TokenURL) assert.Equal(t, config.DefaultGitHubUserProfileURL, cfg.OAuth.Github.UserProfileURL) // --- JWT defaults --- - assert.Equal(t, config.DefaultAccessTokenSecret, cfg.JWT.AccessTokenSecret) assert.Equal(t, config.DefaultAccessTokenExpirationTime, cfg.JWT.AccessTokenExpirationTime) - assert.Equal(t, config.DefaultRefreshTokenSecret, cfg.JWT.RefreshTokenSecret) assert.Equal(t, config.DefaultRefreshTokenExpirationTime, cfg.JWT.RefreshTokenExpirationTime) // --- Mongo defaults --- @@ -271,8 +268,6 @@ OAuth: require.NotNil(t, cfg.Storage.Minio, "Storage.Minio should not be nil when provider is 'minio'") assert.Equal(t, config.DefaultMinioBucket, cfg.Storage.Minio.Bucket) assert.Equal(t, config.DefaultMinioEndpoint, cfg.Storage.Minio.Endpoint) - assert.Equal(t, config.DefaultMinioAccessKey, cfg.Storage.Minio.AccessKey) - assert.Equal(t, config.DefaultMinioSecretKey, cfg.Storage.Minio.SecretKey) // S3 should be nil if not provided. assert.Nil(t, cfg.Storage.S3, "Storage.S3 should be nil by default") } diff --git a/backend-go/internal/config/jwt.go b/backend-go/internal/config/jwt.go index 99943392..9158c292 100644 --- a/backend-go/internal/config/jwt.go +++ b/backend-go/internal/config/jwt.go @@ -6,9 +6,7 @@ import ( ) const ( - DefaultAccessTokenSecret = "your_access_token_secret" DefaultAccessTokenExpirationTime = 24 * time.Hour - DefaultRefreshTokenSecret = "your_refresh_token_secret" DefaultRefreshTokenExpirationTime = 168 * time.Hour ) @@ -20,15 +18,9 @@ type JWT struct { } func (j *JWT) ensureDefaultValue() { - if j.AccessTokenSecret == "" { - j.AccessTokenSecret = DefaultAccessTokenSecret - } if j.AccessTokenExpirationTime == 0 { j.AccessTokenExpirationTime = DefaultAccessTokenExpirationTime } - if j.RefreshTokenSecret == "" { - j.RefreshTokenSecret = DefaultRefreshTokenSecret - } if j.RefreshTokenExpirationTime == 0 { j.RefreshTokenExpirationTime = DefaultRefreshTokenExpirationTime } @@ -41,6 +33,11 @@ func (j *JWT) validate() error { if j.RefreshTokenSecret == "" { return fmt.Errorf("refresh token secret cannot be empty") } - + if j.AccessTokenExpirationTime <= 0 { + return fmt.Errorf("access token expiration time must be positive") + } + if j.RefreshTokenExpirationTime <= j.AccessTokenExpirationTime { + return fmt.Errorf("refresh token expiration time must be greater than access token expiration time") + } return nil } diff --git a/backend-go/internal/config/oauth.go b/backend-go/internal/config/oauth.go index 12962c46..f726aad7 100644 --- a/backend-go/internal/config/oauth.go +++ b/backend-go/internal/config/oauth.go @@ -6,9 +6,8 @@ import ( const ( DefaultGitHubAuthorizationURL = "https://github.com/login/oauth/authorize" - DefaultGitHubTokenURL = "https://github.com/login/oauth/access_token" DefaultGitHubUserProfileURL = "https://api.github.com/user" - DefaultGitHubCallbackURL = "http://localhost:3000/auth/login/github" + DefaultGitHubCallbackURL = "https://localhost:3000/auth/login/github" ) type OAuth struct { @@ -45,9 +44,6 @@ func (g *GitHubOAuth) ensureDefaultValue() { if g.AuthorizationURL == "" { g.AuthorizationURL = DefaultGitHubAuthorizationURL } - if g.TokenURL == "" { - g.TokenURL = DefaultGitHubTokenURL - } if g.UserProfileURL == "" { g.UserProfileURL = DefaultGitHubUserProfileURL } diff --git a/backend-go/internal/config/storage.go b/backend-go/internal/config/storage.go index 9970b4e9..b9ef7c09 100644 --- a/backend-go/internal/config/storage.go +++ b/backend-go/internal/config/storage.go @@ -7,10 +7,8 @@ import ( const ( DefaultStorageProvider = "minio" - DefaultMinioBucket = "default-minio-bucket" - DefaultMinioEndpoint = "localhost:9000" - DefaultMinioAccessKey = "minioadmin" - DefaultMinioSecretKey = "minioadmin" + DefaultMinioBucket = "default-storage" + DefaultMinioEndpoint = "http://localhost:9000" ) type Storage struct { @@ -71,12 +69,6 @@ func (m *Minio) EnsureDefaultValue() { if m.Endpoint == "" { m.Endpoint = DefaultMinioEndpoint } - if m.AccessKey == "" { - m.AccessKey = DefaultMinioAccessKey - } - if m.SecretKey == "" { - m.SecretKey = DefaultMinioSecretKey - } } func (m *Minio) validate() error { From 0157de98954fc305313a41ca70845f378c1b5e5b Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 3 Feb 2025 13:55:46 +0900 Subject: [PATCH 19/26] lint --- backend-go/cmd/codepair/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/backend-go/cmd/codepair/main.go b/backend-go/cmd/codepair/main.go index 18d612e0..71b26bb6 100644 --- a/backend-go/cmd/codepair/main.go +++ b/backend-go/cmd/codepair/main.go @@ -6,6 +6,7 @@ import ( "github.com/labstack/echo/v4" "github.com/labstack/gommon/log" + "github.com/yorkie-team/codepair/backend/internal/config" "github.com/yorkie-team/codepair/backend/internal/server" ) From 3938a0bd8937b4652e75dd6a92d13ab74c82187a Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 3 Feb 2025 14:03:03 +0900 Subject: [PATCH 20/26] fix test --- backend-go/internal/config/config_test.go | 24 +++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/backend-go/internal/config/config_test.go b/backend-go/internal/config/config_test.go index 08239cfa..0853750c 100644 --- a/backend-go/internal/config/config_test.go +++ b/backend-go/internal/config/config_test.go @@ -231,8 +231,18 @@ func TestConfigWithDefaultValues(t *testing.T) { const minimalYAML = ` OAuth: Github: - ClientID: "provided_client_id" - ClientSecret: "provided_client_secret" + ClientID: "is not default" + ClientSecret: "is not default" + TokenURL: "is not default" + +JWT: + AccessTokenSecret: "is not default" + RefreshTokenSecret: "is not default" + +Storage: + Minio: + AccessKey: "is not default" + SecretKey: "is not default" ` filePath := writeTempConfigFile(t, "minimal_config.yaml", minimalYAML) @@ -245,15 +255,18 @@ OAuth: // --- OAuth (GitHub) defaults and provided values --- require.NotNil(t, cfg.OAuth.Github, "OAuth.Github should not be nil") // Provided values. - assert.Equal(t, "provided_client_id", cfg.OAuth.Github.ClientID) - assert.Equal(t, "provided_client_secret", cfg.OAuth.Github.ClientSecret) + assert.Equal(t, "is not default", cfg.OAuth.Github.ClientID) + assert.Equal(t, "is not default", cfg.OAuth.Github.ClientSecret) // Default values. assert.Equal(t, config.DefaultGitHubCallbackURL, cfg.OAuth.Github.CallbackURL) assert.Equal(t, config.DefaultGitHubAuthorizationURL, cfg.OAuth.Github.AuthorizationURL) assert.Equal(t, config.DefaultGitHubUserProfileURL, cfg.OAuth.Github.UserProfileURL) + assert.Equal(t, "is not default", cfg.OAuth.Github.TokenURL) // --- JWT defaults --- + assert.Equal(t, "is not default", cfg.JWT.AccessTokenSecret) assert.Equal(t, config.DefaultAccessTokenExpirationTime, cfg.JWT.AccessTokenExpirationTime) + assert.Equal(t, "is not default", cfg.JWT.RefreshTokenSecret) assert.Equal(t, config.DefaultRefreshTokenExpirationTime, cfg.JWT.RefreshTokenExpirationTime) // --- Mongo defaults --- @@ -268,6 +281,9 @@ OAuth: require.NotNil(t, cfg.Storage.Minio, "Storage.Minio should not be nil when provider is 'minio'") assert.Equal(t, config.DefaultMinioBucket, cfg.Storage.Minio.Bucket) assert.Equal(t, config.DefaultMinioEndpoint, cfg.Storage.Minio.Endpoint) + assert.Equal(t, "is not default", cfg.Storage.Minio.AccessKey) + assert.Equal(t, "is not default", cfg.Storage.Minio.SecretKey) + // S3 should be nil if not provided. assert.Nil(t, cfg.Storage.S3, "Storage.S3 should be nil by default") } From ed314aba36b1f2083851075f31f67eb058a3b388 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 3 Feb 2025 15:15:07 +0900 Subject: [PATCH 21/26] import validator --- backend-go/go.mod | 11 ++++++++--- backend-go/go.sum | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/backend-go/go.mod b/backend-go/go.mod index f9e28428..1c898c67 100644 --- a/backend-go/go.mod +++ b/backend-go/go.mod @@ -12,7 +12,12 @@ require ( require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.24.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -30,10 +35,10 @@ require ( github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.31.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/backend-go/go.sum b/backend-go/go.sum index 21e407ee..21b9d179 100644 --- a/backend-go/go.sum +++ b/backend-go/go.sum @@ -6,6 +6,14 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg= +github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -18,6 +26,8 @@ github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaa github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -71,14 +81,20 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From db9c4d9408b0f5ed2966de095cfd73e9c31881e1 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 3 Feb 2025 15:55:46 +0900 Subject: [PATCH 22/26] Refactor validator by library --- backend-go/internal/config/config.go | 3 ++ backend-go/internal/config/jwt.go | 23 ++++------ backend-go/internal/config/mongo.go | 18 ++++---- backend-go/internal/config/oauth.go | 41 +++++++----------- backend-go/internal/config/server.go | 11 ++--- backend-go/internal/config/storage.go | 61 ++++++++++----------------- backend-go/internal/config/yorkie.go | 11 +++-- 7 files changed, 69 insertions(+), 99 deletions(-) diff --git a/backend-go/internal/config/config.go b/backend-go/internal/config/config.go index 11bb36e0..10b2bae1 100644 --- a/backend-go/internal/config/config.go +++ b/backend-go/internal/config/config.go @@ -4,9 +4,12 @@ import ( "errors" "fmt" + "github.com/go-playground/validator/v10" "github.com/spf13/viper" ) +var validate = validator.New() + // Config holds the application configuration. type Config struct { Server Server `mapstructure:"Server"` diff --git a/backend-go/internal/config/jwt.go b/backend-go/internal/config/jwt.go index 9158c292..08b9b1bc 100644 --- a/backend-go/internal/config/jwt.go +++ b/backend-go/internal/config/jwt.go @@ -11,12 +11,13 @@ const ( ) type JWT struct { - AccessTokenSecret string `mapstructure:"AccessTokenSecret"` - AccessTokenExpirationTime time.Duration `mapstructure:"AccessTokenExpirationTime"` - RefreshTokenSecret string `mapstructure:"RefreshTokenSecret"` - RefreshTokenExpirationTime time.Duration `mapstructure:"RefreshTokenExpirationTime"` + AccessTokenSecret string `mapstructure:"AccessTokenSecret" validate:"required"` + AccessTokenExpirationTime time.Duration `mapstructure:"AccessTokenExpirationTime" validate:"gt=0"` + RefreshTokenSecret string `mapstructure:"RefreshTokenSecret" validate:"required"` + RefreshTokenExpirationTime time.Duration `mapstructure:"RefreshTokenExpirationTime" validate:"gt=0"` } +// ensureDefaultValue applies default expiration times when not provided. func (j *JWT) ensureDefaultValue() { if j.AccessTokenExpirationTime == 0 { j.AccessTokenExpirationTime = DefaultAccessTokenExpirationTime @@ -26,18 +27,10 @@ func (j *JWT) ensureDefaultValue() { } } +// validate uses the validator library to validate the struct fields. func (j *JWT) validate() error { - if j.AccessTokenSecret == "" { - return fmt.Errorf("access token secret cannot be empty") - } - if j.RefreshTokenSecret == "" { - return fmt.Errorf("refresh token secret cannot be empty") - } - if j.AccessTokenExpirationTime <= 0 { - return fmt.Errorf("access token expiration time must be positive") - } - if j.RefreshTokenExpirationTime <= j.AccessTokenExpirationTime { - return fmt.Errorf("refresh token expiration time must be greater than access token expiration time") + if err := validate.Struct(j); err != nil { + return fmt.Errorf("JWT config validation failed: %w", err) } return nil } diff --git a/backend-go/internal/config/mongo.go b/backend-go/internal/config/mongo.go index df72b51a..362c8646 100644 --- a/backend-go/internal/config/mongo.go +++ b/backend-go/internal/config/mongo.go @@ -13,12 +13,13 @@ const ( ) type Mongo struct { - ConnectionTimeout time.Duration `mapstructure:"ConnectionTimeout"` - ConnectionURI string `mapstructure:"ConnectionURI"` - PingTimeout time.Duration `mapstructure:"PingTimeout"` - DatabaseName string `mapstructure:"DatabaseName"` + ConnectionTimeout time.Duration `mapstructure:"ConnectionTimeout" validate:"gt=0"` + ConnectionURI string `mapstructure:"ConnectionURI" validate:"required,url"` + PingTimeout time.Duration `mapstructure:"PingTimeout" validate:"gt=0"` + DatabaseName string `mapstructure:"DatabaseName" validate:"required"` } +// ensureDefaultValue applies defaults if a field is zero-valued. func (m *Mongo) ensureDefaultValue() { if m.ConnectionTimeout == 0 { m.ConnectionTimeout = DefaultConnectionTimeout @@ -34,13 +35,10 @@ func (m *Mongo) ensureDefaultValue() { } } +// validate uses the validator library to validate the struct fields. func (m *Mongo) validate() error { - if m.ConnectionURI == "" { - return fmt.Errorf("mongo connection URI cannot be empty") - } - if m.DatabaseName == "" { - return fmt.Errorf("mongo database name cannot be empty") + if err := validate.Struct(m); err != nil { + return fmt.Errorf("mongo config validation failed: %w", err) } - return nil } diff --git a/backend-go/internal/config/oauth.go b/backend-go/internal/config/oauth.go index f726aad7..4d182005 100644 --- a/backend-go/internal/config/oauth.go +++ b/backend-go/internal/config/oauth.go @@ -1,8 +1,6 @@ package config -import ( - "fmt" -) +import "fmt" const ( DefaultGitHubAuthorizationURL = "https://github.com/login/oauth/authorize" @@ -11,33 +9,35 @@ const ( ) type OAuth struct { - Github *GitHubOAuth `mapstructure:"Github"` + Github *Github `mapstructure:"Github" validate:"required"` } -type GitHubOAuth struct { - ClientID string `mapstructure:"ClientID"` - ClientSecret string `mapstructure:"ClientSecret"` - CallbackURL string `mapstructure:"CallbackURL"` - AuthorizationURL string `mapstructure:"AuthorizationURL"` - TokenURL string `mapstructure:"TokenURL"` - UserProfileURL string `mapstructure:"UserProfileURL"` +type Github struct { + ClientID string `mapstructure:"ClientID" validate:"required"` + ClientSecret string `mapstructure:"ClientSecret" validate:"required"` + CallbackURL string `mapstructure:"CallbackURL" validate:"required,url"` + AuthorizationURL string `mapstructure:"AuthorizationURL" validate:"required,url"` + TokenURL string `mapstructure:"TokenURL" validate:"required,url"` + UserProfileURL string `mapstructure:"UserProfileURL" validate:"required,url"` } +// ensureDefaultValue applies defaults for GitHub URLs if they are not provided. func (o *OAuth) ensureDefaultValue() { if o.Github == nil { - o.Github = &GitHubOAuth{} + o.Github = &Github{} } o.Github.ensureDefaultValue() } +// validate uses the validator library to validate the struct fields. func (o *OAuth) validate() error { - if err := o.Github.validate(); err != nil { - return err + if err := validate.Struct(o); err != nil { + return fmt.Errorf("OAuth config validation failed: %w", err) } return nil } -func (g *GitHubOAuth) ensureDefaultValue() { +func (g *Github) ensureDefaultValue() { if g.CallbackURL == "" { g.CallbackURL = DefaultGitHubCallbackURL } @@ -48,14 +48,3 @@ func (g *GitHubOAuth) ensureDefaultValue() { g.UserProfileURL = DefaultGitHubUserProfileURL } } - -func (g *GitHubOAuth) validate() error { - if g.ClientID == "" { - return fmt.Errorf("github client_id cannot be empty") - } - if g.ClientSecret == "" { - return fmt.Errorf("github client_secret cannot be empty") - } - - return nil -} diff --git a/backend-go/internal/config/server.go b/backend-go/internal/config/server.go index bc0a1e5d..7d8e6af7 100644 --- a/backend-go/internal/config/server.go +++ b/backend-go/internal/config/server.go @@ -6,21 +6,22 @@ const ( DefaultServerPort = 3001 ) -// Server holds your server configuration. type Server struct { - Port int `mapstructure:"Port"` + Port int `mapstructure:"Port" validate:"required,min=1,max=65535"` } -// ensureDefaultValue sets a default if Port is not provided. +// ensureDefaultValue sets a default port if none is provided. func (s *Server) ensureDefaultValue() { if s.Port == 0 { s.Port = DefaultServerPort } } +// validate uses the validator library to validate the struct fields. func (s *Server) validate() error { - if s.Port < 1 || 65535 < s.Port { - return fmt.Errorf("must be between 1 and 65535, given %d", s.Port) + if err := validate.Struct(s); err != nil { + return fmt.Errorf("server config validation failed: %w", err) } + return nil } diff --git a/backend-go/internal/config/storage.go b/backend-go/internal/config/storage.go index b9ef7c09..98dc6928 100644 --- a/backend-go/internal/config/storage.go +++ b/backend-go/internal/config/storage.go @@ -6,31 +6,31 @@ import ( const ( DefaultStorageProvider = "minio" - - DefaultMinioBucket = "default-storage" - DefaultMinioEndpoint = "http://localhost:9000" + DefaultMinioBucket = "default-storage" + DefaultMinioEndpoint = "http://localhost:9000" ) type Storage struct { - Provider string `mapstructure:"provider"` - S3 *S3 `mapstructure:"s3"` - Minio *Minio `mapstructure:"minio"` + Provider string `mapstructure:"provider" validate:"required,oneof=minio s3"` + S3 *S3 `mapstructure:"s3" validate:"omitempty"` + Minio *Minio `mapstructure:"minio" validate:"omitempty"` } type S3 struct { - Bucket string `mapstructure:"bucket"` - Region string `mapstructure:"region"` - AccessKey string `mapstructure:"accessKey"` - SecretKey string `mapstructure:"secretKey"` + Bucket string `mapstructure:"bucket" validate:"required"` + Region string `mapstructure:"region" validate:"required"` + AccessKey string `mapstructure:"accessKey" validate:"required"` + SecretKey string `mapstructure:"secretKey" validate:"required"` } type Minio struct { - Bucket string `mapstructure:"bucket"` - Endpoint string `mapstructure:"endpoint"` - AccessKey string `mapstructure:"accessKey"` - SecretKey string `mapstructure:"secretKey"` + Bucket string `mapstructure:"bucket" validate:"required"` + Endpoint string `mapstructure:"endpoint" validate:"required,url"` + AccessKey string `mapstructure:"accessKey" validate:"required"` + SecretKey string `mapstructure:"secretKey" validate:"required"` } +// ensureDefaultValue applies default values for provider and Minio. func (s *Storage) ensureDefaultValue() { if s.Provider == "" { s.Provider = DefaultStorageProvider @@ -41,24 +41,21 @@ func (s *Storage) ensureDefaultValue() { s.Minio.EnsureDefaultValue() } +// validate uses the validator library to validate the struct fields. +// We also check that the nested config for the chosen provider is not nil. func (s *Storage) validate() error { - if s.Provider != "minio" && s.Provider != "s3" { - return fmt.Errorf("invalid storage provider: %s (only 'minio' or 's3' are supported)", s.Provider) - } - switch s.Provider { case "minio": - if s.Minio == nil { - return fmt.Errorf("storage provider is minio but Minio config is nil") + if err := validate.Struct(s.Minio); err != nil { + return fmt.Errorf("minio config validation failed: %w", err) } - return s.Minio.validate() case "s3": - if s.S3 == nil { - return fmt.Errorf("storage provider is s3 but S3 config is nil") + if err := validate.Struct(s.S3); err != nil { + return fmt.Errorf("s3 config validation failed: %w", err) } - return s.S3.validate() + default: + return fmt.Errorf("invalid storage provider: %s", s.Provider) } - return nil } @@ -70,17 +67,3 @@ func (m *Minio) EnsureDefaultValue() { m.Endpoint = DefaultMinioEndpoint } } - -func (m *Minio) validate() error { - if m.Bucket == "" || m.Endpoint == "" { - return fmt.Errorf("minio requires Bucket and Endpoint to be set") - } - return nil -} - -func (s3 *S3) validate() error { - if s3.Bucket == "" || s3.Region == "" || s3.AccessKey == "" || s3.SecretKey == "" { - return fmt.Errorf("s3 requires Bucket and Region to be set") - } - return nil -} diff --git a/backend-go/internal/config/yorkie.go b/backend-go/internal/config/yorkie.go index fdc5729c..e6c537e4 100644 --- a/backend-go/internal/config/yorkie.go +++ b/backend-go/internal/config/yorkie.go @@ -9,11 +9,12 @@ const ( ) type Yorkie struct { - APIAddr string `mapstructure:"APIAddr"` - ProjectName string `mapstructure:"ProjectName"` + APIAddr string `mapstructure:"APIAddr" validate:"required,url"` + ProjectName string `mapstructure:"ProjectName" validate:"required"` ProjectSecretKey string `mapstructure:"ProjectSecretKey"` } +// ensureDefaultValue applies defaults for Yorkie. func (y *Yorkie) ensureDefaultValue() { if y.APIAddr == "" { y.APIAddr = DefaultYorkieAPIAddr @@ -26,9 +27,11 @@ func (y *Yorkie) ensureDefaultValue() { } } +// validate uses the validator library to validate the struct fields. func (y *Yorkie) validate() error { - if y.APIAddr == "" { - return fmt.Errorf("yorkie APIAddr cannot be empty") + if err := validate.Struct(y); err != nil { + return fmt.Errorf("yorkie config validation failed: %w", err) } + return nil } From 186034e9607916c4e995a2e55b907dd3644a79d8 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 3 Feb 2025 15:56:42 +0900 Subject: [PATCH 23/26] refactor test - edit invalid test url --- backend-go/internal/config/config_test.go | 70 ++++++++++++++--------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/backend-go/internal/config/config_test.go b/backend-go/internal/config/config_test.go index 0853750c..86c414cd 100644 --- a/backend-go/internal/config/config_test.go +++ b/backend-go/internal/config/config_test.go @@ -29,39 +29,52 @@ func writeTempConfigFile(t *testing.T, filename, content string) string { } func TestConfigWithEnvVars(t *testing.T) { - // Define environment variables in a map. envs := map[string]string{ + // --- Server --- "SERVER_PORT": "3002", - "OAUTH_GITHUB_CLIENT_ID": "test_value", - "OAUTH_GITHUB_CLIENT_SECRET": "test_value", - "OAUTH_GITHUB_CALLBACK_URL": "test_value", - "OAUTH_GITHUB_AUTHORIZATION_URL": "test_value", - "OAUTH_GITHUB_TOKEN_URL": "test_value", - "OAUTH_GITHUB_USER_PROFILE_URL": "test_value", + // --- OAuth (GitHub) --- + "OAUTH_GITHUB_CLIENT_ID": "test_value", + "OAUTH_GITHUB_CLIENT_SECRET": "test_value", + // Use valid URLs for fields with url validation. + "OAUTH_GITHUB_CALLBACK_URL": "http://test_value/callback", + "OAUTH_GITHUB_AUTHORIZATION_URL": "http://test_value/auth", + "OAUTH_GITHUB_TOKEN_URL": "http://test_value/token", + "OAUTH_GITHUB_USER_PROFILE_URL": "http://test_value/profile", - "AUTH_FRONTEND_BASE_URL": "test_value", + // --- (Optional) FrontendBaseURL if needed --- + "AUTH_FRONTEND_BASE_URL": "http://test_value", + // --- JWT --- "JWT_ACCESS_TOKEN_SECRET": "test_value", "JWT_ACCESS_TOKEN_EXPIRATION_TIME": "48h", "JWT_REFRESH_TOKEN_SECRET": "test_value", "JWT_REFRESH_TOKEN_EXPIRATION_TIME": "72h", - "YORKIE_API_ADDR": "test_value", + // --- Yorkie --- + // APIAddr must be a valid URL. + "YORKIE_API_ADDR": "http://test_value", "YORKIE_PROJECT_NAME": "test_value", "YORKIE_PROJECT_SECRET_KEY": "test_value", + // --- Mongo --- + // ConnectionURI must be a valid URL; we use a mongodb:// URL. "MONGO_CONNECTION_TIMEOUT": "10s", - "MONGO_CONNECTION_URI": "test_value", + "MONGO_CONNECTION_URI": "mongodb://test_value:27017/codepair", "MONGO_PING_TIMEOUT": "3s", "MONGO_DATABASE_NAME": "test_value", - "STORAGE_PROVIDER": "minio", + // --- Storage --- + // Provider must be "minio" or "s3". + "STORAGE_PROVIDER": "minio", + + // --- Minio (when provider is minio, Endpoint must be a valid URL) --- "STORAGE_MINIO_BUCKET": "test_value", - "STORAGE_MINIO_ENDPOINT": "test_value", + "STORAGE_MINIO_ENDPOINT": "http://test_value:9000", "STORAGE_MINIO_ACCESS_KEY": "test_value", "STORAGE_MINIO_SECRET_KEY": "test_value", + // --- S3 --- "STORAGE_S3_BUCKET": "test_value", "STORAGE_S3_REGION": "test_value", "STORAGE_S3_ACCESS_KEY": "test_value", @@ -79,10 +92,11 @@ func TestConfigWithEnvVars(t *testing.T) { require.NotNil(t, cfg.OAuth.Github, "GitHub OAuth config should not be nil") assert.Equal(t, "test_value", cfg.OAuth.Github.ClientID) assert.Equal(t, "test_value", cfg.OAuth.Github.ClientSecret) - assert.Equal(t, "test_value", cfg.OAuth.Github.CallbackURL) - assert.Equal(t, "test_value", cfg.OAuth.Github.AuthorizationURL) - assert.Equal(t, "test_value", cfg.OAuth.Github.TokenURL) - assert.Equal(t, "test_value", cfg.OAuth.Github.UserProfileURL) + // Expect the valid URL strings. + assert.Equal(t, "http://test_value/callback", cfg.OAuth.Github.CallbackURL) + assert.Equal(t, "http://test_value/auth", cfg.OAuth.Github.AuthorizationURL) + assert.Equal(t, "http://test_value/token", cfg.OAuth.Github.TokenURL) + assert.Equal(t, "http://test_value/profile", cfg.OAuth.Github.UserProfileURL) // --- JWT --- assert.Equal(t, "test_value", cfg.JWT.AccessTokenSecret) @@ -91,13 +105,13 @@ func TestConfigWithEnvVars(t *testing.T) { assert.Equal(t, 72*time.Hour, cfg.JWT.RefreshTokenExpirationTime) // --- Yorkie --- - assert.Equal(t, "test_value", cfg.Yorkie.APIAddr) + assert.Equal(t, "http://test_value", cfg.Yorkie.APIAddr) assert.Equal(t, "test_value", cfg.Yorkie.ProjectName) assert.Equal(t, "test_value", cfg.Yorkie.ProjectSecretKey) // --- Mongo --- assert.Equal(t, 10*time.Second, cfg.Mongo.ConnectionTimeout) - assert.Equal(t, "test_value", cfg.Mongo.ConnectionURI) + assert.Equal(t, "mongodb://test_value:27017/codepair", cfg.Mongo.ConnectionURI) assert.Equal(t, 3*time.Second, cfg.Mongo.PingTimeout) assert.Equal(t, "test_value", cfg.Mongo.DatabaseName) @@ -105,15 +119,14 @@ func TestConfigWithEnvVars(t *testing.T) { assert.Equal(t, "minio", cfg.Storage.Provider, "Storage.Provider must be 'minio' or 's3'") // --- Minio --- - // For Minio: ensure the block is not nil and values match. require.NotNil(t, cfg.Storage.Minio, "Storage.Minio must not be nil if provider=minio") assert.Equal(t, "test_value", cfg.Storage.Minio.Bucket) - assert.Equal(t, "test_value", cfg.Storage.Minio.Endpoint) + assert.Equal(t, "http://test_value:9000", cfg.Storage.Minio.Endpoint) assert.Equal(t, "test_value", cfg.Storage.Minio.AccessKey) assert.Equal(t, "test_value", cfg.Storage.Minio.SecretKey) // --- S3 --- - // For S3: the struct exists though values may be default or provided. + // In this test S3 values are set via environment variables. require.NotNil(t, cfg.Storage.S3, "Storage.S3 struct can still exist") assert.Equal(t, "test_value", cfg.Storage.S3.Bucket) assert.Equal(t, "test_value", cfg.Storage.S3.Region) @@ -150,7 +163,7 @@ JWT: RefreshTokenExpirationTime: "168h" Yorkie: - APIAddr: "http://config-yorkie:8080" + APIAddr: "https://config-yorkie:8080" ProjectName: "config_project" ProjectSecretKey: "config_project_secret" @@ -165,7 +178,7 @@ Storage: Minio: Bucket: "config-storage" - Endpoint: "http://config-minio:9000" + Endpoint: "https://config-minio:9000" AccessKey: "config_minioadmin" SecretKey: "config_minioadmin" @@ -198,7 +211,7 @@ Storage: assert.Equal(t, 168*time.Hour, cfg.JWT.RefreshTokenExpirationTime) // --- Yorkie --- - assert.Equal(t, "http://config-yorkie:8080", cfg.Yorkie.APIAddr) + assert.Equal(t, "https://config-yorkie:8080", cfg.Yorkie.APIAddr) assert.Equal(t, "config_project", cfg.Yorkie.ProjectName) assert.Equal(t, "config_project_secret", cfg.Yorkie.ProjectSecretKey) @@ -214,7 +227,7 @@ Storage: // Minio require.NotNil(t, cfg.Storage.Minio, "Storage.Minio should not be nil if the provider is 'minio'") assert.Equal(t, "config-storage", cfg.Storage.Minio.Bucket) - assert.Equal(t, "http://config-minio:9000", cfg.Storage.Minio.Endpoint) + assert.Equal(t, "https://config-minio:9000", cfg.Storage.Minio.Endpoint) assert.Equal(t, "config_minioadmin", cfg.Storage.Minio.AccessKey) assert.Equal(t, "config_minioadmin", cfg.Storage.Minio.SecretKey) @@ -228,12 +241,14 @@ Storage: } func TestConfigWithDefaultValues(t *testing.T) { + // In the minimal YAML below, only the required fields that do not have defaults + // are provided. For fields with URL validation and defaults, we supply valid values when needed. const minimalYAML = ` OAuth: Github: ClientID: "is not default" ClientSecret: "is not default" - TokenURL: "is not default" + TokenURL: "http://is-not-default/token" # provided but not default; note valid URL format JWT: AccessTokenSecret: "is not default" @@ -244,7 +259,6 @@ Storage: AccessKey: "is not default" SecretKey: "is not default" ` - filePath := writeTempConfigFile(t, "minimal_config.yaml", minimalYAML) cfg, err := config.LoadConfig(filePath) require.NoError(t, err, "LoadConfig should succeed with a minimal config file") @@ -257,11 +271,11 @@ Storage: // Provided values. assert.Equal(t, "is not default", cfg.OAuth.Github.ClientID) assert.Equal(t, "is not default", cfg.OAuth.Github.ClientSecret) + assert.Equal(t, "http://is-not-default/token", cfg.OAuth.Github.TokenURL) // Default values. assert.Equal(t, config.DefaultGitHubCallbackURL, cfg.OAuth.Github.CallbackURL) assert.Equal(t, config.DefaultGitHubAuthorizationURL, cfg.OAuth.Github.AuthorizationURL) assert.Equal(t, config.DefaultGitHubUserProfileURL, cfg.OAuth.Github.UserProfileURL) - assert.Equal(t, "is not default", cfg.OAuth.Github.TokenURL) // --- JWT defaults --- assert.Equal(t, "is not default", cfg.JWT.AccessTokenSecret) From 352280c0673a206126caac8ba03b99ccdcb0b39d Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 3 Feb 2025 20:19:14 +0900 Subject: [PATCH 24/26] refactor auth config - `oauth` -> `auth` - add `FrontendBaseURL` to auth config --- backend-go/config.yaml | 5 +- .../internal/config/{oauth.go => auth.go} | 19 ++--- backend-go/internal/config/config.go | 6 +- backend-go/internal/config/config_test.go | 79 ++++++++++--------- backend-go/internal/config/env.go | 14 ++-- 5 files changed, 62 insertions(+), 61 deletions(-) rename backend-go/internal/config/{oauth.go => auth.go} (73%) diff --git a/backend-go/config.yaml b/backend-go/config.yaml index 3e89c6b7..92fcfba9 100644 --- a/backend-go/config.yaml +++ b/backend-go/config.yaml @@ -1,7 +1,7 @@ Server: Port: 3001 -OAuth: +Auth: Github: # WARNING: Replace these values with your GitHub OAuth credentials ClientID: "" @@ -10,8 +10,7 @@ OAuth: AuthorizationURL: "https://github.com/login/oauth/authorize" TokenURL: "https://github.com/login/oauth/access_token" UserProfileURL: "https://api.github.com/user" - -FrontendBaseURL: "http://localhost:5173" + FrontendBaseURL: "http://localhost:5173" JWT: # WARNING: Use strong, unique secrets in production diff --git a/backend-go/internal/config/oauth.go b/backend-go/internal/config/auth.go similarity index 73% rename from backend-go/internal/config/oauth.go rename to backend-go/internal/config/auth.go index 4d182005..08a3f42e 100644 --- a/backend-go/internal/config/oauth.go +++ b/backend-go/internal/config/auth.go @@ -8,8 +8,9 @@ const ( DefaultGitHubCallbackURL = "https://localhost:3000/auth/login/github" ) -type OAuth struct { - Github *Github `mapstructure:"Github" validate:"required"` +type Auth struct { + Github *Github `mapstructure:"Github" validate:"required"` + FrontendBaseURL string `mapstructure:"FrontendBaseURL" validate:"required,url"` } type Github struct { @@ -22,17 +23,17 @@ type Github struct { } // ensureDefaultValue applies defaults for GitHub URLs if they are not provided. -func (o *OAuth) ensureDefaultValue() { - if o.Github == nil { - o.Github = &Github{} +func (a *Auth) ensureDefaultValue() { + if a.Github == nil { + a.Github = &Github{} } - o.Github.ensureDefaultValue() + a.Github.ensureDefaultValue() } // validate uses the validator library to validate the struct fields. -func (o *OAuth) validate() error { - if err := validate.Struct(o); err != nil { - return fmt.Errorf("OAuth config validation failed: %w", err) +func (a *Auth) validate() error { + if err := validate.Struct(a); err != nil { + return fmt.Errorf("auth config validation failed: %w", err) } return nil } diff --git a/backend-go/internal/config/config.go b/backend-go/internal/config/config.go index 10b2bae1..82d9cb54 100644 --- a/backend-go/internal/config/config.go +++ b/backend-go/internal/config/config.go @@ -13,7 +13,7 @@ var validate = validator.New() // Config holds the application configuration. type Config struct { Server Server `mapstructure:"Server"` - OAuth OAuth `mapstructure:"OAuth"` + Auth Auth `mapstructure:"Auth"` JWT JWT `mapstructure:"JWT"` Yorkie Yorkie `mapstructure:"Yorkie"` Mongo Mongo `mapstructure:"Mongo"` @@ -81,7 +81,7 @@ func readConfigFile(filePath string) error { func (c *Config) ensureDefaultValue() { c.Server.ensureDefaultValue() - c.OAuth.ensureDefaultValue() + c.Auth.ensureDefaultValue() c.JWT.ensureDefaultValue() c.Yorkie.ensureDefaultValue() c.Mongo.ensureDefaultValue() @@ -92,7 +92,7 @@ func (c *Config) validate() error { if err := c.Server.validate(); err != nil { return fmt.Errorf("server config invalid: %w", err) } - if err := c.OAuth.validate(); err != nil { + if err := c.Auth.validate(); err != nil { return fmt.Errorf("oauth config invalid: %w", err) } if err := c.JWT.validate(); err != nil { diff --git a/backend-go/internal/config/config_test.go b/backend-go/internal/config/config_test.go index 86c414cd..06fb2614 100644 --- a/backend-go/internal/config/config_test.go +++ b/backend-go/internal/config/config_test.go @@ -33,17 +33,14 @@ func TestConfigWithEnvVars(t *testing.T) { // --- Server --- "SERVER_PORT": "3002", - // --- OAuth (GitHub) --- - "OAUTH_GITHUB_CLIENT_ID": "test_value", - "OAUTH_GITHUB_CLIENT_SECRET": "test_value", - // Use valid URLs for fields with url validation. - "OAUTH_GITHUB_CALLBACK_URL": "http://test_value/callback", - "OAUTH_GITHUB_AUTHORIZATION_URL": "http://test_value/auth", - "OAUTH_GITHUB_TOKEN_URL": "http://test_value/token", - "OAUTH_GITHUB_USER_PROFILE_URL": "http://test_value/profile", - - // --- (Optional) FrontendBaseURL if needed --- - "AUTH_FRONTEND_BASE_URL": "http://test_value", + // --- Auth --- + "AUTH_GITHUB_CLIENT_ID": "test_value", + "AUTH_GITHUB_CLIENT_SECRET": "test_value", + "AUTH_GITHUB_CALLBACK_URL": "http://test_value/callback", + "AUTH_GITHUB_AUTHORIZATION_URL": "http://test_value/auth", + "AUTH_GITHUB_TOKEN_URL": "http://test_value/token", + "AUTH_GITHUB_USER_PROFILE_URL": "http://test_value/profile", + "AUTH_FRONTEND_BASE_URL": "http://test_value", // --- JWT --- "JWT_ACCESS_TOKEN_SECRET": "test_value", @@ -88,15 +85,16 @@ func TestConfigWithEnvVars(t *testing.T) { // --- Server --- assert.Equal(t, 3002, cfg.Server.Port, "Server.Port should reflect 'SERVER_PORT' env var") - // --- OAuth (GitHub) --- - require.NotNil(t, cfg.OAuth.Github, "GitHub OAuth config should not be nil") - assert.Equal(t, "test_value", cfg.OAuth.Github.ClientID) - assert.Equal(t, "test_value", cfg.OAuth.Github.ClientSecret) + // --- Auth (GitHub) --- + require.NotNil(t, cfg.Auth.Github, "GitHub Auth config should not be nil") + assert.Equal(t, "test_value", cfg.Auth.Github.ClientID) + assert.Equal(t, "test_value", cfg.Auth.Github.ClientSecret) // Expect the valid URL strings. - assert.Equal(t, "http://test_value/callback", cfg.OAuth.Github.CallbackURL) - assert.Equal(t, "http://test_value/auth", cfg.OAuth.Github.AuthorizationURL) - assert.Equal(t, "http://test_value/token", cfg.OAuth.Github.TokenURL) - assert.Equal(t, "http://test_value/profile", cfg.OAuth.Github.UserProfileURL) + assert.Equal(t, "http://test_value/callback", cfg.Auth.Github.CallbackURL) + assert.Equal(t, "http://test_value/auth", cfg.Auth.Github.AuthorizationURL) + assert.Equal(t, "http://test_value/token", cfg.Auth.Github.TokenURL) + assert.Equal(t, "http://test_value/profile", cfg.Auth.Github.UserProfileURL) + assert.Equal(t, "http://test_value", cfg.Auth.FrontendBaseURL) // --- JWT --- assert.Equal(t, "test_value", cfg.JWT.AccessTokenSecret) @@ -145,7 +143,7 @@ func TestLoadConfigFromFile(t *testing.T) { Server: Port: 3001 -OAuth: +Auth: Github: ClientID: "config_client_id" ClientSecret: "config_client_secret" @@ -153,8 +151,7 @@ OAuth: AuthorizationURL: "https://config.example.com/login/oauth/authorize" TokenURL: "https://config.example.com/login/oauth/access_token" UserProfileURL: "https://config.example.com/api/user" - -FrontendBaseURL: "http://config-frontend:5173" + FrontendBaseURL: "http://config-frontend:5173" JWT: AccessTokenSecret: "config_access_token_secret" @@ -195,14 +192,15 @@ Storage: // --- Server --- assert.Equal(t, 3001, cfg.Server.Port, "Server.Port should be 3001") - // --- OAuth (GitHub) --- - require.NotNil(t, cfg.OAuth.Github, "OAuth.Github should not be nil") - assert.Equal(t, "config_client_id", cfg.OAuth.Github.ClientID) - assert.Equal(t, "config_client_secret", cfg.OAuth.Github.ClientSecret) - assert.Equal(t, "https://config.example.com/auth/login/github", cfg.OAuth.Github.CallbackURL) - assert.Equal(t, "https://config.example.com/login/oauth/authorize", cfg.OAuth.Github.AuthorizationURL) - assert.Equal(t, "https://config.example.com/login/oauth/access_token", cfg.OAuth.Github.TokenURL) - assert.Equal(t, "https://config.example.com/api/user", cfg.OAuth.Github.UserProfileURL) + // --- Auth (GitHub) --- + require.NotNil(t, cfg.Auth.Github, "Auth.Github should not be nil") + assert.Equal(t, "config_client_id", cfg.Auth.Github.ClientID) + assert.Equal(t, "config_client_secret", cfg.Auth.Github.ClientSecret) + assert.Equal(t, "https://config.example.com/auth/login/github", cfg.Auth.Github.CallbackURL) + assert.Equal(t, "https://config.example.com/login/oauth/authorize", cfg.Auth.Github.AuthorizationURL) + assert.Equal(t, "https://config.example.com/login/oauth/access_token", cfg.Auth.Github.TokenURL) + assert.Equal(t, "https://config.example.com/api/user", cfg.Auth.Github.UserProfileURL) + assert.Equal(t, "http://config-frontend:5173", cfg.Auth.FrontendBaseURL) // --- JWT --- assert.Equal(t, "config_access_token_secret", cfg.JWT.AccessTokenSecret) @@ -244,11 +242,13 @@ func TestConfigWithDefaultValues(t *testing.T) { // In the minimal YAML below, only the required fields that do not have defaults // are provided. For fields with URL validation and defaults, we supply valid values when needed. const minimalYAML = ` -OAuth: +Auth: Github: ClientID: "is not default" ClientSecret: "is not default" TokenURL: "http://is-not-default/token" # provided but not default; note valid URL format + FrontendBaseURL: "http://is-not-default" + JWT: AccessTokenSecret: "is not default" @@ -266,16 +266,17 @@ Storage: // --- Server defaults --- assert.Equal(t, config.DefaultServerPort, cfg.Server.Port, "Server.Port should default to DefaultServerPort") - // --- OAuth (GitHub) defaults and provided values --- - require.NotNil(t, cfg.OAuth.Github, "OAuth.Github should not be nil") + // --- Auth (GitHub) defaults and provided values --- + require.NotNil(t, cfg.Auth.Github, "Auth.Github should not be nil") // Provided values. - assert.Equal(t, "is not default", cfg.OAuth.Github.ClientID) - assert.Equal(t, "is not default", cfg.OAuth.Github.ClientSecret) - assert.Equal(t, "http://is-not-default/token", cfg.OAuth.Github.TokenURL) + assert.Equal(t, "is not default", cfg.Auth.Github.ClientID) + assert.Equal(t, "is not default", cfg.Auth.Github.ClientSecret) + assert.Equal(t, "http://is-not-default/token", cfg.Auth.Github.TokenURL) // Default values. - assert.Equal(t, config.DefaultGitHubCallbackURL, cfg.OAuth.Github.CallbackURL) - assert.Equal(t, config.DefaultGitHubAuthorizationURL, cfg.OAuth.Github.AuthorizationURL) - assert.Equal(t, config.DefaultGitHubUserProfileURL, cfg.OAuth.Github.UserProfileURL) + assert.Equal(t, config.DefaultGitHubCallbackURL, cfg.Auth.Github.CallbackURL) + assert.Equal(t, config.DefaultGitHubAuthorizationURL, cfg.Auth.Github.AuthorizationURL) + assert.Equal(t, config.DefaultGitHubUserProfileURL, cfg.Auth.Github.UserProfileURL) + assert.Equal(t, "http://is-not-default", cfg.Auth.FrontendBaseURL) // --- JWT defaults --- assert.Equal(t, "is not default", cfg.JWT.AccessTokenSecret) diff --git a/backend-go/internal/config/env.go b/backend-go/internal/config/env.go index 585782e4..177c1b56 100644 --- a/backend-go/internal/config/env.go +++ b/backend-go/internal/config/env.go @@ -3,13 +3,13 @@ package config var EnvVarMap = map[string]string{ "server.port": "SERVER_PORT", - "oauth.github.ClientId": "OAUTH_GITHUB_CLIENT_ID", - "oauth.github.ClientSecret": "OAUTH_GITHUB_CLIENT_SECRET", - "oauth.github.CallbackUrl": "OAUTH_GITHUB_CALLBACK_URL", - "oauth.github.AuthorizationUrl": "OAUTH_GITHUB_AUTHORIZATION_URL", - "oauth.github.TokenUrl": "OAUTH_GITHUB_TOKEN_URL", - "oauth.github.UserProfileUrl": "OAUTH_GITHUB_USER_PROFILE_URL", - "auth.FrontendBaseUrl": "AUTH_FRONTEND_BASE_URL", + "auth.github.ClientId": "AUTH_GITHUB_CLIENT_ID", + "auth.github.ClientSecret": "AUTH_GITHUB_CLIENT_SECRET", + "auth.github.CallbackUrl": "AUTH_GITHUB_CALLBACK_URL", + "auth.github.AuthorizationUrl": "AUTH_GITHUB_AUTHORIZATION_URL", + "auth.github.TokenUrl": "AUTH_GITHUB_TOKEN_URL", + "auth.github.UserProfileUrl": "AUTH_GITHUB_USER_PROFILE_URL", + "auth.FrontendBaseUrl": "AUTH_FRONTEND_BASE_URL", "jwt.AccessTokenSecret": "JWT_ACCESS_TOKEN_SECRET", "jwt.AccessTokenExpirationTime": "JWT_ACCESS_TOKEN_EXPIRATION_TIME", From 9765f8871e2b351bbdd8c70e209a1fe0df97a754 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 3 Feb 2025 20:28:17 +0900 Subject: [PATCH 25/26] refactor GitHub config - set GitHub ClientID empty - set GitHub GithubSecret empty --- backend-go/config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend-go/config.yaml b/backend-go/config.yaml index 92fcfba9..162359fa 100644 --- a/backend-go/config.yaml +++ b/backend-go/config.yaml @@ -4,8 +4,8 @@ Server: Auth: Github: # WARNING: Replace these values with your GitHub OAuth credentials - ClientID: "" - ClientSecret: "" + ClientID: "" + ClientSecret: "" CallbackURL: "http://localhost:3000/auth/login/github" AuthorizationURL: "https://github.com/login/oauth/authorize" TokenURL: "https://github.com/login/oauth/access_token" From 2951bc87345974ecfcd770e97a4aca324c7d9ce8 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Thu, 6 Feb 2025 16:24:06 +0900 Subject: [PATCH 26/26] remove `mapstructure` --- backend-go/internal/config/auth.go | 16 ++++++++-------- backend-go/internal/config/config.go | 13 ++++++------- backend-go/internal/config/jwt.go | 8 ++++---- backend-go/internal/config/mongo.go | 8 ++++---- backend-go/internal/config/server.go | 2 +- backend-go/internal/config/storage.go | 22 +++++++++++----------- backend-go/internal/config/yorkie.go | 6 +++--- 7 files changed, 37 insertions(+), 38 deletions(-) diff --git a/backend-go/internal/config/auth.go b/backend-go/internal/config/auth.go index 08a3f42e..57e1a914 100644 --- a/backend-go/internal/config/auth.go +++ b/backend-go/internal/config/auth.go @@ -9,17 +9,17 @@ const ( ) type Auth struct { - Github *Github `mapstructure:"Github" validate:"required"` - FrontendBaseURL string `mapstructure:"FrontendBaseURL" validate:"required,url"` + Github *Github `validate:"required"` + FrontendBaseURL string `validate:"required,url"` } type Github struct { - ClientID string `mapstructure:"ClientID" validate:"required"` - ClientSecret string `mapstructure:"ClientSecret" validate:"required"` - CallbackURL string `mapstructure:"CallbackURL" validate:"required,url"` - AuthorizationURL string `mapstructure:"AuthorizationURL" validate:"required,url"` - TokenURL string `mapstructure:"TokenURL" validate:"required,url"` - UserProfileURL string `mapstructure:"UserProfileURL" validate:"required,url"` + ClientID string `validate:"required"` + ClientSecret string `validate:"required"` + CallbackURL string `validate:"required,url"` + AuthorizationURL string `validate:"required,url"` + TokenURL string `validate:"required,url"` + UserProfileURL string `validate:"required,url"` } // ensureDefaultValue applies defaults for GitHub URLs if they are not provided. diff --git a/backend-go/internal/config/config.go b/backend-go/internal/config/config.go index 82d9cb54..67a453fa 100644 --- a/backend-go/internal/config/config.go +++ b/backend-go/internal/config/config.go @@ -10,14 +10,13 @@ import ( var validate = validator.New() -// Config holds the application configuration. type Config struct { - Server Server `mapstructure:"Server"` - Auth Auth `mapstructure:"Auth"` - JWT JWT `mapstructure:"JWT"` - Yorkie Yorkie `mapstructure:"Yorkie"` - Mongo Mongo `mapstructure:"Mongo"` - Storage Storage `mapstructure:"Storage"` + Server Server + Auth Auth + JWT JWT + Yorkie Yorkie + Mongo Mongo + Storage Storage } // LoadConfig loads configuration settings from a file (if provided) and from environment variables. diff --git a/backend-go/internal/config/jwt.go b/backend-go/internal/config/jwt.go index 08b9b1bc..b1488fd5 100644 --- a/backend-go/internal/config/jwt.go +++ b/backend-go/internal/config/jwt.go @@ -11,10 +11,10 @@ const ( ) type JWT struct { - AccessTokenSecret string `mapstructure:"AccessTokenSecret" validate:"required"` - AccessTokenExpirationTime time.Duration `mapstructure:"AccessTokenExpirationTime" validate:"gt=0"` - RefreshTokenSecret string `mapstructure:"RefreshTokenSecret" validate:"required"` - RefreshTokenExpirationTime time.Duration `mapstructure:"RefreshTokenExpirationTime" validate:"gt=0"` + AccessTokenSecret string `validate:"required"` + AccessTokenExpirationTime time.Duration `validate:"gt=0"` + RefreshTokenSecret string `validate:"required"` + RefreshTokenExpirationTime time.Duration `validate:"gt=0"` } // ensureDefaultValue applies default expiration times when not provided. diff --git a/backend-go/internal/config/mongo.go b/backend-go/internal/config/mongo.go index 362c8646..02a0f610 100644 --- a/backend-go/internal/config/mongo.go +++ b/backend-go/internal/config/mongo.go @@ -13,10 +13,10 @@ const ( ) type Mongo struct { - ConnectionTimeout time.Duration `mapstructure:"ConnectionTimeout" validate:"gt=0"` - ConnectionURI string `mapstructure:"ConnectionURI" validate:"required,url"` - PingTimeout time.Duration `mapstructure:"PingTimeout" validate:"gt=0"` - DatabaseName string `mapstructure:"DatabaseName" validate:"required"` + ConnectionTimeout time.Duration `validate:"gt=0"` + ConnectionURI string `validate:"required,url"` + PingTimeout time.Duration `validate:"gt=0"` + DatabaseName string `validate:"required"` } // ensureDefaultValue applies defaults if a field is zero-valued. diff --git a/backend-go/internal/config/server.go b/backend-go/internal/config/server.go index 7d8e6af7..8b956b07 100644 --- a/backend-go/internal/config/server.go +++ b/backend-go/internal/config/server.go @@ -7,7 +7,7 @@ const ( ) type Server struct { - Port int `mapstructure:"Port" validate:"required,min=1,max=65535"` + Port int `validate:"required,min=1,max=65535"` } // ensureDefaultValue sets a default port if none is provided. diff --git a/backend-go/internal/config/storage.go b/backend-go/internal/config/storage.go index 98dc6928..b38172f6 100644 --- a/backend-go/internal/config/storage.go +++ b/backend-go/internal/config/storage.go @@ -11,23 +11,23 @@ const ( ) type Storage struct { - Provider string `mapstructure:"provider" validate:"required,oneof=minio s3"` - S3 *S3 `mapstructure:"s3" validate:"omitempty"` - Minio *Minio `mapstructure:"minio" validate:"omitempty"` + Provider string `validate:"required,oneof=minio s3"` + S3 *S3 `validate:"omitempty"` + Minio *Minio `validate:"omitempty"` } type S3 struct { - Bucket string `mapstructure:"bucket" validate:"required"` - Region string `mapstructure:"region" validate:"required"` - AccessKey string `mapstructure:"accessKey" validate:"required"` - SecretKey string `mapstructure:"secretKey" validate:"required"` + Bucket string `validate:"required"` + Region string `validate:"required"` + AccessKey string `validate:"required"` + SecretKey string `validate:"required"` } type Minio struct { - Bucket string `mapstructure:"bucket" validate:"required"` - Endpoint string `mapstructure:"endpoint" validate:"required,url"` - AccessKey string `mapstructure:"accessKey" validate:"required"` - SecretKey string `mapstructure:"secretKey" validate:"required"` + Bucket string `validate:"required"` + Endpoint string `validate:"required,url"` + AccessKey string `validate:"required"` + SecretKey string `validate:"required"` } // ensureDefaultValue applies default values for provider and Minio. diff --git a/backend-go/internal/config/yorkie.go b/backend-go/internal/config/yorkie.go index e6c537e4..35566fb1 100644 --- a/backend-go/internal/config/yorkie.go +++ b/backend-go/internal/config/yorkie.go @@ -9,9 +9,9 @@ const ( ) type Yorkie struct { - APIAddr string `mapstructure:"APIAddr" validate:"required,url"` - ProjectName string `mapstructure:"ProjectName" validate:"required"` - ProjectSecretKey string `mapstructure:"ProjectSecretKey"` + APIAddr string `validate:"required,url"` + ProjectName string `validate:"required"` + ProjectSecretKey string } // ensureDefaultValue applies defaults for Yorkie.