├── docs └── testtask.pdf ├── cmd └── umbrella-test-task │ └── main.go ├── internal ├── app │ ├── service │ │ └── service.go │ ├── mw │ │ └── mw.go │ └── endpoint │ │ └── endpoint.go └── pkg │ └── app │ └── app.go ├── go.mod ├── README.md └── go.sum /docs/testtask.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatecon/echo-middleware-assessment/HEAD/docs/testtask.pdf -------------------------------------------------------------------------------- /cmd/umbrella-test-task/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/spatecon/echo-middleware-assessment/internal/pkg/app" 7 | ) 8 | 9 | func main() { 10 | a, err := app.New() 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | 15 | err = a.Run() 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/app/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Service struct { 8 | } 9 | 10 | func New() *Service { 11 | return &Service{} 12 | } 13 | 14 | func (s *Service) DaysLeft() int64 { 15 | d := time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC) 16 | 17 | dur := time.Until(d) 18 | 19 | return int64(dur.Hours()) / 24 20 | } 21 | -------------------------------------------------------------------------------- /internal/app/mw/mw.go: -------------------------------------------------------------------------------- 1 | package mw 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | 7 | "github.com/labstack/echo/v4" 8 | ) 9 | 10 | const roleAdmin = "admin" 11 | 12 | func RoleCheck(next echo.HandlerFunc) echo.HandlerFunc { 13 | return func(ctx echo.Context) error { 14 | val := ctx.Request().Header.Get("User-Role") 15 | val = strings.ToLower(val) 16 | 17 | if strings.Contains(val, roleAdmin) { 18 | log.Println("red button user detected") 19 | } 20 | 21 | err := next(ctx) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | return nil 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/app/endpoint/endpoint.go: -------------------------------------------------------------------------------- 1 | package endpoint 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/labstack/echo/v4" 8 | ) 9 | 10 | type Service interface { 11 | DaysLeft() int64 12 | } 13 | 14 | type Endpoint struct { 15 | s Service 16 | } 17 | 18 | func New(s Service) *Endpoint { 19 | return &Endpoint{ 20 | s: s, 21 | } 22 | } 23 | 24 | func (e *Endpoint) Status(ctx echo.Context) error { 25 | d := e.s.DaysLeft() 26 | 27 | s := fmt.Sprintf("Days left: %d", d) 28 | 29 | err := ctx.String(http.StatusOK, s) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/spatecon/echo-middleware-assessment 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/labstack/echo/v4 v4.9.1 // indirect 7 | github.com/labstack/gommon v0.4.0 // indirect 8 | github.com/mattn/go-colorable v0.1.11 // indirect 9 | github.com/mattn/go-isatty v0.0.14 // indirect 10 | github.com/valyala/bytebufferpool v1.0.0 // indirect 11 | github.com/valyala/fasttemplate v1.2.1 // indirect 12 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect 13 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect 14 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect 15 | golang.org/x/text v0.3.7 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /internal/pkg/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/labstack/echo/v4" 7 | 8 | "github.com/spatecon/echo-middleware-assessment/internal/app/endpoint" 9 | "github.com/spatecon/echo-middleware-assessment/internal/app/mw" 10 | "github.com/spatecon/echo-middleware-assessment/internal/app/service" 11 | ) 12 | 13 | type App struct { 14 | e *endpoint.Endpoint 15 | s *service.Service 16 | echo *echo.Echo 17 | } 18 | 19 | func New() (*App, error) { 20 | a := &App{} 21 | 22 | a.s = service.New() 23 | 24 | a.e = endpoint.New(a.s) 25 | 26 | a.echo = echo.New() 27 | 28 | a.echo.Use(mw.RoleCheck) 29 | 30 | a.echo.GET("/status", a.e.Status) 31 | 32 | return a, nil 33 | } 34 | 35 | func (a *App) Run() error { 36 | fmt.Println("server running") 37 | 38 | err := a.echo.Start(":8080") 39 | if err != nil { 40 | return fmt.Errorf("failed to start http server: %w", err) 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build echo middleware 2 | 3 | Example project of test assessment from imaginary company. 4 | 5 | - Full text of the task can be found [here](docs/testtask.pdf) 6 | - Video [here](https://youtu.be/Lsh3ylmXdJ8) 7 | 8 | ## Task 9 | 10 | You have **1 hour** to solve the task. If you are lucky enough and have time left, please try to optimise your solution. 11 | 12 | ### Build a middleware using echo framework 13 | 14 | First of all, you should create a handler which sends how many days left until 1 Jan 2025 and response with HTTP 200 OK status code. 15 | 16 | Secondly, build a middleware, which checks HTTP header `User-Role` presents and contains `admin` and prints `red button user detected` to the console (using default log package or any 3rd party) if so. 17 | 18 | ## Run 19 | 20 | ```shell 21 | go run github.com/spatecon/echo-middleware-assessment/cmd/umbrella-test-task 22 | ``` 23 | 24 | ## Test 25 | 26 | ```shell 27 | curl --location --request GET '127.0.0.1:8080/status' \ 28 | --header 'User-Role: admin' 29 | ``` 30 | 31 | ## License 32 | 33 | You can share the source code or use it in learning purposes. 34 | 35 | ## Author 36 | 37 | Ilia Pavliukov
38 | i [at] ela.sh
39 | 2022 40 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y= 4 | github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo= 5 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= 6 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 7 | github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= 8 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 9 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 10 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 14 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 15 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 16 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= 17 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 18 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= 19 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 20 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= 21 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 22 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 24 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= 25 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 26 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 27 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 28 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 29 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 30 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 31 | --------------------------------------------------------------------------------