Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manabie - Internship BE 2022 #332

Open
wants to merge 42 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8c21fb3
structure project
qanghaa Jun 21, 2022
e6ce863
create few task
qanghaa Jun 21, 2022
d1125ad
update fake code
qanghaa Jun 22, 2022
2150f2d
struture and implement few function
qanghaa Jun 22, 2022
435a53e
litte update
qanghaa Jun 22, 2022
356407f
implements users, task controller and add middleware
qanghaa Jun 23, 2022
4ebf8e3
implement few controller functions
qanghaa Jun 23, 2022
7d3aa12
commplete logic code
qanghaa Jun 24, 2022
166f61c
make it better
qanghaa Jun 24, 2022
53ff17b
implement DeleteMe function and fix bugss
qanghaa Jun 24, 2022
227041e
structure folder models vs controllers
qanghaa Jun 25, 2022
8f2e24f
fix updateMe function
qanghaa Jun 26, 2022
1c17843
add user api testing with httptest
qanghaa Jun 27, 2022
e8d6428
implement task, payment integration test and restructure source code …
qanghaa Jun 28, 2022
ed40cbe
divide app file to 3 components (file)
qanghaa Jun 28, 2022
b18a1b9
udpate testing
qanghaa Jun 29, 2022
41332fc
update testing functions and add rollback features
qanghaa Jun 30, 2022
e9a5b31
update dockerfiles
qanghaa Jun 30, 2022
af6aa5f
write README.md
qanghaa Jun 30, 2022
4ffdb4a
fix preview :D
qanghaa Jun 30, 2022
35fff07
update README
qanghaa Jun 30, 2022
641b34b
Update README.md
qanghaa Jun 30, 2022
15f3e31
Update README.md
qanghaa Jul 1, 2022
1103a36
changes Dockerfile to build postgresql container
qanghaa Jul 1, 2022
6ffcce9
add argument for Init, to help load env file
qanghaa Jul 1, 2022
720b0ef
update load connect_str env variable
qanghaa Jul 1, 2022
77c163c
Update controllers_main_test.go
qanghaa Jul 1, 2022
28f0702
add script.sql to auto create table in docker container
qanghaa Jul 1, 2022
1b6c3e7
Update README.md
qanghaa Jul 1, 2022
b694c6a
Update README.md
qanghaa Jul 1, 2022
f950754
Update README.md
qanghaa Jul 1, 2022
d3ab0da
Update README.md
qanghaa Jul 1, 2022
6f3bc86
update
qanghaa Jul 3, 2022
8959832
Update README.md
qanghaa Jul 3, 2022
a3cdc84
push .env
qanghaa Jul 3, 2022
a9e8ddf
Merge branch 'master' of https://github.com/qanghaa/togo
qanghaa Jul 3, 2022
d3236ca
Update README.md
qanghaa Jul 3, 2022
c559514
Update README.md
qanghaa Jul 3, 2022
8dcb769
Update README.md
qanghaa Jul 3, 2022
a9a4ca4
Update server.Dockerfile
qanghaa Jul 3, 2022
c2b2517
Update README.md
qanghaa Jul 3, 2022
54591ec
Update README.md
qanghaa Jul 4, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
README.md
.vscode
db
6 changes: 6 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
PORT=3000
CONNECT_STR=postgres://postgres:manabie@database:5432/togo?sslmode=disable
SECRET_TOKEN=token
POSTGRES_PASSWORD=manabie
POSTGRES_DB=togo
CONNECT_STR_FOR_TEST=postgres://postgres:manabie@localhost:2345/togo?sslmode=disable
40 changes: 40 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

{
"name": "Attach to Process",
"type": "go",
"request": "attach",
"mode": "local",
"processId": 0
},

{
"name": "Launch file",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${file}"
},
{
"name": "Connect to server",
"type": "go",
"request": "attach",
"mode": "remote",
"remotePath": "${workspaceFolder}",
"port": 2345,
"host": "127.0.0.1"
},
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceRoot}"
}
]
}
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"manabie"
]
}
72 changes: 42 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
### Requirements

- Implement one single API which accepts a todo task and records it
- There is a maximum **limit of N tasks per user** that can be added **per day**.
- Different users can have **different** maximum daily limit.
- Write integration (functional) tests
- Write unit tests
- Choose a suitable architecture to make your code simple, organizable, and maintainable
- Write a concise README
- How to run your code locally?
- A sample “curl” command to call your API
- How to run your unit tests locally?
- What do you love about your solution?
- What else do you want us to know about however you do not have enough time to complete?

### Notes

- We're using Golang at Manabie. **However**, we encourage you to use the programming language that you are most comfortable with because we want you to **shine** with all your skills and knowledge.

### How to submit your solution?

- Fork this repo and show us your development progress via a PR

### Interesting facts about Manabie

