diff --git a/backend-go/cmd/codepair/main.go b/backend-go/cmd/codepair/main.go index b65106d9..71b26bb6 100644 --- a/backend-go/cmd/codepair/main.go +++ b/backend-go/cmd/codepair/main.go @@ -1,17 +1,60 @@ 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) + 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/config.yaml b/backend-go/config.yaml index e69de29b..162359fa 100644 --- a/backend-go/config.yaml +++ b/backend-go/config.yaml @@ -0,0 +1,46 @@ +Server: + Port: 3001 + +Auth: + Github: + # 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" + UserProfileURL: "https://api.github.com/user" + FrontendBaseURL: "http://localhost:5173" + +JWT: + # WARNING: Use strong, unique secrets in production + AccessTokenSecret: "" + AccessTokenExpirationTime: "24h" + RefreshTokenSecret: "" + RefreshTokenExpirationTime: "168h" + +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" diff --git a/backend-go/go.mod b/backend-go/go.mod index 5b7f61ba..1c898c67 100644 --- a/backend-go/go.mod +++ b/backend-go/go.mod @@ -2,16 +2,44 @@ 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/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 + 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 - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.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.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 a37c9e39..21b9d179 100644 --- a/backend-go/go.sum +++ b/backend-go/go.sum @@ -1,31 +1,107 @@ -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/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= +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/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= 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/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= +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= diff --git a/backend-go/internal/config/auth.go b/backend-go/internal/config/auth.go new file mode 100644 index 00000000..57e1a914 --- /dev/null +++ b/backend-go/internal/config/auth.go @@ -0,0 +1,51 @@ +package config + +import "fmt" + +const ( + DefaultGitHubAuthorizationURL = "https://github.com/login/oauth/authorize" + DefaultGitHubUserProfileURL = "https://api.github.com/user" + DefaultGitHubCallbackURL = "https://localhost:3000/auth/login/github" +) + +type Auth struct { + Github *Github `validate:"required"` + FrontendBaseURL string `validate:"required,url"` +} + +type Github struct { + 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. +func (a *Auth) ensureDefaultValue() { + if a.Github == nil { + a.Github = &Github{} + } + a.Github.ensureDefaultValue() +} + +// validate uses the validator library to validate the struct fields. +func (a *Auth) validate() error { + if err := validate.Struct(a); err != nil { + return fmt.Errorf("auth config validation failed: %w", err) + } + return nil +} + +func (g *Github) ensureDefaultValue() { + if g.CallbackURL == "" { + g.CallbackURL = DefaultGitHubCallbackURL + } + if g.AuthorizationURL == "" { + g.AuthorizationURL = DefaultGitHubAuthorizationURL + } + if g.UserProfileURL == "" { + g.UserProfileURL = DefaultGitHubUserProfileURL + } +} diff --git a/backend-go/internal/config/config.go b/backend-go/internal/config/config.go index d32ecf4a..67a453fa 100644 --- a/backend-go/internal/config/config.go +++ b/backend-go/internal/config/config.go @@ -1,13 +1,111 @@ package config +import ( + "errors" + "fmt" + + "github.com/go-playground/validator/v10" + "github.com/spf13/viper" +) + +var validate = validator.New() + type Config struct { - Server *Server + 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. +// It returns the populated Config, a status message describing which sources were used, and an error if any. +func LoadConfig(filePath string) (*Config, error) { + if err := bindEnvironmentVariables(); 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() 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) + } + } + + return nil +} + +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 errors.As(err, &nf) { + if filePath != "" { + return fmt.Errorf("file path given but not found: %w", err) + } + return nil + } + return fmt.Errorf("failed to read config file: %w", err) + } + + return nil } -func LoadConfig() *Config { - return &Config{ - Server: &Server{ - Port: DefaultServerPort, - }, +func (c *Config) ensureDefaultValue() { + c.Server.ensureDefaultValue() + c.Auth.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.Auth.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 } diff --git a/backend-go/internal/config/config_test.go b/backend-go/internal/config/config_test.go new file mode 100644 index 00000000..06fb2614 --- /dev/null +++ b/backend-go/internal/config/config_test.go @@ -0,0 +1,304 @@ +package config_test + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "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), 0600) + require.NoError(t, err, "Should be able to create a temporary config file") + return filePath +} + +func TestConfigWithEnvVars(t *testing.T) { + envs := map[string]string{ + // --- Server --- + "SERVER_PORT": "3002", + + // --- 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", + "JWT_ACCESS_TOKEN_EXPIRATION_TIME": "48h", + "JWT_REFRESH_TOKEN_SECRET": "test_value", + "JWT_REFRESH_TOKEN_EXPIRATION_TIME": "72h", + + // --- 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": "mongodb://test_value:27017/codepair", + "MONGO_PING_TIMEOUT": "3s", + "MONGO_DATABASE_NAME": "test_value", + + // --- 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": "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", + "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") + + // --- Server --- + assert.Equal(t, 3002, cfg.Server.Port, "Server.Port should reflect 'SERVER_PORT' env var") + + // --- 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.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) + 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, "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, "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) + + // --- Storage --- + assert.Equal(t, "minio", cfg.Storage.Provider, "Storage.Provider must be 'minio' or 's3'") + + // --- Minio --- + 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, "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 --- + // 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) + 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", func(t *testing.T) { + _, err := config.LoadConfig("invalidPath") + assert.Error(t, err) + }) + + t.Run("load config from file", func(t *testing.T) { + const sampleYAML = ` +Server: + Port: 3001 + +Auth: + Github: + ClientID: "config_client_id" + ClientSecret: "config_client_secret" + 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" + FrontendBaseURL: "http://config-frontend:5173" + +JWT: + AccessTokenSecret: "config_access_token_secret" + AccessTokenExpirationTime: "24h" + RefreshTokenSecret: "config_refresh_token_secret" + RefreshTokenExpirationTime: "168h" + +Yorkie: + APIAddr: "https://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: "https://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" +` + 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") + + // --- Server --- + assert.Equal(t, 3001, cfg.Server.Port, "Server.Port should be 3001") + + // --- 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) + 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 --- + 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) + + // --- Mongo --- + 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) + 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) + + // S3 + require.NotNil(t, cfg.Storage.S3, "Storage.S3 struct should not be nil") + 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) { + // 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 = ` +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" + RefreshTokenSecret: "is not default" + +Storage: + Minio: + 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") + + // --- Server defaults --- + assert.Equal(t, config.DefaultServerPort, cfg.Server.Port, "Server.Port should default to DefaultServerPort") + + // --- 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.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.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) + 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 --- + 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, "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) + 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") +} diff --git a/backend-go/internal/config/env.go b/backend-go/internal/config/env.go new file mode 100644 index 00000000..177c1b56 --- /dev/null +++ b/backend-go/internal/config/env.go @@ -0,0 +1,37 @@ +package config + +var EnvVarMap = map[string]string{ + "server.port": "SERVER_PORT", + + "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", + "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", +} diff --git a/backend-go/internal/config/jwt.go b/backend-go/internal/config/jwt.go new file mode 100644 index 00000000..b1488fd5 --- /dev/null +++ b/backend-go/internal/config/jwt.go @@ -0,0 +1,36 @@ +package config + +import ( + "fmt" + "time" +) + +const ( + DefaultAccessTokenExpirationTime = 24 * time.Hour + DefaultRefreshTokenExpirationTime = 168 * time.Hour +) + +type JWT struct { + 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. +func (j *JWT) ensureDefaultValue() { + if j.AccessTokenExpirationTime == 0 { + j.AccessTokenExpirationTime = DefaultAccessTokenExpirationTime + } + if j.RefreshTokenExpirationTime == 0 { + j.RefreshTokenExpirationTime = DefaultRefreshTokenExpirationTime + } +} + +// validate uses the validator library to validate the struct fields. +func (j *JWT) validate() error { + 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 new file mode 100644 index 00000000..02a0f610 --- /dev/null +++ b/backend-go/internal/config/mongo.go @@ -0,0 +1,44 @@ +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 `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. +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 + } +} + +// validate uses the validator library to validate the struct fields. +func (m *Mongo) validate() error { + 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/server.go b/backend-go/internal/config/server.go index 9316bc81..8b956b07 100644 --- a/backend-go/internal/config/server.go +++ b/backend-go/internal/config/server.go @@ -1,9 +1,27 @@ package config +import "fmt" + const ( DefaultServerPort = 3001 ) type Server struct { - Port int + Port int `validate:"required,min=1,max=65535"` +} + +// 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 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 new file mode 100644 index 00000000..b38172f6 --- /dev/null +++ b/backend-go/internal/config/storage.go @@ -0,0 +1,69 @@ +package config + +import ( + "fmt" +) + +const ( + DefaultStorageProvider = "minio" + DefaultMinioBucket = "default-storage" + DefaultMinioEndpoint = "http://localhost:9000" +) + +type Storage struct { + Provider string `validate:"required,oneof=minio s3"` + S3 *S3 `validate:"omitempty"` + Minio *Minio `validate:"omitempty"` +} + +type S3 struct { + Bucket string `validate:"required"` + Region string `validate:"required"` + AccessKey string `validate:"required"` + SecretKey string `validate:"required"` +} + +type Minio struct { + 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. +func (s *Storage) ensureDefaultValue() { + if s.Provider == "" { + s.Provider = DefaultStorageProvider + } + if s.Minio == nil { + s.Minio = &Minio{} + } + 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 { + switch s.Provider { + case "minio": + if err := validate.Struct(s.Minio); err != nil { + return fmt.Errorf("minio config validation failed: %w", err) + } + case "s3": + if err := validate.Struct(s.S3); err != nil { + return fmt.Errorf("s3 config validation failed: %w", err) + } + default: + return fmt.Errorf("invalid storage provider: %s", s.Provider) + } + return nil +} + +func (m *Minio) EnsureDefaultValue() { + if m.Bucket == "" { + m.Bucket = DefaultMinioBucket + } + if m.Endpoint == "" { + m.Endpoint = DefaultMinioEndpoint + } +} diff --git a/backend-go/internal/config/yorkie.go b/backend-go/internal/config/yorkie.go new file mode 100644 index 00000000..35566fb1 --- /dev/null +++ b/backend-go/internal/config/yorkie.go @@ -0,0 +1,37 @@ +package config + +import "fmt" + +const ( + DefaultYorkieAPIAddr = "http://localhost:8080" + DefaultYorkieProjectName = "default" + DefaultYorkieProjectSecret = "" +) + +type Yorkie struct { + APIAddr string `validate:"required,url"` + ProjectName string `validate:"required"` + ProjectSecretKey string +} + +// ensureDefaultValue applies defaults for Yorkie. +func (y *Yorkie) ensureDefaultValue() { + if y.APIAddr == "" { + y.APIAddr = DefaultYorkieAPIAddr + } + if y.ProjectName == "" { + y.ProjectName = DefaultYorkieProjectName + } + if y.ProjectSecretKey == "" { + y.ProjectSecretKey = DefaultYorkieProjectSecret + } +} + +// validate uses the validator library to validate the struct fields. +func (y *Yorkie) validate() error { + if err := validate.Struct(y); err != nil { + return fmt.Errorf("yorkie config validation failed: %w", err) + } + + return nil +}