From 9250d0cb300a4324c0958875d6af92e106aa5395 Mon Sep 17 00:00:00 2001 From: Nam Nguyen Date: Tue, 30 May 2023 01:05:12 +0700 Subject: [PATCH] feat: remove employee github and team email access --- cmd/server/main.go | 6 +- go.mod | 19 +- go.sum | 41 ++-- pkg/config/config.go | 24 ++- ...etLineManagers.go => get_line_managers.go} | 0 .../employee/updateEmployeeStatus.go | 128 ------------ ...ateBaseSalary.go => update_base_salary.go} | 6 +- .../employee/update_employee_status.go | 193 ++++++++++++++++++ ...eGeneralInfo.go => update_general_info.go} | 0 ...ersonalInfo.go => update_personal_info.go} | 0 .../{updateRole.go => update_role.go} | 0 .../{updateSkills.go => update_skills.go} | 0 .../{uploadAvatar.go => upload_avatar.go} | 0 pkg/handler/payroll/payroll.go | 4 +- pkg/handler/project/project.go | 2 + pkg/model/social_account.go | 9 + pkg/service/basecamp/basecamp.go | 2 +- pkg/service/github/errors.go | 8 + pkg/service/github/github.go | 111 ++++++++++ pkg/service/github/service.go | 13 ++ pkg/service/googleadmin/google_admin.go | 94 +++++++++ pkg/service/googleadmin/interface.go | 7 + pkg/service/service.go | 27 ++- 23 files changed, 521 insertions(+), 173 deletions(-) rename pkg/controller/employee/{getLineManagers.go => get_line_managers.go} (100%) delete mode 100644 pkg/controller/employee/updateEmployeeStatus.go rename pkg/controller/employee/{updateBaseSalary.go => update_base_salary.go} (99%) create mode 100644 pkg/controller/employee/update_employee_status.go rename pkg/controller/employee/{updateGeneralInfo.go => update_general_info.go} (100%) rename pkg/controller/employee/{updatePersonalInfo.go => update_personal_info.go} (100%) rename pkg/controller/employee/{updateRole.go => update_role.go} (100%) rename pkg/controller/employee/{updateSkills.go => update_skills.go} (100%) rename pkg/controller/employee/{uploadAvatar.go => upload_avatar.go} (100%) create mode 100644 pkg/service/github/errors.go create mode 100644 pkg/service/github/github.go create mode 100644 pkg/service/github/service.go create mode 100644 pkg/service/googleadmin/google_admin.go create mode 100644 pkg/service/googleadmin/interface.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 0659738ef..7b8620e0f 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -40,13 +40,13 @@ func main() { log := logger.NewLogrusLogger() log.Infof("Server starting") - vault, err := vault.New(cfg) + v, err := vault.New(cfg) if err != nil { log.Error(err, "failed to init vault") } - if vault != nil { - cfg = config.Generate(vault) + if v != nil { + cfg = config.Generate(v) } s := store.New() diff --git a/go.mod b/go.mod index 8c6bc9683..5659ae5fe 100644 --- a/go.mod +++ b/go.mod @@ -19,12 +19,13 @@ require ( github.com/golang-jwt/jwt/v4 v4.4.3 github.com/golang/mock v1.4.4 github.com/google/go-cmp v0.5.9 + github.com/google/go-github/v52 v52.0.0 github.com/hashicorp/vault/api v1.8.2 github.com/issyl0/go-improvmx v0.17.0 github.com/jackc/pgtype v1.12.0 github.com/jinzhu/now v1.1.5 github.com/joho/godotenv v1.4.0 - github.com/k0kubun/pp v3.0.1+incompatible + github.com/k0kubun/pp/v3 v3.2.0 github.com/lib/pq v1.10.6 github.com/matoous/go-nanoid v1.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -38,9 +39,9 @@ require ( github.com/swaggo/gin-swagger v1.5.3 github.com/swaggo/swag v1.8.7 github.com/thoas/go-funk v0.9.3 - golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa + golang.org/x/crypto v0.7.0 golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 - golang.org/x/oauth2 v0.5.0 + golang.org/x/oauth2 v0.7.0 google.golang.org/api v0.110.0 gorm.io/datatypes v1.0.7 gorm.io/driver/postgres v1.4.5 @@ -53,6 +54,7 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v0.12.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/andybalholm/brotli v1.0.4 // indirect @@ -60,6 +62,7 @@ require ( github.com/armon/go-metrics v0.3.10 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect + github.com/cloudflare/circl v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect @@ -75,6 +78,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect @@ -108,7 +112,6 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect @@ -137,11 +140,11 @@ require ( github.com/ugorji/go/codec v1.2.7 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect - golang.org/x/tools v0.2.0 // indirect + golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44 // indirect diff --git a/go.sum b/go.sum index 2743589fc..b9086d029 100644 --- a/go.sum +++ b/go.sum @@ -61,6 +61,8 @@ github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6Xge github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= @@ -92,6 +94,7 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwmarrin/discordgo v0.27.0 h1:4ZK9KN+rGIxZ0fdGTmgdCcliQeW8Zhu6MnlFI92nf0Q= github.com/bwmarrin/discordgo v0.27.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= @@ -105,6 +108,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -252,6 +257,10 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/google/go-github/v52 v52.0.0 h1:uyGWOY+jMQ8GVGSX8dkSwCzlehU3WfdxQ7GweO/JP7M= +github.com/google/go-github/v52 v52.0.0/go.mod h1:WJV6VEEUPuMo5pXqqa2ZCZEdbQqua4zAk2MZTIo+m+4= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -422,10 +431,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= -github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= +github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= +github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -687,8 +694,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -726,7 +734,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -773,8 +781,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -784,8 +792,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -849,6 +857,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -856,8 +865,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -871,8 +880,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -935,8 +944,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/config/config.go b/pkg/config/config.go index 93e440aee..2ce8a2aa6 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -29,6 +29,7 @@ type Config struct { Invoice Invoice Sendgrid Sendgrid + Github Github APIKey string Debug bool @@ -61,6 +62,7 @@ type Google struct { GCSCredentials string GCPProjectID string AccountingGoogleRefreshToken string + AdminGoogleRefreshToken string AccountingEmailID string TeamGoogleRefreshToken string TeamEmailID string @@ -95,6 +97,10 @@ type Notion struct { Databases NotionDatabase } +type Github struct { + Token string +} + type NotionDatabase struct { AuditCycle string AuditActionItem string @@ -170,19 +176,22 @@ func Generate(v ENV) *Config { Pass: v.GetString("DB_PASS"), SSLMode: v.GetString("DB_SSL_MODE"), }, - + Github: Github{ + Token: v.GetString("GITHUB_ACCESS_TOKEN"), + }, Google: Google{ - ClientSecret: v.GetString("GOOGLE_API_CLIENT_SECRET"), - ClientID: v.GetString("GOOGLE_API_CLIENT_ID"), + AccountingEmailID: v.GetString("ACCOUNTING_EMAIL_ID"), + AccountingGoogleRefreshToken: v.GetString("ACCOUNTING_GOOGLE_REFRESH_TOKEN"), + AdminGoogleRefreshToken: v.GetString("ADMIN_GOOGLE_REFRESH_TOKEN"), AppName: v.GetString("GOOGLE_API_APP_NAME"), + ClientID: v.GetString("GOOGLE_API_CLIENT_ID"), + ClientSecret: v.GetString("GOOGLE_API_CLIENT_SECRET"), GCPProjectID: v.GetString("GCP_PROJECT_ID"), GCSBucketName: v.GetString("GCS_BUCKET_NAME"), - GCSProjectID: v.GetString("GCS_PROJECT_ID"), GCSCredentials: v.GetString("GCS_CREDENTIALS"), - AccountingGoogleRefreshToken: v.GetString("ACCOUNTING_GOOGLE_REFRESH_TOKEN"), - AccountingEmailID: v.GetString("ACCOUNTING_EMAIL_ID"), - TeamGoogleRefreshToken: v.GetString("TEAM_GOOGLE_REFRESH_TOKEN"), + GCSProjectID: v.GetString("GCS_PROJECT_ID"), TeamEmailID: v.GetString("TEAM_EMAIL_ID"), + TeamGoogleRefreshToken: v.GetString("TEAM_GOOGLE_REFRESH_TOKEN"), }, Wise: Wise{ @@ -193,7 +202,6 @@ func Generate(v ENV) *Config { CurrencyLayer: CurrencyLayer{ APIKey: v.GetString("CURRENCY_LAYER_API_KEY"), }, - Vault: Vault{ Address: v.GetString("VAULT_ADDR"), Token: v.GetString("VAULT_TOKEN"), diff --git a/pkg/controller/employee/getLineManagers.go b/pkg/controller/employee/get_line_managers.go similarity index 100% rename from pkg/controller/employee/getLineManagers.go rename to pkg/controller/employee/get_line_managers.go diff --git a/pkg/controller/employee/updateEmployeeStatus.go b/pkg/controller/employee/updateEmployeeStatus.go deleted file mode 100644 index f7c58b3b8..000000000 --- a/pkg/controller/employee/updateEmployeeStatus.go +++ /dev/null @@ -1,128 +0,0 @@ -package employee - -import ( - "errors" - "time" - - "gorm.io/gorm" - "gorm.io/gorm/utils" - - "github.com/dwarvesf/fortress-api/pkg/logger" - "github.com/dwarvesf/fortress-api/pkg/model" -) - -type UpdateWorkingStatusInput struct { - EmployeeStatus model.WorkingStatus -} - -func (r *controller) UpdateEmployeeStatus(employeeID string, body UpdateWorkingStatusInput) (*model.Employee, error) { - l := r.logger.Fields(logger.Fields{ - "controller": "employee", - "method": "UpdateEmployeeStatus", - }) - - now := time.Now() - emp, err := r.store.Employee.One(r.repo.DB(), employeeID, true) - if err != nil { - l.Errorf(err, "failed to get Employee ", employeeID) - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, ErrEmployeeNotFound - } - return nil, err - } - - emp.WorkingStatus = body.EmployeeStatus - emp.LeftDate = &now - - if body.EmployeeStatus != model.WorkingStatusLeft { - emp.LeftDate = nil - } - - tx, done := r.repo.NewTransaction() - defer func() { - _ = done(nil) - }() - - _, err = r.store.Employee.UpdateSelectedFieldsByID(tx.DB(), employeeID, *emp, "working_status", "left_date") - if err != nil { - return nil, done(err) - } - - // If employee working status is left, do off-boarding flow - if body.EmployeeStatus == model.WorkingStatusLeft { - err = r.store.ProjectMember.UpdateMemberToInActiveByID(tx.DB(), employeeID, &now) - if err != nil { - return nil, done(err) - } - - discordInfo := model.SocialAccounts(emp.SocialAccounts).GetDiscord() - if discordInfo != nil { - err = r.updateDiscordRoles(discordInfo.AccountID) - if err != nil { - l.Errorf(err, "failed to update discord roles", "employeeID", employeeID, "discordID", discordInfo.AccountID) - return nil, err - } - } - - err = r.removeBasecampAccess(emp.BasecampID) - if err != nil { - l.Errorf(err, "failed to remove basecamp access", "employeeID", employeeID, "basecampID", emp.BasecampID) - return nil, err - } - } - - return emp, err -} - -func (r *controller) updateDiscordRoles(discordUserID string) error { - if r.config.Env != "prod" { - return nil - } - - if discordUserID == "" { - return nil - } - - roles, err := r.service.Discord.GetRoles() - if err != nil { - return err - } - - dfRoles := roles.DwarvesRoles() - - discordMember, err := r.service.Discord.GetMember(discordUserID) - if err != nil { - return err - } - - for _, role := range dfRoles { - if utils.Contains(discordMember.Roles, role.ID) { - err = r.service.Discord.RemoveRole(discordUserID, role.ID) - if err != nil { - return err - } - } - } - - // Assign alumni role - alumniRole := roles.ByCode("alumni") - err = r.service.Discord.AddRole(discordUserID, alumniRole.ID) - if err != nil { - return err - } - - return nil -} - -func (r *controller) removeBasecampAccess(baseCampID int) error { - if r.config.Env != "prod" { - return nil - } - - err := r.service.Basecamp.People.Remove(int64(baseCampID)) - if err != nil { - return err - } - - return nil -} diff --git a/pkg/controller/employee/updateBaseSalary.go b/pkg/controller/employee/update_base_salary.go similarity index 99% rename from pkg/controller/employee/updateBaseSalary.go rename to pkg/controller/employee/update_base_salary.go index 0cf240db5..d63e2de9a 100644 --- a/pkg/controller/employee/updateBaseSalary.go +++ b/pkg/controller/employee/update_base_salary.go @@ -2,10 +2,12 @@ package employee import ( "errors" + "time" + + "gorm.io/gorm" + "github.com/dwarvesf/fortress-api/pkg/logger" "github.com/dwarvesf/fortress-api/pkg/model" - "gorm.io/gorm" - "time" ) type UpdateBaseSalaryInput struct { diff --git a/pkg/controller/employee/update_employee_status.go b/pkg/controller/employee/update_employee_status.go new file mode 100644 index 000000000..1d22fa0a8 --- /dev/null +++ b/pkg/controller/employee/update_employee_status.go @@ -0,0 +1,193 @@ +package employee + +import ( + "context" + "errors" + "time" + + "gorm.io/gorm" + "gorm.io/gorm/utils" + + "github.com/dwarvesf/fortress-api/pkg/logger" + "github.com/dwarvesf/fortress-api/pkg/model" +) + +type UpdateWorkingStatusInput struct { + EmployeeStatus model.WorkingStatus +} + +func (r *controller) UpdateEmployeeStatus(employeeID string, body UpdateWorkingStatusInput) (*model.Employee, error) { + l := r.logger.Fields(logger.Fields{ + "controller": "employee", + "method": "UpdateEmployeeStatus", + }) + + now := time.Now() + e, err := r.store.Employee.One(r.repo.DB(), employeeID, true) + if err != nil { + l.Errorf(err, "failed to get Employee ", employeeID) + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrEmployeeNotFound + } + return nil, err + } + + e.WorkingStatus = body.EmployeeStatus + e.LeftDate = &now + + if body.EmployeeStatus != model.WorkingStatusLeft { + e.LeftDate = nil + } + + tx, done := r.repo.NewTransaction() + defer func() { + _ = done(nil) + }() + + _, err = r.store.Employee.UpdateSelectedFieldsByID(tx.DB(), employeeID, *e, "working_status", "left_date") + if err != nil { + return nil, done(err) + } + + //If employee working status is left, do off-boarding flow + if body.EmployeeStatus == model.WorkingStatusLeft { + err = r.store.ProjectMember.UpdateMemberToInActiveByID(tx.DB(), employeeID, &now) + if err != nil { + return nil, done(err) + } + + // Do Off-boarding process + r.processOffBoardingEmployee(l, e) + } + + return e, err +} + +func (r *controller) processOffBoardingEmployee(l logger.Logger, e *model.Employee) { + discordInfo := model.SocialAccounts(e.SocialAccounts).GetDiscord() + if discordInfo != nil { + err := r.updateDiscordRoles(discordInfo.AccountID) + if err != nil { + l.Errorf(err, "failed to update discord roles", "employeeID", e.ID.String(), "discordID", discordInfo.AccountID) + } + } + + err := r.removeBasecampAccess(e.BasecampID) + if err != nil { + l.Errorf(err, "failed to remove basecamp access", "employeeID", e.ID.String(), "basecampID", e.BasecampID) + } + + err = r.removeTeamEmailForward(e.TeamEmail) + if err != nil { + l.Errorf(err, "failed to remove team email forward", "employeeID", e.ID.String(), "email", e.TeamEmail) + } + + err = r.removeTeamEmail(e.TeamEmail) + if err != nil { + l.Errorf(err, "failed to delete google account", "employeeID", e.ID.String(), "email", e.TeamEmail) + } + + err = r.removeGithubFromOrganization(e) + if err != nil { + l.Errorf(err, "failed to remove github user from organization", "employeeID", e.ID.String()) + } +} + +func (r *controller) updateDiscordRoles(discordUserID string) error { + if r.config.Env != "prod" { + return nil + } + + if discordUserID == "" { + return nil + } + + roles, err := r.service.Discord.GetRoles() + if err != nil { + return err + } + + dfRoles := roles.DwarvesRoles() + + discordMember, err := r.service.Discord.GetMember(discordUserID) + if err != nil { + return err + } + + for _, role := range dfRoles { + if utils.Contains(discordMember.Roles, role.ID) { + err = r.service.Discord.RemoveRole(discordUserID, role.ID) + if err != nil { + return err + } + } + } + + // Assign alumni role + alumniRole := roles.ByCode("alumni") + err = r.service.Discord.AddRole(discordUserID, alumniRole.ID) + if err != nil { + return err + } + + return nil +} + +func (r *controller) removeBasecampAccess(baseCampID int) error { + if r.config.Env != "prod" { + return nil + } + + err := r.service.Basecamp.People.Remove(int64(baseCampID)) + if err != nil { + return err + } + + return nil +} + +func (r *controller) removeTeamEmailForward(teamEmail string) error { + if r.config.Env != "prod" { + return nil + } + + err := r.service.ImprovMX.DeleteAccount(teamEmail) + if err != nil { + return err + } + + return nil +} + +func (r *controller) removeTeamEmail(teamEmail string) error { + if r.config.Env != "prod" { + return nil + } + + err := r.service.GoogleAdmin.DeleteAccount(teamEmail) + if err != nil { + return err + } + + return nil +} + +func (r *controller) removeGithubFromOrganization(e *model.Employee) error { + if r.config.Env != "prod" { + return nil + } + + githubSA := model.SocialAccounts(e.SocialAccounts).GetGithub() + if githubSA != nil { + if githubSA.AccountID == "" { + return nil + } + + err := r.service.Github.RemoveFromOrganizationByUsername(context.Background(), githubSA.AccountID) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/controller/employee/updateGeneralInfo.go b/pkg/controller/employee/update_general_info.go similarity index 100% rename from pkg/controller/employee/updateGeneralInfo.go rename to pkg/controller/employee/update_general_info.go diff --git a/pkg/controller/employee/updatePersonalInfo.go b/pkg/controller/employee/update_personal_info.go similarity index 100% rename from pkg/controller/employee/updatePersonalInfo.go rename to pkg/controller/employee/update_personal_info.go diff --git a/pkg/controller/employee/updateRole.go b/pkg/controller/employee/update_role.go similarity index 100% rename from pkg/controller/employee/updateRole.go rename to pkg/controller/employee/update_role.go diff --git a/pkg/controller/employee/updateSkills.go b/pkg/controller/employee/update_skills.go similarity index 100% rename from pkg/controller/employee/updateSkills.go rename to pkg/controller/employee/update_skills.go diff --git a/pkg/controller/employee/uploadAvatar.go b/pkg/controller/employee/upload_avatar.go similarity index 100% rename from pkg/controller/employee/uploadAvatar.go rename to pkg/controller/employee/upload_avatar.go diff --git a/pkg/handler/payroll/payroll.go b/pkg/handler/payroll/payroll.go index f489a85c2..1728535bb 100644 --- a/pkg/handler/payroll/payroll.go +++ b/pkg/handler/payroll/payroll.go @@ -24,7 +24,7 @@ import ( "github.com/dwarvesf/fortress-api/pkg/service/currency" "github.com/dwarvesf/fortress-api/pkg/store" "github.com/dwarvesf/fortress-api/pkg/store/employee" - commissionStore "github.com/dwarvesf/fortress-api/pkg/store/employeecommission" + "github.com/dwarvesf/fortress-api/pkg/store/employeecommission" "github.com/dwarvesf/fortress-api/pkg/store/payroll" "github.com/dwarvesf/fortress-api/pkg/utils" "github.com/dwarvesf/fortress-api/pkg/utils/timeutil" @@ -255,7 +255,7 @@ func GetPayrollDetailHandler(h *handler, month, year, batch int, email string) ( // !isForecast if batchDate.Month() <= time.Now().Month() || (batchDate.Month() == 12 && time.Now().Month() == 1) { toDate := batchDate.AddDate(0, 1, 0) - commissionQuery := commissionStore.Query{ + commissionQuery := employeecommission.Query{ EmployeeID: payrolls[i].Employee.ID.String(), IsPaid: isPaid, FromDate: &batchDate, diff --git a/pkg/handler/project/project.go b/pkg/handler/project/project.go index 3d683b275..3eacf6561 100644 --- a/pkg/handler/project/project.go +++ b/pkg/handler/project/project.go @@ -9,6 +9,7 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/k0kubun/pp/v3" "github.com/shopspring/decimal" "gorm.io/gorm" @@ -3057,5 +3058,6 @@ func (h *handler) SyncProjectMemberStatus(c *gin.Context) { return } + pp.Println("synced project member status") c.JSON(http.StatusOK, view.CreateResponse[any](nil, nil, nil, nil, "ok")) } diff --git a/pkg/model/social_account.go b/pkg/model/social_account.go index 77d0c34e4..6d5e83d00 100644 --- a/pkg/model/social_account.go +++ b/pkg/model/social_account.go @@ -53,3 +53,12 @@ func (e SocialAccounts) GetDiscord() *SocialAccount { } return nil } + +func (e SocialAccounts) GetGithub() *SocialAccount { + for _, account := range e { + if account.Type == SocialAccountTypeGitHub { + return &account + } + } + return nil +} diff --git a/pkg/service/basecamp/basecamp.go b/pkg/service/basecamp/basecamp.go index aaf5b8a6b..88fa862d2 100644 --- a/pkg/service/basecamp/basecamp.go +++ b/pkg/service/basecamp/basecamp.go @@ -48,7 +48,7 @@ type Service struct { Wise wise.IService } -func NewService(store *store.Store, repo store.DBRepo, cfg *config.Config, bc *model.Basecamp, logger logger.Logger) *Service { +func New(store *store.Store, repo store.DBRepo, cfg *config.Config, bc *model.Basecamp, logger logger.Logger) *Service { c, err := client.NewClient(bc, cfg) if err != nil { logger.Error(err, "init basecamp service") diff --git a/pkg/service/github/errors.go b/pkg/service/github/errors.go new file mode 100644 index 000000000..1161eb91e --- /dev/null +++ b/pkg/service/github/errors.go @@ -0,0 +1,8 @@ +package github + +import "errors" + +var ( + ErrFailedToGetGithubAccount = errors.New("failed to get github account") + ErrFoundOneMoreGithubAccount = errors.New("failed to get github account due to more than 1 github account found") +) diff --git a/pkg/service/github/github.go b/pkg/service/github/github.go new file mode 100644 index 000000000..135209fb2 --- /dev/null +++ b/pkg/service/github/github.go @@ -0,0 +1,111 @@ +package github + +import ( + "context" + + "github.com/google/go-github/v52/github" + "golang.org/x/oauth2" + + "github.com/dwarvesf/fortress-api/pkg/config" + "github.com/dwarvesf/fortress-api/pkg/logger" + "github.com/dwarvesf/fortress-api/pkg/model" +) + +type githubService struct { + Client *github.Client + log logger.Logger +} + +var ( + defaultRole = "direct_member" + dwarvesFoundationOrg = "dwarvesf" +) + +func New(cfg *config.Config, l logger.Logger) IService { + if cfg.Github.Token == "" { + return &githubService{} + } + return &githubService{ + Client: github.NewClient( + oauth2.NewClient(context.Background(), oauth2.StaticTokenSource( + &oauth2.Token{ + AccessToken: cfg.Github.Token, + }, + ))), + log: l, + } +} + +func (s githubService) SendInvitationByEmail(ctx context.Context, e *model.Employee) error { + if s.Client == nil { + s.log.Warn("[SendInvitationByEmail] github token is empty") + return nil + } + + role := defaultRole + opt := github.CreateOrgInvitationOptions{ + Email: &e.PersonalEmail, + Role: &role, + TeamID: []int64{}, + } + + s.log.Infof("[SendInvitationByEmail] Send invitation to user", "email", e.PersonalEmail) + _, _, err := s.Client.Organizations.CreateOrgInvitation(ctx, dwarvesFoundationOrg, &opt) + if err != nil { + s.log.Errorf(err, "[SendInvitationByEmail] Fail to send invitation", "email", e.PersonalEmail) + return err + } + + return nil +} + +func (s githubService) RemoveFromOrganizationByEmail(ctx context.Context, email string) error { + if s.Client == nil { + return nil + } + + sOpts := github.SearchOptions{} + + result, _, err := s.Client.Search.Users(ctx, email, &sOpts) + if err != nil { + s.log.Errorf(err, "[RemoveFromOrganizationByEmail] fail to search user by email", "email", email) + return err + } + + switch { + case len(result.Users) > 1: + s.log.Errorf(ErrFoundOneMoreGithubAccount, "[RemoveFromOrganizationByEmail] more than 1 result return", "email", email) + return ErrFoundOneMoreGithubAccount + case len(result.Users) == 0: + s.log.Errorf(ErrFailedToGetGithubAccount, "[RemoveFromOrganizationByEmail] can not found github account from user email", "email", email) + return ErrFailedToGetGithubAccount + } + + s.log.Infof("[RemoveFromOrganizationByEmail] Remove github member out of organization", "username", result.Users[0].GetLogin()) + _, err = s.Client.Organizations.RemoveMember(ctx, dwarvesFoundationOrg, result.Users[0].GetLogin()) + if err != nil { + return err + } + + return nil +} + +func (s githubService) RemoveFromOrganizationByUsername(ctx context.Context, username string) error { + if s.Client == nil { + return nil + } + + result, _, err := s.Client.Users.Get(ctx, username) + if err != nil { + s.log.Errorf(err, "[RemoveFromOrganizationByUsername] fail to search user by email", "username", username) + return err + } + + s.log.Infof("[RemoveFromOrganizationByUsername] remove github member out of organization", "username", result.GetLogin()) + _, err = s.Client.Organizations.RemoveMember(ctx, dwarvesFoundationOrg, result.GetLogin()) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/service/github/service.go b/pkg/service/github/service.go new file mode 100644 index 000000000..b208ccd02 --- /dev/null +++ b/pkg/service/github/service.go @@ -0,0 +1,13 @@ +package github + +import ( + "context" + + "github.com/dwarvesf/fortress-api/pkg/model" +) + +type IService interface { + RemoveFromOrganizationByEmail(ctx context.Context, email string) error + RemoveFromOrganizationByUsername(ctx context.Context, username string) error + SendInvitationByEmail(ctx context.Context, e *model.Employee) error +} diff --git a/pkg/service/googleadmin/google_admin.go b/pkg/service/googleadmin/google_admin.go new file mode 100644 index 000000000..2fca4a546 --- /dev/null +++ b/pkg/service/googleadmin/google_admin.go @@ -0,0 +1,94 @@ +package googleadmin + +import ( + "context" + "errors" + "fmt" + "google.golang.org/api/option" + + "golang.org/x/oauth2" + admin "google.golang.org/api/admin/directory/v1" + + "github.com/dwarvesf/fortress-api/pkg/config" +) + +type googleService struct { + config *oauth2.Config + token *oauth2.Token + service *admin.Service + appConfig *config.Config +} + +// New function return Google service +func New(config *oauth2.Config, appConfig *config.Config) IService { + return &googleService{ + config: config, + appConfig: appConfig, + } +} + +func (g *googleService) DeleteAccount(mail string) error { + if err := g.ensureToken(g.appConfig.Google.AdminGoogleRefreshToken); err != nil { + return err + } + + if err := g.prepareService(); err != nil { + return err + } + + err := g.service.Users.Delete(mail).Do() + return err +} + +func (g *googleService) GetGroupMemberEmails(groupEmail string) ([]string, error) { + if err := g.ensureToken(g.appConfig.Google.AccountingGoogleRefreshToken); err != nil { + return nil, err + } + + if err := g.prepareService(); err != nil { + return nil, err + } + + var memberEmails []string + + members, err := g.service.Members.List(groupEmail).Do() + if err != nil { + return nil, err + } + + if members == nil { + return nil, fmt.Errorf("No member in group %v", groupEmail) + } + + for _, m := range members.Members { + memberEmails = append(memberEmails, m.Email) + } + + return memberEmails, nil +} + +func (g *googleService) ensureToken(rToken string) error { + token := &oauth2.Token{ + RefreshToken: rToken, + } + + if !g.token.Valid() { + tks := g.config.TokenSource(context.Background(), token) + tok, err := tks.Token() + if err != nil { + return err + } + g.token = tok + } + return nil +} + +func (g *googleService) prepareService() error { + client := g.config.Client(context.Background(), g.token) + service, err := admin.NewService(context.Background(), option.WithHTTPClient(client)) + if err != nil { + return errors.New("failed to prepare google admin service " + err.Error()) + } + g.service = service + return nil +} diff --git a/pkg/service/googleadmin/interface.go b/pkg/service/googleadmin/interface.go new file mode 100644 index 000000000..a1acb7078 --- /dev/null +++ b/pkg/service/googleadmin/interface.go @@ -0,0 +1,7 @@ +package googleadmin + +// IService interface contain related google calendar method +type IService interface { + GetGroupMemberEmails(groupEmail string) ([]string, error) + DeleteAccount(mail string) error +} diff --git a/pkg/service/service.go b/pkg/service/service.go index 346fb3bc7..7b5c63de1 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -3,10 +3,11 @@ package service import ( "time" - cache "github.com/patrickmn/go-cache" + "github.com/patrickmn/go-cache" "golang.org/x/oauth2" "golang.org/x/oauth2/google" - gmail "google.golang.org/api/gmail/v1" + admin "google.golang.org/api/admin/directory/v1" + "google.golang.org/api/gmail/v1" "github.com/dwarvesf/fortress-api/pkg/config" "github.com/dwarvesf/fortress-api/pkg/logger" @@ -14,7 +15,9 @@ import ( "github.com/dwarvesf/fortress-api/pkg/service/basecamp/model" "github.com/dwarvesf/fortress-api/pkg/service/currency" "github.com/dwarvesf/fortress-api/pkg/service/discord" + "github.com/dwarvesf/fortress-api/pkg/service/github" googleauth "github.com/dwarvesf/fortress-api/pkg/service/google" + "github.com/dwarvesf/fortress-api/pkg/service/googleadmin" "github.com/dwarvesf/fortress-api/pkg/service/googledrive" "github.com/dwarvesf/fortress-api/pkg/service/googlemail" "github.com/dwarvesf/fortress-api/pkg/service/improvmx" @@ -30,9 +33,11 @@ type Service struct { Cache *cache.Cache Currency currency.IService Discord discord.IService + Github github.IService Google googleauth.IService GoogleDrive googledrive.IService GoogleMail googlemail.IService + GoogleAdmin googleadmin.IService ImprovMX improvmx.IService Mochi mochi.IService Notion notion.IService @@ -69,6 +74,16 @@ func New(cfg *config.Config, store *store.Store, repo store.DBRepo) *Service { googleDriveSvc := googledrive.New(driveConfig, cfg) + googleAdminConfig := &oauth2.Config{ + ClientID: cfg.Google.ClientID, + ClientSecret: cfg.Google.ClientSecret, + Endpoint: google.Endpoint, + Scopes: []string{admin.AdminDirectoryUserScope, + admin.AdminDirectoryGroupScope, + }, + } + googleAdminSvc := googleadmin.New(googleAdminConfig, cfg) + mailConfig := &oauth2.Config{ ClientID: cfg.Google.ClientID, ClientSecret: cfg.Google.ClientSecret, @@ -76,7 +91,7 @@ func New(cfg *config.Config, store *store.Store, repo store.DBRepo) *Service { Scopes: []string{gmail.MailGoogleComScope}, } - googleMailService := googlemail.New( + googleMailSvc := googlemail.New( mailConfig, cfg, ) @@ -88,13 +103,15 @@ func New(cfg *config.Config, store *store.Store, repo store.DBRepo) *Service { Currency := currency.New(cfg) return &Service{ - Basecamp: basecamp.NewService(store, repo, cfg, &bc, logger.L), + Basecamp: basecamp.New(store, repo, cfg, &bc, logger.L), Cache: cch, Currency: Currency, Discord: discord.New(cfg), + Github: github.New(cfg, logger.L), Google: googleSvc, + GoogleAdmin: googleAdminSvc, GoogleDrive: googleDriveSvc, - GoogleMail: googleMailService, + GoogleMail: googleMailSvc, ImprovMX: improvmx.New(cfg.ImprovMX.Token), Mochi: mochi.New(cfg, logger.L), Notion: notion.New(cfg.Notion.Secret, cfg.Notion.Databases.Project, logger.L),