- Monthly there are about 2 million lines of code changes (inserted/updated/deleted) committed into our GitHub repositories. To avoid **regression bugs**, we write different kinds of **automated tests** (unit/integration (functionality)/end2end) as parts of the definition of done of our assigned tasks.
- We nurture the cultural values: **knowledge sharing** and **good communication**, therefore good written documents and readable, organizable, and maintainable code are in our blood when we build any features to grow our products.
- We have **collaborative** culture at Manabie. Feel free to ask [email protected] any questions. We are very happy to answer all of them.

Thank you for spending time to read and attempt our take-home assessment. We are looking forward to your submission.
# TOGO

## Description

  **Simple API using Golang, Postgresql and jwt for authentication**

## 1. How to run your code locally?
- Requirements
- Install [Docker Engine](https://docs.docker.com/engine/install/)
- Install [docker-compose](https://docs.docker.com/compose/install/)
- Git clone repository

```bash
git clone https://github.com/qanghaa/togo.git
```
- Go to ***togo***:open_file_folder: directory
- Using `docker-compose` commands with ***root*** permission
```bash
# this command make sure next command working as expected
sudo docker-compose down --volumes .
```

```bash
sudo docker-compose up
```

## 2. Sample “curl” Command:
  API Document: [here](https://documenter.getpostman.com/view/15522883/UzBvHPBC)
<br> &emsp;&emsp;Note: ***Using Bearer Authorization Header for endpoints required***

## 3. How to run your unit tests locally?
- Go to ***togo***:open_file_folder: directory
- type in cmd:
```bash
go test ./...
```

## 4. What do you love about your solution?
&emsp;&emsp;Overall, nothing outstanding. However I quite like the payment feature. Although the implementation is simple, it will help the user become a Premium user LOL. This feature helps users to overcome the limit of creating tasks in 1 day (20 tasks/day) instead òf 10 as usual. Not related to feature, probably Docker, I spent 2 days learning and trying to work on it and I was able to use it in this project :D.

## 5. What else do you want us to know about however you do not have enough time to complete?
Probably Testing. I haven't writed unit test enough possible scenarios with my API yet.
36 changes: 36 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package app

import (
"database/sql"
"fmt"
"log"
"net/http"

"github.com/gorilla/mux"
_ "github.com/lib/pq"
)

type App struct {
Router *mux.Router
DB *sql.DB
}

func (a *App) Init(connectURL string) {
db, err := sql.Open("postgres", connectURL)

if err != nil {
log.Fatal(err)
}
err = db.Ping()
if err != nil {
panic(err)
}
fmt.Println("Database is connected!")
a.DB = db
a.Router = mux.NewRouter()
a.Routes()
}

func (a *App) Run(host string) {
log.Fatal(http.ListenAndServe(host, a.Router))
}
51 changes: 51 additions & 0 deletions app/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package app

import (
"net/http"

c "github.com/manabie-com/togo/controllers"
)

func (a *App) GetMe(w http.ResponseWriter, r *http.Request) {
c.GetMe(a.DB, w, r)
}

func (a *App) SignUp(w http.ResponseWriter, r *http.Request) {
c.SignUp(a.DB, w, r)
}

func (a *App) Login(w http.ResponseWriter, r *http.Request) {
c.Login(a.DB, w, r)
}

func (a *App) UpdateMe(w http.ResponseWriter, r *http.Request) {
c.UpdateMe(a.DB, w, r)
}

func (a *App) DeleteMe(w http.ResponseWriter, r *http.Request) {
c.DeleteMe(a.DB, w, r)
}

func (a *App) GetTasks(w http.ResponseWriter, r *http.Request) {
c.GetTasks(a.DB, w, r)
}

func (a *App) GetTask(w http.ResponseWriter, r *http.Request) {
c.GetTask(a.DB, w, r)
}

func (a *App) Add(w http.ResponseWriter, r *http.Request) {
c.Add(a.DB, w, r)
}

func (a *App) Edit(w http.ResponseWriter, r *http.Request) {
c.Edit(a.DB, w, r)
}

func (a *App) Delete(w http.ResponseWriter, r *http.Request) {
c.Delete(a.DB, w, r)
}

func (a *App) Payment(w http.ResponseWriter, r *http.Request) {
c.Payment(a.DB, w, r)
}
31 changes: 31 additions & 0 deletions app/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package app

import (
"github.com/gorilla/mux"
"github.com/manabie-com/togo/controllers"
)

func (a *App) Routes() {

router := a.Router
// sub router like http://<HOST>:<PORT>/api/users
userRouter := router.PathPrefix("/api/users").Subrouter()
userRouter.HandleFunc("/me", a.GetMe).Methods("GET")
userRouter.HandleFunc("/signup", a.SignUp).Methods("POST")
userRouter.HandleFunc("/login", a.Login).Methods("POST")
userRouter.HandleFunc("/edit", a.UpdateMe).Methods("PATCH")
userRouter.HandleFunc("/delete", a.DeleteMe).Methods("DELETE")
// sub router like http://<HOST>:<PORT>/api/tasks
taskRouter := router.PathPrefix("/api/tasks").Subrouter()
taskRouter.HandleFunc("", a.GetTasks).Methods("GET")
taskRouter.HandleFunc("/{id}", a.GetTask).Methods("GET")
taskRouter.HandleFunc("/add", a.Add).Methods("POST")
taskRouter.HandleFunc("/{id}", a.Edit).Methods("PATCH")
taskRouter.HandleFunc("/{id}", a.Delete).Methods("DELETE")
// sub routes like http://<HOST>:<PORT>/api/payments
paymentRouter := router.PathPrefix("/api/payments").Subrouter()
paymentRouter.HandleFunc("", a.Payment).Methods("POST")
// runs database
router.Use(mux.CORSMethodMiddleware(router))
router.Use(controllers.JwtAuthentication)
}
60 changes: 60 additions & 0 deletions controllers/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package controllers

import (
"context"
"net/http"
"os"
"strings"

"github.com/manabie-com/togo/models"
u "github.com/manabie-com/togo/utils"

"github.com/dgrijalva/jwt-go"
)

var JwtAuthentication = func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

notAuth := []string{"/api/users/signup", "/api/users/login"} //List of endpoints that doesn't require auth
requestPath := r.URL.Path //current request path

//check if request does not need authentication, serve the request if it doesn't need it
for _, value := range notAuth {
if value == requestPath {
next.ServeHTTP(w, r)
return
}
}

tokenHeader := r.Header.Get("Authorization") //Grab the token from the header
if tokenHeader == "" { //Token is missing, returns with error code 403 Unauthorized
u.FailureRespond(w, http.StatusForbidden, "Missing auth token")
return
}

splitted := strings.Split(tokenHeader, " ") //The token normally comes in format `Bearer {token-body}`, we check if the retrieved token matched this requirement
if len(splitted) != 2 {
u.FailureRespond(w, http.StatusForbidden, "Invalid/Malformed auth token")
return
}

tokenPart := splitted[1] //Grab the token part, what we are truly interested in
tk := &models.Token{}
token, err := jwt.ParseWithClaims(tokenPart, tk, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("SECRET_TOKEN")), nil
})
if err != nil { //Malformed token, returns with http code 403 as usual
u.FailureRespond(w, http.StatusForbidden, "Malformed authentication token")
return
}

if !token.Valid { //Token is invalid, maybe not signed on this server
u.FailureRespond(w, http.StatusForbidden, "Token is not valid.")
return
}
//Everything went well, proceed with the request and set the caller to the user retrieved from the parsed token
//Useful for monitoring
ctx := context.WithValue(r.Context(), "user", tk)
next.ServeHTTP(w, r.WithContext(ctx)) //proceed in the middleware chain!
})
}
79 changes: 79 additions & 0 deletions controllers/controllers_main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package controllers_test

