From 4075a2c39b5160502eed0bcf2bfb5d514bd427d5 Mon Sep 17 00:00:00 2001 From: Urko Date: Fri, 31 Mar 2023 11:49:03 +0200 Subject: [PATCH] feat: initial commit - retrieve data from http xml response - parse to struct - save on database - expose rest server to list database collection documents --- .gitignore | 102 +++++++++++++ cmd/etl/main.go | 141 +++++++++++++++++ cmd/server/main.go | 71 +++++++++ config/config.go | 40 +++++ docker-compose.yaml | 21 +++ go.mod | 40 +++++ go.sum | 144 ++++++++++++++++++ internal/api/http/handler/employee_wi.go | 39 +++++ internal/api/http/handler/errors.go | 18 +++ internal/api/http/server.go | 59 +++++++ internal/request/request.go | 75 +++++++++ internal/services/employee_wi.go | 50 ++++++ internal/xml_loader/employee_wi.go | 48 ++++++ .../repository/mongodb/employee_wi/find.go | 45 ++++++ .../repository/mongodb/employee_wi/insert.go | 38 +++++ .../mongodb/employee_wi/repository.go | 13 ++ pkg/adapter/repository/mongodb/object_id.go | 11 ++ pkg/crono/crono.go | 84 ++++++++++ pkg/domain/employee_wi.go | 48 ++++++ 19 files changed, 1087 insertions(+) create mode 100644 .gitignore create mode 100644 cmd/etl/main.go create mode 100644 cmd/server/main.go create mode 100644 config/config.go create mode 100644 docker-compose.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/api/http/handler/employee_wi.go create mode 100644 internal/api/http/handler/errors.go create mode 100644 internal/api/http/server.go create mode 100644 internal/request/request.go create mode 100644 internal/services/employee_wi.go create mode 100644 internal/xml_loader/employee_wi.go create mode 100644 pkg/adapter/repository/mongodb/employee_wi/find.go create mode 100644 pkg/adapter/repository/mongodb/employee_wi/insert.go create mode 100644 pkg/adapter/repository/mongodb/employee_wi/repository.go create mode 100644 pkg/adapter/repository/mongodb/object_id.go create mode 100644 pkg/crono/crono.go create mode 100644 pkg/domain/employee_wi.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2282dbe --- /dev/null +++ b/.gitignore @@ -0,0 +1,102 @@ +.env +viper.default.yaml +.vscode +certs +coverage +.notes + +# frontend +node_modules +*-error.log +*.lock +sw.* +# Created by .ignore support plugin (hsz.mobi) +### Node template +# Logs +/logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# Nuxt generate +dist + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +# IDE / Editor +.idea + +# Service worker +sw.* + +# macOS +.DS_Store + +# Vim swap files +*.swp diff --git a/cmd/etl/main.go b/cmd/etl/main.go new file mode 100644 index 0000000..28e9e63 --- /dev/null +++ b/cmd/etl/main.go @@ -0,0 +1,141 @@ +package main + +import ( + "context" + "fmt" + "log" + _ "net/http/pprof" + "os" + "os/signal" + "sync" + "syscall" + + "gitea.urkob.com/urko/ess-etl-go/config" + "gitea.urkob.com/urko/ess-etl-go/internal/request" + "gitea.urkob.com/urko/ess-etl-go/internal/xml_loader" + "gitea.urkob.com/urko/ess-etl-go/pkg/adapter/repository/mongodb/employee_wi" + "gitea.urkob.com/urko/ess-etl-go/pkg/crono" + "gitea.urkob.com/urko/ess-etl-go/pkg/domain" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") + +func main() { + // flag.Parse() + // if *cpuprofile != "" { + // f, err := os.Create(*cpuprofile) + // if err != nil { + // log.Fatal(err) + // } + // pprof.StartCPUProfile(f) + // defer pprof.StopCPUProfile() + // } + + // // Add pprof endpoints + // go func() { + // log.Println(http.ListenAndServe("localhost:6060", nil)) + // }() + + cr := crono.New() + defer cr.Table() + cfg := config.NewConfig(".env") + + ctx := context.Background() + + dbOpts := options.Client() + dbOpts.ApplyURI(cfg.DbAddress) + + client, err := mongo.NewClient(dbOpts) + if err != nil { + log.Fatalln("mongo.NewClient", err) + } + + log.Println("mongodb client is connected") + + if err = client.Connect(ctx); err != nil { + log.Fatalln("client.Connect", err) + } + + employeeWICollection := client.Database(cfg.DbName).Collection(cfg.EmployeeWorkInformationCollection) + if err = employeeWICollection.Drop(ctx); err != nil { + log.Fatalln("employeeWICollection.Drop", err) + } + professionalRepo := employee_wi.NewRepo(employeeWICollection) + + r := request.NewRequestService(cfg.AmsApi, cfg.AmsApiKey) + + employeeIDList := cfg.EmployeeIdList + + ewiLoader := xml_loader.NewEmployeeWILoader(r) + from, to := "2023-01-01", "2023-01-31" + cr.MarkAndRestart("dependencies loaded") + ctx, cancel := context.WithCancel(signalContext(context.Background())) + errChan := make(chan error, 1) + ewiChan := make(chan []domain.EmployeeWorkInformation, len(employeeIDList)) + wg := &sync.WaitGroup{} + wg.Add(1) + + go func() { + defer wg.Done() + for _, v := range employeeIDList { + wg.Add(1) + go func(v string) { + cr.Restart() + defer wg.Done() + wi, err := ewiLoader.LoadEmployee(v, from, to) + if err != nil { + errChan <- err + return + } + ewiChan <- wi + cr.MarkAndRestart(fmt.Sprintf("ewiLoader.LoadEmployee | %s | from: %s to: %s", v, from, to)) + }(v) + } + }() + + go func() { + if err := <-errChan; err != nil { + log.Fatalln("error while process", err) + cancel() + } + }() + + go func() { + for v := range ewiChan { + log.Println("len v", len(v)) + err := professionalRepo.InsertMany(ctx, v) + if err != nil { + errChan <- err + return + } + cr.MarkAndRestart(fmt.Sprintf("database inserted: %d", len(v))) + } + + log.Println("cancel") + errChan <- nil + }() + wg.Wait() + // <-ctx.Done() + log.Println("gracefully shutdown") +} + +func signalContext(ctx context.Context) context.Context { + ctx, cancel := context.WithCancel(ctx) + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + go func() { + log.Println("listening for shutdown signal") + <-sigs + log.Println("shutdown signal received") + signal.Stop(sigs) + close(sigs) + cancel() + }() + + return ctx +} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..acfcc17 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "context" + "log" + "os" + "os/signal" + "syscall" + + "gitea.urkob.com/urko/ess-etl-go/config" + "gitea.urkob.com/urko/ess-etl-go/internal/api/http" + "gitea.urkob.com/urko/ess-etl-go/internal/services" + "gitea.urkob.com/urko/ess-etl-go/pkg/adapter/repository/mongodb/employee_wi" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +func main() { + cfg := config.NewConfig(".env") + ctx := context.Background() + + dbOpts := options.Client() + dbOpts.ApplyURI(cfg.DbAddress) + + client, err := mongo.NewClient(dbOpts) + if err != nil { + log.Fatalln("mongo.NewClient", err) + } + + log.Println("mongodb client is connected") + + if err = client.Connect(ctx); err != nil { + log.Fatalln("client.Connect", err) + } + + employeeWICollection := client.Database(cfg.DbName).Collection(cfg.EmployeeWorkInformationCollection) + professionalRepo := employee_wi.NewRepo(employeeWICollection) + + restServer := http. + NewRestServer(cfg). + WithProfessionalHandler(services.NewEmployeeWIService(ctx, professionalRepo)) + + if err = restServer.Start(cfg.ApiPort, ""); err != nil { + log.Fatalln("restServer.Start", err) + } + + ctx, cancel := context.WithCancel(signalContext(context.Background())) + defer cancel() + <-ctx.Done() + restServer.Shutdown() + log.Println("gracefully shutdown") +} + +func signalContext(ctx context.Context) context.Context { + ctx, cancel := context.WithCancel(ctx) + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + go func() { + log.Println("listening for shutdown signal") + <-sigs + log.Println("shutdown signal received") + signal.Stop(sigs) + close(sigs) + cancel() + }() + + return ctx +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..7fe77c3 --- /dev/null +++ b/config/config.go @@ -0,0 +1,40 @@ +package config + +import ( + "log" + + root_dir "gitea.urkob.com/urko/go-root-dir" + "github.com/joho/godotenv" + "github.com/kelseyhightower/envconfig" +) + +func RootDir() string { + return root_dir.RootDir("ess-etl-go") +} + +type Config struct { + ApiPort string `required:"true" split_words:"true"` + AmsApi string `required:"true" split_words:"true"` + AmsApiKey string `required:"true" split_words:"true"` + DbAddress string `required:"true" split_words:"true"` + DbName string `required:"true" split_words:"true"` + EmployeeWorkInformationCollection string `required:"true" split_words:"true"` + EmployeeIdList []string `required:"true" split_words:"true"` +} + +func NewConfig(envFile string) *Config { + if envFile != "" { + err := godotenv.Load(RootDir() + "/" + envFile) + if err != nil { + log.Fatalln("godotenv.Load:", err) + } + } + + cfg := &Config{} + err := envconfig.Process("", cfg) + if err != nil { + log.Fatalf("envconfig.Process: %s\n", err) + } + + return cfg +} diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..553c576 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,21 @@ +version: "3.4" +services: + mongodb: + image: mongo + restart: always + ports: + - "127.0.0.1:${MONGODB_PORT}:${MONGODB_PORT}" + environment: + MONGO_INITDB_ROOT_USERNAME: "${MONGODB_USER}" + MONGO_INITDB_ROOT_PASSWORD: "${MONGODB_PASS}" + MONGO_INITDB_DATABASE: "${DB_NAME}" + volumes: + - mongodata:/data/db + networks: + - mongonet +networks: + mongonet: + driver: bridge + +volumes: + mongodata: diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d0abd1f --- /dev/null +++ b/go.mod @@ -0,0 +1,40 @@ +module gitea.urkob.com/urko/ess-etl-go + +go 1.19 + +require ( + gitea.urkob.com/urko/go-root-dir v0.0.0-20230311113851-2f6d4355888a + github.com/gofiber/fiber/v2 v2.43.0 + github.com/jedib0t/go-pretty/v6 v6.4.6 + github.com/joho/godotenv v1.5.1 + github.com/kelseyhightower/envconfig v1.4.0 + go.mongodb.org/mongo-driver v1.11.3 +) + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/klauspost/compress v1.16.3 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/philhofer/fwd v1.1.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect + github.com/tinylib/msgp v1.1.8 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.45.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.1 // indirect + github.com/xdg-go/stringprep v1.0.3 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9f6fc77 --- /dev/null +++ b/go.sum @@ -0,0 +1,144 @@ +gitea.urkob.com/urko/go-root-dir v0.0.0-20230311113851-2f6d4355888a h1:s73cd3bRR6v0LGiBei841iIolbBJN2tbkUwN54X9vVg= +gitea.urkob.com/urko/go-root-dir v0.0.0-20230311113851-2f6d4355888a/go.mod h1:mU9nRHl70tBhJFbgKotpoXMV+s0wx+1uJ988p4oEpSo= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gofiber/fiber/v2 v2.43.0 h1:yit3E4kHf178B60p5CQBa/3v+WVuziWMa/G2ZNyLJB0= +github.com/gofiber/fiber/v2 v2.43.0/go.mod h1:mpS1ZNE5jU+u+BA4FbM+KKnUzJ4wzTK+FT2tG3tU+6I= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= +github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= +github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= +github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= +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/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= +github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +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/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA= +github.com/valyala/fasthttp v1.45.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y= +go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/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/mod v0.3.0/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.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/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.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/internal/api/http/handler/employee_wi.go b/internal/api/http/handler/employee_wi.go new file mode 100644 index 0000000..574af75 --- /dev/null +++ b/internal/api/http/handler/employee_wi.go @@ -0,0 +1,39 @@ +package handler + +import ( + "fmt" + "strconv" + + "gitea.urkob.com/urko/ess-etl-go/internal/services" + + "github.com/gofiber/fiber/v2" +) + +type EmployeeWorkInformationHandler struct { + employeeWISrv services.EmployeeWIService +} + +func NewEmployeeWorkInformation(employeeWISrv services.EmployeeWIService) *EmployeeWorkInformationHandler { + return &EmployeeWorkInformationHandler{ + employeeWISrv: employeeWISrv, + } +} + +func (hdl *EmployeeWorkInformationHandler) Get(c *fiber.Ctx) error { + id := c.Params("id", "") + if id == "" { + return JSONError(c, fiber.StatusBadRequest, fmt.Errorf("id is empty"), defaultErrMessage) + } + + employeeNumber, err := strconv.Atoi(id) + if err != nil { + return JSONError(c, fiber.StatusBadRequest, fmt.Errorf("strconv.Atoi: %s", err), defaultErrMessage) + } + + employeeWI, err := hdl.employeeWISrv.GetByEmployeeNumber(c.Context(), employeeNumber) + if err != nil { + return JSONError(c, fiber.StatusBadRequest, fmt.Errorf("professionalSrv.GetByID: %s", err), defaultErrMessage) + } + + return c.Status(fiber.StatusOK).JSON(employeeWI) +} diff --git a/internal/api/http/handler/errors.go b/internal/api/http/handler/errors.go new file mode 100644 index 0000000..a5b03d8 --- /dev/null +++ b/internal/api/http/handler/errors.go @@ -0,0 +1,18 @@ +package handler + +import ( + "log" + + "github.com/gofiber/fiber/v2" +) + +const ( + defaultErrMessage = "couldn't process request" +) + +func JSONError(c *fiber.Ctx, status int, err error, message string) error { + if err != nil { + log.Printf("JSONError: %s\n", err) + } + return c.Status(status).SendString("error: " + message) +} diff --git a/internal/api/http/server.go b/internal/api/http/server.go new file mode 100644 index 0000000..3b5e466 --- /dev/null +++ b/internal/api/http/server.go @@ -0,0 +1,59 @@ +package http + +import ( + "fmt" + "log" + + "gitea.urkob.com/urko/ess-etl-go/config" + "gitea.urkob.com/urko/ess-etl-go/internal/api/http/handler" + "gitea.urkob.com/urko/ess-etl-go/internal/services" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" +) + +type restServer struct { + app *fiber.App + cfg *config.Config + employeeWIHdl *handler.EmployeeWorkInformationHandler +} + +func NewRestServer(cfg *config.Config) *restServer { + return &restServer{ + cfg: cfg, + } +} + +func (s *restServer) WithProfessionalHandler(employeeWISrv services.EmployeeWIService) *restServer { + s.employeeWIHdl = handler.NewEmployeeWorkInformation(employeeWISrv) + return s +} +func (s *restServer) Start(apiPort, bearerToken string) error { + s.app = fiber.New() + + s.app.Use(cors.New(cors.Config{ + AllowMethods: "GET,POST,OPTIONS", + AllowOrigins: "*", + AllowHeaders: "Origin, Accept, Content-Type, X-CSRF-Token, Authorization", + ExposeHeaders: "Origin", + })) + + s.app.Get("/employee_wi/:id", func(c *fiber.Ctx) error { + return s.employeeWIHdl.Get(c) + }) + + s.app.Get("/employee_wi/:id", func(c *fiber.Ctx) error { + return s.employeeWIHdl.Get(c) + }) + + if err := s.app.Listen(":" + apiPort); err != nil { + return fmt.Errorf("app.Listen: %s", err) + } + return nil +} + +func (s *restServer) Shutdown() { + if err := s.app.Server().Shutdown(); err != nil { + log.Printf("app.Server().Shutdown(): %s\n", err) + } +} diff --git a/internal/request/request.go b/internal/request/request.go new file mode 100644 index 0000000..af3e235 --- /dev/null +++ b/internal/request/request.go @@ -0,0 +1,75 @@ +package request + +import ( + "bytes" + "fmt" + "io" + "net/http" + "strings" +) + +type RequestService struct { + api string + apiKey string +} + +func NewRequestService(api, apiKey string) RequestService { + return RequestService{ + api: api, + apiKey: apiKey, + } +} + +func getPayload(employeeIDList []string) (string, error) { + employees := strings.Builder{} + _, err := employees.Write([]byte(``)) + if err != nil { + return "", fmt.Errorf("error while write headers: %s", err) + } + + for _, employeeID := range employeeIDList { + employees.Write([]byte("" + employeeID + "")) + } + _, err = employees.Write([]byte("")) + if err != nil { + return "", fmt.Errorf("error while write footer: %s", err) + } + + return employees.String(), nil +} + +func (r RequestService) EmployeeWorkInformation(employeeIDList []string, from, to string) (io.Reader, error) { + url := r.api + "/EmployeeWorkInformation/Search/" + from + "/" + to + "/" + method := "POST" + + stringPayload, err := getPayload(employeeIDList) + if err != nil { + return nil, fmt.Errorf("getPayload: %s", err) + } + + payload := strings.NewReader(stringPayload) + client := &http.Client{} + req, err := http.NewRequest(method, url, payload) + + if err != nil { + return nil, fmt.Errorf("http.NewRequest: %s", err) + } + req.Header.Add("Cache-Control", "no-cache") + req.Header.Add("Authorization", r.apiKey) + req.Header.Add("Content-Type", "application/xml") + + res, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("client.Do: %s", err) + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("ioutil.ReadAll: %s", err) + } + + //log.Println("readed", string(body)) + + return bytes.NewReader(body), nil +} diff --git a/internal/services/employee_wi.go b/internal/services/employee_wi.go new file mode 100644 index 0000000..5d598f1 --- /dev/null +++ b/internal/services/employee_wi.go @@ -0,0 +1,50 @@ +package services + +import ( + "context" + "fmt" + + "gitea.urkob.com/urko/ess-etl-go/pkg/adapter/repository/mongodb" + "gitea.urkob.com/urko/ess-etl-go/pkg/adapter/repository/mongodb/employee_wi" + "gitea.urkob.com/urko/ess-etl-go/pkg/domain" + + "go.mongodb.org/mongo-driver/bson" +) + +type EmployeeWIService struct { + ctx context.Context + repo *employee_wi.Repo +} + +func NewEmployeeWIService(ctx context.Context, repo *employee_wi.Repo) EmployeeWIService { + return EmployeeWIService{ + ctx: ctx, + repo: repo, + } +} + +func (p *EmployeeWIService) GetByEmployeeNumber(ctx context.Context, employeeNumber int) ([]domain.EmployeeWorkInformation, error) { + model, err := p.repo.Find(ctx, bson.M{ + "employee_number": employeeNumber, + }) + if err != nil { + return nil, fmt.Errorf("repo.FindById: %s", err) + } + return model, nil +} + +func (p *EmployeeWIService) GetByAll(ctx context.Context, id string) (*domain.EmployeeWorkInformation, error) { + model, err := p.repo.FindById(ctx, mongodb.MustObjectID(id)) + if err != nil { + return nil, fmt.Errorf("repo.FindById: %s", err) + } + return model, nil +} + +func (p *EmployeeWIService) InsertMany(ctx context.Context, ei []domain.EmployeeWorkInformation) error { + err := p.repo.InsertMany(ctx, ei) + if err != nil { + return fmt.Errorf("repo.AddAppointmentTo: %s", err) + } + return nil +} diff --git a/internal/xml_loader/employee_wi.go b/internal/xml_loader/employee_wi.go new file mode 100644 index 0000000..2cfe7ac --- /dev/null +++ b/internal/xml_loader/employee_wi.go @@ -0,0 +1,48 @@ +package xml_loader + +import ( + "encoding/xml" + "fmt" + "io" + + "gitea.urkob.com/urko/ess-etl-go/internal/request" + "gitea.urkob.com/urko/ess-etl-go/pkg/domain" +) + +type EmployeeWILoader struct { + r request.RequestService +} + +func NewEmployeeWILoader(r request.RequestService) EmployeeWILoader { + return EmployeeWILoader{r: r} +} + +func (e EmployeeWILoader) LoadEmployeeList(employeeIDList []string, from, to string) ([]domain.EmployeeWorkInformation, error) { + reader, err := e.r.EmployeeWorkInformation(employeeIDList, from, to) + if err != nil { + return nil, fmt.Errorf("r.EmployeeWorkInformation: %s", err) + } + + return loadFromXML(reader) +} + +func (e EmployeeWILoader) LoadEmployee(employeeID, from, to string) ([]domain.EmployeeWorkInformation, error) { + employeeIDList := []string{employeeID} + + reader, err := e.r.EmployeeWorkInformation(employeeIDList, from, to) + if err != nil { + return nil, fmt.Errorf("r.EmployeeWorkInformation: %s", err) + } + + return loadFromXML(reader) +} + +func loadFromXML(xmlFile io.Reader) ([]domain.EmployeeWorkInformation, error) { + var awi domain.ArrayOfEmployeeWorkInformation + + if err := xml.NewDecoder(xmlFile).Decode(&awi); err != nil { + return nil, fmt.Errorf("xml.NewDecoder.Decode: %s", err) + } + + return awi.EmployeeWorkInfos, nil +} diff --git a/pkg/adapter/repository/mongodb/employee_wi/find.go b/pkg/adapter/repository/mongodb/employee_wi/find.go new file mode 100644 index 0000000..ee08998 --- /dev/null +++ b/pkg/adapter/repository/mongodb/employee_wi/find.go @@ -0,0 +1,45 @@ +package employee_wi + +import ( + "context" + "fmt" + + "gitea.urkob.com/urko/ess-etl-go/pkg/domain" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func (p *Repo) Find(ctx context.Context, filter bson.M) ([]domain.EmployeeWorkInformation, error) { + cursor, err := p.collection.Find(ctx, filter) + if err != nil { + return nil, fmt.Errorf("p.collection.Find: %s", err) + } + + var pList []domain.EmployeeWorkInformation + if err := cursor.All(ctx, &pList); err != nil { + return nil, fmt.Errorf("p.cursor.All: %s", err) + } + + return pList, nil +} + +func (p *Repo) FindOne(ctx context.Context) (*domain.EmployeeWorkInformation, error) { + res := p.collection.FindOne(ctx, bson.M{}) + var model *domain.EmployeeWorkInformation + if err := res.Decode(&model); err != nil { + return nil, fmt.Errorf("doc.Decode: %s", err) + } + + return model, nil +} + +func (p *Repo) FindById(ctx context.Context, id primitive.ObjectID) (*domain.EmployeeWorkInformation, error) { + res := p.collection.FindOne(ctx, bson.M{"_id": id}) + var model *domain.EmployeeWorkInformation + if err := res.Decode(model); err != nil { + return nil, fmt.Errorf("doc.Decode: %s", err) + } + + return model, nil +} diff --git a/pkg/adapter/repository/mongodb/employee_wi/insert.go b/pkg/adapter/repository/mongodb/employee_wi/insert.go new file mode 100644 index 0000000..25d76e8 --- /dev/null +++ b/pkg/adapter/repository/mongodb/employee_wi/insert.go @@ -0,0 +1,38 @@ +package employee_wi + +import ( + "context" + "fmt" + + "gitea.urkob.com/urko/ess-etl-go/pkg/domain" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func (p *Repo) InsertOne(ctx context.Context, dto *domain.EmployeeWorkInformation) (string, error) { + inserted, err := p.collection.InsertOne(ctx, dto) + if err != nil { + return "", fmt.Errorf("p.collection.InsertOne: %s", err) + } + + oid, ok := inserted.InsertedID.(primitive.ObjectID) + if !ok { + return "", fmt.Errorf("res.InsertedID is not valid _id") + } + + return oid.Hex(), nil +} + +func (p *Repo) InsertMany(ctx context.Context, dto []domain.EmployeeWorkInformation) error { + elements := make([]interface{}, 0, len(dto)) + for _, v := range dto { + v.ID = primitive.NewObjectID() + elements = append(elements, v) + } + _, err := p.collection.InsertMany(ctx, elements) + if err != nil { + return fmt.Errorf("p.collection.InsertMany: %s", err) + } + + return nil +} diff --git a/pkg/adapter/repository/mongodb/employee_wi/repository.go b/pkg/adapter/repository/mongodb/employee_wi/repository.go new file mode 100644 index 0000000..904f562 --- /dev/null +++ b/pkg/adapter/repository/mongodb/employee_wi/repository.go @@ -0,0 +1,13 @@ +package employee_wi + +import ( + "go.mongodb.org/mongo-driver/mongo" +) + +func NewRepo(collection *mongo.Collection) *Repo { + return &Repo{collection: collection} +} + +type Repo struct { + collection *mongo.Collection +} diff --git a/pkg/adapter/repository/mongodb/object_id.go b/pkg/adapter/repository/mongodb/object_id.go new file mode 100644 index 0000000..bfe002a --- /dev/null +++ b/pkg/adapter/repository/mongodb/object_id.go @@ -0,0 +1,11 @@ +package mongodb + +import "go.mongodb.org/mongo-driver/bson/primitive" + +func MustObjectID(id string) primitive.ObjectID { + objectID, err := primitive.ObjectIDFromHex(id) + if err != nil { + return primitive.NilObjectID + } + return objectID +} diff --git a/pkg/crono/crono.go b/pkg/crono/crono.go new file mode 100644 index 0000000..ed37ef1 --- /dev/null +++ b/pkg/crono/crono.go @@ -0,0 +1,84 @@ +package crono + +import ( + "fmt" + "os" + "time" + + "github.com/jedib0t/go-pretty/v6/table" +) + +type Timing struct { + Msg string + Elapsed time.Duration +} + +type Crono struct { + Begin time.Time + Cursor time.Time + Timings []Timing +} + +// new cronograph with autostart. +func New() *Crono { + c := new(Crono) + c.Timings = make([]Timing, 0) + c.Begin = time.Now() + c.Cursor = c.Begin + return c +} + +// restart the crono +// keeps the begin time. +func (c *Crono) Restart() { + c.Cursor = time.Now() +} + +// push a mark. +func (c *Crono) Mark(msg string) { + c.Timings = append(c.Timings, Timing{msg, c.GetElapsed()}) +} + +// push a mark and restart the cursor time. +func (c *Crono) MarkAndRestart(msg string) { + c.Mark(msg) + c.Restart() +} + +// time in seconds since last start +// it doesn't restart the crono. +func (c *Crono) GetElapsed() time.Duration { + return time.Since(c.Cursor) +} + +// total time sice start. +func (c *Crono) Total() time.Duration { + return time.Since(c.Begin) +} + +func (c *Crono) Table() { + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"#", "Description", "Elapsed"}) + for i, e := range c.Timings { + t.AppendRow([]interface{}{i, e.Msg, durationToString(e.Elapsed)}) + } + t.AppendSeparator() + t.AppendFooter(table.Row{"", "Total", durationToString(c.Total())}) + t.Render() +} + +func durationToString(t time.Duration) string { + timeString := fmt.Sprintf("%dms", t.Milliseconds()) + if t.Seconds() > 3 { + timeString += " !!!" + } else if t.Seconds() > 2 { + timeString += " .!!" + } else if t.Seconds() > 1 { + timeString += " ..!" + } else { + timeString += " ..." + } + + return timeString +} diff --git a/pkg/domain/employee_wi.go b/pkg/domain/employee_wi.go new file mode 100644 index 0000000..2d95d70 --- /dev/null +++ b/pkg/domain/employee_wi.go @@ -0,0 +1,48 @@ +package domain + +import ( + "encoding/xml" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type WorkInfo struct { + EmployeeNumber int `xml:"EmployeeNumber" json:"employee_number" bson:"employee_number"` + Date string `xml:"Date" json:"date" bson:"date"` + Shifts []Shift `xml:"Shifts>Shift" json:"shifts" bson:"shifts"` +} + +type Shift struct { + Start string `xml:"Start" json:"start" bson:"start"` + End string `xml:"End" json:"end" bson:"end"` + ShiftCode string `xml:"ShiftCode" json:"shift_code" bson:"shift_code"` + ActualStart string `xml:"ActualStart" json:"actual_start" bson:"actual_start"` + ActualEnd string `xml:"ActualEnd" json:"actual_end" bson:"actual_end"` + RoleCode string `xml:"RoleCode" json:"role_code" bson:"role_code"` + ShiftCategoryCode string `xml:"ShiftCategoryCode" json:"shift_category_code" bson:"shift_category_code"` + CustomFields []CustomField `xml:"CustomFields>CustomField" json:"custom_fields" bson:"custom_fields"` +} + +type Baseline struct { + BaselineType string `xml:"BaselineType,attr" json:"baseline_type" bson:"baseline_type"` + Shifts []Shift `xml:"Shifts>Shift" json:"shifts" bson:"shifts"` +} + +type CustomField struct { + Name string `xml:"Name" json:"name" bson:"name"` + Value string `xml:"Value" json:"value" bson:"value"` +} + +type EmployeeWorkInformation struct { + ID primitive.ObjectID `json:"id" bson:"_id"` + EmployeeNumber int `xml:"EmployeeNumber" json:"employee_number" bson:"employee_number"` + Date string `xml:"Date" json:"date"` + WorkInformation WorkInfo `xml:"WorkInformation" json:"work_information" bson:"work_information"` + Baselines []Baseline `json:"baselines" bson:"baselines"` + CustomFields []CustomField `json:"custom_fields" bson:"custom_fields"` +} + +type ArrayOfEmployeeWorkInformation struct { + XMLName xml.Name `xml:"ArrayOfEmployeeWorkInformation"` + EmployeeWorkInfos []EmployeeWorkInformation `xml:"EmployeeWorkInformation"` +}