├── .DS_Store ├── GoTour ├── FlowControlStatement │ ├── defer.go │ ├── exercise.go │ ├── forContinued.go │ ├── forLoop.go │ ├── if.go │ ├── ifShort.go │ ├── ifelse.go │ ├── nakedSwitch.go │ ├── stackingDefers.go │ ├── switch.go │ ├── switchOrder.go │ └── while.go ├── MethodsInterfaces │ ├── emptyInterface.go │ ├── errors.go │ ├── interfaceValues.go │ ├── interfaces.go │ ├── interfaces1.go │ ├── interfacesNil.go │ ├── methods.go │ ├── methodsAreFunctions.go │ ├── methodsPointerIndirection.go │ ├── methodsPointerIndirection2.go │ ├── nilIntervaceValues.go │ ├── pointerReceivers.go │ ├── pointersFunctions.go │ ├── stringers.go │ ├── typeAssertions.go │ ├── typeSwitches.go │ └── valueOrPointerReceivers.go ├── MoreTypes │ ├── appendingSlice.go │ ├── arrays.go │ ├── funcClosures.go │ ├── functionValues.go │ ├── mapLiterals.go │ ├── mapLiteralsContinued.go │ ├── maps.go │ ├── mutatingMaps.go │ ├── nilSlice.go │ ├── omitRange.go │ ├── pointers.go │ ├── pointersToStruct.go │ ├── range.go │ ├── slice.go │ ├── sliceAsReferences.go │ ├── sliceDefault.go │ ├── sliceLengthCapacity.go │ ├── sliceLiterals.go │ ├── sliceOfSlice.go │ ├── sliceWithMake.go │ ├── struct.go │ ├── structFields.go │ └── structLiterals.go └── PackageVariableFunction │ ├── addition.go │ ├── basicTypes.go │ ├── constants.go │ ├── hello.go │ ├── multipleResults.go │ ├── namedReturned.go │ ├── numericConstants.go │ ├── shortVarDeclaration.go │ ├── sqrt.go │ ├── stringFormat.go │ ├── typeConversion.go │ ├── varInitialized.go │ └── variables.go ├── GolangBasic ├── fungsi_variadic.go ├── interface.go └── pointer.go ├── README.md ├── analytics-cli-app ├── .gitignore ├── README.md ├── analytics │ ├── go.mod │ ├── main.go │ └── main_test.go ├── generate │ ├── go.mod │ ├── main.go │ └── main_test.go └── tasks.txt ├── benchmarking-article-material ├── main.go └── main_test.go ├── benchmarking ├── Makefile ├── README.md ├── coverage.out ├── go.mod ├── main.go ├── main_test.go └── stress.bash ├── concurrency ├── README.md ├── channels │ └── channels.go ├── goroutines │ └── goroutine.go ├── maps │ └── maps.go ├── mutex │ └── mutex.go ├── waitgroup │ └── waitgroup.go └── workerpool │ └── workerpool.go ├── conductor-orchestration-notification ├── .env ├── .idea │ ├── .gitignore │ ├── conductor-orchestration-notification.iml │ ├── modules.xml │ ├── sqldialects.xml │ └── vcs.xml ├── Dockerfile ├── docker-compose.yaml ├── go.mod ├── go.sum ├── main.go ├── postgres.Dockerfile ├── sql │ └── script.sql └── tmp │ └── main ├── containerized-dev ├── .devcontainer │ ├── Dockerfile │ ├── devcontainer.json │ └── docker-compose.yaml ├── go.mod ├── go.sum └── main.go ├── db-experiment ├── .air.toml ├── .gitignore ├── .idea │ ├── .gitignore │ ├── dataSources.xml │ ├── db-experiment.iml │ ├── modules.xml │ ├── sqldialects.xml │ └── vcs.xml ├── Dockerfile ├── config │ ├── config.go │ └── database.go ├── docker-compose.yaml ├── go.mod ├── go.sum ├── handlers │ ├── todo.go │ ├── todo_test.go │ ├── todo_v2.go │ └── todo_v2_test.go ├── main.go ├── models │ ├── abstract_concrete.go │ ├── config.go │ ├── filter.go │ ├── null.go │ ├── response.go │ ├── todo.go │ └── todo_v2.go ├── postgres.Dockerfile ├── repositories │ ├── todo.go │ └── todo_v2.go ├── server │ ├── handler.go │ ├── repository.go │ ├── setup.go │ └── usecase.go ├── sql │ └── script.sql ├── usecases │ ├── todo.go │ └── todo_v2.go └── util │ ├── filter.go │ └── truncate.go ├── functional-programming ├── README.md ├── content-1 │ └── main.go └── content-2 │ └── main.go ├── gin-restapi ├── go.mod ├── go.sum ├── main.go └── models │ ├── book.go │ └── setup.go ├── go-notes ├── README.md ├── anti-patterns.md ├── benchmarking.md ├── best-practices-and-patterns.md ├── coding-conventions.md ├── concurrency.md ├── error-handling.md ├── functional-programming.md ├── idiomatic-go.md ├── images │ ├── img.png │ ├── img_1.png │ ├── img_2.png │ ├── img_3.png │ ├── img_4.png │ ├── img_5.png │ ├── img_6.png │ ├── img_7.png │ └── img_8.png ├── importance-of-coding-conventions-and-standards.md ├── inner-go.md ├── main.go ├── naming-conventions.md ├── practical-go.md ├── project-structure-in-go.md ├── solid-go.md └── testing.md ├── go-ocr-service ├── .air.toml ├── go.mod ├── go.sum └── main.go ├── grpc-auth ├── auth.proto ├── auth │ ├── auth.go │ └── auth.pb.go ├── config │ └── database.go ├── generate.sh ├── go.mod ├── go.sum ├── main.go ├── models │ └── user.go ├── repository │ └── user.go ├── usecase │ └── user.go └── utils │ ├── hash_password.go │ └── token.go ├── grpc-todo ├── config │ └── database.go ├── generate.sh ├── go.mod ├── go.sum ├── main.go ├── models │ └── todo.go ├── repository │ └── todo.go ├── todo.proto ├── todo │ ├── todo.go │ └── todo.pb.go └── usecase │ └── todo.go ├── hexagonal-architecture └── README.md ├── lbyl-eafp-article-material ├── README.md ├── go.mod └── main.go ├── learn-restapi-testing ├── .idea │ ├── .gitignore │ ├── learn-restapi-testing.iml │ ├── modules.xml │ └── vcs.xml ├── example │ ├── main.go │ └── main_test.go ├── go.mod ├── go.sum ├── main.go ├── main_test.go └── notes.md ├── learn_gokit ├── cmd │ └── main.go ├── endpoint.go ├── go.mod ├── go.sum ├── server.go ├── service.go ├── service_test.go └── transport.go ├── learn_grpc ├── chat.proto ├── chat │ ├── chat.go │ └── chat.pb.go ├── client.go ├── go.mod ├── go.sum ├── main.go └── server.go ├── load-test-experiment ├── .air.toml ├── .gitignore ├── .idea │ ├── .gitignore │ ├── dataSources.xml │ ├── load-test-experiment.iml │ ├── modules.xml │ ├── sqldialects.xml │ └── vcs.xml ├── Dockerfile ├── config │ ├── config.go │ └── database.go ├── docker-compose.yaml ├── go.mod ├── go.sum ├── handler │ ├── heavy_v1.go │ ├── light_v1.go │ ├── light_v2.go │ ├── medium_v1.go │ └── medium_v2.go ├── main.go ├── model │ ├── config.go │ ├── filter.go │ ├── light_v1.go │ ├── light_v2.go │ ├── medium_v1.go │ ├── medium_v2.go │ ├── nulls.go │ ├── request.go │ ├── response.go │ └── route.go ├── postgres.Dockerfile ├── repository │ ├── light_v1.go │ ├── light_v2.go │ ├── medium_v1.go │ └── medium_v2.go ├── sql │ └── script.sql ├── usecase │ ├── light_v1.go │ ├── light_v2.go │ ├── medium_v1.go │ └── medium_v2.go └── utils │ ├── actions │ └── light_v1.go │ ├── filter.go │ ├── response.go │ └── time.go ├── muxone └── main.go ├── play-with-reflect ├── .idea │ ├── .gitignore │ ├── modules.xml │ ├── play-with-reflect.iml │ └── vcs.xml └── main.go ├── redis-yo ├── .gitignore ├── Dockerfile ├── configs │ └── redis.go ├── docker-compose.yaml ├── go.mod ├── go.sum ├── handlers │ └── example.go └── main.go ├── restapi-test-app ├── .air.toml ├── .gitignore ├── .idea │ ├── .gitignore │ ├── dataSources.xml │ ├── modules.xml │ ├── restapi-tested-app.iml │ ├── sqldialects.xml │ └── vcs.xml ├── Dockerfile ├── README.md ├── config │ ├── config.go │ └── db.go ├── docker-compose.yaml ├── entities │ ├── config.entities.go │ ├── response.entities.go │ └── tweet.entities.go ├── go.mod ├── go.sum ├── handlers │ ├── tweet.go │ └── tweet_mocked_test.go ├── main.go ├── mocks │ ├── TweetRepository.go │ └── TweetUsecase.go ├── postgres.Dockerfile ├── repositories │ ├── tweet.go │ └── tweet_test.go ├── sample.env ├── server │ ├── handlers.go │ ├── repositories.go │ ├── setup.go │ └── usecases.go ├── sql │ └── tweet.sql ├── usecases │ ├── tweet.go │ └── tweet_test.go └── utils │ ├── server_http.go │ └── truncate_table.go ├── restapi ├── .gitignore ├── config │ └── database.config.go ├── go.mod ├── go.sum ├── handler │ ├── auth.handler.go │ ├── service.handler.go │ └── user.handler.go ├── main.go ├── makefile ├── middleware │ └── auth.middleware.go ├── model │ ├── service.model.go │ └── user.model.go ├── repository │ ├── service.repository.go │ └── user.repository.go ├── sql │ └── base.sql └── utils │ ├── password.go │ ├── response.go │ └── token.go ├── restfull └── main.go ├── swagger-docs-app ├── .gitignore ├── Makefile ├── api │ └── example.go ├── docs │ └── example.go ├── go.mod ├── go.sum ├── main.go └── swagger.yaml ├── testify-yo ├── .idea │ ├── .gitignore │ ├── modules.xml │ ├── testify-yo.iml │ └── vcs.xml ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── mockyo │ └── mockyo_test.go └── suiteyo │ └── suiteyo_test.go ├── testing-yo ├── .idea │ ├── .gitignore │ ├── modules.xml │ ├── testing-yo.iml │ └── vcs.xml ├── README.md ├── main.go └── main_test.go └── twit ├── .air.toml ├── .gitignore ├── .idea ├── .gitignore ├── modules.xml ├── twit.iml └── vcs.xml ├── Dockerfile ├── Dockerfile.test ├── Makefile ├── configs ├── config.go └── db.go ├── dev.env ├── docker-compose-test.yaml ├── docker-compose.yaml ├── docs └── user.docs.go ├── go.mod ├── go.sum ├── handlers └── user.handlers.go ├── main.go ├── middlewares └── auth.go ├── models ├── config.models.go ├── errors.models.go ├── requests │ └── user.requests.go ├── responses │ ├── main.responses.go │ └── user.models.go └── user.models.go ├── repositories └── user.repositories.go ├── servers ├── server.go └── structs.go ├── swagger.yaml ├── test.env ├── test_result.txt ├── tests ├── user_test.go └── users │ ├── handlers │ ├── login_user_test.go │ └── register_user_test.go │ └── repositories │ └── register_user_test.go ├── tmp ├── air_errors.log └── main ├── usecases └── user.usecases.go └── utils ├── logging.go ├── password.utils.go ├── timing.go ├── token.go └── truncate-table.go /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/.DS_Store -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/defer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | defer fmt.Println("Sekardayu Hana Pradiani") 7 | 8 | fmt.Println("Saskia Nurul Azhima") 9 | } -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/exercise.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | func Sqrt(x float64) float64 { 9 | var z float64 = 1 10 | for i := 0; i < 10; i++ { 11 | z -= (z*z - x) / (2*z) 12 | } 13 | return z 14 | } 15 | 16 | func main() { 17 | fmt.Println(Sqrt(2)) 18 | fmt.Println(math.Sqrt(2)) 19 | } 20 | -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/forContinued.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | sum := 1 7 | for ; sum < 1000; { 8 | sum += sum 9 | } 10 | fmt.Println(sum) 11 | } -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/forLoop.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | for i := 0; i <= 10; i++ { 7 | fmt.Println(i) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/if.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | func square_root(x float64) (y string) { 9 | if x < 0 { 10 | return square_root(-x) + "i" 11 | } 12 | return fmt.Sprint(math.Sqrt(x)) 13 | } 14 | 15 | func main() { 16 | fmt.Println(square_root(-4), square_root(4)) 17 | } -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/ifShort.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | func pow(x, n, lim float64) float64 { 9 | if v := math.Pow(x, n); v < lim { 10 | return v 11 | } 12 | return lim 13 | } 14 | 15 | func main() { 16 | fmt.Println(pow(3, 2, 10)) 17 | fmt.Println(pow(3, 3, 20)) 18 | } -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/ifelse.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | func pow(x, n, lim float64) float64 { 9 | if v := math.Pow(x, n); v < lim { 10 | return v 11 | } else { 12 | fmt.Printf("%g >= %g\n", v, lim) 13 | } 14 | // can't use v here, though 15 | return lim 16 | } 17 | 18 | func main() { 19 | fmt.Println( 20 | pow(3, 2, 10), 21 | pow(3, 3, 20), 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/nakedSwitch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | num := 100 7 | switch { 8 | case num < 10: 9 | fmt.Println("Less than 10") 10 | case num < 20: 11 | fmt.Println("Greater or equal to ten and less than 20") 12 | default: 13 | fmt.Println("What are you talking about!") 14 | } 15 | } -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/stackingDefers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("First") 7 | 8 | for i := 0; i < 10; i++ { 9 | defer fmt.Println("Ten times last") 10 | } 11 | 12 | fmt.Println("Second") 13 | } -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/switch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | switch num := 20; num { 7 | case 10: 8 | fmt.Println("Ten") 9 | case 20: 10 | fmt.Println("Twenty") 11 | default: 12 | fmt.Println("Default") 13 | } 14 | } -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/switchOrder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | today := time.Now().Weekday() 10 | switch today { 11 | case today + 0: 12 | fmt.Println("Today wohooo!") 13 | default: 14 | fmt.Println("I dont't know when") 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GoTour/FlowControlStatement/while.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | sum := 1 7 | for sum < 1000 { 8 | sum += sum 9 | } 10 | fmt.Println(sum) 11 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/emptyInterface.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func describe(i interface{}) { 6 | fmt.Printf("(%v, %T)\n", i, i) 7 | } 8 | 9 | func main() { 10 | var a interface{} 11 | describe(a) 12 | 13 | b := 21 14 | describe(b) 15 | 16 | c := "Sekardayu" 17 | describe(c) 18 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/errors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type MyError struct{ 9 | When time.Time 10 | What string 11 | } 12 | 13 | func (e *MyError) Error() string { 14 | return fmt.Sprintf("at %v with problem %v", e.When, e.What) 15 | } 16 | 17 | func run() error { 18 | return &MyError{time.Now(), "What is wrong bruh?"} 19 | } 20 | 21 | func main() { 22 | v := run() 23 | fmt.Println(v) 24 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/interfaceValues.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type I interface { 6 | P() 7 | } 8 | 9 | type S struct { 10 | name string 11 | } 12 | 13 | func (s *S) P() { 14 | fmt.Println(s.name) 15 | } 16 | 17 | type F float64 18 | 19 | func (f F) P() { 20 | fmt.Println(f) 21 | } 22 | 23 | func describe(i I) { 24 | fmt.Printf("Value: %v, Type: %T", i, i) 25 | } 26 | 27 | func main() { 28 | var i I 29 | i = &S{"Sekar"} 30 | describe(i) 31 | i.P() 32 | 33 | var j I 34 | j = F(-14) 35 | describe(j) 36 | j.P() 37 | } 38 | 39 | -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/interfaces.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | func main() { 9 | var a, b Abser 10 | x := MyFloat(math.Sqrt2) 11 | y := Point{3, 4} 12 | 13 | fmt.Println(x, y) 14 | a = &x 15 | b = &y 16 | 17 | fmt.Println(a.Abs()) 18 | fmt.Println(b.Abs()) 19 | } 20 | 21 | type Abser interface { 22 | Abs() float64 23 | } 24 | 25 | type Point struct { 26 | X, Y float64 27 | } 28 | 29 | type MyFloat float64 30 | 31 | func (f *MyFloat) Abs() float64 { 32 | if *f < 0 { 33 | return -float64(*f) 34 | } 35 | return float64(*f) 36 | } 37 | 38 | func (p *Point) Abs() float64 { 39 | return math.Sqrt(p.X*p.X + p.Y*p.Y) 40 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/interfaces1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var a LivingBeing = Human{"Sekar", 22} 7 | var b LivingBeing = Animal{"Dog", 4} 8 | 9 | fmt.Println(a) 10 | fmt.Println(b) 11 | } 12 | 13 | type LivingBeing interface { 14 | Eat() string 15 | } 16 | 17 | type Human struct { 18 | name string 19 | age int 20 | } 21 | 22 | type Animal struct { 23 | species string 24 | leg int 25 | } 26 | 27 | func (h Human) Eat() string { 28 | return "I will eat everything!" 29 | } 30 | 31 | func (a Animal) Eat() string { 32 | return "Munch... Munch... Munch..." 33 | } 34 | -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/interfacesNil.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type I interface{ 6 | P() 7 | } 8 | 9 | type S struct { 10 | name string 11 | } 12 | 13 | func (s *S) P() { 14 | if s == nil { 15 | fmt.Println("") 16 | return 17 | } 18 | fmt.Println(s.name) 19 | } 20 | 21 | func describe(i I) { 22 | fmt.Printf("Value: %v, Type: %T\n", i, i) 23 | } 24 | 25 | func main() { 26 | var i I 27 | var s *S 28 | describe(i) 29 | i = s 30 | describe(i) 31 | i.P() 32 | 33 | var t = &S{"Sekardayu"} 34 | describe(t) 35 | t.P() 36 | } 37 | 38 | -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/methods.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | type Point struct { 9 | X, Y float64 10 | } 11 | 12 | func (p Point) Distance() float64 { 13 | return math.Sqrt(p.X*p.X + p.Y*p.Y) 14 | } 15 | 16 | func main() { 17 | pOne := Point{3, 4} 18 | fmt.Println(pOne.Distance()) 19 | pTwo := Point{5, 12} 20 | fmt.Println(pTwo.Distance()) 21 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/methodsAreFunctions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | type Point struct { 9 | X, Y float64 10 | } 11 | 12 | func Distance(p Point) float64 { 13 | return math.Sqrt(p.X*p.X + p.Y*p.Y) 14 | } 15 | 16 | func main() { 17 | p := Point{5, 12} 18 | fmt.Println(Distance(p)) 19 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/methodsPointerIndirection.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Point struct { 6 | X, Y float64 7 | } 8 | 9 | func (p *Point) Scale(f float64) { 10 | p.X = p.X * f 11 | p.Y = p.Y * f 12 | } 13 | 14 | func ScaleFunc(p *Point, f float64) { 15 | p.X = p.X * f 16 | p.Y = p.Y * f 17 | } 18 | 19 | func main() { 20 | p1 := Point{3, 4} 21 | p1.Scale(5) 22 | fmt.Println(p1) 23 | ScaleFunc(&p1, 5) 24 | fmt.Println(p1) 25 | 26 | p2 := &Point{5, 12} 27 | p2.Scale(3) 28 | fmt.Println(p2) 29 | ScaleFunc(p2, 3) 30 | fmt.Println(p2) 31 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/methodsPointerIndirection2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | type Point struct { 9 | X, Y float64 10 | } 11 | 12 | func (p Point) Abs() float64 { 13 | return math.Sqrt(p.X*p.X + p.Y*p.Y) 14 | } 15 | 16 | func AbsFunc(p Point) float64 { 17 | return math.Sqrt(p.X*p.X + p.Y*p.Y) 18 | } 19 | 20 | func main() { 21 | p1 := Point{3, 4} 22 | fmt.Println(p1.Abs()) 23 | fmt.Println(AbsFunc(p1)) 24 | 25 | p2 := &Point{5, 12} 26 | fmt.Println(p2.Abs()) 27 | fmt.Println(AbsFunc(*p2)) 28 | } 29 | 30 | -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/nilIntervaceValues.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type I interface { 6 | M() 7 | } 8 | 9 | func main() { 10 | var i I 11 | describe(i) 12 | i.M() 13 | } 14 | 15 | func describe(i I) { 16 | fmt.Printf("(%v, %T)\n", i, i) 17 | } 18 | -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/pointerReceivers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | type Point struct { 9 | X, Y float64 10 | } 11 | 12 | func (p Point) Abs() float64 { 13 | return math.Sqrt(p.X*p.X + p.Y*p.Y) 14 | } 15 | 16 | func (p *Point) Scale(factor float64) { 17 | p.X = p.X * factor 18 | p.Y = p.Y * factor 19 | } 20 | 21 | func main() { 22 | p := Point{3, 4} 23 | p.Scale(5) 24 | fmt.Println(p.Abs()) 25 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/pointersFunctions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | type Point struct { 9 | X, Y float64 10 | } 11 | 12 | func Abs(p Point) float64{ 13 | return math.Sqrt(p.X*p.X + p.Y*p.Y) 14 | } 15 | 16 | func Scale(p *Point, factor float64) { 17 | p.X = p.X * factor 18 | p.Y = p.Y * factor 19 | } 20 | 21 | func main() { 22 | p := Point{3, 4} 23 | fmt.Println(Abs(p)) 24 | Scale(&p, 5) 25 | fmt.Println(p) 26 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/stringers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Person struct { 6 | Name string 7 | Age int 8 | } 9 | 10 | func (p Person) String() string { 11 | return fmt.Sprintf("My name is %v, and I am %v years old.", p.Name, p.Age) 12 | } 13 | 14 | func main() { 15 | sekar := Person{"Sekardayu Hana Pradiani", 22} 16 | saskia := Person{"Saskia Nurul Azhima", 20} 17 | 18 | fmt.Println(sekar) 19 | fmt.Println(saskia) 20 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/typeAssertions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var i interface{} = "Sekardayu" 7 | 8 | a := i.(string) 9 | fmt.Println(a) 10 | 11 | a, ok := i.(string) 12 | fmt.Println(a, ok) 13 | 14 | b, ok := i.(int) 15 | fmt.Println(b, ok) 16 | 17 | b = i.(int) // Panic 18 | fmt.Println(b) 19 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/typeSwitches.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func do(i interface{}) { 6 | switch v := i.(type) { 7 | case string: 8 | fmt.Printf("I am a string, and I am %v\n", v) 9 | case int: 10 | fmt.Printf("Two plus two is %v\n", v) 11 | default: 12 | fmt.Printf("What am I? %T\n", v) 13 | } 14 | } 15 | 16 | 17 | func main() { 18 | i := 10 19 | do(i) 20 | 21 | j := "Sekardayu" 22 | do(j) 23 | } -------------------------------------------------------------------------------- /GoTour/MethodsInterfaces/valueOrPointerReceivers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | type Point struct { 9 | X, Y float64 10 | } 11 | 12 | func (p *Point) Abs() float64 { 13 | return math.Sqrt(p.X*p.X + p.Y*p.Y) 14 | } 15 | 16 | func (p *Point) Scale(f float64) { 17 | p.X = p.X * f 18 | p.Y = p.Y * f 19 | } 20 | 21 | func main() { 22 | p := Point{3, 4} 23 | fmt.Printf("Value before scaling: %f\n", p.Abs()) 24 | p.Scale(5) 25 | fmt.Printf("Value after scaling: %f\n", p.Abs()) 26 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/appendingSlice.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | arr := []int{1, 2, 3} 7 | printSlice(arr) 8 | 9 | arr = append(arr, 4) 10 | printSlice(arr) 11 | 12 | arr = append(arr, 5, 6, 7) 13 | printSlice(arr) 14 | } 15 | 16 | func printSlice(s []int) { 17 | fmt.Println(len(s), cap(s), s) 18 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/arrays.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | arr := [5]int{1, 2, 3, 4, 5} 7 | fmt.Println(arr) 8 | var names [2]string 9 | fmt.Println(names) 10 | names[0] = "Sekardayu Hana Pradiani" 11 | names[1] = "Saskia Nurul Azhima" 12 | fmt.Println(names) 13 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/funcClosures.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func adder() func(float64) float64 { 6 | var sum float64 = 0 7 | return func(num float64) float64 { 8 | sum += num 9 | return sum 10 | } 11 | } 12 | 13 | func main() { 14 | pos, neg := adder(), adder() 15 | for i := 0; i < 10; i++ { 16 | fmt.Println(pos(float64(i)), neg(-float64(i))) 17 | } 18 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/functionValues.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | func compute(fn func(float64, float64) float64) float64 { 9 | return fn(3, 4) 10 | } 11 | 12 | func main() { 13 | hypothenuse := func(x, y float64) float64 { 14 | return math.Sqrt(x*x + y*y) 15 | } 16 | 17 | fmt.Println(hypothenuse(3, 4)) 18 | fmt.Println(compute(hypothenuse)) 19 | fmt.Println(compute(math.Pow)) 20 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/mapLiterals.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Person struct { 6 | name string 7 | age int 8 | } 9 | 10 | func main() { 11 | names := map[string]Person{ 12 | "sekar": Person{ 13 | "Sekardayu Hana Pradiani", 14 | 22, 15 | }, 16 | "saskia": Person{ 17 | "Saskia Nurul Azhima", 18 | 20, 19 | }, 20 | } 21 | 22 | fmt.Println(names) 23 | fmt.Println(names["sekar"]) 24 | fmt.Println(names["saskia"]) 25 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/mapLiteralsContinued.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Person struct { 6 | name string 7 | age int 8 | } 9 | 10 | func main() { 11 | names := map[string]Person{ 12 | "sekar": {"Sekardayu Hana Pradiani", 22}, 13 | "saskia": {"Saskia Nurul Azhima", 20}, 14 | } 15 | 16 | fmt.Println(names) 17 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/maps.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Person struct { 6 | name string 7 | age int 8 | } 9 | 10 | var m map[string]Person 11 | 12 | func main() { 13 | fmt.Println(m) 14 | m = make(map[string]Person) 15 | m["sekar"] = Person{"Sekardayu Hana Pradiani", 22} 16 | fmt.Println(m) 17 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/mutatingMaps.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | m := make(map[string]string) 7 | fmt.Println(m) 8 | 9 | m["sekar"] = "Sekardayu Hana Pradiani" 10 | fmt.Println(m) 11 | 12 | m["sekar"] = "My Girlfriend" 13 | fmt.Println(m) 14 | 15 | delete(m, "sekar") 16 | fmt.Println(m) 17 | 18 | v, ok := m["sekar"] 19 | fmt.Println(v, ok) 20 | } 21 | -------------------------------------------------------------------------------- /GoTour/MoreTypes/nilSlice.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var arr []int 7 | fmt.Println(arr, len(arr), cap(arr)) 8 | 9 | if arr == nil { 10 | fmt.Println("Nilllllllll!!!!!!!!!!") 11 | } else { 12 | fmt.Println("Relax bruh!") 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/omitRange.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var pows = []int{1, 4, 9, 16, 25} 7 | for _, v := range pows { 8 | fmt.Println(v) 9 | } 10 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/pointers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | x, y := 14, 21 7 | fmt.Println(x, y) 8 | 9 | i := &x 10 | j := &y 11 | fmt.Println(i, j) 12 | fmt.Println(*i, *j) 13 | 14 | *i = *i + 7 15 | *j = *j + 7 16 | fmt.Println(x, y, *i, *j) 17 | 18 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/pointersToStruct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Person struct { 6 | name string 7 | age int 8 | } 9 | 10 | func main() { 11 | sekar := Person{"Sekardayu Hana Pradiani", 22} 12 | fmt.Println(sekar.name) 13 | girlfriend := &sekar 14 | fmt.Println(girlfriend) 15 | girlfriend.age =17 16 | fmt.Println(girlfriend) 17 | fmt.Println(sekar) 18 | } 19 | -------------------------------------------------------------------------------- /GoTour/MoreTypes/range.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | var doubles = []int{2, 4, 6, 8, 10} 6 | 7 | func main() { 8 | for i, v := range doubles { 9 | fmt.Printf("2*%d = %d\n", i+1, v) 10 | } 11 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/slice.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | arr := [5]int{1, 2, 3, 4, 5} 7 | fmt.Println(arr) 8 | fmt.Println(arr[0:3]) 9 | var some []int = arr[3:5] 10 | fmt.Println(some) 11 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/sliceAsReferences.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | beatles := [4]string{ 7 | "John Lennon", 8 | "Paul McCartney", 9 | "George Harrison", 10 | "Ringo Starr", 11 | } 12 | 13 | fmt.Println(beatles) 14 | bestDuo := beatles[0:2] 15 | fmt.Println(bestDuo) 16 | bestDuo[0] = "John" 17 | fmt.Println(bestDuo) 18 | fmt.Println(beatles) 19 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/sliceDefault.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | arr := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 7 | fmt.Println(arr) 8 | fmt.Println(arr[:5]) 9 | fmt.Println(arr[5:]) 10 | fmt.Println(arr[:]) 11 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/sliceLengthCapacity.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | arr := []int{0, 1, 2, 3, 4, 5, 6, 7} 7 | printSlice(arr) 8 | 9 | arr = arr[:0] 10 | printSlice(arr) 11 | arr = arr[:5] 12 | printSlice(arr) 13 | arr = arr[3:] 14 | printSlice(arr) 15 | } 16 | 17 | func printSlice(s []int) { 18 | fmt.Printf("len=%v, cap=%v, %v\n", len(s), cap(s), s) 19 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/sliceLiterals.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | arr1 := []int{21, 9, 97} 7 | fmt.Println(arr1) 8 | 9 | arr2 := []struct{ 10 | name string 11 | age int 12 | }{ 13 | {"Sekar", 22}, 14 | {"Saskia", 20}, 15 | } 16 | 17 | fmt.Println(arr2) 18 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/sliceOfSlice.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func main() { 9 | // Create a tic-tac-toe board. 10 | board := [][]string{ 11 | []string{"_", "_", "_"}, 12 | []string{"_", "_", "_"}, 13 | []string{"_", "_", "_"}, 14 | } 15 | 16 | fmt.Println(board) 17 | 18 | // The players take turns. 19 | board[0][0] = "X" 20 | board[2][2] = "O" 21 | board[1][2] = "X" 22 | board[1][0] = "O" 23 | board[0][2] = "X" 24 | 25 | for i := 0; i < len(board); i++ { 26 | fmt.Printf("%s\n", strings.Join(board[i], " ")) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /GoTour/MoreTypes/sliceWithMake.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | a := make([]int, 5) 7 | printSlice("a", a) 8 | 9 | b := make([]int, 0, 5) 10 | printSlice("b", b) 11 | 12 | c := b[:2] 13 | printSlice("c", c) 14 | 15 | d := c[2:5] 16 | printSlice("d", d) 17 | } 18 | 19 | func printSlice(s string, x []int) { 20 | fmt.Printf("%s len=%d cap=%d %v\n", 21 | s, len(x), cap(x), x) 22 | } 23 | -------------------------------------------------------------------------------- /GoTour/MoreTypes/struct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Sekar struct { 6 | name string 7 | age int 8 | } 9 | 10 | func main() { 11 | fmt.Println(Sekar{"Sekardayu Hana Pradiani", 22}) 12 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/structFields.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Person struct { 6 | name string 7 | age int 8 | } 9 | 10 | func main() { 11 | sekar := Person{"Sekardayu Hana Pradiani", 22} 12 | fmt.Println(sekar.name) 13 | fmt.Println(sekar.age) 14 | } -------------------------------------------------------------------------------- /GoTour/MoreTypes/structLiterals.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Person struct { 6 | name string 7 | age int 8 | } 9 | 10 | func main() { 11 | person1 := Person{"Agus", 22} 12 | person2 := Person{name: "Sekar"} 13 | person3 := &Person{"Saskia", 20} 14 | person$ := Person{} 15 | fmt.Println(person1) 16 | fmt.Println(person2) 17 | fmt.Println(person3) 18 | fmt.Println(person4) 19 | } -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/addition.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func add0(x int, y int) int { 6 | return x + y 7 | } 8 | 9 | func add1(x, y int) int { 10 | return x + y 11 | } 12 | 13 | func main() { 14 | fmt.Println(add0(21, 14)) 15 | } -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/basicTypes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/cmplx" 6 | ) 7 | 8 | var ( 9 | agus string = "Agus Richard Lubis" 10 | age int = 22 11 | compNum complex128 = cmplx.Sqrt(8 + 9i) 12 | ) 13 | 14 | func main() { 15 | fmt.Printf("Type %T Value %v\n", agus, agus) 16 | fmt.Printf("Type %T Value %v\n", compNum, compNum) 17 | } -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/constants.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | const eulerNumber = 2.71828 6 | 7 | func main() { 8 | eulerNumber = 3.14 // Can't reassign 9 | fmt.Println(eulerNumber) 10 | } -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/hello.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("Hello Golang!") 7 | } -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/multipleResults.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func something(name string, age int) (string, int) { 6 | return "Hello my name is " + name, age + 5 7 | } 8 | 9 | func main() { 10 | fmt.Println(something("Agus Richard Lubis", 22)) 11 | } -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/namedReturned.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func something(num int) (x, y int) { 6 | x = num - 5 7 | y = num + 5 8 | return 9 | } 10 | 11 | func main() { 12 | fmt.Println(something(10)) 13 | } 14 | 15 | -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/numericConstants.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | const ( 6 | // Create a huge number by shifting a 1 bit left 100 places. 7 | // In other words, the binary number that is 1 followed by 100 zeroes. 8 | Big = 1 << 100 9 | // Shift it right again 99 places, so we end up with 1<<1, or 2. 10 | Small = Big >> 99 11 | ) 12 | 13 | func needInt(x int) int { return x*10 + 1 } 14 | func needFloat(x float64) float64 { 15 | return x * 0.1 16 | } 17 | 18 | func main() { 19 | fmt.Println(needInt(Small)) 20 | fmt.Println(needFloat(Small)) 21 | fmt.Println(needFloat(Big)) 22 | } 23 | -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/shortVarDeclaration.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | var x, y = 21, 14 6 | 7 | func main() { 8 | var agus = "Agus Richard Lubis" 9 | sekar := "Sekardayu Hana Pradiani" 10 | fmt.Println(agus, sekar, x, y) 11 | } -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/sqrt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | func main() { 9 | fmt.Printf("Square root of 21 is %f", math.Sqrt(21)) 10 | } -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/stringFormat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | fmt.Println("Hello Golang") 9 | fmt.Printf("My age is %d years old.", 22) 10 | } -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/typeConversion.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | func main() { 9 | var x int = 12 10 | var y int = 13 11 | var f float64 = math.Sqrt(float64(x*x + y*y)) 12 | fmt.Println(x, y, f) 13 | } -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/varInitialized.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | var x, y int = 21, 14 6 | 7 | func main() { 8 | var name = "Agus Richard Lubis" 9 | fmt.Println(x, y, name) 10 | } -------------------------------------------------------------------------------- /GoTour/PackageVariableFunction/variables.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | var sekar, saskia, arifa string 6 | 7 | func main() { 8 | var i int 9 | fmt.Println(i, sekar, saskia, arifa) 10 | } -------------------------------------------------------------------------------- /GolangBasic/fungsi_variadic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | avg := average(1, 2, 3, 4, 5) 7 | fmt.Println(avg) 8 | } 9 | 10 | func average(numbers ...int) float64 { 11 | var total int = 0 12 | for _, number := range numbers { 13 | total += number 14 | } 15 | 16 | fmt.Println(numbers) 17 | var avg = float64(total) / float64(len(numbers)) 18 | return avg 19 | } 20 | -------------------------------------------------------------------------------- /GolangBasic/interface.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Vehicle interface { 6 | Introduce() string 7 | Accelerate(factor int) int 8 | } 9 | 10 | type Plane struct { 11 | name string 12 | speed int 13 | wingspan int 14 | } 15 | 16 | type Car struct { 17 | name string 18 | speed int 19 | numOfTires int 20 | } 21 | 22 | func (car *Car) Introduce() string { 23 | return fmt.Sprintf("%v, %v, %v", car.name, car.speed, car.numOfTires) 24 | } 25 | 26 | func (car *Car) Accelerate(factor int) int { 27 | return factor * car.speed 28 | } 29 | 30 | func (plane *Plane) Introduce() string { 31 | return fmt.Sprintf("%v - %v - %v", plane.name, plane.speed, plane.wingspan) 32 | } 33 | 34 | func (plane *Plane) Accelerate(factor int) int { 35 | return factor * plane.speed 36 | } 37 | 38 | func main() { 39 | var vechicles []Vehicle 40 | car := Car{ 41 | name: "BMW", 42 | speed: 100, 43 | numOfTires: 4, 44 | } 45 | plane := Plane{ 46 | name: "Boeing", 47 | speed: 500, 48 | wingspan: 100, 49 | } 50 | 51 | vechicles = append(vechicles, &car) 52 | vechicles = append(vechicles, &plane) 53 | fmt.Println(vechicles) 54 | } 55 | -------------------------------------------------------------------------------- /GolangBasic/pointer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var num1 int = 21 7 | var num2 *int = &num1 8 | 9 | fmt.Println(num1) 10 | fmt.Println(num2) 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Workbook 2 | Workbook for learning Go language and more 3 | -------------------------------------------------------------------------------- /analytics-cli-app/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | /analytics/analytics -------------------------------------------------------------------------------- /analytics-cli-app/README.md: -------------------------------------------------------------------------------- 1 | # CLI Tool to Analyze Log Data 2 | 3 | ## How to run the app: 4 | - There are two Go code to run, one to generate dummy log data (generate) and the other one is the main app (analytics) 5 | - Go to folder "generate", run: `cd generate/` 6 | - In main.go, there are tweakable constants, that are NUM_OF_FILES (specifying number of files to be generated) and NUM_OF_LINES (specifying number of log lines in a log file) 7 | - Generate the dummy data by running command: `go run main.go` 8 | - The log files will be stored inside analytics directory 9 | - Go to folder "analytics", run: `cd ../analytics` (from generate folder) 10 | - Build the CLI app by running: `go build` 11 | - Make it runnable from command line by copy the executable file into /usr/local/bin, run: `cp analytics /usr/local/bin` 12 | - Now, run this command to get the last 10 minutes worth of log data, run: `analytics -t 10m -d ./logs` 13 | 14 | ## Regarding tests: 15 | - If you're in "generate" folder, run the tests by running command: `go test -v ./...` 16 | - Still in the "generate" folder, run the benchmarking by running command: `go test -bench=Bench` 17 | - Before running tests inside the analytics folder make sure you have the log files inside /logs. Otherwise, no data will be read and the tests won't pass 18 | - Inside the "analytics" folder, run the run the tests by running command: `go test -v ./...` 19 | - Inside the "analytics" folder, run the benchmarking by running command: `go test -bench=Bench` 20 | -------------------------------------------------------------------------------- /analytics-cli-app/analytics/go.mod: -------------------------------------------------------------------------------- 1 | module analytics 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /analytics-cli-app/analytics/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestTimeInLog(t *testing.T) { 9 | type test struct { 10 | name string 11 | input string 12 | expected time.Time 13 | } 14 | 15 | tests := []test{ 16 | { 17 | name: "one", 18 | input: `127.0.0.1 user-identifier frank [06/Dec/2021:18:49:17 +0000] "GET /api/endpoint HTTP/1.0" 200 5134`, 19 | expected: time.Date(2021, time.December, 6, 18, 49, 17, 0, time.UTC), 20 | }, 21 | { 22 | name: "two", 23 | input: `127.0.0.1 user-identifier frank [13/Dec/2021:09:00:00 +0000] "GET /api/endpoint HTTP/1.0" 200 5134`, 24 | expected: time.Date(2021, time.December, 13, 9, 0, 0, 0, time.UTC), 25 | }, 26 | { 27 | name: "three", 28 | input: `127.0.0.1 user-identifier frank [21/Sep/1997:07:00:00 +0000] "GET /api/endpoint HTTP/1.0" 200 5134`, 29 | expected: time.Date(1997, time.September, 21, 7, 0, 0, 0, time.UTC), 30 | }, 31 | } 32 | 33 | for _, tc := range tests { 34 | t.Run(tc.name, func(t *testing.T) { 35 | actual, err := findTimeInLog(tc.input) 36 | if err != nil { 37 | t.Errorf("unexpected error: %v", err) 38 | } 39 | 40 | if actual != tc.expected { 41 | t.Errorf("expected %v, got %v", tc.expected, actual) 42 | } 43 | }) 44 | } 45 | } 46 | 47 | func BenchmarkAnalyze(b *testing.B) { 48 | for i := 0; i < b.N; i++ { 49 | minutes := "10m" 50 | dir := "./logs" 51 | analyze(&minutes, &dir) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /analytics-cli-app/generate/go.mod: -------------------------------------------------------------------------------- 1 | module generate 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /analytics-cli-app/tasks.txt: -------------------------------------------------------------------------------- 1 | Starts with displaying the tasks: 2 | x Generate logging examples 3 | x Read one line of log and parse it into time 4 | x List all files in a directory 5 | x Read files and process i 6 | x Make condition to only process t minutes log data 7 | -------------------------------------------------------------------------------- /benchmarking-article-material/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | func Calculate(x, y int64) int64 { 12 | // get random integer between 0 and 2000 13 | n := rand.Intn(2000) 14 | // sleep for a random duration between 0 and 2000 milliseconds 15 | time.Sleep(time.Duration(n) * time.Millisecond) 16 | 17 | return x + y 18 | } 19 | 20 | func CalculateSlow(x, y int64) int64 { 21 | // sleep for 3 seconds to mock heavy calculation 22 | time.Sleep(2 * time.Second) 23 | 24 | return x + y 25 | } 26 | 27 | func handler(w http.ResponseWriter, r *http.Request) { 28 | // Get the query string from the request 29 | queryX, queryY := r.URL.Query().Get("x"), r.URL.Query().Get("y") 30 | 31 | // Parse the input from user to int or returns error if it fails 32 | x, err := strconv.ParseInt(queryX, 10, 64) 33 | if err != nil { 34 | http.Error(w, err.Error(), http.StatusBadRequest) 35 | return 36 | } 37 | 38 | // Parse the input from user to int or returns error if it fails 39 | y, err := strconv.ParseInt(queryY, 10, 64) 40 | if err != nil { 41 | http.Error(w, err.Error(), http.StatusBadRequest) 42 | return 43 | } 44 | 45 | result := make(chan int64, 1) 46 | go func(r chan<- int64) { 47 | r <- Calculate(x, y) 48 | }(result) 49 | 50 | resultSlow := make(chan int64, 1) 51 | go func(r chan<- int64) { 52 | r <- CalculateSlow(x, y) 53 | }(resultSlow) 54 | 55 | fmt.Fprintf(w, "%d", <-result+<-resultSlow) 56 | } 57 | 58 | func main() { 59 | http.HandleFunc("/", handler) 60 | http.ListenAndServe(":8080", nil) 61 | } 62 | -------------------------------------------------------------------------------- /benchmarking/Makefile: -------------------------------------------------------------------------------- 1 | GO=go 2 | GOCOVER=$(GO) tool cover 3 | .PHONY: test/cover 4 | test/cover: 5 | $(GOTEST) -v -coverprofile=coverage.out ./... 6 | $(GOCOVER) -func=coverage.out 7 | $(GOCOVER) -html=coverage.out -------------------------------------------------------------------------------- /benchmarking/coverage.out: -------------------------------------------------------------------------------- 1 | mode: set 2 | benchmarking-yo/main.go:5.27,8.2 2 1 3 | benchmarking-yo/main.go:10.13,13.2 2 0 4 | -------------------------------------------------------------------------------- /benchmarking/go.mod: -------------------------------------------------------------------------------- 1 | module benchmarking-yo 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /benchmarking/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func Calculate(n int) int { 6 | result := n+2 7 | return result 8 | } 9 | 10 | func main() { 11 | fmt.Println("Hello World") 12 | fmt.Println(Calculate(3)) 13 | } 14 | -------------------------------------------------------------------------------- /benchmarking/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var result *interface{} 9 | var i int 10 | 11 | func setupTestCase(t testing.TB) func(t testing.TB) { 12 | t.Log("setup test case") 13 | return func(t testing.TB) { 14 | t.Log("teardown test case") 15 | } 16 | } 17 | 18 | func setupSubTest(t testing.TB) func(t testing.TB) { 19 | t.Log("setup sub test") 20 | return func(t testing.TB) { 21 | t.Log("teardown sub test") 22 | } 23 | } 24 | 25 | func TestCalculate(t *testing.T) { 26 | cases := []struct { 27 | n int 28 | expected int 29 | }{ 30 | {1, 3}, 31 | {2, 4}, 32 | {3, 5}, 33 | {4, 6}, 34 | {5, 7}, 35 | } 36 | 37 | teardownTestCase := setupTestCase(t) 38 | defer teardownTestCase(t) 39 | 40 | for _, tc := range cases { 41 | fmt.Println("here") 42 | t.Run(fmt.Sprintf("running test when n=%d", tc.n), func(t *testing.T) { 43 | teardownSubTest := setupSubTest(t) 44 | defer teardownSubTest(t) 45 | 46 | actual := Calculate(tc.n) 47 | if actual != tc.expected { 48 | t.Fatalf("expect %v got %v", tc.expected, actual) 49 | } 50 | }) 51 | } 52 | } 53 | 54 | func BenchmarkCalculate(b *testing.B) { 55 | for n := 0; n < b.N; n++ { 56 | Calculate(1) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /benchmarking/stress.bash: -------------------------------------------------------------------------------- 1 | go test -c 2 | # comment above and uncomment below to enable the race builder 3 | # go test -c -race 4 | PKG=$(basename $(pwd)) 5 | 6 | while true ; do 7 | export GOMAXPROCS=$[ 1 + $[ RANDOM % 128 ]] 8 | ./$PKG.test $@ 2>&1 9 | done -------------------------------------------------------------------------------- /concurrency/goroutines/goroutine.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func expensiveComputation() { 10 | for i := 0; i < 10; i++ { 11 | fmt.Println(i) 12 | } 13 | } 14 | 15 | func sleepyHead() { 16 | for i := 0; i < 10; i++ { 17 | fmt.Println(i) 18 | dur := time.Duration(rand.Intn(250)) 19 | time.Sleep(dur * time.Millisecond) 20 | } 21 | } 22 | 23 | func pinger(c chan<- string) { 24 | for i := 0; ; i++ { 25 | c <- "ping" 26 | time.Sleep(1000 * time.Millisecond) 27 | } 28 | } 29 | 30 | func ponger(c chan<- string) { 31 | for i := 0; ; i++ { 32 | c <- "pong" 33 | time.Sleep(500 * time.Millisecond) 34 | } 35 | } 36 | 37 | func printer(c <-chan string) { 38 | for { 39 | fmt.Println(<-c) 40 | } 41 | } 42 | 43 | func hello(quit <-chan struct{}) { 44 | for { 45 | select { 46 | case <-quit: 47 | return 48 | default: 49 | fmt.Println("Hello") 50 | } 51 | } 52 | } 53 | 54 | func main() { 55 | fmt.Println("Running") 56 | defer func() { 57 | fmt.Println("Done") 58 | }() 59 | 60 | go expensiveComputation() 61 | 62 | for i := 0; i < 10; i++ { 63 | go sleepyHead() 64 | } 65 | 66 | c := make(chan string) 67 | go pinger(c) 68 | go ponger(c) 69 | go printer(c) 70 | 71 | quit := make(chan struct{}) 72 | go hello(quit) 73 | time.Sleep(1 * time.Second) 74 | quit <- struct{}{} 75 | 76 | var input string 77 | fmt.Scanln(&input) 78 | } 79 | -------------------------------------------------------------------------------- /concurrency/maps/maps.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | mymap := make(map[string]int) 7 | mymap["sekar"] = 23 8 | mymap["saskia"] = 21 9 | mymap["arifa"] = 23 10 | 11 | var myKey []string 12 | for key, value := range mymap { 13 | fmt.Println(key, value) 14 | myKey = append(myKey, key) 15 | } 16 | 17 | fmt.Println(myKey) 18 | 19 | // deleting key from map 20 | mymap["jane"] = 22 21 | delete(mymap, "jane") 22 | fmt.Println(mymap["jane"]) 23 | } 24 | -------------------------------------------------------------------------------- /concurrency/mutex/mutex.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // var ( 9 | // mutex sync.Mutex 10 | // balance int 11 | // ) 12 | 13 | // func init() { 14 | // balance = 1000 15 | // } 16 | 17 | // func deposit(value int, wg *sync.WaitGroup) { 18 | // mutex.Lock() 19 | // fmt.Printf("Depositing %d to account with balance: %d\n", value, balance) 20 | // balance += value 21 | // mutex.Unlock() 22 | // wg.Done() 23 | // } 24 | 25 | // func withdraw(value int, wg *sync.WaitGroup) { 26 | // mutex.Lock() 27 | // fmt.Printf("Withdrawing %d from account with balance: %d\n", value, balance) 28 | // balance -= value 29 | // mutex.Unlock() 30 | // wg.Done() 31 | // } 32 | 33 | // func main() { 34 | // fmt.Println("Go Mutex Example") 35 | 36 | // var wg sync.WaitGroup 37 | // wg.Add(2) 38 | // go withdraw(700, &wg) 39 | // fmt.Println("here1") 40 | // go deposit(500, &wg) 41 | // fmt.Println("here2") 42 | // wg.Wait() 43 | 44 | // fmt.Printf("New Balance %d\n", balance) 45 | // } 46 | 47 | func appendToSlice(s *[]int, wg *sync.WaitGroup, mu *sync.Mutex) { 48 | mu.Lock() 49 | fmt.Println("Appending the slice") 50 | *s = append(*s, 10) 51 | mu.Unlock() 52 | wg.Done() 53 | } 54 | 55 | func popTheSlice(s *[]int, wg *sync.WaitGroup, mu *sync.Mutex) { 56 | mu.Lock() 57 | fmt.Println("Popping the slice") 58 | *s = (*s)[:len(*s)-1] 59 | mu.Unlock() 60 | wg.Done() 61 | } 62 | 63 | func main() { 64 | var wg sync.WaitGroup 65 | var mu sync.Mutex 66 | 67 | s := []int{1, 2, 3, 4, 5} 68 | wg.Add(2) 69 | go appendToSlice(&s, &wg, &mu) 70 | go popTheSlice(&s, &wg, &mu) 71 | fmt.Println("I don't know what I am doing in here") 72 | fmt.Println("Yeah... Me too!") 73 | 74 | wg.Wait() 75 | fmt.Println("s", s) 76 | } 77 | -------------------------------------------------------------------------------- /concurrency/workerpool/workerpool.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func worker(id int, jobs <-chan int, results chan<- int) { 9 | for j := range jobs { 10 | fmt.Println("worker", id, "started job", j) 11 | time.Sleep(time.Second) 12 | fmt.Println("worker", id, "finished job", j) 13 | results <- j * 2 14 | } 15 | } 16 | 17 | func main() { 18 | 19 | const numJobs = 5 20 | jobs := make(chan int, numJobs) 21 | results := make(chan int, numJobs) 22 | 23 | for w := 1; w <= 3; w++ { 24 | go worker(w, jobs, results) 25 | } 26 | 27 | for j := 1; j <= numJobs; j++ { 28 | jobs <- j 29 | } 30 | close(jobs) 31 | 32 | for a := 1; a <= numJobs; a++ { 33 | <-results 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /conductor-orchestration-notification/.env: -------------------------------------------------------------------------------- 1 | POSTGRES_USER=agusrichard 2 | POSTGRES_PASSWORD=agusrichard 3 | DB=conductor_orchestration_notification_db 4 | DB_HOST=db 5 | DB_PORT=5432 -------------------------------------------------------------------------------- /conductor-orchestration-notification/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /conductor-orchestration-notification/.idea/conductor-orchestration-notification.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /conductor-orchestration-notification/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /conductor-orchestration-notification/.idea/sqldialects.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /conductor-orchestration-notification/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /conductor-orchestration-notification/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest 2 | 3 | ARG USER_ID 4 | ARG USERNAME 5 | ARG GROUP_ID 6 | 7 | RUN apt update && apt upgrade -y && \ 8 | apt install -y git \ 9 | make openssh-client 10 | 11 | WORKDIR /app 12 | 13 | RUN curl -fLo install.sh https://raw.githubusercontent.com/cosmtrek/air/master/install.sh \ 14 | && chmod +x install.sh && sh install.sh && cp ./bin/air /bin/air 15 | 16 | RUN addgroup --gid $USER_ID $USERNAME 17 | RUN adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID $USERNAME 18 | USER $USERNAME 19 | 20 | CMD air -------------------------------------------------------------------------------- /conductor-orchestration-notification/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | app: 4 | build: 5 | args: 6 | - USER_ID=1000 7 | - GROUP_ID=1000 8 | - USERNAME=arichard 9 | context: . 10 | dockerfile: Dockerfile 11 | container_name: conductor_orchestration_notification 12 | env_file: 13 | - ".env" 14 | ports: 15 | - "3000:3000" 16 | depends_on: 17 | - db 18 | volumes: 19 | - ./:/app 20 | links: 21 | - db 22 | db: 23 | build: 24 | context: . 25 | dockerfile: postgres.Dockerfile 26 | ports: 27 | - "5432:5432" 28 | container_name: conductor_orchestration_notification_db 29 | env_file: 30 | - ".env" 31 | environment: 32 | - POSTGRES_USER=${POSTGRES_USER} 33 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 34 | - POSTGRES_DB=${DB} 35 | volumes: 36 | - conductor_orchestration_notification_volume:/var/lib/postgresql/data 37 | volumes: 38 | conductor_orchestration_notification_volume: 39 | driver: local -------------------------------------------------------------------------------- /conductor-orchestration-notification/go.mod: -------------------------------------------------------------------------------- 1 | module conductor-orchestration-notification 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.2 // indirect 7 | github.com/go-playground/validator/v10 v10.6.1 // indirect 8 | github.com/golang/protobuf v1.5.2 // indirect 9 | github.com/json-iterator/go v1.1.11 // indirect 10 | github.com/leodido/go-urn v1.2.1 // indirect 11 | github.com/mattn/go-isatty v0.0.13 // indirect 12 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 13 | github.com/modern-go/reflect2 v1.0.1 // indirect 14 | github.com/ugorji/go v1.2.6 // indirect 15 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect 16 | golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea // indirect 17 | golang.org/x/text v0.3.6 // indirect 18 | gopkg.in/yaml.v2 v2.4.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /conductor-orchestration-notification/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | fmt.Println("Server is running?") 11 | router := gin.Default() 12 | 13 | router.GET("/", func(ctx *gin.Context) { 14 | ctx.JSON(http.StatusOK, gin.H{ 15 | "message": "Hello World", 16 | "sekar": "Sekardayu Hana Pradiani", 17 | }) 18 | }) 19 | 20 | router.Run(":3000") 21 | } -------------------------------------------------------------------------------- /conductor-orchestration-notification/postgres.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres 2 | WORKDIR /docker-entrypoint-initdb.d 3 | ADD ./sql/script.sql /docker-entrypoint-initdb.d 4 | EXPOSE 5432 -------------------------------------------------------------------------------- /conductor-orchestration-notification/sql/script.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/conductor-orchestration-notification/sql/script.sql -------------------------------------------------------------------------------- /conductor-orchestration-notification/tmp/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/conductor-orchestration-notification/tmp/main -------------------------------------------------------------------------------- /containerized-dev/.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.205.2/containers/go/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.16, 1.17, 1-bullseye, 1.16-bullseye, 1.17-bullseye, 1-buster, 1.16-buster, 1.17-buster 4 | ARG VARIANT="1.17-bullseye" 5 | FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT} 6 | 7 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 8 | ARG NODE_VERSION="none" 9 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 10 | 11 | # [Optional] Uncomment this section to install additional OS packages. 12 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 13 | # && apt-get -y install --no-install-recommends 14 | 15 | # [Optional] Uncomment the next lines to use go get to install anything else you need 16 | # USER vscode 17 | # RUN go get -x 18 | 19 | # [Optional] Uncomment this line to install global node packages. 20 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 21 | -------------------------------------------------------------------------------- /containerized-dev/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.205.2/containers/go 3 | { 4 | "name": "Go", 5 | "dockerComposeFile": "docker-compose.yaml", 6 | "service": "app", 7 | "workspaceFolder": "/workspace/containerized-dev", 8 | "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], 9 | 10 | // Set *default* container specific settings.json values on container create. 11 | "settings": { 12 | "go.toolsManagement.checkForUpdates": "local", 13 | "go.useLanguageServer": true, 14 | "go.gopath": "/go", 15 | "go.goroot": "/usr/local/go", 16 | "sqltools.connections": [{ 17 | "name": "Container database", 18 | "driver": "PostgreSQL", 19 | "previewLimit": 50, 20 | "server": "localhost", 21 | "port": 3000, 22 | "database": "containerized_dev", 23 | "username": "postgres", 24 | "password": "postgres" 25 | }], 26 | }, 27 | 28 | // Add the IDs of extensions you want installed when the container is created. 29 | "extensions": [ 30 | "golang.Go", 31 | "mtxr.sqltools", 32 | "mtxr.sqltools-driver-pg" 33 | ], 34 | 35 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 36 | "forwardPorts": [3000], 37 | 38 | // Use 'postCreateCommand' to run commands after the container is created. 39 | // "postCreateCommand": "go version", 40 | 41 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 42 | // "remoteUser": "vscode", 43 | "features": { 44 | "git": "latest", 45 | "github-cli": "latest" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /containerized-dev/.devcontainer/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | app: 4 | build: 5 | context: .. 6 | dockerfile: .devcontainer/Dockerfile 7 | args: 8 | # Update the VARIANT arg to pick a version of Go: 1, 1.16, 1.17 9 | # Append -bullseye or -buster to pin to an OS version. 10 | # Use -bullseye variants on local arm64/Apple Silicon. 11 | VARIANT: 1 12 | # Optional Node.js version to install 13 | NODE_VERSION: "none" 14 | 15 | volumes: 16 | - ..:/workspace/containerized-dev:cached 17 | 18 | # Overrides default command so things don't shut down after the process ends. 19 | command: sleep infinity 20 | 21 | # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. 22 | network_mode: service:db 23 | 24 | # Uncomment the next line to use a non-root user for all processes. 25 | # user: vscode 26 | 27 | db: 28 | image: postgres:latest 29 | restart: unless-stopped 30 | volumes: 31 | - postgres-data:/var/lib/postgresql/data 32 | environment: 33 | POSTGRES_USER: postgres 34 | POSTGRES_DB: containerized_dev 35 | POSTGRES_PASSWORD: postgres 36 | ports: 37 | - 3000:5432 38 | volumes: 39 | postgres-data: -------------------------------------------------------------------------------- /containerized-dev/go.mod: -------------------------------------------------------------------------------- 1 | module containerized-dev 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/jmoiron/sqlx v1.3.4 // indirect 7 | github.com/lib/pq v1.10.4 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /containerized-dev/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 2 | github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= 3 | github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= 4 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 5 | github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= 6 | github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 7 | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 8 | -------------------------------------------------------------------------------- /db-experiment/.air.toml: -------------------------------------------------------------------------------- 1 | # Config file for [Air](https://github.com/cosmtrek/air) in TOML format 2 | 3 | # Working directory 4 | # . or absolute path, please note that the directories following must be under root. 5 | root = "." 6 | tmp_dir = "tmp" 7 | 8 | [build] 9 | # Just plain old shell command. You could use `make` as well. 10 | cmd = "go build -o ./tmp/main ." 11 | # Binary file yields from `cmd`. 12 | bin = "tmp/main" 13 | # Customize binary. 14 | full_bin = "APP_ENV=dev APP_USER=air ./tmp/main" 15 | # Watch these filename extensions. 16 | include_ext = ["go"] 17 | # Ignore these filename extensions or directories. 18 | exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] 19 | # Watch these directories if you specified. 20 | include_dir = [] 21 | # Exclude files. 22 | exclude_file = [] 23 | # Exclude specific regular expressions. 24 | exclude_regex = [] 25 | # Exclude unchanged files. 26 | exclude_unchanged = true 27 | # Follow symlink for directories 28 | follow_symlink = true 29 | # This log file places in your tmp_dir. 30 | log = "air.log" 31 | # It's not necessary to trigger build each time file changes if it's too frequent. 32 | delay = 1000 # ms 33 | # Stop running old binary when build errors occur. 34 | stop_on_error = true 35 | # Send Interrupt signal before killing process (windows does not support this feature) 36 | send_interrupt = false 37 | # Delay after sending Interrupt signal 38 | kill_delay = 500 # ms 39 | 40 | [log] 41 | # Show log time 42 | time = false 43 | 44 | [color] 45 | # Customize each part's color. If no color found, use the raw app log. 46 | main = "magenta" 47 | watcher = "cyan" 48 | build = "yellow" 49 | runner = "green" 50 | 51 | [misc] 52 | # Delete tmp directory on exit 53 | clean_on_exit = true -------------------------------------------------------------------------------- /db-experiment/.gitignore: -------------------------------------------------------------------------------- 1 | /tmp/ 2 | .env 3 | -------------------------------------------------------------------------------- /db-experiment/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /db-experiment/.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | postgresql 6 | true 7 | org.postgresql.Driver 8 | jdbc:postgresql://0.0.0.0:5432/db_experiment_db 9 | $ProjectFileDir$ 10 | 11 | 12 | -------------------------------------------------------------------------------- /db-experiment/.idea/db-experiment.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /db-experiment/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /db-experiment/.idea/sqldialects.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /db-experiment/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /db-experiment/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest 2 | 3 | ARG USER_ID 4 | ARG USERNAME 5 | ARG GROUP_ID 6 | 7 | RUN apt update && apt upgrade -y && \ 8 | apt install -y git \ 9 | make openssh-client 10 | 11 | WORKDIR /app 12 | 13 | RUN curl -fLo install.sh https://raw.githubusercontent.com/cosmtrek/air/master/install.sh \ 14 | && chmod +x install.sh && sh install.sh && cp ./bin/air /bin/air 15 | 16 | RUN addgroup --gid $USER_ID $USERNAME 17 | RUN adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID $USERNAME 18 | USER $USERNAME 19 | 20 | CMD air -------------------------------------------------------------------------------- /db-experiment/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "db-experiment/models" 5 | "github.com/joho/godotenv" 6 | "log" 7 | "os" 8 | "strconv" 9 | ) 10 | 11 | func GetConfig() *model.Config { 12 | _, err := os.Stat(".env") 13 | 14 | if !os.IsNotExist(err) { 15 | err := godotenv.Load(".env") 16 | 17 | if err != nil { 18 | log.Println("Error while reading the env file", err) 19 | panic(err) 20 | } 21 | } 22 | 23 | dbPort, err := strconv.Atoi(os.Getenv("DB_PORT")) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | config := &model.Config{ 29 | Database: model.DatabaseConfig{ 30 | Host: os.Getenv("DB_HOST"), 31 | Port: dbPort, 32 | DbName: os.Getenv("DB"), 33 | Username: os.Getenv("POSTGRES_USER"), 34 | Password: os.Getenv("POSTGRES_PASSWORD"), 35 | }, 36 | TimeZone: "Asia/Jakarta", 37 | SecretKey: os.Getenv("SECRET_KEY"), 38 | } 39 | 40 | return config 41 | } 42 | -------------------------------------------------------------------------------- /db-experiment/config/database.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "db-experiment/models" 5 | "fmt" 6 | "github.com/jmoiron/sqlx" 7 | _ "github.com/lib/pq" 8 | "log" 9 | ) 10 | 11 | var DB *sqlx.DB 12 | 13 | // ConnectDB to get all needed db connections for application 14 | func ConnectDB(config *model.Config) *sqlx.DB { 15 | DB = getDBConnection(config) 16 | 17 | return DB 18 | } 19 | 20 | func getDBConnection(config *model.Config) *sqlx.DB { 21 | var dbConnectionStr string 22 | 23 | dbConnectionStr = fmt.Sprintf( 24 | "host=%s port=%d dbname=%s user=%s password=%s sslmode=disable", 25 | config.Database.Host, 26 | config.Database.Port, 27 | config.Database.DbName, 28 | config.Database.Username, 29 | config.Database.Password, 30 | ) 31 | 32 | db, err := sqlx.Open("postgres", dbConnectionStr) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | err = db.Ping() 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | //TODO: experiment with correct values 43 | db.SetMaxIdleConns(1) 44 | db.SetMaxOpenConns(5) 45 | 46 | log.Println("Connected to DB") 47 | return db 48 | } -------------------------------------------------------------------------------- /db-experiment/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | app: 4 | build: 5 | args: 6 | - USER_ID=1000 7 | - GROUP_ID=1000 8 | - USERNAME=arichard 9 | context: . 10 | dockerfile: Dockerfile 11 | container_name: db_experiment_app 12 | env_file: 13 | - ".env" 14 | ports: 15 | - "9000:9000" 16 | depends_on: 17 | - db 18 | volumes: 19 | - ./:/app 20 | links: 21 | - db 22 | db: 23 | build: 24 | context: . 25 | dockerfile: postgres.Dockerfile 26 | ports: 27 | - "5432:5432" 28 | container_name: db_experiment_db 29 | env_file: 30 | - ".env" 31 | environment: 32 | - POSTGRES_USER=${POSTGRES_USER} 33 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 34 | - POSTGRES_DB=${DB} 35 | volumes: 36 | - db-experiment-db-volume:/var/lib/postgresql/data 37 | volumes: 38 | db-experiment-db-volume: 39 | driver: local 40 | -------------------------------------------------------------------------------- /db-experiment/go.mod: -------------------------------------------------------------------------------- 1 | module db-experiment 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.2 // indirect 7 | github.com/jmoiron/sqlx v1.3.4 // indirect 8 | github.com/joho/godotenv v1.3.0 // indirect 9 | github.com/lib/pq v1.10.2 // indirect 10 | github.com/pkg/errors v0.9.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /db-experiment/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "db-experiment/server" 4 | 5 | func main() { 6 | s := server.SetupServer() 7 | 8 | s.Run(":9000") 9 | } -------------------------------------------------------------------------------- /db-experiment/models/abstract_concrete.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | func ToConcrete(abstractPointer interface{}, emptyConcretePointer interface{}, baseStruct interface{}) interface{} { 8 | tVal := reflect.ValueOf(abstractPointer).Elem() 9 | cVal := reflect.New(reflect.TypeOf(emptyConcretePointer)) 10 | 11 | numOfFields := cVal.Elem().NumField() 12 | for i := 0; i < numOfFields; i++ { 13 | if tVal.Field(i).Type() == reflect.TypeOf(baseStruct) { 14 | cVal.Elem().Field(i).Set(tVal.Field(i)) 15 | continue 16 | } 17 | 18 | if tVal.Field(i).Type() == reflect.TypeOf(NullString{}) { 19 | cVal.Elem().Field(i).SetString(tVal.Field(i).Interface().(NullString).String) 20 | continue 21 | } 22 | 23 | if tVal.Field(i).Type() == reflect.TypeOf(NullBool{}) { 24 | cVal.Elem().Field(i).SetBool(tVal.Field(i).Interface().(NullBool).Bool) 25 | continue 26 | } 27 | 28 | if tVal.Field(i).Type() == reflect.TypeOf(NullFloat64{}) { 29 | cVal.Elem().Field(i).SetFloat(tVal.Field(i).Interface().(NullFloat64).Float64) 30 | continue 31 | } 32 | 33 | if tVal.Field(i).Type() == reflect.TypeOf(NullInt64{}) { 34 | cVal.Elem().Field(i).SetInt(tVal.Field(i).Interface().(NullInt64).Int64) 35 | continue 36 | } 37 | 38 | if tVal.Field(i).Type() == reflect.TypeOf(NullTime{}) { 39 | cVal.Elem().Field(i).Set(reflect.ValueOf(tVal.Field(i).Interface().(NullTime).Time)) 40 | continue 41 | } 42 | } 43 | 44 | return cVal.Elem().Interface() 45 | } -------------------------------------------------------------------------------- /db-experiment/models/config.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Config struct { 4 | Database DatabaseConfig 5 | TimeZone string 6 | SecretKey string 7 | } 8 | 9 | type DatabaseConfig struct { 10 | Host string 11 | Port int 12 | DbName string 13 | Username string 14 | Password string 15 | } -------------------------------------------------------------------------------- /db-experiment/models/filter.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Filter struct { 4 | Type string `form:"type" json:"type" xml:"type"` 5 | Value string `form:"value" json:"value" xml:"value"` 6 | Field string `form:"field" json:"field" xml:"field"` 7 | Start string `form:"start" json:"start" xml:"start"` 8 | End string `form:"end" json:"end" xml:"end"` 9 | } 10 | 11 | type Query struct { 12 | Skip int `form:"skip" json:"skip" xml:"skip"` 13 | Take int `form:"take" json:"take" xml:"take"` 14 | Order string `form:"order" json:"order" xml:"order"` 15 | Search string `form:"search" json:"search" xml:"search"` 16 | FilterString string `form:"filter" json:"filter" xml:"filter"` 17 | } 18 | -------------------------------------------------------------------------------- /db-experiment/models/response.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Response struct { 4 | Success bool `json:"success"` 5 | Message string `json:"message"` 6 | Data interface{} `json:"data"` 7 | } 8 | -------------------------------------------------------------------------------- /db-experiment/models/todo.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Todo struct { 8 | ID int `json:"id" db:"id"` 9 | Username string `json:"username" db:"username"` 10 | Title string `json:"title" db:"title"` 11 | Description MyNullString `json:"description" db:"description"` 12 | Deadline NullTime `json:"deadline" db:"deadline"` 13 | IsImportant NullBool `json:"is_important" db:"is_important"` 14 | BudgetAmount NullFloat64 `json:"budget_amount" db:"budget_amount"` 15 | CreatedAt time.Time `json:"created_at" db:"created_at"` 16 | ModifiedAt NullTime `json:"modofied_at" db:"modified_at"` 17 | } 18 | 19 | func (t *Todo) IsValid() bool { 20 | return t.Username != "" && t.Title != "" 21 | } 22 | -------------------------------------------------------------------------------- /db-experiment/models/todo_v2.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql" 5 | "time" 6 | ) 7 | 8 | type TodoShape struct { 9 | ID int `json:"id" db:"id"` 10 | Username string `json:"username" db:"username"` 11 | Title string `json:"title" db:"title"` 12 | CreatedAt time.Time `json:"createdAt" db:"created_at"` 13 | Description string `json:"description" db:"description"` 14 | ModifiedAt time.Time `json:"modifiedAt" db:"modified_at"` 15 | } 16 | 17 | type TodoModel struct { 18 | ID int `json:"id" db:"id"` 19 | Username string `json:"username" db:"username"` 20 | Title string `json:"title" db:"title"` 21 | CreatedAt time.Time `json:"createdAt" db:"created_at"` 22 | Description sql.NullString `json:"description" db:"description"` 23 | ModifiedAt sql.NullTime `json:"modifiedAt" db:"modified_at"` 24 | } 25 | 26 | func (t *TodoShape) IsValid() bool { 27 | return t.Username != "" || t.Title != "" 28 | } -------------------------------------------------------------------------------- /db-experiment/postgres.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres 2 | WORKDIR /docker-entrypoint-initdb.d 3 | ADD ./sql/script.sql /docker-entrypoint-initdb.d 4 | EXPOSE 5432 -------------------------------------------------------------------------------- /db-experiment/server/handler.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import handler "db-experiment/handlers" 4 | 5 | type handlers struct { 6 | todoHandler handler.TodoHandler 7 | todoHandlerV2 handler.TodoHandlerV2 8 | } 9 | 10 | func setupHandlers(uscs *usecases) *handlers { 11 | todoHandler := handler.InitializeTodoHandler(uscs.todoUsecase) 12 | todoHandlerV2 := handler.InitializeTodoHandlerV2(uscs.todoUsecaseV2) 13 | 14 | return &handlers{ 15 | todoHandler: todoHandler, 16 | todoHandlerV2: todoHandlerV2, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /db-experiment/server/repository.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | repository "db-experiment/repositories" 5 | "github.com/jmoiron/sqlx" 6 | ) 7 | 8 | type repositories struct { 9 | todoRepository repository.TodoRepository 10 | todoRepositoryV2 repository.TodoRepositoryV2 11 | } 12 | 13 | func setupRepositories(db *sqlx.DB) *repositories { 14 | todoRepository := repository.InitializeTodoRepository(db) 15 | todoRepositoryV2 := repository.InitializeTodoRepositoryV2(db) 16 | 17 | return &repositories{ 18 | todoRepository: todoRepository, 19 | todoRepositoryV2: todoRepositoryV2, 20 | } 21 | } -------------------------------------------------------------------------------- /db-experiment/server/usecase.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "db-experiment/usecases" 4 | 5 | type usecases struct { 6 | todoUsecase usecase.TodoUsecase 7 | todoUsecaseV2 usecase.TodoUsecaseV2 8 | } 9 | 10 | func setupUsecases(r *repositories) *usecases { 11 | todoUsecase := usecase.InitializeTodoUsecase(r.todoRepository) 12 | todoUsecaseV2 := usecase.InitializeTodoUsecaseV2(r.todoRepositoryV2) 13 | 14 | return &usecases{ 15 | todoUsecase: todoUsecase, 16 | todoUsecaseV2: todoUsecaseV2, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /db-experiment/sql/script.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS todos 2 | ( 3 | id serial PRIMARY KEY, 4 | username VARCHAR(128) NOT NULL, 5 | title VARCHAR(128) NOT NULL, 6 | description TEXT NULL DEFAULT NULL, 7 | deadline timestamptz NULL, 8 | is_important BOOLEAN NULL, 9 | budget_amount FLOAT NULL, 10 | created_at timestamptz NOT NULL DEFAULT Now(), 11 | modified_at timestamptz NOT NULL DEFAULT Now() 12 | ) -------------------------------------------------------------------------------- /db-experiment/util/truncate.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jmoiron/sqlx" 6 | ) 7 | 8 | type TruncateTableExecutor struct { 9 | db *sqlx.DB 10 | } 11 | 12 | func InitTruncateTableExecutor(db *sqlx.DB) TruncateTableExecutor { 13 | return TruncateTableExecutor{ 14 | db, 15 | } 16 | } 17 | 18 | func (executor *TruncateTableExecutor) TruncateTable(tableNames []string) { 19 | var err error 20 | 21 | tx, err := executor.db.Beginx() 22 | if err != nil { 23 | panic(err) 24 | return 25 | } 26 | 27 | for _, name := range tableNames { 28 | query := fmt.Sprintf("TRUNCATE TABLE %s RESTART IDENTITY CASCADE;", name) 29 | _, err := tx.Exec(query) 30 | if err != nil { 31 | tx.Rollback() 32 | panic(err) 33 | return 34 | } 35 | } 36 | 37 | tx.Commit() 38 | } -------------------------------------------------------------------------------- /functional-programming/content-1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func add(x int) func(y int) int { 6 | return func(y int) int { 7 | return x + y 8 | } 9 | } 10 | 11 | func factorial(n int) int { 12 | if n == 0 { 13 | return 1 14 | } 15 | 16 | return n * factorial(n-1) 17 | } 18 | 19 | func recursiveSummation(nums []int) int { 20 | if len(nums) == 1 { 21 | return nums[0] 22 | } 23 | 24 | return nums[0] + recursiveSummation(nums[1:]) 25 | } 26 | 27 | func main() { 28 | // Nonfunctional programming - Updating string 29 | name := "Sherlock" 30 | name = name + " Holmes" 31 | fmt.Println(name) 32 | 33 | // Functional programming - Updating string 34 | firstname := "Sherlock" 35 | lastname := "Holmes" 36 | fullname := firstname + " " + lastname // Or we could use string formatting 37 | fmt.Println(fullname) 38 | 39 | // Nonfunctional programming - Avoid updating arrays 40 | friends1 := [3]string{"Joe", "John"} 41 | friends1[2] = "James" 42 | fmt.Println(friends1) 43 | 44 | // Functional programming - Avoid updating arrays 45 | friends2 := []string{"Joe", "John"} 46 | friends2 = append(friends2, "James") 47 | fmt.Println(friends2) 48 | 49 | // Nonfunctional programming - Avoid updating maps 50 | fruits1 := map[string]int{"Orange": 1} 51 | fruits1["Banana"] = 2 52 | fmt.Println(fruits1) 53 | 54 | // Functional programming - Avoid updating maps 55 | fruits2 := map[string]int{"Orange": 2} 56 | newFruits := map[string]int{"Banana": 3} 57 | 58 | allFruits := make(map[string]int, len(fruits2)+len(newFruits)) 59 | 60 | for key, val := range fruits2 { 61 | allFruits[key] = val 62 | } 63 | 64 | for key, val := range newFruits { 65 | allFruits[key] = val 66 | } 67 | fmt.Println(allFruits) 68 | 69 | // Currying example 70 | add10 := add(10) 71 | add20 := add10(20) 72 | fmt.Println(add20) 73 | 74 | // Recursion example - eliminating the usage of loops 75 | fmt.Println(factorial(3)) 76 | fmt.Println(recursiveSummation([]int{1, 2, 3, 4, 5})) 77 | } 78 | -------------------------------------------------------------------------------- /functional-programming/content-2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func addition(x, y int) int { 6 | return x + y 7 | } 8 | 9 | func curryAddition(x int) func(y int) int { 10 | return func(y int) int { 11 | return x + y 12 | } 13 | } 14 | 15 | func main() { 16 | fmt.Println(addition(1, 2)) 17 | 18 | add1 := curryAddition(1) 19 | add10 := add1(10) 20 | fmt.Println(add10) 21 | } 22 | -------------------------------------------------------------------------------- /gin-restapi/go.mod: -------------------------------------------------------------------------------- 1 | module gin-restapi 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.6.3 7 | github.com/go-playground/validator/v10 v10.3.0 // indirect 8 | github.com/golang/protobuf v1.4.2 // indirect 9 | github.com/jinzhu/gorm v1.9.16 10 | github.com/json-iterator/go v1.1.10 // indirect 11 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 12 | github.com/modern-go/reflect2 v1.0.1 // indirect 13 | golang.org/x/sys v0.0.0-20200817155316-9781c653f443 // indirect 14 | google.golang.org/protobuf v1.25.0 // indirect 15 | gopkg.in/yaml.v2 v2.3.0 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /gin-restapi/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func main() { 6 | router := gin.Default() 7 | 8 | router.GET("/ping", func(c *gin.Context) { 9 | c.JSON(200, gin.H{ 10 | "message": "Hello world!", 11 | }) 12 | }) 13 | router.Run() 14 | } 15 | -------------------------------------------------------------------------------- /gin-restapi/models/book.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Book Model 4 | type Book struct { 5 | ID uint `json:"id" gorm:"primary_key"` 6 | Title string `json:"title"` 7 | Author string `json:"author"` 8 | } 9 | -------------------------------------------------------------------------------- /gin-restapi/models/setup.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | ) 6 | 7 | // DB -- Database 8 | var DB *gorm.DB 9 | 10 | func connectDatabase() { 11 | database, err := gorm.Open("sqlite3", "test.db") 12 | 13 | if err != nil { 14 | panic("Failed to connect to database!") 15 | } 16 | 17 | database.AutoMigrate(&Book{}) 18 | } 19 | -------------------------------------------------------------------------------- /go-notes/README.md: -------------------------------------------------------------------------------- 1 | # Index File of Go Notes 2 | 3 | ## List of Contents 4 | 5 | ### 1. [Benchmarking](./benchmarking.md) 6 | ### 2. [Error Handling](./error-handling.md) 7 | ### 3. [Importance of Coding Conventions and Standards](./importance-of-coding-conventions-and-standards.md) 8 | ### 4. [Naming Conventions](./naming-conventions.md) 9 | ### 5. [Testing](./testing.md) 10 | ### 6. [Project Structure](./project-structure-in-go.md) 11 | ### 7. [General Coding Conventions](./coding-conventions.md) 12 | ### 8. [Idiomatic Go](./idiomatic-go.md) 13 | ### 9. [Inner Go](./inner-go.md) 14 | ### 10. [Anti Patterns](./anti-patterns.md) 15 | ### 11. [Concurrency](./concurrency.md) 16 | ### 12. [Functional Programming](./functional-programming.md) 17 | -------------------------------------------------------------------------------- /go-notes/images/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/go-notes/images/img.png -------------------------------------------------------------------------------- /go-notes/images/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/go-notes/images/img_1.png -------------------------------------------------------------------------------- /go-notes/images/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/go-notes/images/img_2.png -------------------------------------------------------------------------------- /go-notes/images/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/go-notes/images/img_3.png -------------------------------------------------------------------------------- /go-notes/images/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/go-notes/images/img_4.png -------------------------------------------------------------------------------- /go-notes/images/img_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/go-notes/images/img_5.png -------------------------------------------------------------------------------- /go-notes/images/img_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/go-notes/images/img_6.png -------------------------------------------------------------------------------- /go-notes/images/img_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/go-notes/images/img_7.png -------------------------------------------------------------------------------- /go-notes/images/img_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/go-notes/images/img_8.png -------------------------------------------------------------------------------- /go-notes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func Print[T any](s []T) { 6 | for _, v := range s { 7 | fmt.Println(v) 8 | } 9 | } 10 | 11 | func main() { 12 | Print([]int{1, 2, 3}) 13 | Print([]string{"a", "b", "c"}) 14 | } 15 | -------------------------------------------------------------------------------- /go-ocr-service/.air.toml: -------------------------------------------------------------------------------- 1 | # Config file for [Air](https://github.com/cosmtrek/air) in TOML format 2 | 3 | # Working directory 4 | # . or absolute path, please note that the directories following must be under root. 5 | root = "." 6 | tmp_dir = "tmp" 7 | 8 | [build] 9 | # Just plain old shell command. You could use `make` as well. 10 | cmd = "go build -o ./tmp/main ." 11 | # Binary file yields from `cmd`. 12 | bin = "tmp/main" 13 | # Customize binary. 14 | full_bin = "APP_ENV=dev APP_USER=air ./tmp/main" 15 | # Watch these filename extensions. 16 | include_ext = ["go", "tpl", "tmpl", "html"] 17 | # Ignore these filename extensions or directories. 18 | exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] 19 | # Watch these directories if you specified. 20 | include_dir = [] 21 | # Exclude files. 22 | exclude_file = [] 23 | # Exclude unchanged files. 24 | exclude_unchanged = true 25 | # This log file places in your tmp_dir. 26 | log = "air.log" 27 | # It's not necessary to trigger build each time file changes if it's too frequent. 28 | delay = 1000 # ms 29 | # Stop running old binary when build errors occur. 30 | stop_on_error = true 31 | # Send Interrupt signal before killing process (windows does not support this feature) 32 | send_interrupt = false 33 | # Delay after sending Interrupt signal 34 | kill_delay = 500 # ms 35 | 36 | [log] 37 | # Show log time 38 | time = false 39 | 40 | [color] 41 | # Customize each part's color. If no color found, use the raw app log. 42 | main = "magenta" 43 | watcher = "cyan" 44 | build = "yellow" 45 | runner = "green" 46 | 47 | [misc] 48 | # Delete tmp directory on exit 49 | clean_on_exit = true -------------------------------------------------------------------------------- /go-ocr-service/go.mod: -------------------------------------------------------------------------------- 1 | module go-ocr-service 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/cosmtrek/air v1.26.0 // indirect 7 | github.com/pelletier/go-toml v1.9.0 // indirect 8 | golang.org/x/sys v0.0.0-20210414055047-fe65e336abe0 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /go-ocr-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("Sekardayu Hana Pradiani") 7 | } 8 | -------------------------------------------------------------------------------- /grpc-auth/auth.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package auth; 3 | 4 | message RegisterRequest { 5 | string username = 1; 6 | string password = 2; 7 | } 8 | 9 | message RegisterResponse { 10 | bool success = 1; 11 | string message = 2; 12 | } 13 | 14 | message LoginRequest { 15 | string username = 1; 16 | string password = 2; 17 | } 18 | 19 | message LoginResponse { 20 | bool success = 1; 21 | string message = 2; 22 | string token = 3; 23 | } 24 | 25 | message ValidateTokenRequest { 26 | string token = 1; 27 | } 28 | 29 | message ValidateTokenResponse { 30 | bool success = 1; 31 | string message = 2; 32 | } 33 | 34 | service AuthService { 35 | rpc Register(RegisterRequest) returns (RegisterResponse) {} 36 | rpc Login(LoginRequest) returns (LoginResponse) {} 37 | rpc ValidateToken(ValidateTokenRequest) returns (ValidateTokenResponse) {} 38 | } -------------------------------------------------------------------------------- /grpc-auth/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "grpc-auth/usecase" 5 | "log" 6 | 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | type Server struct { 11 | userUsecase usecase.UserUsecase 12 | } 13 | 14 | func InitServer(userUsecase usecase.UserUsecase) Server { 15 | return Server{ 16 | userUsecase, 17 | } 18 | } 19 | 20 | func (s *Server) Register(ctx context.Context, request *RegisterRequest) (*RegisterResponse, error) { 21 | _, err := s.userUsecase.Register(request.Username, request.Password) 22 | if err != nil { 23 | log.Println("Error to register user", err) 24 | return &RegisterResponse{Success: false, Message: "Failed to register"}, err 25 | } 26 | return &RegisterResponse{Success: true, Message: "Succeed to register"}, nil 27 | } 28 | 29 | func (s *Server) Login(ctx context.Context, request *LoginRequest) (*LoginResponse, error) { 30 | token, err := s.userUsecase.Login(request.Username, request.Password) 31 | if err != nil { 32 | log.Println("Error to register user", err) 33 | return &LoginResponse{Success: false, Message: "Failed to login", Token: ""}, err 34 | } 35 | return &LoginResponse{Success: true, Message: "Login success", Token: token}, nil 36 | } 37 | 38 | func (s *Server) ValidateToken(ctx context.Context, request *ValidateTokenRequest) (*ValidateTokenResponse, error) { 39 | result, err := s.userUsecase.ValidateToken(request.Token) 40 | if err != nil { 41 | log.Println("Error to validate token", err) 42 | return &ValidateTokenResponse{Success: false, Message: "Invalid token"}, err 43 | } 44 | return &ValidateTokenResponse{Success: true, Message: result}, nil 45 | } 46 | -------------------------------------------------------------------------------- /grpc-auth/config/database.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jmoiron/sqlx" 7 | _ "github.com/lib/pq" 8 | ) 9 | 10 | var DB *sqlx.DB 11 | 12 | // ConnectDB to get all needed db connections for application 13 | func ConnectDB() *sqlx.DB { 14 | DB = getDBConnection() 15 | 16 | return DB 17 | } 18 | 19 | func getDBConnection() *sqlx.DB { 20 | var dbConnectionStr string 21 | 22 | dbConnectionStr = fmt.Sprintf( 23 | "host=%s port=%d dbname=%s user=%s password=%s sslmode=disable", 24 | "localhost", 25 | 5432, 26 | "grpc_auth", 27 | "postgres", 28 | "postgres", 29 | ) 30 | 31 | db, err := sqlx.Open("postgres", dbConnectionStr) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | err = db.Ping() 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | //TODO: experiment with correct values 42 | db.SetMaxIdleConns(1) 43 | db.SetMaxOpenConns(5) 44 | 45 | fmt.Println("Connected to DB") 46 | return db 47 | } 48 | -------------------------------------------------------------------------------- /grpc-auth/generate.sh: -------------------------------------------------------------------------------- 1 | protoc --go_out=plugins=grpc:auth auth.proto -------------------------------------------------------------------------------- /grpc-auth/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-auth 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/golang/protobuf v1.4.2 8 | github.com/jmoiron/sqlx v1.3.1 9 | github.com/lib/pq v1.9.0 10 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad 11 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 12 | google.golang.org/grpc v1.35.0 13 | google.golang.org/protobuf v1.25.0 14 | ) 15 | -------------------------------------------------------------------------------- /grpc-auth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | 8 | "grpc-auth/auth" 9 | "grpc-auth/config" 10 | "grpc-auth/repository" 11 | "grpc-auth/usecase" 12 | 13 | "google.golang.org/grpc" 14 | ) 15 | 16 | func main() { 17 | db := config.ConnectDB() 18 | userRepository := repository.InitUserRepository(db) 19 | userUsecase := usecase.InitUserUsecase(userRepository) 20 | 21 | s := auth.InitServer(userUsecase) 22 | 23 | grpcServer := grpc.NewServer() 24 | 25 | auth.RegisterAuthServiceServer(grpcServer, &s) 26 | 27 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 9000)) 28 | if err != nil { 29 | log.Println("failed to listen: %v", err) 30 | } 31 | fmt.Println("Listen to port 9000") 32 | 33 | if err := grpcServer.Serve(lis); err != nil { 34 | log.Println("failed to serve: %s", err) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /grpc-auth/models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type User struct { 4 | ID int64 5 | Username string 6 | Password string 7 | } 8 | -------------------------------------------------------------------------------- /grpc-auth/repository/user.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "database/sql" 5 | "grpc-auth/models" 6 | "log" 7 | 8 | "github.com/jmoiron/sqlx" 9 | ) 10 | 11 | type userRepository struct { 12 | db *sqlx.DB 13 | } 14 | 15 | type UserRepository interface { 16 | CreateUser(username string, password string) (bool, error) 17 | GetUserByUsername(username string) (models.User, error) 18 | } 19 | 20 | func InitUserRepository(db *sqlx.DB) UserRepository { 21 | return &userRepository{ 22 | db, 23 | } 24 | } 25 | 26 | func (userRepository *userRepository) CreateUser(username string, password string) (bool, error) { 27 | var err error 28 | var result bool 29 | 30 | tx, errTx := userRepository.db.Begin() 31 | if errTx != nil { 32 | log.Println("Error create user: ", errTx) 33 | } else { 34 | err = insertUser(tx, username, password) 35 | if err != nil { 36 | log.Println("Error create user: ", err) 37 | } 38 | } 39 | 40 | if err == nil { 41 | result = true 42 | tx.Commit() 43 | } else { 44 | result = false 45 | tx.Rollback() 46 | log.Println("Error create user: ", err) 47 | } 48 | 49 | return result, err 50 | } 51 | 52 | func insertUser(tx *sql.Tx, username string, password string) error { 53 | _, err := tx.Exec(` 54 | INSERT INTO users ( 55 | username, 56 | password 57 | ) 58 | VALUES( 59 | $1, 60 | $2 61 | ); 62 | `, 63 | username, 64 | password, 65 | ) 66 | 67 | return err 68 | } 69 | 70 | func (userRepository *userRepository) GetUserByUsername(username string) (models.User, error) { 71 | var user models.User 72 | var id int64 73 | 74 | err := userRepository.db.QueryRow(` 75 | SELECT id, username, password FROM users WHERE username=$1; 76 | `, username).Scan(&id, &(user.Username), &(user.Password)) 77 | 78 | user.ID = id 79 | 80 | if err != nil { 81 | log.Println("Error to get user by username", err) 82 | } 83 | 84 | return user, err 85 | } 86 | -------------------------------------------------------------------------------- /grpc-auth/utils/hash_password.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "golang.org/x/crypto/bcrypt" 4 | 5 | func HashPassword(password string) (string, error) { 6 | bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) 7 | return string(bytes), err 8 | } 9 | 10 | func CheckPasswordHash(password, hash string) bool { 11 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) 12 | return err == nil 13 | } 14 | -------------------------------------------------------------------------------- /grpc-auth/utils/token.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "grpc-auth/models" 6 | "log" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/dgrijalva/jwt-go" 11 | ) 12 | 13 | var ( 14 | SecretKey = []byte("ThisIsASecretKey") 15 | ) 16 | 17 | func GenerateToken(tokenResult models.User) (string, error) { 18 | token := jwt.New(jwt.SigningMethodHS256) 19 | claims := token.Claims.(jwt.MapClaims) 20 | claims["id"] = tokenResult.ID 21 | claims["username"] = tokenResult.Username 22 | claims["exp"] = time.Now().Add(time.Hour * 24).Unix() 23 | tokenString, err := token.SignedString(SecretKey) 24 | if err != nil { 25 | log.Println("Error in generating key") 26 | return "", err 27 | } 28 | return tokenString, nil 29 | } 30 | 31 | func ParseToken(tokenStr string) (models.User, error) { 32 | token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { 33 | return SecretKey, nil 34 | }) 35 | if err != nil { 36 | log.Println("Error to parse token", err) 37 | return models.User{}, err 38 | } 39 | 40 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 41 | idStr := fmt.Sprintf("%v", claims["id"]) 42 | id, _ := strconv.ParseInt(idStr, 10, 64) 43 | username := claims["username"].(string) 44 | return models.User{Username: username, ID: id}, nil 45 | } 46 | 47 | return models.User{}, err 48 | } 49 | -------------------------------------------------------------------------------- /grpc-todo/config/database.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jmoiron/sqlx" 7 | _ "github.com/lib/pq" 8 | ) 9 | 10 | var DB *sqlx.DB 11 | 12 | // ConnectDB to get all needed db connections for application 13 | func ConnectDB() *sqlx.DB { 14 | DB = getDBConnection() 15 | 16 | return DB 17 | } 18 | 19 | func getDBConnection() *sqlx.DB { 20 | var dbConnectionStr string 21 | 22 | dbConnectionStr = fmt.Sprintf( 23 | "host=%s port=%d dbname=%s user=%s password=%s sslmode=disable", 24 | "localhost", 25 | 5432, 26 | "grpc_todo", 27 | "postgres", 28 | "postgres", 29 | ) 30 | 31 | db, err := sqlx.Open("postgres", dbConnectionStr) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | err = db.Ping() 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | //TODO: experiment with correct values 42 | db.SetMaxIdleConns(1) 43 | db.SetMaxOpenConns(5) 44 | 45 | fmt.Println("Connected to DB") 46 | return db 47 | } 48 | -------------------------------------------------------------------------------- /grpc-todo/generate.sh: -------------------------------------------------------------------------------- 1 | protoc --go_out=plugins=grpc:todo todo.proto -------------------------------------------------------------------------------- /grpc-todo/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-todo 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/golang/protobuf v1.4.3 7 | github.com/jmoiron/sqlx v1.3.1 8 | github.com/lib/pq v1.9.0 9 | google.golang.org/grpc v1.35.0 10 | google.golang.org/protobuf v1.25.0 11 | ) 12 | -------------------------------------------------------------------------------- /grpc-todo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "grpc-todo/config" 6 | "grpc-todo/repository" 7 | "grpc-todo/todo" 8 | "grpc-todo/usecase" 9 | "log" 10 | "net" 11 | 12 | "google.golang.org/grpc" 13 | ) 14 | 15 | func main() { 16 | db := config.ConnectDB() 17 | TodoRepository := repository.InitTodoRepository(db) 18 | todoUsecase := usecase.InitUserUsecase(TodoRepository) 19 | 20 | s := todo.InitServer(todoUsecase) 21 | 22 | grpcServer := grpc.NewServer() 23 | 24 | todo.RegisterTodoServiceServer(grpcServer, &s) 25 | 26 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 3000)) 27 | if err != nil { 28 | log.Println("failed to listen: %v", err) 29 | } 30 | fmt.Println("Listen to port 3000") 31 | 32 | if err := grpcServer.Serve(lis); err != nil { 33 | log.Println("failed to serve: %s", err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /grpc-todo/models/todo.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Todo struct { 4 | ID int64 `json:"id"` 5 | Title string `json:"title"` 6 | Description string `json:"description"` 7 | UserID int64 `json:"userID"` 8 | } 9 | -------------------------------------------------------------------------------- /grpc-todo/todo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package todo; 3 | 4 | message CreateTodoRequest { 5 | int64 userID = 1; 6 | string title = 2; 7 | string description = 3; 8 | } 9 | 10 | message CreateTodoResponse { 11 | bool success = 1; 12 | string message = 2; 13 | } 14 | 15 | message GetTodosRequest { 16 | int64 userID = 1; 17 | } 18 | 19 | message GetTodosResponse { 20 | bool success = 1; 21 | string message = 2; 22 | string data = 3; 23 | } 24 | 25 | message UpdateTodoRequest { 26 | int64 id = 1; 27 | string title = 2; 28 | string description = 3; 29 | } 30 | 31 | message UpdateTodoResponse { 32 | bool success = 1; 33 | string message = 2; 34 | } 35 | 36 | message DeleteTodoRequest { 37 | int64 id = 1; 38 | } 39 | 40 | message DeleteTodoResponse { 41 | bool success = 1; 42 | string message = 2; 43 | } 44 | 45 | service TodoService { 46 | rpc CreateTodo(CreateTodoRequest) returns (CreateTodoResponse) {} 47 | rpc GetTodos(GetTodosRequest) returns (GetTodosResponse) {} 48 | rpc UpdateTodo(UpdateTodoRequest) returns (UpdateTodoResponse) {} 49 | rpc DeleteTodo(DeleteTodoRequest) returns (DeleteTodoResponse) {} 50 | } -------------------------------------------------------------------------------- /grpc-todo/usecase/todo.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "encoding/json" 5 | "grpc-todo/models" 6 | "grpc-todo/repository" 7 | "log" 8 | ) 9 | 10 | type todoUsecase struct { 11 | todoRepository repository.TodoRepository 12 | } 13 | 14 | type TodoUsecase interface { 15 | CreateTodo(todo models.Todo) (bool, error) 16 | GetTodos(userID int64) (string, error) 17 | UpdateTodo(todo models.Todo) (bool, error) 18 | DeleteTodo(id int64) (bool, error) 19 | } 20 | 21 | func InitUserUsecase(todoRepository repository.TodoRepository) TodoUsecase { 22 | return &todoUsecase{ 23 | todoRepository, 24 | } 25 | } 26 | 27 | func (todoUsecase *todoUsecase) CreateTodo(todo models.Todo) (bool, error) { 28 | _, err := todoUsecase.todoRepository.CreateTodo(todo) 29 | if err != nil { 30 | log.Println("Error create todo", err) 31 | return false, err 32 | } 33 | 34 | return true, nil 35 | } 36 | 37 | func (todoUsecase *todoUsecase) GetTodos(userID int64) (string, error) { 38 | todos, err := todoUsecase.todoRepository.GetTodos(userID) 39 | if err != nil { 40 | log.Println("Error get todos", err) 41 | return "", err 42 | } 43 | 44 | result, err := json.Marshal(todos) 45 | 46 | return string(result), nil 47 | } 48 | 49 | func (todoUsecase *todoUsecase) UpdateTodo(todo models.Todo) (bool, error) { 50 | _, err := todoUsecase.todoRepository.UpdateTodo(todo) 51 | if err != nil { 52 | log.Println("Error update todo", err) 53 | return false, err 54 | } 55 | 56 | return true, nil 57 | } 58 | 59 | func (todoUsecase *todoUsecase) DeleteTodo(id int64) (bool, error) { 60 | _, err := todoUsecase.todoRepository.DeleteTodo(id) 61 | if err != nil { 62 | log.Println("Error delete todo", err) 63 | return false, err 64 | } 65 | 66 | return true, nil 67 | } 68 | -------------------------------------------------------------------------------- /lbyl-eafp-article-material/go.mod: -------------------------------------------------------------------------------- 1 | module lbyl-eafp-article-material 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /lbyl-eafp-article-material/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("Hello World!") 7 | } 8 | -------------------------------------------------------------------------------- /learn-restapi-testing/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /learn-restapi-testing/.idea/learn-restapi-testing.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /learn-restapi-testing/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /learn-restapi-testing/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /learn-restapi-testing/example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | ) 8 | 9 | type Response struct { 10 | Message string `json:"message"` 11 | Result float64 `json:"result"` 12 | } 13 | 14 | // Main function to be tested 15 | func doubleMe(x float64) float64 { 16 | return x * 2 17 | } 18 | 19 | // Main handler function that receives the request and returns the response 20 | func handler(w http.ResponseWriter, r *http.Request) { 21 | 22 | // Get the query string from the request 23 | queryX := r.URL.Query().Get("x") 24 | 25 | // Parse the input from user to float and returns error if it fails 26 | x, err := strconv.ParseFloat(queryX, 64) 27 | if err != nil { 28 | http.Error(w, err.Error(), http.StatusBadRequest) 29 | return 30 | } 31 | 32 | // Send back the appropriate response 33 | resp := Response{ 34 | Message: "Hello!", 35 | Result: doubleMe(x), 36 | } 37 | 38 | w.Header().Set("Content-Type", "application/json") 39 | json.NewEncoder(w).Encode(resp) 40 | } 41 | 42 | func main() { 43 | http.HandleFunc("/", handler) 44 | http.ListenAndServe(":8080", nil) 45 | } 46 | -------------------------------------------------------------------------------- /learn-restapi-testing/go.mod: -------------------------------------------------------------------------------- 1 | module learn-restapi-testing 2 | 3 | go 1.15 4 | 5 | require github.com/stretchr/testify v1.7.0 6 | -------------------------------------------------------------------------------- /learn-restapi-testing/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 8 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /learn-restapi-testing/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type messageService struct {} 6 | 7 | type MessageService interface { 8 | SendChargeNotification(value int) error 9 | } 10 | 11 | func InitializeMessageService() MessageService { 12 | return &messageService{} 13 | } 14 | 15 | func (service *messageService) SendChargeNotification(value int) error { 16 | fmt.Println("Send notification!") 17 | return nil 18 | } 19 | 20 | type myService struct { 21 | messageService MessageService 22 | } 23 | 24 | type MyService interface { 25 | ChargeCustomer(value int) error 26 | } 27 | 28 | func InitializeMyService(service MessageService) MyService { 29 | fmt.Println("service", service) 30 | return &myService{service} 31 | } 32 | 33 | func (service *myService) ChargeCustomer(value int) error { 34 | err := service.messageService.SendChargeNotification(value) 35 | fmt.Printf("Charge customer %d\n", value) 36 | return err 37 | } 38 | 39 | func main() { 40 | fmt.Println("Hello") 41 | serviceOne := InitializeMessageService() 42 | serviceTwo := InitializeMyService(serviceOne) 43 | serviceTwo.ChargeCustomer(100) 44 | } -------------------------------------------------------------------------------- /learn-restapi-testing/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/mock" 6 | "testing" 7 | ) 8 | 9 | type smsServiceMockT struct { 10 | mock.Mock 11 | } 12 | 13 | func (m *smsServiceMockT) SendChargeNotification(value int) error { 14 | fmt.Println("Send notification!") 15 | args := m.Called(value) 16 | return args.Error(0) 17 | } 18 | 19 | func TestMyService_ChargeCustomer(t *testing.T) { 20 | serviceOne := new(smsServiceMockT) 21 | serviceTwo := InitializeMyService(serviceOne) 22 | 23 | serviceOne.On("SendChargeNotification", 100).Return(nil) 24 | serviceTwo.ChargeCustomer(100) 25 | 26 | serviceOne.AssertExpectations(t) 27 | } -------------------------------------------------------------------------------- /learn_gokit/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | 13 | "learn_gokit" 14 | ) 15 | 16 | func main() { 17 | var ( 18 | httpAddr = flag.String("http", ":8080", "http listen address") 19 | ) 20 | flag.Parse() 21 | ctx := context.Background() 22 | // our learn_gokit service 23 | srv := learn_gokit.NewService() 24 | errChan := make(chan error) 25 | 26 | go func() { 27 | c := make(chan os.Signal, 1) 28 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 29 | errChan <- fmt.Errorf("%s", <-c) 30 | }() 31 | 32 | // mapping endpoints 33 | endpoints := learn_gokit.Endpoints{ 34 | GetEndpoint: learn_gokit.MakeGetEndpoint(srv), 35 | StatusEndpoint: learn_gokit.MakeStatusEndpoint(srv), 36 | ValidateEndpoint: learn_gokit.MakeValidateEndpoint(srv), 37 | } 38 | 39 | // HTTP transport 40 | go func() { 41 | log.Println("learn_gokit is listening on port:", *httpAddr) 42 | handler := learn_gokit.NewHTTPServer(ctx, endpoints) 43 | errChan <- http.ListenAndServe(*httpAddr, handler) 44 | }() 45 | 46 | log.Fatalln(<-errChan) 47 | } 48 | -------------------------------------------------------------------------------- /learn_gokit/go.mod: -------------------------------------------------------------------------------- 1 | module learn_gokit 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/go-kit/kit v0.10.0 7 | github.com/gorilla/mux v1.7.3 8 | ) 9 | -------------------------------------------------------------------------------- /learn_gokit/server.go: -------------------------------------------------------------------------------- 1 | package learn_gokit 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | httptransport "github.com/go-kit/kit/transport/http" 8 | "github.com/gorilla/mux" 9 | ) 10 | 11 | // NewHTTPServer is a good little server 12 | func NewHTTPServer(ctx context.Context, endpoints Endpoints) http.Handler { 13 | r := mux.NewRouter() 14 | r.Use(commonMiddleware) // @see https://stackoverflow.com/a/51456342 15 | 16 | r.Methods("GET").Path("/status").Handler(httptransport.NewServer( 17 | endpoints.StatusEndpoint, 18 | decodeStatusRequest, 19 | encodeResponse, 20 | )) 21 | 22 | r.Methods("GET").Path("/get").Handler(httptransport.NewServer( 23 | endpoints.GetEndpoint, 24 | decodeGetRequest, 25 | encodeResponse, 26 | )) 27 | 28 | r.Methods("POST").Path("/validate").Handler(httptransport.NewServer( 29 | endpoints.ValidateEndpoint, 30 | decodeValidateRequest, 31 | encodeResponse, 32 | )) 33 | 34 | return r 35 | } 36 | 37 | func commonMiddleware(next http.Handler) http.Handler { 38 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 39 | w.Header().Add("Content-Type", "application/json") 40 | next.ServeHTTP(w, r) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /learn_gokit/service.go: -------------------------------------------------------------------------------- 1 | package learn_gokit 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // Service provides some "date capabilities" to your application 9 | type Service interface { 10 | Status(ctx context.Context) (string, error) 11 | Get(ctx context.Context) (string, error) 12 | Validate(ctx context.Context, date string) (bool, error) 13 | } 14 | 15 | type dateService struct{} 16 | 17 | // NewService makes a new Service. 18 | func NewService() Service { 19 | return dateService{} 20 | } 21 | 22 | // Status only tell us that our service is ok! 23 | func (dateService) Status(ctx context.Context) (string, error) { 24 | return "ok", nil 25 | } 26 | 27 | // Get will return today's date 28 | func (dateService) Get(ctx context.Context) (string, error) { 29 | now := time.Now() 30 | return now.Format("02/01/2006"), nil 31 | } 32 | 33 | // Validate will check if the date today's date 34 | func (dateService) Validate(ctx context.Context, date string) (bool, error) { 35 | _, err := time.Parse("02/01/2006", date) 36 | if err != nil { 37 | return false, err 38 | } 39 | return true, nil 40 | } 41 | -------------------------------------------------------------------------------- /learn_gokit/service_test.go: -------------------------------------------------------------------------------- 1 | package learn_gokit 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestStatus(t *testing.T) { 10 | srv, ctx := setup() 11 | 12 | s, err := srv.Status(ctx) 13 | if err != nil { 14 | t.Errorf("Error: %s", err) 15 | } 16 | 17 | // testing status 18 | ok := s == "ok" 19 | if !ok { 20 | t.Errorf("expected service to be ok") 21 | } 22 | } 23 | 24 | func TestGet(t *testing.T) { 25 | srv, ctx := setup() 26 | d, err := srv.Get(ctx) 27 | if err != nil { 28 | t.Errorf("Error: %s", err) 29 | } 30 | 31 | time := time.Now() 32 | today := time.Format("02/01/2006") 33 | 34 | // testing today's date 35 | ok := today == d 36 | if !ok { 37 | t.Errorf("expected dates to be equal") 38 | } 39 | } 40 | func TestValidate(t *testing.T) { 41 | srv, ctx := setup() 42 | b, err := srv.Validate(ctx, "31/12/2019") 43 | if err != nil { 44 | t.Errorf("Error: %s", err) 45 | } 46 | // testing that the date is valid 47 | if !b { 48 | t.Errorf("date should be valid") 49 | } 50 | // testing an invalid date 51 | b, err = srv.Validate(ctx, "31/31/2019") 52 | if b { 53 | t.Errorf("date should be invalid") 54 | } 55 | 56 | // testing a USA date date 57 | b, err = srv.Validate(ctx, "12/31/2019") 58 | if b { 59 | t.Errorf("USA date should be invalid") 60 | } 61 | } 62 | 63 | func setup() (srv Service, ctx context.Context) { 64 | return NewService(), context.Background() 65 | } 66 | -------------------------------------------------------------------------------- /learn_gokit/transport.go: -------------------------------------------------------------------------------- 1 | package learn_gokit 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // In the first part of the file we are mapping requests and responses to their JSON payload. 10 | type getRequest struct{} 11 | 12 | type getResponse struct { 13 | Date string `json:"date"` 14 | Err string `json:"err,omitempty"` 15 | } 16 | 17 | type validateRequest struct { 18 | Date string `json:"date"` 19 | } 20 | 21 | type validateResponse struct { 22 | Valid bool `json:"valid"` 23 | Err string `json:"err,omitempty"` 24 | } 25 | 26 | type statusRequest struct{} 27 | 28 | type statusResponse struct { 29 | Status string `json:"status"` 30 | } 31 | 32 | // In the second part we will write "decoders" for our incoming requests 33 | func decodeGetRequest(ctx context.Context, r *http.Request) (interface{}, error) { 34 | var req getRequest 35 | return req, nil 36 | } 37 | 38 | func decodeValidateRequest(ctx context.Context, r *http.Request) (interface{}, error) { 39 | var req validateRequest 40 | err := json.NewDecoder(r.Body).Decode(&req) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return req, nil 45 | } 46 | 47 | func decodeStatusRequest(ctx context.Context, r *http.Request) (interface{}, error) { 48 | var req statusRequest 49 | return req, nil 50 | } 51 | 52 | // Last but not least, we have the encoder for the response output 53 | func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { 54 | return json.NewEncoder(w).Encode(response) 55 | } 56 | -------------------------------------------------------------------------------- /learn_grpc/chat.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package chat; 3 | 4 | message Message { 5 | string body = 1; 6 | } 7 | 8 | service ChatService { 9 | rpc SayHello(Message) returns (Message) {} 10 | } -------------------------------------------------------------------------------- /learn_grpc/chat/chat.go: -------------------------------------------------------------------------------- 1 | package chat 2 | 3 | import ( 4 | "log" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | type Server struct { 10 | } 11 | 12 | func (s *Server) SayHello(ctx context.Context, in *Message) (*Message, error) { 13 | log.Printf("Receive message body from client: %s", in.Body) 14 | return &Message{Body: "Hello From the Server!"}, nil 15 | } 16 | -------------------------------------------------------------------------------- /learn_grpc/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "learn-grpc/chat" 5 | "log" 6 | 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | func main() { 12 | var conn *grpc.ClientConn 13 | conn, err := grpc.Dial(":9000", grpc.WithInsecure()) 14 | if err != nil { 15 | log.Fatalf("did not connect: %s", err) 16 | } 17 | defer conn.Close() 18 | 19 | c := chat.NewChatServiceClient(conn) 20 | 21 | response, err := c.SayHello(context.Background(), &chat.Message{Body: "Hello from client!"}) 22 | if err != nil { 23 | log.Fatalf("error when calling SayHello: %s", err) 24 | } 25 | log.Printf("Response from server: %s", response.Body) 26 | } 27 | -------------------------------------------------------------------------------- /learn_grpc/go.mod: -------------------------------------------------------------------------------- 1 | module learn-grpc 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/golang/protobuf v1.4.3 7 | golang.org/x/net v0.0.0-20190311183353-d8887717615a 8 | google.golang.org/grpc v1.27.0 9 | google.golang.org/protobuf v1.25.0 10 | ) 11 | -------------------------------------------------------------------------------- /learn_grpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | "google.golang.org/grpc" 8 | ) 9 | 10 | func main() { 11 | lis, err := net.Listen("tcp", ":9000") 12 | if err != nil { 13 | log.Fatalf("Failed to listen: %v", err) 14 | } 15 | 16 | grpcServer := grpc.NewServer() 17 | 18 | if err := grpcServer.Serve(lis); err != nil { 19 | log.Fatalf("failed to serve: %s", err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /learn_grpc/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | 8 | "learn-grpc/chat" 9 | 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | func main() { 14 | 15 | fmt.Println("Go gRPC Beginners Tutorial!") 16 | 17 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 9000)) 18 | if err != nil { 19 | log.Fatalf("failed to listen: %v", err) 20 | } 21 | 22 | s := chat.Server{} 23 | 24 | grpcServer := grpc.NewServer() 25 | 26 | chat.RegisterChatServiceServer(grpcServer, &s) 27 | 28 | if err := grpcServer.Serve(lis); err != nil { 29 | log.Fatalf("failed to serve: %s", err) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /load-test-experiment/.air.toml: -------------------------------------------------------------------------------- 1 | # Config file for [Air](https://github.com/cosmtrek/air) in TOML format 2 | 3 | # Working directory 4 | # . or absolute path, please note that the directories following must be under root. 5 | root = "." 6 | tmp_dir = "tmp" 7 | 8 | [build] 9 | # Just plain old shell command. You could use `make` as well. 10 | cmd = "go build -o ./tmp/main ." 11 | # Binary file yields from `cmd`. 12 | bin = "tmp/main" 13 | # Customize binary. 14 | full_bin = "APP_ENV=dev APP_USER=air ./tmp/main" 15 | # Watch these filename extensions. 16 | include_ext = ["go"] 17 | # Ignore these filename extensions or directories. 18 | exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] 19 | # Watch these directories if you specified. 20 | include_dir = [] 21 | # Exclude files. 22 | exclude_file = [] 23 | # Exclude specific regular expressions. 24 | exclude_regex = [] 25 | # Exclude unchanged files. 26 | exclude_unchanged = true 27 | # Follow symlink for directories 28 | follow_symlink = true 29 | # This log file places in your tmp_dir. 30 | log = "air.log" 31 | # It's not necessary to trigger build each time file changes if it's too frequent. 32 | delay = 1000 # ms 33 | # Stop running old binary when build errors occur. 34 | stop_on_error = true 35 | # Send Interrupt signal before killing process (windows does not support this feature) 36 | send_interrupt = false 37 | # Delay after sending Interrupt signal 38 | kill_delay = 500 # ms 39 | 40 | [log] 41 | # Show log time 42 | time = false 43 | 44 | [color] 45 | # Customize each part's color. If no color found, use the raw app log. 46 | main = "magenta" 47 | watcher = "cyan" 48 | build = "yellow" 49 | runner = "green" 50 | 51 | [misc] 52 | # Delete tmp directory on exit 53 | clean_on_exit = true -------------------------------------------------------------------------------- /load-test-experiment/.gitignore: -------------------------------------------------------------------------------- 1 | /tmp/ 2 | .env 3 | -------------------------------------------------------------------------------- /load-test-experiment/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /load-test-experiment/.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | postgresql 6 | true 7 | org.postgresql.Driver 8 | jdbc:postgresql://0.0.0.0:5432/load_test_experiment_db 9 | $ProjectFileDir$ 10 | 11 | 12 | -------------------------------------------------------------------------------- /load-test-experiment/.idea/load-test-experiment.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /load-test-experiment/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /load-test-experiment/.idea/sqldialects.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /load-test-experiment/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /load-test-experiment/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest 2 | 3 | ARG USER_ID 4 | ARG USERNAME 5 | ARG GROUP_ID 6 | 7 | RUN apt update && apt upgrade -y && \ 8 | apt install -y git \ 9 | make openssh-client 10 | 11 | WORKDIR /app 12 | 13 | RUN curl -fLo install.sh https://raw.githubusercontent.com/cosmtrek/air/master/install.sh \ 14 | && chmod +x install.sh && sh install.sh && cp ./bin/air /bin/air 15 | 16 | RUN addgroup --gid $USER_ID $USERNAME 17 | RUN adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID $USERNAME 18 | USER $USERNAME 19 | 20 | CMD air -------------------------------------------------------------------------------- /load-test-experiment/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/joho/godotenv" 5 | "load-test-experiment/model" 6 | "log" 7 | "os" 8 | "strconv" 9 | ) 10 | 11 | func GetConfig() *model.Config { 12 | _, err := os.Stat(".env") 13 | 14 | if !os.IsNotExist(err) { 15 | err := godotenv.Load(".env") 16 | 17 | if err != nil { 18 | log.Println("Error while reading the env file", err) 19 | panic(err) 20 | } 21 | } 22 | 23 | dbPort, err := strconv.Atoi(os.Getenv("DB_PORT")) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | config := &model.Config{ 29 | Database: model.DatabaseConfig{ 30 | Host: os.Getenv("DB_HOST"), 31 | Port: dbPort, 32 | DbName: os.Getenv("DB"), 33 | Username: os.Getenv("POSTGRES_USER"), 34 | Password: os.Getenv("POSTGRES_PASSWORD"), 35 | }, 36 | TimeZone: os.Getenv("TIME_ZONE"), 37 | SecretKey: os.Getenv("SECRET_KEY"), 38 | } 39 | 40 | return config 41 | } 42 | -------------------------------------------------------------------------------- /load-test-experiment/config/database.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jmoiron/sqlx" 6 | _ "github.com/lib/pq" 7 | "load-test-experiment/model" 8 | "log" 9 | ) 10 | 11 | var DB *sqlx.DB 12 | 13 | // ConnectDB to get all needed db connections for application 14 | func ConnectDB(config *model.Config) *sqlx.DB { 15 | DB = getDBConnection(config) 16 | 17 | return DB 18 | } 19 | 20 | func getDBConnection(config *model.Config) *sqlx.DB { 21 | var dbConnectionStr string 22 | 23 | dbConnectionStr = fmt.Sprintf( 24 | "host=%s port=%d dbname=%s user=%s password=%s sslmode=disable", 25 | config.Database.Host, 26 | config.Database.Port, 27 | config.Database.DbName, 28 | config.Database.Username, 29 | config.Database.Password, 30 | ) 31 | 32 | db, err := sqlx.Open("postgres", dbConnectionStr) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | err = db.Ping() 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | //TODO: experiment with correct values 43 | db.SetMaxIdleConns(1) 44 | db.SetMaxOpenConns(5) 45 | 46 | log.Println("Connected to DB") 47 | return db 48 | } -------------------------------------------------------------------------------- /load-test-experiment/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | app: 4 | build: 5 | args: 6 | - USER_ID=1000 7 | - GROUP_ID=1000 8 | - USERNAME=arichard 9 | context: . 10 | dockerfile: Dockerfile 11 | container_name: load_test_experiment_app 12 | env_file: 13 | - ".env" 14 | ports: 15 | - "9000:9000" 16 | depends_on: 17 | - db 18 | volumes: 19 | - ./:/app 20 | links: 21 | - db 22 | db: 23 | build: 24 | context: . 25 | dockerfile: postgres.Dockerfile 26 | ports: 27 | - "5432:5432" 28 | container_name: load_test_experiment_db 29 | env_file: 30 | - ".env" 31 | environment: 32 | - POSTGRES_USER=${POSTGRES_USER} 33 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 34 | - POSTGRES_DB=${DB} 35 | volumes: 36 | - db-volume:/var/lib/postgresql/data 37 | volumes: 38 | db-volume: 39 | driver: local 40 | -------------------------------------------------------------------------------- /load-test-experiment/go.mod: -------------------------------------------------------------------------------- 1 | module load-test-experiment 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.2 7 | github.com/jmoiron/sqlx v1.3.4 8 | github.com/joho/godotenv v1.3.0 9 | github.com/lib/pq v1.10.2 10 | github.com/pkg/errors v0.9.1 11 | ) 12 | -------------------------------------------------------------------------------- /load-test-experiment/handler/heavy_v1.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "load-test-experiment/model" 6 | "load-test-experiment/utils/actions" 7 | "net/http" 8 | ) 9 | 10 | type heavyV1Handler struct {} 11 | 12 | type HeavyV1Handler interface{ 13 | Handle() gin.HandlerFunc 14 | } 15 | 16 | func NewHeavyV1Handler() HeavyV1Handler { 17 | return &heavyV1Handler{} 18 | } 19 | 20 | func (h *heavyV1Handler) Handle() gin.HandlerFunc { 21 | return func(ctx *gin.Context) { 22 | r := model.Request{} 23 | if err := ctx.ShouldBindJSON(&r); err != nil { 24 | ctx.JSON(http.StatusBadRequest, model.Response{ 25 | Message: "Bad Request", 26 | Data: struct{}{}, 27 | }) 28 | } 29 | 30 | for _, val := range h.registerRoutes() { 31 | if val.Action == r.Action { 32 | val.Handler(ctx) 33 | return 34 | } 35 | } 36 | 37 | ctx.JSON(http.StatusNotFound, model.Response{ 38 | Message: "Action Not Found", 39 | Data: struct{}{}, 40 | }) 41 | } 42 | } 43 | 44 | func (h *heavyV1Handler) registerRoutes() []model.Route { 45 | return []model.Route{ 46 | { 47 | Action: actions.CREATE, 48 | Handler: h.create, 49 | }, 50 | { 51 | Action: actions.GET, 52 | Handler: h.get, 53 | }, 54 | } 55 | } 56 | 57 | func (h *heavyV1Handler) create(ctx *gin.Context) { 58 | ctx.JSON(http.StatusOK, model.Response{ 59 | Message: "Create", 60 | Data: struct{}{}, 61 | }) 62 | } 63 | 64 | func (h *heavyV1Handler) get(ctx *gin.Context) { 65 | ctx.JSON(http.StatusOK, model.Response{ 66 | Message: "Get", 67 | Data: struct{}{}, 68 | }) 69 | } -------------------------------------------------------------------------------- /load-test-experiment/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "load-test-experiment/config" 7 | "load-test-experiment/handler" 8 | "load-test-experiment/repository" 9 | "load-test-experiment/usecase" 10 | ) 11 | 12 | func main() { 13 | fmt.Println("LOAD TEST EXPERIMENT") 14 | 15 | // Setup config and database 16 | configModel := config.GetConfig() 17 | db := config.ConnectDB(configModel) 18 | 19 | // Register repositories version one 20 | liOneRp := repository.NewLightV1Repository(db) 21 | mdOneRp := repository.NewMediumV1Repository(db) 22 | 23 | // Register repositories version one 24 | liOneUc := usecase.NewLightV1Usecase(liOneRp) 25 | mdOneUc := usecase.NewMediumV1Usecase(mdOneRp) 26 | 27 | // Register handlers version one 28 | liOneHn := handler.NewLightV1Handler(liOneUc) 29 | mdOneHn := handler.NewMediumV1Handler(mdOneUc) 30 | 31 | 32 | // Register repositories version two 33 | liTwoRp := repository.NewLightV2Repository(db) 34 | mdTwoRp := repository.NewMediumV2Repository(db) 35 | 36 | // Register repositories version two 37 | liTwoUc := usecase.NewLightV2Usecase(liTwoRp) 38 | mdTwoUc := usecase.NewMediumV2Usecase(mdTwoRp) 39 | 40 | // Register handlers version two 41 | liTwoHn := handler.NewLightV2Handler(liTwoUc) 42 | mdTwoHn := handler.NewMediumV2Handler(mdTwoUc) 43 | 44 | 45 | // Setup gin server 46 | gin.SetMode(gin.ReleaseMode) 47 | router := gin.Default() 48 | 49 | // Register routes 50 | v1 := router.Group("/v1") 51 | { 52 | v1.POST("/light/create", liOneHn.Create()) 53 | v1.POST("/light/get", liOneHn.Get()) 54 | v1.POST("/medium/create", mdOneHn.Create()) 55 | v1.POST("/medium/get", mdOneHn.Get()) 56 | } 57 | 58 | v2 := router.Group("/v2") 59 | { 60 | v2.POST("/light/create", liTwoHn.Create()) 61 | v2.POST("/light/get", liTwoHn.Get()) 62 | v2.POST("/medium/create", mdTwoHn.Create()) 63 | v2.POST("/medium/get", mdTwoHn.Get()) 64 | } 65 | 66 | // Initialize server config and run the server 67 | router.Run(":9000") 68 | } -------------------------------------------------------------------------------- /load-test-experiment/model/config.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Config struct { 4 | Database DatabaseConfig 5 | TimeZone string 6 | SecretKey string 7 | } 8 | 9 | type DatabaseConfig struct { 10 | Host string 11 | Port int 12 | DbName string 13 | Username string 14 | Password string 15 | } -------------------------------------------------------------------------------- /load-test-experiment/model/filter.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Query struct { 4 | Skip int `form:"skip" json:"skip" xml:"skip"` 5 | Take int `form:"take" json:"take" xml:"take"` 6 | Orders []Order `form:"orders" json:"orders" xml:"orders"` 7 | Search string `form:"search" json:"search" xml:"search"` 8 | Filters []Filter `form:"filters" json:"filters" xml:"filters"` 9 | } 10 | 11 | type Filter struct { 12 | Type string `form:"type" json:"type" xml:"type"` 13 | Value string `form:"value" json:"value" xml:"value"` 14 | Field string `form:"field" json:"field" xml:"field"` 15 | Start string `form:"start" json:"start" xml:"start"` 16 | End string `form:"end" json:"end" xml:"end"` 17 | } 18 | 19 | type Order struct { 20 | Field string // `form:"field" json:"field" xml:"field"` 21 | Dir string // `form:"dir" json:"dir" xml:"dir"` 22 | } 23 | -------------------------------------------------------------------------------- /load-test-experiment/model/light_v1.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql" 5 | ) 6 | 7 | type LightV1Model struct { 8 | ID int `json:"id"` 9 | FieldOne string `json:"fieldOne"` 10 | FieldTwo float64 `json:"fieldTwo"` 11 | FieldThree sql.NullString `json:"fieldThree"` 12 | FieldFour sql.NullTime `json:"fieldFour"` 13 | } 14 | 15 | type LightV1Shape struct { 16 | ID int `json:"id"` 17 | FieldOne string `json:"fieldOne"` 18 | FieldTwo float64 `json:"fieldTwo"` 19 | FieldThree string `json:"fieldThree"` 20 | FieldFour string `json:"fieldFour" time_format:"2006-01-02T15:04:05Z07:00"` 21 | } 22 | 23 | type LightV1Request struct { 24 | Request 25 | Data LightV1Shape 26 | } 27 | -------------------------------------------------------------------------------- /load-test-experiment/model/light_v2.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type LightV2Model struct { 4 | ID int `json:"id" db:"id"` 5 | FieldOne string `json:"fieldOne" db:"field_one"` 6 | FieldTwo float64 `json:"fieldTwo" db:"field_two"` 7 | FieldThree NullString `json:"fieldThree" db:"field_three"` 8 | FieldFour NullTime `json:"fieldFour" db:"field_four"` 9 | } 10 | 11 | type LightV2Request struct { 12 | Request 13 | Data LightV2Model 14 | } 15 | -------------------------------------------------------------------------------- /load-test-experiment/model/request.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Request struct { 4 | Action string `json:"action"` 5 | Query Query `json:"query"` 6 | } 7 | -------------------------------------------------------------------------------- /load-test-experiment/model/response.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Response struct { 4 | Data interface{} `json:"data"` 5 | Message string `json:"message"` 6 | } -------------------------------------------------------------------------------- /load-test-experiment/model/route.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | type Route struct { 6 | Action string 7 | Handler func(ctx *gin.Context) 8 | } 9 | -------------------------------------------------------------------------------- /load-test-experiment/postgres.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres 2 | WORKDIR /docker-entrypoint-initdb.d 3 | ADD ./sql/script.sql /docker-entrypoint-initdb.d 4 | EXPOSE 5432 -------------------------------------------------------------------------------- /load-test-experiment/repository/light_v2.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jmoiron/sqlx" 6 | "github.com/pkg/errors" 7 | "load-test-experiment/model" 8 | ) 9 | 10 | type lightV2Repository struct { 11 | db *sqlx.DB 12 | } 13 | 14 | type LightV2Repository interface { 15 | Create(m *model.LightV2Model) error 16 | Get(filterQuery string, skip, take int) (*[]model.LightV2Model, error) 17 | } 18 | 19 | func NewLightV2Repository(db *sqlx.DB) LightV2Repository { 20 | return &lightV2Repository{db} 21 | } 22 | 23 | func (r *lightV2Repository) Create(m *model.LightV2Model) error { 24 | if m == nil { 25 | return errors.New("LightV2Repository: Create: m is nil;") 26 | } 27 | 28 | tx, err := r.db.Beginx() 29 | if err != nil { 30 | return errors.Wrap(err, "LightV2Repository: Create: failed to initiate transaction;") 31 | } 32 | 33 | err = insertM(tx, m) 34 | if err != nil { 35 | tx.Rollback() 36 | return errors.Wrap(err, "LightV2Repository: Create: failed to insert m in repository;") 37 | } 38 | 39 | tx.Commit() 40 | 41 | return nil 42 | } 43 | 44 | func insertM(tx *sqlx.Tx, m *model.LightV2Model) error { 45 | _, err := tx.NamedExec(` 46 | INSERT INTO light_table(field_one, field_two, field_three, field_four) 47 | VALUES (:field_one, :field_two, :field_three, :field_four); 48 | ; `, m) 49 | 50 | return err 51 | } 52 | 53 | 54 | func (r *lightV2Repository) Get(filterQuery string, skip, take int) (*[]model.LightV2Model, error) { 55 | var result []model.LightV2Model 56 | 57 | query := fmt.Sprintf(` 58 | SELECT id, field_one, field_two, field_three, field_four 59 | FROM light_table 60 | %s 61 | OFFSET %d 62 | LIMIT %d; 63 | `, filterQuery, skip, take) 64 | 65 | err := r.db.Select(&result, query) 66 | if err != nil { 67 | return nil, errors.Wrap(err, "LightV2Repository: Create: failed to get data;") 68 | } 69 | 70 | return &result, nil 71 | } -------------------------------------------------------------------------------- /load-test-experiment/usecase/light_v2.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "load-test-experiment/model" 6 | "load-test-experiment/repository" 7 | ) 8 | 9 | type lightV2Usecase struct { 10 | lightV2Repository repository.LightV2Repository 11 | } 12 | 13 | type LightV2Usecase interface { 14 | Create(m *model.LightV2Model) error 15 | Get(filterQuery string, skip, take int) (*[]model.LightV2Model, error) 16 | } 17 | 18 | func NewLightV2Usecase(lightV2Repository repository.LightV2Repository) LightV2Usecase { 19 | return &lightV2Usecase{lightV2Repository} 20 | } 21 | 22 | func (u *lightV2Usecase) Create(m *model.LightV2Model) error { 23 | if m == nil { 24 | return errors.New("LightV2Usecase: Create: m is nil;") 25 | } 26 | 27 | err := u.lightV2Repository.Create(m) 28 | if err != nil { 29 | return errors.New("LightV2Usecase: Create: failed create m;") 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func (u *lightV2Usecase) Get(filterQuery string, skip, take int) (*[]model.LightV2Model, error) { 36 | result, err := u.lightV2Repository.Get(filterQuery, skip, take) 37 | if err != nil { 38 | return nil, errors.Wrap(err, "todo usecase: filter todos: error get data") 39 | } 40 | 41 | return result, nil 42 | } -------------------------------------------------------------------------------- /load-test-experiment/usecase/medium_v2.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "load-test-experiment/model" 6 | "load-test-experiment/repository" 7 | ) 8 | 9 | type mediumV2Usecase struct { 10 | mediumV2Repository repository.MediumV2Repository 11 | } 12 | 13 | type MediumV2Usecase interface { 14 | Create(m *model.MediumV2Model) error 15 | Get(filterQuery string, skip, take int) (*[]model.MediumV2Model, error) 16 | } 17 | 18 | func NewMediumV2Usecase(mediumV2Repository repository.MediumV2Repository) MediumV2Usecase { 19 | return &mediumV2Usecase{mediumV2Repository} 20 | } 21 | 22 | func (u *mediumV2Usecase) Create(m *model.MediumV2Model) error { 23 | if m == nil { 24 | return errors.New("MediumV2Usecase: Create: m is nil;") 25 | } 26 | 27 | err := u.mediumV2Repository.Create(m) 28 | if err != nil { 29 | return errors.Wrap(err, "MediumV2Usecase: Create: failed create m;") 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func (u *mediumV2Usecase) Get(filterQuery string, skip, take int) (*[]model.MediumV2Model, error) { 36 | largeList, err := u.mediumV2Repository.GetLarge(filterQuery, skip, take) 37 | if err != nil { 38 | return nil, errors.Wrap(err, "MediumV2Usecase: Get: failed get large list;") 39 | } 40 | 41 | for i, val := range *largeList { 42 | smallList, err := u.mediumV2Repository.GetSmall(val.ID) 43 | if err != nil { 44 | return nil, errors.Wrap(err, "MediumV2Usecase: Get: failed get small list;") 45 | } 46 | (*largeList)[i].MediumSmallModelList = *smallList 47 | } 48 | 49 | return largeList, nil 50 | } -------------------------------------------------------------------------------- /load-test-experiment/utils/actions/light_v1.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | const GET = "GET" 4 | const CREATE = "CREATE" -------------------------------------------------------------------------------- /load-test-experiment/utils/response.go: -------------------------------------------------------------------------------- 1 | package util 2 | -------------------------------------------------------------------------------- /load-test-experiment/utils/time.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func ParseTime(strTime string) (time.Time, error) { 8 | if strTime == "" { 9 | return time.Time{}, nil 10 | } 11 | 12 | t, err := time.Parse(time.RFC3339, strTime) 13 | if err != nil { 14 | return time.Time{}, err 15 | } 16 | return t, nil 17 | } 18 | 19 | func TimeToStr(t time.Time) (string, error) { 20 | return "", nil 21 | } -------------------------------------------------------------------------------- /play-with-reflect/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /play-with-reflect/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /play-with-reflect/.idea/play-with-reflect.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /play-with-reflect/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /redis-yo/.gitignore: -------------------------------------------------------------------------------- 1 | /config/redis.conf -------------------------------------------------------------------------------- /redis-yo/Dockerfile: -------------------------------------------------------------------------------- 1 | # Please keep up to date with the new-version of Golang docker for builder 2 | FROM golang:latest 3 | 4 | ADD . /app 5 | 6 | WORKDIR /app -------------------------------------------------------------------------------- /redis-yo/configs/redis.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import "github.com/go-redis/redis/v8" 4 | 5 | func InitializeRedis() *redis.Client { 6 | rdb := redis.NewClient(&redis.Options{ 7 | Addr: "redis:6379", 8 | Password: "", // no password set 9 | DB: 0, // use default DB 10 | }) 11 | 12 | return rdb 13 | } 14 | -------------------------------------------------------------------------------- /redis-yo/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | redis: 5 | container_name: redis 6 | image: redis:latest 7 | ports: 8 | - 6379:6379 9 | restart: always 10 | app: 11 | build: 12 | context: . 13 | dockerfile: Dockerfile 14 | container_name: redis-yo-app 15 | command: go run main.go 16 | ports: 17 | - 5000:5000 18 | volumes: 19 | - .:/app 20 | links: 21 | - redis 22 | -------------------------------------------------------------------------------- /redis-yo/go.mod: -------------------------------------------------------------------------------- 1 | module redis-yo 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.1 // indirect 7 | github.com/go-redis/redis/v8 v8.8.2 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /redis-yo/handlers/example.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/go-redis/redis/v8" 10 | ) 11 | 12 | type exampleHandler struct { 13 | rdb *redis.Client 14 | } 15 | 16 | type ExampleHandler interface { 17 | MainHandler(ctx *gin.Context) 18 | } 19 | 20 | func InitExampleHandler(rdb *redis.Client) ExampleHandler { 21 | return &exampleHandler{ 22 | rdb, 23 | } 24 | } 25 | 26 | func (handler *exampleHandler) MainHandler(ctx *gin.Context) { 27 | input := ctx.Param("input") 28 | 29 | existanceStatus, _ := handler.rdb.Exists(ctx, input).Result() 30 | 31 | if existanceStatus == 0 { 32 | handler.rdb.Set(ctx, input, "Sekardayu Hana Pradiani", time.Minute).Result() 33 | MockingSomeOperation() 34 | ctx.JSON(http.StatusOK, gin.H{ 35 | "message": "Hello World", 36 | "echo": input, 37 | "data": "", 38 | }) 39 | } else { 40 | result, _ := handler.rdb.Get(ctx, input).Result() 41 | ctx.JSON(http.StatusOK, gin.H{ 42 | "message": "Hello World", 43 | "echo": input, 44 | "data": result, 45 | }) 46 | } 47 | } 48 | 49 | func MockingSomeOperation() { 50 | fmt.Println("Start sleeping") 51 | time.Sleep(time.Second * 5) 52 | fmt.Println("End sleeping") 53 | } 54 | -------------------------------------------------------------------------------- /redis-yo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "redis-yo/configs" 7 | "redis-yo/handlers" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | var ctx = context.Background() 13 | 14 | func main() { 15 | rdb := configs.InitializeRedis() 16 | pong, _ := rdb.Ping(ctx).Result() 17 | fmt.Println(pong) 18 | 19 | router := gin.Default() 20 | 21 | handler := handlers.InitExampleHandler(rdb) 22 | 23 | router.GET("/:input", handler.MainHandler) 24 | 25 | router.Run(":5000") 26 | } 27 | -------------------------------------------------------------------------------- /restapi-test-app/.air.toml: -------------------------------------------------------------------------------- 1 | # Config file for [Air](https://github.com/cosmtrek/air) in TOML format 2 | 3 | # Working directory 4 | # . or absolute path, please note that the directories following must be under root. 5 | root = "." 6 | tmp_dir = "tmp" 7 | 8 | [build] 9 | # Just plain old shell command. You could use `make` as well. 10 | cmd = "go build -o ./tmp/main ." 11 | # Binary file yields from `cmd`. 12 | bin = "tmp/main" 13 | # Customize binary. 14 | full_bin = "APP_ENV=dev APP_USER=air ./tmp/main" 15 | # Watch these filename extensions. 16 | include_ext = ["go"] 17 | # Ignore these filename extensions or directories. 18 | exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] 19 | # Watch these directories if you specified. 20 | include_dir = [] 21 | # Exclude files. 22 | exclude_file = [] 23 | # Exclude specific regular expressions. 24 | exclude_regex = [] 25 | # Exclude unchanged files. 26 | exclude_unchanged = true 27 | # Follow symlink for directories 28 | follow_symlink = true 29 | # This log file places in your tmp_dir. 30 | log = "air.log" 31 | # It's not necessary to trigger build each time file changes if it's too frequent. 32 | delay = 1000 # ms 33 | # Stop running old binary when build errors occur. 34 | stop_on_error = true 35 | # Send Interrupt signal before killing process (windows does not support this feature) 36 | send_interrupt = false 37 | # Delay after sending Interrupt signal 38 | kill_delay = 500 # ms 39 | 40 | [log] 41 | # Show log time 42 | time = false 43 | 44 | [color] 45 | # Customize each part's color. If no color found, use the raw app log. 46 | main = "magenta" 47 | watcher = "cyan" 48 | build = "yellow" 49 | runner = "green" 50 | 51 | [misc] 52 | # Delete tmp directory on exit 53 | clean_on_exit = true -------------------------------------------------------------------------------- /restapi-test-app/.gitignore: -------------------------------------------------------------------------------- 1 | /tmp/ 2 | .env 3 | -------------------------------------------------------------------------------- /restapi-test-app/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /restapi-test-app/.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | postgresql 6 | true 7 | org.postgresql.Driver 8 | jdbc:postgresql://0.0.0.0:5432/restapi_test_app 9 | $ProjectFileDir$ 10 | 11 | 12 | postgresql 13 | true 14 | org.postgresql.Driver 15 | jdbc:postgresql://0.0.0.0:5432/restapi_db 16 | $ProjectFileDir$ 17 | 18 | 19 | -------------------------------------------------------------------------------- /restapi-test-app/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /restapi-test-app/.idea/restapi-tested-app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /restapi-test-app/.idea/sqldialects.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /restapi-test-app/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /restapi-test-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest 2 | 3 | ARG USER_ID 4 | ARG USERNAME 5 | ARG GROUP_ID 6 | 7 | RUN apt update && apt upgrade -y && \ 8 | apt install -y git \ 9 | make openssh-client 10 | 11 | WORKDIR /app 12 | 13 | RUN curl -fLo install.sh https://raw.githubusercontent.com/cosmtrek/air/master/install.sh \ 14 | && chmod +x install.sh && sh install.sh && cp ./bin/air /bin/air 15 | 16 | RUN addgroup --gid $USER_ID $USERNAME 17 | RUN adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID $USERNAME 18 | USER $USERNAME 19 | 20 | COPY sql/tweet.sql /docker-entrypoint-initdb.d/ 21 | 22 | CMD air -------------------------------------------------------------------------------- /restapi-test-app/README.md: -------------------------------------------------------------------------------- 1 | # RestAPI Application with Unit Test using Testify and Mockery 2 | 3 | If you want to run this application locally, you can download/clone this repo entirely and keep this folder only. 4 | By running this application, you'll install all dependencies needed to run this application and it will spawn a server running on port 9090 (the default port I set). 5 | 6 | ## How to run: 7 | 1. Using docker container 8 | 1. Create your own .env file with keys listed in sample.env, then you can fill the values. 9 | 2. Set the specified values of build_args in docker-compose file. Fill it with your linux user id, user group id, and username. The purpose of doing this is to change default user inside docker container. 10 | 3. Run `docker-compose up` to fetch and build images need for this project. 11 | 12 | 2. The ol' go way: 13 | 1. Make sure you have a database running. 14 | 2. Create tweets table using sql script inside sql folder. 15 | 3. Create and fill the .env file with the specified values (you can look on sample.env) 16 | 4. Then run `go run main.go`. This will install the dependencies and run the server. 17 | 18 | PS: There are several endpoints without tests. So I think you can apply your newly acquired knowledge here :) 19 | 20 | Feel free to contact me if you have any feedbacks or questions through my email [agus.richard21@gmail.com](mailto:agus.richard21@gmail.com) -------------------------------------------------------------------------------- /restapi-test-app/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/joho/godotenv" 5 | "log" 6 | "os" 7 | "restapi-tested-app/entities" 8 | "strconv" 9 | ) 10 | 11 | func GetConfig() *entities.Config { 12 | _, err := os.Stat(".env") 13 | 14 | if !os.IsNotExist(err) { 15 | err := godotenv.Load(".env") 16 | 17 | if err != nil { 18 | log.Println("Error while reading the env file", err) 19 | panic(err) 20 | } 21 | } 22 | 23 | dbPort, err := strconv.Atoi(os.Getenv("DB_PORT")) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | config := &entities.Config{ 29 | Database: entities.DatabaseConfig{ 30 | Host: os.Getenv("DB_HOST"), 31 | Port: dbPort, 32 | DbName: os.Getenv("DB"), 33 | Username: os.Getenv("POSTGRES_USER"), 34 | Password: os.Getenv("POSTGRES_PASSWORD"), 35 | }, 36 | TimeZone: "Asia/Jakarta", 37 | SecretKey: os.Getenv("SECRET_KEY"), 38 | } 39 | 40 | return config 41 | } 42 | -------------------------------------------------------------------------------- /restapi-test-app/config/db.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jmoiron/sqlx" 6 | _ "github.com/lib/pq" 7 | "restapi-tested-app/entities" 8 | ) 9 | 10 | var DB *sqlx.DB 11 | 12 | // ConnectDB to get all needed db connections for application 13 | func ConnectDB(config *entities.Config) *sqlx.DB { 14 | DB = getDBConnection(config) 15 | 16 | return DB 17 | } 18 | 19 | func getDBConnection(config *entities.Config) *sqlx.DB { 20 | var dbConnectionStr string 21 | 22 | dbConnectionStr = fmt.Sprintf( 23 | "host=%s port=%d dbname=%s user=%s password=%s sslmode=disable", 24 | config.Database.Host, 25 | config.Database.Port, 26 | config.Database.DbName, 27 | config.Database.Username, 28 | config.Database.Password, 29 | ) 30 | 31 | db, err := sqlx.Open("postgres", dbConnectionStr) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | err = db.Ping() 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | //TODO: experiment with correct values 42 | db.SetMaxIdleConns(1) 43 | db.SetMaxOpenConns(5) 44 | 45 | fmt.Println("Connected to DB") 46 | return db 47 | } -------------------------------------------------------------------------------- /restapi-test-app/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | app: 4 | build: 5 | args: 6 | - USER_ID=1000 7 | - GROUP_ID=1000 8 | - USERNAME=arichard 9 | context: . 10 | dockerfile: Dockerfile 11 | container_name: restapi_app 12 | env_file: 13 | - ".env" 14 | ports: 15 | - "9090:9090" 16 | depends_on: 17 | - db 18 | volumes: 19 | - ./:/app 20 | links: 21 | - db 22 | db: 23 | build: 24 | context: . 25 | dockerfile: postgres.Dockerfile 26 | ports: 27 | - "5432:5432" 28 | container_name: restapi_db 29 | env_file: 30 | - ".env" 31 | environment: 32 | - POSTGRES_USER=${POSTGRES_USER} 33 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 34 | - POSTGRES_DB=${DB} 35 | volumes: 36 | - restapi_db_volume:/var/lib/postgresql/data 37 | volumes: 38 | restapi_db_volume: 39 | driver: local 40 | -------------------------------------------------------------------------------- /restapi-test-app/entities/config.entities.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Config struct { 4 | Database DatabaseConfig 5 | TimeZone string 6 | SecretKey string 7 | } 8 | 9 | type DatabaseConfig struct { 10 | Host string 11 | Port int 12 | DbName string 13 | Username string 14 | Password string 15 | } -------------------------------------------------------------------------------- /restapi-test-app/entities/response.entities.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Response struct { 4 | Success bool `json:"success"` 5 | Message string `json:"message"` 6 | Data interface{} `json:"data"` 7 | } 8 | 9 | type AppError struct { 10 | Err error 11 | StatusCode int 12 | } 13 | 14 | func (appError *AppError) Error() string { 15 | return appError.Err.Error() 16 | } 17 | 18 | type AppResult struct { 19 | Data interface{} 20 | Message string 21 | Err error 22 | StatusCode int 23 | } -------------------------------------------------------------------------------- /restapi-test-app/entities/tweet.entities.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Tweet struct { 8 | ID int `json:"id" db:"id"` 9 | Username string `json:"username" db:"username"` 10 | Text string `json:"text" db:"text"` 11 | CreatedAt time.Time `json:"createdAt" db:"created_at"` 12 | ModifiedAt time.Time `json:"modifiedAt" db:"modified_at"` 13 | } 14 | 15 | func (tweet *Tweet) IsValid() bool { 16 | if tweet.Username == "" || tweet.Text == "" { 17 | return false 18 | } 19 | return true 20 | } -------------------------------------------------------------------------------- /restapi-test-app/go.mod: -------------------------------------------------------------------------------- 1 | module restapi-tested-app 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.2 7 | github.com/go-playground/validator/v10 v10.6.1 // indirect 8 | github.com/golang/protobuf v1.5.2 // indirect 9 | github.com/jmoiron/sqlx v1.3.4 10 | github.com/joho/godotenv v1.3.0 11 | github.com/json-iterator/go v1.1.11 // indirect 12 | github.com/leodido/go-urn v1.2.1 // indirect 13 | github.com/lib/pq v1.10.2 14 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 15 | github.com/modern-go/reflect2 v1.0.1 // indirect 16 | github.com/stretchr/testify v1.7.0 17 | github.com/ugorji/go v1.2.6 // indirect 18 | github.com/vektra/mockery/v2 v2.7.5 // indirect 19 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect 20 | golang.org/x/sys v0.0.0-20210521203332-0cec03c779c1 // indirect 21 | golang.org/x/text v0.3.6 // indirect 22 | gopkg.in/yaml.v2 v2.4.0 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /restapi-test-app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "restapi-tested-app/server" 5 | ) 6 | 7 | func main() { 8 | server.SetupServer() 9 | } -------------------------------------------------------------------------------- /restapi-test-app/postgres.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres 2 | WORKDIR /docker-entrypoint-initdb.d 3 | ADD ./sql/tweet.sql /docker-entrypoint-initdb.d 4 | EXPOSE 5432 -------------------------------------------------------------------------------- /restapi-test-app/sample.env: -------------------------------------------------------------------------------- 1 | POSTGRES_USER= 2 | POSTGRES_PASSWORD= 3 | DB= 4 | DB_HOST= 5 | DB_PORT= -------------------------------------------------------------------------------- /restapi-test-app/server/handlers.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "restapi-tested-app/handlers" 5 | ) 6 | 7 | type Handlers struct { 8 | TweetHandler handlers.TweetHandler 9 | } 10 | 11 | func SetupHandlers(uscs *Usecases) *Handlers { 12 | tweetHandlers := handlers.InitializeTweetHandler(uscs.TweetUsecase) 13 | 14 | return &Handlers{ 15 | TweetHandler: tweetHandlers, 16 | } 17 | } -------------------------------------------------------------------------------- /restapi-test-app/server/repositories.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/jmoiron/sqlx" 5 | "restapi-tested-app/repositories" 6 | ) 7 | 8 | type Repositories struct { 9 | TweetRepository repositories.TweetRepository 10 | } 11 | 12 | func SetupRepositories(db *sqlx.DB) *Repositories { 13 | tweetRepository := repositories.InitializeTweetRepository(db) 14 | 15 | return &Repositories{ 16 | TweetRepository: tweetRepository, 17 | } 18 | } -------------------------------------------------------------------------------- /restapi-test-app/server/setup.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "restapi-tested-app/config" 8 | "restapi-tested-app/entities" 9 | "restapi-tested-app/utils" 10 | ) 11 | 12 | 13 | func rootHandler() gin.HandlerFunc { 14 | return func(ctx *gin.Context) { 15 | ctx.JSON(http.StatusOK, entities.Response{ 16 | Success: true, 17 | Message: "Hello World!", 18 | Data: struct{}{}, 19 | }) 20 | } 21 | } 22 | 23 | func registerRoutes(router *gin.Engine, hndlrs *Handlers) { 24 | serveHttp := utils.ServeHTTP 25 | 26 | router.GET("/", rootHandler()) 27 | router.GET("/tweet", serveHttp(hndlrs.TweetHandler.GetAllTweets)) 28 | router.GET("/tweet/:id", serveHttp(hndlrs.TweetHandler.GetTweetByID)) 29 | router.GET("/tweet/search", serveHttp(hndlrs.TweetHandler.SearchTweetByText)) 30 | router.POST("/tweet", serveHttp(hndlrs.TweetHandler.CreateTweet)) 31 | router.PUT("/tweet", serveHttp(hndlrs.TweetHandler.UpdateTweet)) 32 | router.DELETE("/tweet/:id", serveHttp(hndlrs.TweetHandler.DeleteTweet)) 33 | } 34 | 35 | func SetupServer() { 36 | fmt.Println("Setting up server") 37 | 38 | configs := config.GetConfig() 39 | db := config.ConnectDB(configs) 40 | 41 | repos := SetupRepositories(db) 42 | uscs := SetupUsecases(repos) 43 | hdnlrs := SetupHandlers(uscs) 44 | 45 | router := gin.Default() 46 | 47 | registerRoutes(router, hdnlrs) 48 | 49 | router.Run(":9090") 50 | } 51 | -------------------------------------------------------------------------------- /restapi-test-app/server/usecases.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "restapi-tested-app/usecases" 4 | 5 | type Usecases struct { 6 | TweetUsecase usecases.TweetUsecase 7 | } 8 | 9 | func SetupUsecases(repos *Repositories) *Usecases { 10 | tweetUsecase := usecases.InitializeTweetUsecase(repos.TweetRepository) 11 | 12 | return &Usecases{ 13 | TweetUsecase: tweetUsecase, 14 | } 15 | } -------------------------------------------------------------------------------- /restapi-test-app/sql/tweet.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS tweets 2 | ( 3 | id serial PRIMARY KEY, 4 | username VARCHAR(128) NOT NULL, 5 | "text" TEXT NOT NULL, 6 | created_at timestamptz NOT NULL DEFAULT Now(), 7 | modified_at timestamptz NOT NULL DEFAULT Now() 8 | ) -------------------------------------------------------------------------------- /restapi-test-app/utils/server_http.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "restapi-tested-app/entities" 7 | ) 8 | 9 | type appHandler func(ctx *gin.Context) *entities.AppResult 10 | 11 | func ServeHTTP(handle appHandler) gin.HandlerFunc { 12 | return func(ctx *gin.Context) { 13 | result := handle(ctx) 14 | if result == nil { 15 | ctx.JSON(http.StatusInternalServerError, entities.Response{ 16 | Success: false, 17 | Message: "INTERNAL SERVER ERROR", 18 | Data: nil, 19 | }) 20 | } 21 | 22 | if result.Err == nil { 23 | ctx.JSON(result.StatusCode, entities.Response{ 24 | Success: true, 25 | Message: result.Message, 26 | Data: result.Data, 27 | }) 28 | } else { 29 | ctx.JSON(result.StatusCode, entities.Response{ 30 | Success: false, 31 | Message: result.Err.Error(), 32 | Data: result.Data, 33 | }) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /restapi-test-app/utils/truncate_table.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jmoiron/sqlx" 6 | ) 7 | 8 | type TruncateTableExecutor struct { 9 | db *sqlx.DB 10 | } 11 | 12 | func InitTruncateTableExecutor(db *sqlx.DB) TruncateTableExecutor { 13 | return TruncateTableExecutor{ 14 | db, 15 | } 16 | } 17 | 18 | func (executor *TruncateTableExecutor) TruncateTable(tableNames []string) { 19 | var err error 20 | 21 | tx, err := executor.db.Beginx() 22 | if err != nil { 23 | panic(err) 24 | } else { 25 | for _, name := range tableNames { 26 | query := fmt.Sprintf("TRUNCATE TABLE %s RESTART IDENTITY CASCADE;", name) 27 | _, err := tx.Exec(query) 28 | if err != nil { 29 | panic(err) 30 | } 31 | } 32 | } 33 | 34 | if err == nil { 35 | tx.Commit() 36 | } else { 37 | tx.Rollback() 38 | panic(err) 39 | } 40 | } -------------------------------------------------------------------------------- /restapi/.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /restapi/config/database.config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | 7 | // Postgres 8 | _ "github.com/lib/pq" 9 | ) 10 | 11 | // DB to use database 12 | var DB *sql.DB 13 | 14 | const ( 15 | host = "localhost" 16 | port = "5432" 17 | user = "postgres" 18 | password = "postgres" 19 | dbname = "postgres" 20 | ) 21 | 22 | // InitDb Initializing database connection 23 | func InitDb() { 24 | config := dbConfig() 25 | var err error 26 | psqlInfo := fmt.Sprintf("host=%s port=%s user=%s "+ 27 | "password=%s dbname=%s sslmode=disable", 28 | config[host], config[port], 29 | config[user], config[password], config[dbname]) 30 | 31 | DB, err = sql.Open("postgres", psqlInfo) 32 | if err != nil { 33 | panic(err) 34 | } 35 | err = DB.Ping() 36 | if err != nil { 37 | panic(err) 38 | } 39 | fmt.Println("Successfully connected!") 40 | 41 | } 42 | 43 | func dbConfig() map[string]string { 44 | conf := make(map[string]string) 45 | conf[host] = host 46 | conf[port] = port 47 | conf[user] = user 48 | conf[password] = password 49 | conf[dbname] = dbname 50 | return conf 51 | } 52 | -------------------------------------------------------------------------------- /restapi/go.mod: -------------------------------------------------------------------------------- 1 | module golang-restapi 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/gin-gonic/gin v1.6.3 8 | github.com/go-playground/validator/v10 v10.3.0 // indirect 9 | github.com/golang/protobuf v1.4.2 // indirect 10 | github.com/google/uuid v1.1.1 11 | github.com/joho/godotenv v1.3.0 12 | github.com/json-iterator/go v1.1.10 // indirect 13 | github.com/lib/pq v1.8.0 14 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 15 | github.com/modern-go/reflect2 v1.0.1 // indirect 16 | golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de 17 | golang.org/x/sys v0.0.0-20200819035508-9a32b3aa38f5 // indirect 18 | google.golang.org/protobuf v1.25.0 // indirect 19 | gopkg.in/yaml.v2 v2.3.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /restapi/handler/service.handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "golang-restapi/model" 5 | "golang-restapi/repository" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | // CreateServiceRequest -- create service request 12 | func CreateServiceRequest(c *gin.Context) { 13 | userID := uint64(c.MustGet("userID").(float64)) 14 | var serviceRequest model.Service 15 | c.Bind(&serviceRequest) 16 | repository.CreateServiceRequest(&serviceRequest, userID) 17 | c.JSON(http.StatusOK, gin.H{ 18 | "message": "Success to create service request", 19 | "data": gin.H{ 20 | "requestID": serviceRequest.RequestID, 21 | "status": serviceRequest.Status, 22 | "vesselName": serviceRequest.VesselName, 23 | "serviceType": serviceRequest.ServiceType, 24 | "dataAgent": serviceRequest.DataAgent, 25 | "cargo": serviceRequest.Cargo, 26 | "etd": serviceRequest.ETD, 27 | "eta": serviceRequest.ETA, 28 | }, 29 | }) 30 | } 31 | 32 | // GetServices -- Get all services 33 | func GetServices(c *gin.Context) { 34 | userID := uint64(c.MustGet("userID").(float64)) 35 | var services []model.Service = repository.GetServices(userID) 36 | c.JSON(http.StatusOK, gin.H{ 37 | "message": "Success to get all services", 38 | "data": services, 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /restapi/handler/user.handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "golang-restapi/repository" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | // GetUserData -- Retrieve user data 12 | func GetUserData(c *gin.Context) { 13 | userID := uint64(c.MustGet("userID").(float64)) 14 | fmt.Println("GetUserData userID", userID) 15 | user, _ := repository.GetUserByID(userID) 16 | c.JSON(http.StatusOK, gin.H{ 17 | "message": "Nice to see you bruh!", 18 | "data": gin.H{ 19 | "_id": user.ID, 20 | "email": user.Email, 21 | "password": user.Password, 22 | }, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /restapi/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "golang-restapi/config" 5 | "golang-restapi/handler" 6 | "golang-restapi/middleware" 7 | "log" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/joho/godotenv" 11 | ) 12 | 13 | func main() { 14 | err := godotenv.Load() 15 | if err != nil { 16 | log.Fatal("Error loading .env file") 17 | } 18 | r := gin.Default() 19 | 20 | // Initialize database connection 21 | config.InitDb() 22 | 23 | // Routes for authentication 24 | authRoute := r.Group("/auth") 25 | { 26 | authRoute.POST("/register", handler.Register) 27 | authRoute.POST("/login", handler.Login) 28 | authRoute.POST("/confirm", handler.ConfirmAccount) 29 | authRoute.POST("/forgot-password", handler.RequestPassword) 30 | authRoute.POST("/forgot-password/confirm", handler.ChangePassword) 31 | } 32 | 33 | // Routes for User 34 | userRoute := r.Group("/user") 35 | userRoute.Use(middleware.AuthMiddleware()) 36 | { 37 | userRoute.GET("/", handler.GetUserData) 38 | } 39 | 40 | // Routes for Service 41 | serviceRoute := r.Group("/service") 42 | serviceRoute.Use(middleware.AuthMiddleware()) 43 | { 44 | serviceRoute.POST("/", handler.CreateServiceRequest) 45 | serviceRoute.GET("/", handler.GetServices) 46 | } 47 | 48 | r.Run() 49 | } 50 | -------------------------------------------------------------------------------- /restapi/makefile: -------------------------------------------------------------------------------- 1 | dev: 2 | go run main.go -------------------------------------------------------------------------------- /restapi/middleware/auth.middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "strings" 7 | 8 | "github.com/dgrijalva/jwt-go" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // AuthMiddleware -- Authentication Middleware 13 | func AuthMiddleware() gin.HandlerFunc { 14 | return func(c *gin.Context) { 15 | validateToken(c) 16 | c.Next() 17 | } 18 | } 19 | 20 | func validateToken(c *gin.Context) { 21 | token := c.Request.Header.Get("Authorization") 22 | if len(token) == 0 { 23 | c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ 24 | "message": "You shall not pass!", 25 | }) 26 | } else { 27 | token = strings.Split(token, "Bearer ")[1] 28 | userID, err := getPayload(token) 29 | if err != nil { 30 | c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ 31 | "message": "You shall not pass!", 32 | }) 33 | } 34 | c.Set("userID", userID) 35 | c.Next() 36 | } 37 | } 38 | 39 | func getPayload(tokenString string) (interface{}, error) { 40 | claims := jwt.MapClaims{} 41 | _, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { 42 | return []byte(os.Getenv("SECRET_KEY")), nil 43 | }) 44 | if err != nil { 45 | // panic(err) 46 | return nil, err 47 | } 48 | return claims["user_id"], nil 49 | } 50 | -------------------------------------------------------------------------------- /restapi/model/service.model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Service -- Representing service request 4 | type Service struct { 5 | ID uint64 `json:"_id"` 6 | RequestID uint64 `json:"requestId"` 7 | Status string `json:"status"` 8 | VesselName string `json:"vesselName"` 9 | ServiceType string `json:"serviceType"` 10 | DataAgent string `json:"dataAgent"` 11 | Cargo string `json:"cargo"` 12 | ETD string `json:"etd"` 13 | ETA string `json:"eta"` 14 | UserID uint64 `json:"userID"` 15 | } 16 | -------------------------------------------------------------------------------- /restapi/model/user.model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // User Model 4 | type User struct { 5 | ID uint64 `json:"_id"` 6 | Email string `json:"email"` 7 | Password string `json:"password"` 8 | UUID string `json:"uuid"` 9 | Confirmed bool `json:"confirmed"` 10 | } 11 | 12 | // ConfirmData --- Used in ConfirmAccount handler 13 | type ConfirmData struct { 14 | Email string `json:"email"` 15 | Password string `json:"password"` 16 | UUID string `json:"uuid"` 17 | } 18 | 19 | // LoginData --- Used in Login handler 20 | type LoginData struct { 21 | Email string `json:"email"` 22 | Password string `json:"password"` 23 | } 24 | 25 | // RequestPasswordData -- Used in RequestPassword 26 | type RequestPasswordData struct { 27 | Email string `json:"email"` 28 | } 29 | 30 | // ForgotPasswordData --- Used in ForgotPassword handler 31 | type ForgotPasswordData struct { 32 | Email string `json:"email"` 33 | UUID string `json:"uuid"` 34 | NewPassword string `json:"newPassword"` 35 | } 36 | -------------------------------------------------------------------------------- /restapi/repository/service.repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "fmt" 5 | "golang-restapi/config" 6 | "golang-restapi/model" 7 | ) 8 | 9 | // CreateServiceRequest -- create service request 10 | func CreateServiceRequest(service *model.Service, userID uint64) { 11 | sqlQuery := ` 12 | INSERT INTO services ( 13 | request_id, 14 | status, 15 | vessel_name, 16 | service_type, 17 | data_agent, 18 | cargo, 19 | etd, 20 | eta, 21 | user_id 22 | ) 23 | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9); 24 | ` 25 | 26 | _, err := config.DB.Exec(sqlQuery, 27 | service.RequestID, 28 | service.Status, 29 | service.VesselName, 30 | service.ServiceType, 31 | service.DataAgent, 32 | service.Cargo, 33 | service.ETD, 34 | service.ETA, 35 | userID, 36 | ) 37 | if err != nil { 38 | panic(err) 39 | } 40 | fmt.Printf("Success to create service request %v\n", *service) 41 | } 42 | 43 | // GetServices -- Get all services by a user 44 | func GetServices(userID uint64) []model.Service { 45 | sqlQuery := ` 46 | SELECT * FROM services 47 | WHERE user_id = $1; 48 | 49 | ` 50 | rows, err := config.DB.Query(sqlQuery, userID) 51 | if err != nil { 52 | panic(err) 53 | } 54 | defer rows.Close() 55 | var result []model.Service 56 | for rows.Next() { 57 | var service = model.Service{} 58 | err = rows.Scan( 59 | &service.ID, 60 | &service.RequestID, 61 | &service.Status, 62 | &service.VesselName, 63 | &service.ServiceType, 64 | &service.DataAgent, 65 | &service.Cargo, 66 | &service.ETD, 67 | &service.ETA, 68 | &service.UserID, 69 | ) 70 | if err != nil { 71 | panic(err) 72 | } 73 | result = append(result, service) 74 | } 75 | err = rows.Err() 76 | if err != nil { 77 | panic(err) 78 | } 79 | fmt.Println("GetServices result", result) 80 | return result 81 | } 82 | -------------------------------------------------------------------------------- /restapi/sql/base.sql: -------------------------------------------------------------------------------- 1 | -- Create users Table 2 | CREATE TABLE users ( 3 | _id SERIAL PRIMARY KEY, 4 | email VARCHAR(256) UNIQUE NOT NULL, 5 | password VARCHAR(256) NOT NULL, 6 | uuid VARCHAR(256), 7 | confirmed BOOLEAN DEFAULT FALSE 8 | ); 9 | 10 | -- Create services table 11 | CREATE TABLE services ( 12 | _id SERIAL PRIMARY KEY, 13 | request_id INT UNIQUE NOT NULL, 14 | status VARCHAR(64) NOT NULL, 15 | vessel_name VARCHAR(256) NOT NULL, 16 | service_type VARCHAR(256) NOT NULL, 17 | data_agent VARCHAR(256) NOT NULL, 18 | cargo VARCHAR(256) NOT NULL, 19 | etd VARCHAR(256) NOT NULL, 20 | eta VARCHAR(256) NOT NULL, 21 | user_id INT NOT NULL, 22 | FOREIGN KEY (user_id) REFERENCES users(_id) ON DELETE CASCADE 23 | ); -------------------------------------------------------------------------------- /restapi/utils/password.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "golang.org/x/crypto/bcrypt" 5 | ) 6 | 7 | // HashPassword -- Generate hashed password 8 | func HashPassword(password string) (string, error) { 9 | bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) 10 | return string(bytes), err 11 | } 12 | 13 | // CheckPasswordHash -- Check user password's validity 14 | func CheckPasswordHash(password, hash string) bool { 15 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) 16 | return err == nil 17 | } 18 | -------------------------------------------------------------------------------- /restapi/utils/response.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // ResponseSuccess ... 10 | func ResponseSuccess(c *gin.Context, message string, data gin.H) { 11 | c.JSON(http.StatusOK, gin.H{ 12 | "message": message, 13 | "data": data, 14 | }) 15 | } 16 | 17 | // ResponseUnauthorized ... 18 | func ResponseUnauthorized(c *gin.Context) { 19 | c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ 20 | "message": "Unauthorized", 21 | }) 22 | } 23 | 24 | // ResponseBadRequest ... 25 | func ResponseBadRequest(c *gin.Context, message string) { 26 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ 27 | "message": message, 28 | }) 29 | } 30 | 31 | // ResponseServerError ... 32 | func ResponseServerError(c *gin.Context) { 33 | c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ 34 | "message": "Internal Server Error", 35 | }) 36 | } 37 | 38 | // ResponseNotFound ... 39 | func ResponseNotFound(c *gin.Context, message string) { 40 | c.AbortWithStatusJSON(http.StatusNotFound, gin.H{ 41 | "message": message, 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /restapi/utils/token.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/dgrijalva/jwt-go" 7 | ) 8 | 9 | // CreateToken -- Create user token 10 | func CreateToken(userID uint64) (string, error) { 11 | var err error 12 | atClaims := jwt.MapClaims{} 13 | atClaims["authorized"] = true 14 | atClaims["user_id"] = userID 15 | at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) 16 | token, err := at.SignedString([]byte(os.Getenv("SECRET_KEY"))) 17 | if err != nil { 18 | return "", err 19 | } 20 | return token, nil 21 | } 22 | -------------------------------------------------------------------------------- /swagger-docs-app/.gitignore: -------------------------------------------------------------------------------- 1 | vendor -------------------------------------------------------------------------------- /swagger-docs-app/Makefile: -------------------------------------------------------------------------------- 1 | check-swagger: 2 | which swagger || (GO111MODULE=off go get -u github.com/go-swagger/go-swagger/cmd/swagger) 3 | 4 | swagger: check-swagger 5 | GO111MODULE=on go mod vendor && GO111MODULE=off swagger generate spec -o ./swagger.yaml --scan-models 6 | 7 | serve-swagger: check-swagger 8 | swagger serve -F=swagger swagger.yaml -------------------------------------------------------------------------------- /swagger-docs-app/api/example.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | type HelloResponse struct { 6 | Message string `json:"message"` 7 | } 8 | 9 | func HelloHandler(ctx *gin.Context) { 10 | resp := HelloResponse{ 11 | Message: "Hello World", 12 | } 13 | ctx.JSON(200, resp) 14 | } 15 | -------------------------------------------------------------------------------- /swagger-docs-app/docs/example.go: -------------------------------------------------------------------------------- 1 | // Package classification Learn Swagger. 2 | // 3 | // Documentation of our Learn Swagger API. 4 | // 5 | // Schemes: http 6 | // BasePath: / 7 | // Version: 1.0.0 8 | // Host: localhost:8080 9 | // 10 | // Consumes: 11 | // - application/json 12 | // 13 | // Produces: 14 | // - application/json 15 | // 16 | // Security: 17 | // - basic 18 | // 19 | // SecurityDefinitions: 20 | // basic: 21 | // type: basic 22 | // 23 | // swagger:meta 24 | package docs 25 | 26 | import "swagger-docs-app/api" 27 | 28 | // swagger:route GET /hello hello-tag idOfHelloEndpoint 29 | // Hello does some amazing stuff. 30 | // responses: 31 | // 200: helloResponse 32 | 33 | // This text will appear as description of your response body. 34 | // swagger:response helloResponse 35 | type helloResponseWrapper struct { 36 | // in:body 37 | Body api.HelloResponse 38 | } 39 | -------------------------------------------------------------------------------- /swagger-docs-app/go.mod: -------------------------------------------------------------------------------- 1 | module swagger-docs-app 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.1 7 | github.com/go-playground/validator/v10 v10.6.0 // indirect 8 | github.com/golang/protobuf v1.5.2 // indirect 9 | github.com/json-iterator/go v1.1.11 // indirect 10 | github.com/kr/text v0.2.0 // indirect 11 | github.com/leodido/go-urn v1.2.1 // indirect 12 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 13 | github.com/modern-go/reflect2 v1.0.1 // indirect 14 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 15 | github.com/stretchr/testify v1.7.0 // indirect 16 | github.com/ugorji/go v1.2.5 // indirect 17 | golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect 18 | golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 // indirect 19 | golang.org/x/text v0.3.6 // indirect 20 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 21 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 22 | gopkg.in/yaml.v2 v2.4.0 // indirect 23 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /swagger-docs-app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "swagger-docs-app/api" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func CORSMiddleware() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") 12 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") 13 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") 14 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") 15 | 16 | if c.Request.Method == "OPTIONS" { 17 | c.AbortWithStatus(204) 18 | return 19 | } 20 | 21 | c.Next() 22 | } 23 | } 24 | 25 | func main() { 26 | r := gin.Default() 27 | r.Use(CORSMiddleware()) 28 | r.GET("/hello", api.HelloHandler) 29 | 30 | r.Run() 31 | } 32 | -------------------------------------------------------------------------------- /swagger-docs-app/swagger.yaml: -------------------------------------------------------------------------------- 1 | basePath: / 2 | consumes: 3 | - application/json 4 | definitions: 5 | HelloResponse: 6 | properties: 7 | message: 8 | type: string 9 | x-go-name: Message 10 | type: object 11 | x-go-package: swagger-docs-app/api 12 | host: localhost:8080 13 | info: 14 | description: Documentation of our Learn Swagger API. 15 | title: Learn Swagger. 16 | version: 1.0.0 17 | paths: 18 | /hello: 19 | get: 20 | operationId: idOfHelloEndpoint 21 | responses: 22 | "200": 23 | $ref: '#/responses/helloResponse' 24 | summary: Hello does some amazing stuff. 25 | tags: 26 | - hello-tag 27 | produces: 28 | - application/json 29 | responses: 30 | helloResponse: 31 | description: This text will appear as description of your response body. 32 | schema: 33 | $ref: '#/definitions/HelloResponse' 34 | schemes: 35 | - http 36 | securityDefinitions: 37 | basic: 38 | type: basic 39 | swagger: "2.0" 40 | -------------------------------------------------------------------------------- /testify-yo/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /testify-yo/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /testify-yo/.idea/testify-yo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /testify-yo/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /testify-yo/go.mod: -------------------------------------------------------------------------------- 1 | module testify-yo 2 | 3 | go 1.15 4 | 5 | require github.com/stretchr/testify v1.7.0 6 | -------------------------------------------------------------------------------- /testify-yo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 8 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /testify-yo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func Add(x, y float64) float64 { 6 | return x + y 7 | } 8 | 9 | func Subtract(x, y float64) float64 { 10 | return x - y 11 | } 12 | 13 | func Multiply(x, y float64) float64 { 14 | return x * y 15 | } 16 | 17 | func Divide(x, y float64) float64 { 18 | return x / y 19 | } 20 | 21 | func main() { 22 | fmt.Println("Hello World") 23 | } 24 | -------------------------------------------------------------------------------- /testify-yo/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestAdd(t *testing.T) { 10 | testCases := []struct { 11 | x, y float64 12 | output float64 13 | }{ 14 | {1, 2, 3}, 15 | {-1, 2, 1}, 16 | {1.51, 2.31, 3.82}, 17 | {0.1, 1.1, 1.2}, 18 | {0, 0, 0}, 19 | } 20 | 21 | for _, c := range testCases { 22 | assert.InDelta(t, Add(c.x, c.y), c.output, 0.0000001) 23 | } 24 | } 25 | 26 | func TestSubtract(t *testing.T) { 27 | testCases := []struct { 28 | x, y float64 29 | output float64 30 | }{ 31 | {3, 2, 1}, 32 | {-1, 2, -3}, 33 | {1.51, 2.31, -0.8}, 34 | {0.1, 1.1, -1}, 35 | {0, 0, 0}, 36 | } 37 | 38 | for _, c := range testCases { 39 | assert.InDelta(t, Subtract(c.x, c.y), c.output, 0.0000001) 40 | } 41 | } 42 | 43 | func TestMultiply(t *testing.T) { 44 | testCases := []struct { 45 | x, y float64 46 | output float64 47 | }{ 48 | {3, 2, 6}, 49 | {-1, 2, -2}, 50 | {1.51, 2.31, 3.4881}, 51 | {0.1, 1.1, 0.11}, 52 | {0, 0, 0}, 53 | } 54 | 55 | for _, c := range testCases { 56 | assert.InDelta(t, Multiply(c.x, c.y), c.output, 0.0000001) 57 | } 58 | } 59 | 60 | func TestDivide(t *testing.T) { 61 | testCases := []struct { 62 | x, y float64 63 | output float64 64 | }{ 65 | {3, 2, 1.5}, 66 | {-1, 2, -0.5}, 67 | {1.51, 2.31, 0.65367965368}, 68 | {0.1, 1.1, 0.09090909091}, 69 | {1, 1, 1}, 70 | } 71 | 72 | for _, c := range testCases { 73 | assert.InDelta(t, Divide(c.x, c.y), c.output, 0.0000001) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /testify-yo/mockyo/mockyo_test.go: -------------------------------------------------------------------------------- 1 | package mockyo 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/mock" 8 | ) 9 | 10 | type MockedObject struct { 11 | mock.Mock 12 | } 13 | 14 | func (m *MockedObject) DoAmazingStuff(input int) (bool, error) { 15 | args := m.Called(input) 16 | return args.Bool(0), args.Error(1) 17 | } 18 | 19 | func WhatIsThisFunction(m *MockedObject) { 20 | fmt.Println("WhatIsThisFunction") 21 | m.DoAmazingStuff(123) 22 | } 23 | 24 | func TestAmazingStuff(t *testing.T) { 25 | testObj := new(MockedObject) 26 | testObj.On("DoAmazingStuff", 123).Return(true, nil) 27 | WhatIsThisFunction(testObj) 28 | testObj.AssertExpectations(t) 29 | } 30 | -------------------------------------------------------------------------------- /testify-yo/suiteyo/suiteyo_test.go: -------------------------------------------------------------------------------- 1 | package suiteyo 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/suite" 8 | ) 9 | 10 | type YoTestSuite struct { 11 | suite.Suite 12 | StartingVariale int64 13 | } 14 | 15 | func (suite *YoTestSuite) SetupTest() { 16 | fmt.Println("SetupTest") 17 | suite.StartingVariale = 21 18 | } 19 | 20 | func (suite *YoTestSuite) TearDownTest() { 21 | fmt.Println("TearDown") 22 | suite.StartingVariale = 0 23 | } 24 | 25 | func (suite *YoTestSuite) TestOne() { 26 | suite.Equal(int64(21), suite.StartingVariale) 27 | } 28 | 29 | func (suite *YoTestSuite) TestTwo() { 30 | suite.StartingVariale = 99 31 | suite.Equal(int64(99), suite.StartingVariale) 32 | } 33 | 34 | func TestYoTestSuite(t *testing.T) { 35 | suite.Run(t, new(YoTestSuite)) 36 | } 37 | -------------------------------------------------------------------------------- /testing-yo/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /testing-yo/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /testing-yo/.idea/testing-yo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /testing-yo/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /testing-yo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type MyInt struct { 6 | number int 7 | } 8 | 9 | type MyIntI interface { 10 | Useless() int 11 | } 12 | 13 | func (m *MyInt) Useless() int { 14 | return m.number 15 | } 16 | 17 | func UsingUseless(m MyIntI) int { 18 | return m.Useless() 19 | } 20 | 21 | type notification struct {} 22 | 23 | type Notification interface { 24 | SendPaymentNotification(amount int) string 25 | } 26 | 27 | func (n *notification) SendPaymentNotification(amount int) string { 28 | return fmt.Sprintf("you have made the payment with an amount of %d", amount) 29 | } 30 | 31 | type payment struct { 32 | notification Notification 33 | } 34 | 35 | type Payment interface { 36 | MakePayment(amount int) bool 37 | } 38 | 39 | func (p *payment) MakePayment(amount int) bool { 40 | fmt.Printf("make payment with an amount of %d\n", amount) 41 | fmt.Println(p.notification.SendPaymentNotification(amount)) 42 | return true 43 | } 44 | 45 | func main() { 46 | fmt.Println("Hello World") 47 | } 48 | -------------------------------------------------------------------------------- /twit/.air.toml: -------------------------------------------------------------------------------- 1 | # Config file for [Air](https://github.com/cosmtrek/air) in TOML format 2 | 3 | # Working directory 4 | # . or absolute path, please note that the directories following must be under root 5 | root = "." 6 | tmp_dir = "tmp" 7 | 8 | [build] 9 | # Just plain old shell command. You could use `make` as well. 10 | cmd = "go build -o ./tmp/main ." 11 | # Binary file yields from `cmd`. 12 | bin = "./tmp/main" 13 | 14 | # Customize binary. 15 | # This is how you start to run your application. Since my application will works like CLI, so to run it, like to make a CLI call. 16 | full_bin = "APP_ENV=dev APP_USER=air ./tmp/main" 17 | # This log file places in your tmp_dir. 18 | log = "air_errors.log" 19 | # Watch these filename extensions. 20 | include_ext = ["go", "yaml"] 21 | # Ignore these filename extensions or directories. 22 | exclude_dir = ["tmp"] 23 | # It's not necessary to trigger build each time file changes if it's too frequent. 24 | delay = 1000 # ms 25 | 26 | [log] 27 | # Show log time 28 | time = true 29 | 30 | [color] 31 | # Customize each part's color. If no color found, use the raw app log. 32 | main = "magenta" 33 | watcher = "cyan" 34 | build = "yellow" 35 | runner = "green" 36 | 37 | [misc] 38 | # Delete tmp directory on exit 39 | clean_on_exit = true -------------------------------------------------------------------------------- /twit/.gitignore: -------------------------------------------------------------------------------- 1 | *.env 2 | config.toml 3 | /tmp -------------------------------------------------------------------------------- /twit/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /twit/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /twit/.idea/twit.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /twit/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /twit/Dockerfile: -------------------------------------------------------------------------------- 1 | # Please keep up to date with the new-version of Golang docker for builder 2 | FROM golang:latest 3 | 4 | RUN apt update && apt upgrade -y && \ 5 | apt install -y git \ 6 | make openssh-client 7 | 8 | WORKDIR /app 9 | 10 | RUN curl -fLo install.sh https://raw.githubusercontent.com/cosmtrek/air/master/install.sh \ 11 | && chmod +x install.sh && sh install.sh && cp ./bin/air /bin/air 12 | 13 | CMD air 14 | 15 | ## Production environment (alias: base) 16 | #FROM golang:1.12-alpine as base 17 | #RUN apk update && apk upgrade && \ 18 | #apk add --no-cache bash git openssh 19 | #WORKDIR /home/my-project 20 | # 21 | ## Development environment 22 | ## Unfortunately, linux alpine doesn't have fswatch package by default, so we will need to download source code and make it by outselves. 23 | #FROM base as dev 24 | #RUN apk add --no-cache autoconf automake libtool gettext gettext-dev make g++ texinfo curl 25 | #WORKDIR /root 26 | #RUN wget https://github.com/emcrisostomo/fswatch/releases/download/1.14.0/fswatch-1.14.0.tar.gz 27 | #RUN tar -xvzf fswatch-1.14.0.tar.gz 28 | #WORKDIR /root/fswatch-1.14.0 29 | #RUN ./configure 30 | #RUN make 31 | #RUN make install 32 | #WORKDIR /home/my-project -------------------------------------------------------------------------------- /twit/Dockerfile.test: -------------------------------------------------------------------------------- 1 | # Please keep up to date with the new-version of Golang docker for builder 2 | FROM golang:latest 3 | 4 | ADD . /app 5 | 6 | WORKDIR /app -------------------------------------------------------------------------------- /twit/Makefile: -------------------------------------------------------------------------------- 1 | run-dev: 2 | docker-compose up --remove-orphans 3 | 4 | down-dev: 5 | docker-compose down 6 | 7 | run-test: 8 | docker-compose -f docker-compose-test.yaml --env-file test.env up --remove-orphans 9 | 10 | down-test: 11 | docker-compose -f docker-compose-test.yaml --env-file test.env down 12 | 13 | check-swagger: 14 | which swagger || (GO111MODULE=off go get -u github.com/go-swagger/go-swagger/cmd/swagger) 15 | 16 | swagger: check-swagger 17 | swagger generate spec -o ./swagger.yaml --scan-models 18 | 19 | serve-swagger: check-swagger 20 | swagger serve -F=swagger swagger.yaml -------------------------------------------------------------------------------- /twit/configs/config.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "twit/models" 7 | 8 | "github.com/joho/godotenv" 9 | ) 10 | 11 | func GetConfig() *models.Config { 12 | _, err := os.Stat(".env") 13 | 14 | if !os.IsNotExist(err) { 15 | err := godotenv.Load(".env") 16 | 17 | if err != nil { 18 | log.Println("Error while reading the env file", err) 19 | panic(err) 20 | } 21 | } 22 | 23 | config := &models.Config{ 24 | Database: models.DatabaseConfig{ 25 | Host: os.Getenv("DB_HOST"), 26 | Port: os.Getenv("DB_PORT"), 27 | DbName: os.Getenv("DB"), 28 | Username: os.Getenv("POSTGRES_USER"), 29 | Password: os.Getenv("POSTGRES_PASSWORD"), 30 | }, 31 | TimeZone: "Asia/Jakarta", 32 | SecretKey: os.Getenv("SECRET_KEY"), 33 | } 34 | 35 | return config 36 | } 37 | -------------------------------------------------------------------------------- /twit/configs/db.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "fmt" 5 | "twit/models" 6 | 7 | "gorm.io/driver/postgres" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | var DB *gorm.DB 12 | 13 | func InitializeDB(config *models.Config) *gorm.DB { 14 | dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=%s", 15 | config.Database.Host, 16 | config.Database.Username, 17 | config.Database.Password, 18 | config.Database.DbName, 19 | config.Database.Port, 20 | config.TimeZone, 21 | ) 22 | db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ 23 | SkipDefaultTransaction: true, 24 | PrepareStmt: true, 25 | }) 26 | 27 | DB = db 28 | 29 | if err != nil { 30 | panic("Failed to connect to the database") 31 | } 32 | 33 | return db 34 | } 35 | 36 | func AutoMigrate(db *gorm.DB) { 37 | // Register model and schema 38 | db.AutoMigrate(&models.User{}) 39 | } 40 | -------------------------------------------------------------------------------- /twit/dev.env: -------------------------------------------------------------------------------- 1 | POSTGRES_USER=agusrichard 2 | POSTGRES_PASSWORD=agusrichard 3 | DB=twit_db 4 | DB_HOST=db 5 | DB_PORT=5432 -------------------------------------------------------------------------------- /twit/docker-compose-test.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | web-test: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile.test 7 | container_name: twit_api_test 8 | env_file: 9 | - "test.env" 10 | ports: 11 | - "9090:9090" 12 | depends_on: 13 | - db-test 14 | volumes: 15 | - ./:/app 16 | links: 17 | - db 18 | db-test: 19 | image: postgres:13 20 | ports: 21 | - "5432:5432" 22 | container_name: twit_db_test 23 | env_file: 24 | - "test.env" 25 | environment: 26 | - POSTGRES_USER=${POSTGRES_USER} 27 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 28 | - POSTGRES_DB=${DB} 29 | volumes: 30 | - twit_db_test:/var/lib/postgresql/data 31 | volumes: 32 | twit_db_test: 33 | driver: local 34 | -------------------------------------------------------------------------------- /twit/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | web: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | container_name: twit_api 8 | env_file: 9 | - ".env" 10 | ports: 11 | - "9090:9090" 12 | depends_on: 13 | - db 14 | volumes: 15 | - ./:/app 16 | links: 17 | - db 18 | db: 19 | image: postgres:13 20 | ports: 21 | - "5432:5432" 22 | container_name: twit_db 23 | env_file: 24 | - ".env" 25 | environment: 26 | - POSTGRES_USER=${POSTGRES_USER} 27 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 28 | - POSTGRES_DB=${DB} 29 | volumes: 30 | - twit_db:/var/lib/postgresql/data 31 | swagger-ui: 32 | image: swaggerapi/swagger-ui 33 | container_name: swagger-ui 34 | ports: 35 | - "8080:8080" 36 | environment: 37 | - API_URL=./swagger.yaml 38 | volumes: 39 | - ./swagger.yaml:/usr/share/nginx/html/swagger.yaml 40 | volumes: 41 | twit_db: 42 | driver: local 43 | -------------------------------------------------------------------------------- /twit/go.mod: -------------------------------------------------------------------------------- 1 | module twit 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/gin-gonic/gin v1.6.3 8 | github.com/go-playground/validator/v10 v10.4.1 // indirect 9 | github.com/golang/protobuf v1.5.1 // indirect 10 | github.com/jackc/pgproto3/v2 v2.0.7 // indirect 11 | github.com/jinzhu/now v1.1.2 // indirect 12 | github.com/joho/godotenv v1.3.0 13 | github.com/json-iterator/go v1.1.10 // indirect 14 | github.com/leodido/go-urn v1.2.1 // indirect 15 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 16 | github.com/modern-go/reflect2 v1.0.1 // indirect 17 | github.com/stretchr/testify v1.7.0 18 | github.com/ugorji/go v1.2.4 // indirect 19 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 20 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect 21 | golang.org/x/text v0.3.5 // indirect 22 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 23 | gopkg.in/yaml.v2 v2.4.0 // indirect 24 | gorm.io/driver/postgres v1.0.8 25 | gorm.io/gorm v1.21.4 26 | ) 27 | -------------------------------------------------------------------------------- /twit/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "twit/servers" 5 | ) 6 | 7 | func main() { 8 | router := servers.SetupServer() 9 | 10 | router.Run(":9090") 11 | } 12 | -------------------------------------------------------------------------------- /twit/middlewares/auth.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/http" 5 | "twit/utils" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func AuthenticateUser() gin.HandlerFunc { 11 | return func(ctx *gin.Context) { 12 | authorizationHeader := ctx.Request.Header["Authorization"][0] 13 | 14 | user, err := utils.ParseToken(authorizationHeader) 15 | if err != nil { 16 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ 17 | "success": false, 18 | "message": "You are unauthorized", 19 | "data": nil, 20 | }) 21 | } else { 22 | ctx.Set("UserID", user.ID) 23 | ctx.Set("Email", user.Email) 24 | ctx.Next() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /twit/models/config.models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Config struct { 4 | Database DatabaseConfig 5 | TimeZone string 6 | SecretKey string 7 | } 8 | 9 | type DatabaseConfig struct { 10 | Host string 11 | Port string 12 | DbName string 13 | Username string 14 | Password string 15 | } 16 | -------------------------------------------------------------------------------- /twit/models/errors.models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "fmt" 4 | 5 | type RequestError struct { 6 | StatusCode int64 7 | Err error 8 | } 9 | 10 | func (r *RequestError) Error() string { 11 | return fmt.Sprintf(r.Err.Error()) 12 | } 13 | -------------------------------------------------------------------------------- /twit/models/requests/user.requests.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | type RegisterUserRequest struct { 4 | Email string `json:"email"` 5 | Password string `json:"password"` 6 | Username string `json:"username"` 7 | } 8 | 9 | type LoginUserRequest struct { 10 | Email string `json:"email"` 11 | Password string `json:"password"` 12 | } 13 | -------------------------------------------------------------------------------- /twit/models/responses/main.responses.go: -------------------------------------------------------------------------------- 1 | package responses 2 | 3 | import "twit/models" 4 | 5 | type Response struct { 6 | Success bool `json:"success"` 7 | Message string `json:"message"` 8 | Data interface{} `json:"data"` 9 | } 10 | 11 | type LoginData struct { 12 | AccessToken string `json:"access-token"` 13 | User models.User `json:"user"` 14 | } 15 | 16 | type LoginUserResponse struct { 17 | Success bool `json:"success"` 18 | Message string `json:"message"` 19 | Data LoginData `json:"data"` 20 | } 21 | -------------------------------------------------------------------------------- /twit/models/responses/user.models.go: -------------------------------------------------------------------------------- 1 | package responses 2 | -------------------------------------------------------------------------------- /twit/models/user.models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type User struct { 10 | ID uint `gorm:"primaryKey" json:"id"` 11 | CreatedAt time.Time `json:"createdAt"` 12 | UpdatedAt time.Time `json:"updatedAt"` 13 | DeletedAt gorm.DeletedAt `gorm:"index"` 14 | Email string `gorm:"uniqueIndex;not null" json:"email"` 15 | Username string `json:"username" json:"username"` 16 | Password string `gorm:"not null" json:"password"` 17 | } 18 | -------------------------------------------------------------------------------- /twit/repositories/user.repositories.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "twit/models" 7 | "twit/utils" 8 | 9 | "gorm.io/gorm" 10 | ) 11 | 12 | type userRepository struct { 13 | db *gorm.DB 14 | } 15 | 16 | type UserRepository interface { 17 | RegisterUser(user models.User) *models.RequestError 18 | GetUserData(email string) (models.User, *models.RequestError) 19 | } 20 | 21 | func InitUserRepository(db *gorm.DB) UserRepository { 22 | return &userRepository{ 23 | db, 24 | } 25 | } 26 | 27 | func (userRepository *userRepository) RegisterUser(user models.User) *models.RequestError { 28 | result := userRepository.db.Select("Email", "Username", "Password").Create(&user) 29 | if result.Error != nil { 30 | err := &models.RequestError{ 31 | StatusCode: http.StatusBadRequest, 32 | Err: errors.New("This email has been registered. Choose another one"), 33 | } 34 | utils.Logging(err) 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | 41 | func (userRepository *userRepository) GetUserData(email string) (models.User, *models.RequestError) { 42 | var user models.User 43 | result := userRepository.db.First(&user, "email = ?", email) 44 | if result.Error != nil { 45 | err := &models.RequestError{ 46 | StatusCode: http.StatusBadRequest, 47 | Err: errors.New("No user found"), 48 | } 49 | utils.Logging(err) 50 | return models.User{}, err 51 | } 52 | 53 | return user, nil 54 | } 55 | -------------------------------------------------------------------------------- /twit/servers/structs.go: -------------------------------------------------------------------------------- 1 | package servers 2 | 3 | import ( 4 | "twit/handlers" 5 | "twit/repositories" 6 | "twit/usecases" 7 | ) 8 | 9 | type Repositories struct { 10 | UserRepository repositories.UserRepository 11 | } 12 | 13 | type Usecases struct { 14 | UserUsecase usecases.UserUsecase 15 | } 16 | 17 | type Handlers struct { 18 | UserHandler handlers.UserHandler 19 | } 20 | -------------------------------------------------------------------------------- /twit/test.env: -------------------------------------------------------------------------------- 1 | POSTGRES_USER=agusrichardtest 2 | POSTGRES_PASSWORD=agusrichardtest 3 | DB=twit_db_test 4 | DB_HOST=db-test 5 | DB_PORT=5432 -------------------------------------------------------------------------------- /twit/test_result.txt: -------------------------------------------------------------------------------- 1 | mode: set 2 | -------------------------------------------------------------------------------- /twit/tests/users/repositories/register_user_test.go: -------------------------------------------------------------------------------- 1 | package userRepositoryTests 2 | 3 | import ( 4 | "testing" 5 | "twit/configs" 6 | "twit/models" 7 | "twit/repositories" 8 | "twit/servers" 9 | "twit/utils" 10 | 11 | "github.com/stretchr/testify/suite" 12 | ) 13 | 14 | type RepositoryRegisterUserSuite struct { 15 | suite.Suite 16 | repository repositories.UserRepository 17 | cleanupExecutor utils.TruncateTableExecutor 18 | } 19 | 20 | func (suite *RepositoryRegisterUserSuite) SetupTest() { 21 | repository := servers.SetupRepositories().UserRepository 22 | 23 | suite.repository = repository 24 | 25 | cleanupExecutor := utils.InitTruncateTableExecutor(configs.DB) 26 | suite.cleanupExecutor = cleanupExecutor 27 | } 28 | 29 | func (suite *RepositoryRegisterUserSuite) TearDownTest() { 30 | defer suite.cleanupExecutor.TruncateTable([]string{"users"}) 31 | } 32 | 33 | func (suite *RepositoryRegisterUserSuite) TestRegisterUserEmptyEmail() { 34 | user := models.User{ 35 | Password: "password", 36 | Username: "username", 37 | } 38 | err := suite.repository.RegisterUser(user) 39 | suite.Error(err) 40 | } 41 | 42 | func (suite *RepositoryRegisterUserSuite) TestRegisterUserEmptyPassword() { 43 | user := models.User{ 44 | Email: "email", 45 | Username: "username", 46 | } 47 | err := suite.repository.RegisterUser(user) 48 | suite.Error(err) 49 | } 50 | 51 | func (suite *RepositoryRegisterUserSuite) TestRegisterUserEmptyUsername() { 52 | user := models.User{ 53 | Email: "email", 54 | Password: "password", 55 | } 56 | err := suite.repository.RegisterUser(user) 57 | suite.Error(err) 58 | } 59 | 60 | func TestRepositoryRegisterUserSuite(t *testing.T) { 61 | suite.Run(t, new(RepositoryRegisterUserSuite)) 62 | } 63 | -------------------------------------------------------------------------------- /twit/tmp/air_errors.log: -------------------------------------------------------------------------------- 1 | exit status 2exit status 1exit status 2exit status 2exit status 2exit status 1exit status 1exit status 1exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 1exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2 -------------------------------------------------------------------------------- /twit/tmp/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusrichard/go-workbook/28289004eafbf4bbf995c7df2bcb388f95aa9938/twit/tmp/main -------------------------------------------------------------------------------- /twit/utils/logging.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "log" 5 | "twit/models" 6 | ) 7 | 8 | func Logging(err *models.RequestError) { 9 | if err != nil { 10 | log.Println(err.Error()) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /twit/utils/password.utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "twit/models" 7 | 8 | "golang.org/x/crypto/bcrypt" 9 | ) 10 | 11 | func HashPassword(password string) (string, *models.RequestError) { 12 | bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) 13 | if err != nil { 14 | return "", &models.RequestError{ 15 | StatusCode: http.StatusInternalServerError, 16 | Err: errors.New("INTERNAL SERVER ERROR"), 17 | } 18 | } 19 | return string(bytes), nil 20 | } 21 | 22 | func CheckPasswordHash(password, hash string) bool { 23 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) 24 | return err == nil 25 | } 26 | -------------------------------------------------------------------------------- /twit/utils/timing.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | func MeasureExecutionTime(start time.Time, name string) { 9 | elapsed := time.Since(start) 10 | log.Printf("%s took %s", name, elapsed) 11 | } 12 | -------------------------------------------------------------------------------- /twit/utils/token.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | "twit/configs" 11 | "twit/models" 12 | 13 | "github.com/dgrijalva/jwt-go" 14 | ) 15 | 16 | var SecretKey = []byte(configs.GetConfig().SecretKey) 17 | 18 | func GenerateToken(user models.User) (string, *models.RequestError) { 19 | token := jwt.New(jwt.SigningMethodHS256) 20 | claims := token.Claims.(jwt.MapClaims) 21 | claims["id"] = user.ID 22 | claims["email"] = user.Email 23 | claims["exp"] = time.Now().Add(time.Hour * 24).Unix() 24 | tokenString, err := token.SignedString(SecretKey) 25 | if err != nil { 26 | err := &models.RequestError{ 27 | StatusCode: http.StatusInternalServerError, 28 | Err: errors.New("INTERNAL SERVER ERROR"), 29 | } 30 | Logging(err) 31 | return "", err 32 | } 33 | return tokenString, nil 34 | } 35 | 36 | func ParseToken(tokenStr string) (models.User, error) { 37 | token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { 38 | return SecretKey, nil 39 | }) 40 | if err != nil { 41 | log.Println("Error to parse token", err) 42 | return models.User{}, err 43 | } 44 | 45 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 46 | idStr := fmt.Sprintf("%v", claims["id"]) 47 | id, _ := strconv.ParseInt(idStr, 10, 64) 48 | email := claims["email"].(string) 49 | return models.User{Email: email, ID: uint(id)}, nil 50 | } 51 | 52 | return models.User{}, err 53 | } 54 | -------------------------------------------------------------------------------- /twit/utils/truncate-table.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type TruncateTableExecutor struct { 10 | db *gorm.DB 11 | } 12 | 13 | func InitTruncateTableExecutor(db *gorm.DB) TruncateTableExecutor { 14 | return TruncateTableExecutor{ 15 | db, 16 | } 17 | } 18 | 19 | func (executor *TruncateTableExecutor) TruncateTable(tableNames []string) { 20 | executor.db.Transaction(func(tx *gorm.DB) error { 21 | for _, tableName := range tableNames { 22 | query := fmt.Sprintf("TRUNCATE TABLE %s RESTART IDENTITY CASCADE;", tableName) 23 | if err := tx.Exec(query).Error; err != nil { 24 | tx.Rollback() 25 | return err 26 | } 27 | } 28 | 29 | return tx.Commit().Error 30 | }) 31 | } 32 | --------------------------------------------------------------------------------