import (
"log"
"os"
"syscall"
"testing"

"net/http"
"net/http/httptest"

"github.com/joho/godotenv"
"github.com/manabie-com/togo/app"
)

type response struct {
Status string
Message string
Data map[string]interface{}
}

var (
a app.App
r response
)

func TestMain(m *testing.M) {
if err := godotenv.Load("../.env"); err != nil {
log.Fatal("Error loading .env file")
}
CONNECT_STR_FOR_TEST, ok := syscall.Getenv("CONNECT_STR_FOR_TEST")
if !ok {
log.Fatal("Please set CONNECT_STR_FOR_TEST environment")
}
a = app.App{}
a.Init(CONNECT_STR_FOR_TEST)
code := m.Run()
os.Exit(code)
}

func executeRequest(r *http.Request) *httptest.ResponseRecorder {
rr := httptest.NewRecorder()
a.Router.ServeHTTP(rr, r)
return rr
}

func checkResponseCode(t *testing.T, expected, actual int) {
if expected != actual {
t.Errorf("Expected response code %d. Got %d\n", expected, actual)
}
}

func checkResponseStatus(t *testing.T, expected, actual string) {
if expected != actual {
t.Errorf("Expected response Status %s. Got %s\n", expected, actual)
}
}

func checkResponseMessage(t *testing.T, expected, actual string) {
if expected != actual {
t.Errorf("Expected response Message %s. Got %s\n", expected, actual)
}
}

func rollbackUser() error {
_, err := a.DB.Exec(`DELETE FROM users WHERE email = $1`, "[email protected]")
if err != nil {
return err
}
return nil
}

func rollbackTask() error {
_, err := a.DB.Exec(`DELETE FROM tasks WHERE user_id = (SELECT users.id FROM users where email = $1)`, "[email protected]")
if err != nil {
return err
}
return nil
}
Loading