diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..caf6efd66 --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +# Common +PORT=8000 + +# JWT +JWT_KEY=eyJhbGciOiJIUzI1NiJ9 +JWT_TIMEOUT=60 + +HOST=localhost + +# Setup DB +DB_DRIVER=postgres +DB_HOST=localhost +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=manabie123 +DB_NAME=togo +DB_SSL_MODE=disable +DSN_POSTGRES=host=${DB_HOST} user=${DB_USER} password=${DB_PASSWORD} dbname=${DB_NAME} sslmode=${DB_SSL_MODE} + +# URL for migration +POSTGRESQL_URL_MIGRATION="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSL_MODE}" + +# INTERGRATION_TEST +INTERGRATION_TEST_PORT=8080 diff --git a/.gitignore b/.gitignore index a512c8b38..c1986bbbe 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ out/ -.idea \ No newline at end of file +.idea +.env diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..9ac8b5be0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Create README +- Make create `integrationtest` +- Make create terminal in `Makefile` +- Initial and structure for project diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..2b1fbc0b1 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +#!make +### Export file .env +include .env +export + +### Migration +migration-up: + @echo migrate -database ${POSTGRESQL_URL_MIGRATION} -path db/migrations up + @migrate -database ${POSTGRESQL_URL_MIGRATION} -path db/migrations up + +migration-down: + @echo migrate -database ${POSTGRESQL_URL_MIGRATION} -path db/migrations down + @migrate -database ${POSTGRESQL_URL_MIGRATION} -path db/migrations down + +### Run project +run: + go run cmd/main.go + +### Unit test +test-unit: + go test -cover -coverprofile coverage.log ./internal/... + +### Integraion test +test-integration: + go test -tags=integration ./integrationtest -v -count=1 diff --git a/README.md b/README.md index 8df9d4d3a..9d34be890 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,217 @@ -### Requirements +# Duong Thanh Tin - Test Manabie -- 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? +## Togo Respository -### Notes +### Features +The repository have a few main features +``` +- Login +- Create user +- Add task by user and create date +``` -- 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. +### Diagrams -### How to submit your solution? +1. #### Sequence Diagram For Flow Feature +- Feature Login +![Sequence](https://raw.githubusercontent.com/DuongThanhTin/togo/master/documents/Flow-Login.svg) -- Fork this repo and show us your development progress via a PR +- Feature Create User +![Sequence](https://raw.githubusercontent.com/DuongThanhTin/togo/master/documents/Flow-CreateUser.svg) -### Interesting facts about Manabie +- Feature Add Task +![Sequence](https://raw.githubusercontent.com/DuongThanhTin/togo/master/documents/Flow-AddTask.svg) -- 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 trieu@manabie.com any questions. We are very happy to answer all of them. +2. #### ERD Diagram -Thank you for spending time to read and attempt our take-home assessment. We are looking forward to your submission. +![ERD](https://raw.githubusercontent.com/DuongThanhTin/togo/master/documents/ERD.svg) + +### Structure Project + +``` +- cmd --> Main applications for this project. + |- middlewares --> Middlewares for this project +- constants --> Contain variable common +- db --> Data for migrations + |- migrations -> You can migration up or down data +- documents --> Contain detail documents for this project +- integrationtest --> Run integration test +- internal + |- api --> You can create different output commands like Api rest, web, GRPC or any other technology. + |- handlers --> Contain API + |- common --> API for common + |- tasks --> API for task + |- users --> API for user + |- routes --> Make create route for API + |- driver --> Config connection to database + |- models --> Application models + |- pkg --> Make create function to use common + |- id --> Make create uuid + |- responses --> Make create many response data + |- repositories --> Repositoryies will action to database (CRUD) + |- authorization --> Repository for auth + |- task --> Repository for task + |- user --> Repository for user + |- usecases --> Usecases to implement action for application + |- authorization --> Usecase for auth + |- task --> Usecase for task + |- user --> Usecase for user +``` + +### How to start + +#### Prepare + +- Install golang [golang](https://go.dev/doc/install) +- Install postgres [postgreSQL](https://www.postgresql.org/download) +- Install migrate If you want to migration [migration](https://github.com/golang-migrate/migrate) + + . MacOS + + ```bash + $ brew install golang-migrate + ``` + + . Windows + + Using [scoop](https://scoop.sh/) + + ```bash + $ scoop install migrate + ``` + + . Linux + + ```bash + $ curl -L https://packagecloud.io/golang-migrate/migrate/gpgkey | apt-key add - + $ echo "deb https://packagecloud.io/golang-migrate/migrate/ubuntu/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/migrate.list + $ apt-get update + $ apt-get install -y migrate + ``` +#### Start local +1. Clone repository +```bash +git clone https://github.com/DuongThanhTin/togo.git +cd togo +cp .env.example .env +``` + +2. Create Database postgres with your config + +3. Change data variable in file .env with your config on step 2 + +4. Migration database with terminal (If you want to migrate) +```bash +make migration-up +``` +5. Run main.go +```bash +go run cmd/main.go +``` + +#### Install +```bash +go install ./... +``` + +#### If you want to integration test after run projection +```bash +go test -tags=integration ./integrationtest -v -count=1 +``` + +#### If you want to unit test test after run projection +```bash +go test ./internal/... +``` + +#### If you want to remove table on database +```bash +make migration-down +``` + +### Flow + +You can see all flow in folder `documents`. I draw three pictures for call API and one picture for ERD. + +#### Endpoints + +I make create three endpoints. +```bash +POST /logins -> Login +POST /tasks -> Create task +POST /users -> Create user +``` + +#### Authentication token +I will create token and set data of for token in `Cookie`. You must login first if you want to create task with endpoint `POST /tasks`. The token will be generated and set in `Cookie` with key `token` and have data `user_id` and `max_task_per_day`. The task will create with data user login. + +#### Call API +1. You can create user with endpoint `POST /users`. In addtion, I have validate `username`, `password` not empty in request. If `username` is exists, you can't create user. +Request JSON: +```bash +{ + "username":"user-manabie-1", + "password": "123456", + "max_task_per_day": 10 +} +``` + +2. You can login with with endpoint `POST /login`. I have validate `username` and `password` not empty in request. First of all, you must create user if you want to login with user as above. +After login, Token will be generated and set in `Cookie` with key `token` and have data `user_id` and `max_task_per_day`. +If you run terminal with `make migration-up`. I create example user for you. +User example: `id: firstUser, username: manabie, password: example, max_task_per_day: 5` +Request JSON: +```bash +{ + "username":"manabie", + "password": "example", +} +``` + +More detail: +I will this response which have token for you. You can easily set this token in `Cookie` with key `token` if you call API with `curl`. + +3. You can create task with endpoint `POST /tasks`. First of all, you must login if you want to create task. I have create token and set it in `Cookie`. This token have the information of `user` such as: +`user_id` and `max_task_per_day`. After that, you call API `POST /tasks`, i will parse this token to get data from this token so i need you login. I have validate number task of day if the number task of day is more than `max_task_per_day` of user, you can't create task. +Request JSON: +```bash +{ + "content":"New task 1", +} +``` + +More detail: +- Field `userID` in `task` get from token +- Field `createDate` in `task` get from date call API + +#### Collection postman +I make create collection postman folder `collection_postman`. You can easily see the endpoint and request json for each API. + +#### CURL Example +1. Create User +```bash +curl -XPOST -d '{ "username":"manabie-user-3", "password": "123456", "max_task_per_day": 10 }' 'http://localhost:8000/users' +``` + +Response: +```bash +{"data":{"username":"manabie-user-3","password":"123456","max_task_per_day":10},"message":"Success","status":201} +``` + +2. Login +```bash +curl -XPOST -d '{ "username":"manabie", "password":"example" }' 'http://localhost:8000/login' +``` +Response: +```bash +{"data":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTY1NTE4NDcsIm1heF90YXNrX3Blcl9kYXkiOiI1IiwidXNlcl9pZCI6ImZpcnN0VXNlciJ9.qwHLe5Nd1lxUJlHPh3LtJUsX68ML2foMv_yjD4x5VJY","message":"Success","status":200} +``` + +3. Create Task +```bash +curl -XPOST -b 'token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTY1MjIzMzYsIm1heF90YXNrX3Blcl9kYXkiOiI1IiwidXNlcl9pZCI6ImZpcnN0VXNlciJ9.RvmCCNF5vOloXQmyZEqAUcZtxQN4lN9_qhkSm4vByOE' -d '{ "content":"New task 2" }' 'http://localhost:8000/tasks' +``` +Response: +```bash +{"data":null,"message":"Success","status":201} +``` \ No newline at end of file diff --git a/cmd/CHANGELOG.md b/cmd/CHANGELOG.md new file mode 100644 index 000000000..4884c5fff --- /dev/null +++ b/cmd/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create folder `middlewares` +- Create connection to database postgres +- Create `main` diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 000000000..f4155f049 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "log" + + "github.com/manabie-com/togo/cmd/middlewares" + "github.com/manabie-com/togo/constants" + "github.com/manabie-com/togo/internal/api/handlers" + "github.com/manabie-com/togo/internal/api/routes" + "github.com/manabie-com/togo/internal/driver" + "github.com/manabie-com/togo/utils" + + "github.com/gin-gonic/gin" + "github.com/pkg/errors" +) + +func run() error { + // Creates a new Gin instance. + r := gin.Default() + + // Load File Environment + utils.LoadEnv(constants.FileEnvironment) + + // Connect to Database + db, err := driver.ConnectDatabase() + if err != nil { + return errors.Wrap(err, "Fail to ConnectDatabase") + } + + r.Use(func(c *gin.Context) { + c.Set("db", db) + }) + + { // Set Middlewares + // Midlewares + // Recovery middleware recovers from any panics and writes a 500 if there was one. + r.Use(gin.Recovery()) + r.Use(middlewares.SetDefaultMiddleWare()) + } + + { //Set Repository + usecases := handlers.NewUseCase(db) + handlers.SetMainUseCase(&usecases) + //Set Route + routes.SetupRoute(r, usecases) + } + + log.Fatal(r.Run(fmt.Sprintf(":%s", utils.Env.Port))) + return nil +} + +func main() { + if err := run(); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/middlewares/CHANGELOG.md b/cmd/middlewares/CHANGELOG.md new file mode 100644 index 000000000..34278c027 --- /dev/null +++ b/cmd/middlewares/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create `SetDefaultMiddleWare` and `ValidateToken` from Cookie + diff --git a/cmd/middlewares/middlewares.go b/cmd/middlewares/middlewares.go new file mode 100644 index 000000000..3671d0ed7 --- /dev/null +++ b/cmd/middlewares/middlewares.go @@ -0,0 +1,36 @@ +package middlewares + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/manabie-com/togo/constants" + "github.com/manabie-com/togo/internal/api/handlers" + "github.com/manabie-com/togo/internal/pkg/responses" + "github.com/manabie-com/togo/utils" +) + +func SetDefaultMiddleWare() gin.HandlerFunc { + return func(ctx *gin.Context) { + ctx.Header("Access-Control-Allow-Origin", "*") + ctx.Header("Access-Control-Allow-Headers", "*") + ctx.Header("Access-Control-Allow-Methods", "*") + + ctx.Header("Content-Type", "application/json") + } +} + +func ValidateToken() gin.HandlerFunc { + return func(ctx *gin.Context) { + value, err := handlers.GetValueCookieFromCtx(ctx, constants.CookieTokenKey) + if err != nil { + responses.ResponseForError(ctx, err, http.StatusUnauthorized, "Fail Parse token") + return + } + + if utils.SafeString(value) == "" { + responses.ResponseForError(ctx, err, http.StatusUnauthorized, "Not have token") + return + } + } +} diff --git a/cmd/middlewares/middlewares_test.go b/cmd/middlewares/middlewares_test.go new file mode 100644 index 000000000..b2d555295 --- /dev/null +++ b/cmd/middlewares/middlewares_test.go @@ -0,0 +1,40 @@ +package middlewares + +import ( + "reflect" + "testing" + + "github.com/gin-gonic/gin" +) + +func TestSetDefaultMiddleWare(t *testing.T) { + tests := []struct { + name string + want gin.HandlerFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SetDefaultMiddleWare(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("SetDefaultMiddleWare() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestValidateToken(t *testing.T) { + tests := []struct { + name string + want gin.HandlerFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ValidateToken(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ValidateToken() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/collection_postman/CHANGELOG.md b/collection_postman/CHANGELOG.md new file mode 100644 index 000000000..acb15ddd8 --- /dev/null +++ b/collection_postman/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create collection_postman for `login`, `task` and `user` diff --git a/collection_postman/login.md b/collection_postman/login.md new file mode 100644 index 000000000..7001f347b --- /dev/null +++ b/collection_postman/login.md @@ -0,0 +1,9 @@ +POST /login HTTP/1.1 +Host: localhost:8000 +Content-Type: application/json +Cache-Control: no-cache + +{ + "username":"manabie", + "password":"example" +} diff --git a/collection_postman/task.md b/collection_postman/task.md new file mode 100644 index 000000000..9f0f620dd --- /dev/null +++ b/collection_postman/task.md @@ -0,0 +1,8 @@ +POST /tasks HTTP/1.1 +Host: localhost:8000 +Content-Type: application/json +Cache-Control: no-cache + +{ + "content":"New task 1" +} diff --git a/collection_postman/user.md b/collection_postman/user.md new file mode 100644 index 000000000..0bdba302e --- /dev/null +++ b/collection_postman/user.md @@ -0,0 +1,10 @@ +POST /users HTTP/1.1 +Host: localhost:8000 +Content-Type: application/json +Cache-Control: no-cache + +{ + "username":"manabie-user-1", + "password": "123456", + "max_task_per_day": 10 +} diff --git a/constants/CHANGELOG.md b/constants/CHANGELOG.md new file mode 100644 index 000000000..40138c1ea --- /dev/null +++ b/constants/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Create variable to get `.env` +- Create file `constants.go` diff --git a/constants/constants.go b/constants/constants.go new file mode 100644 index 000000000..30e7e79e0 --- /dev/null +++ b/constants/constants.go @@ -0,0 +1,24 @@ +package constants + +const ( + FileEnvironment = ".env" + DsnEnvironment = "DSN_POSTGRES" +) + +// Response +const ( + ResponseStatus = "status" + + // Error + ResponseError = "error" + ResponseErrorDetail = "error_detail" + + // Ok + ResponseMessage = "message" + ResponseData = "data" +) + +// Key Cookie +const ( + CookieTokenKey string = "token" +) diff --git a/datajson/CHANGELOG.md b/datajson/CHANGELOG.md new file mode 100644 index 000000000..d39e2be6b --- /dev/null +++ b/datajson/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create datajson diff --git a/datajson/login.json b/datajson/login.json new file mode 100644 index 000000000..562f4fabb --- /dev/null +++ b/datajson/login.json @@ -0,0 +1,4 @@ +{ + "username":"manabie", + "password":"example" +} diff --git a/datajson/task.json b/datajson/task.json new file mode 100644 index 000000000..e0350bb61 --- /dev/null +++ b/datajson/task.json @@ -0,0 +1,3 @@ +{ + "content":"New Task 1" +} diff --git a/datajson/user.json b/datajson/user.json new file mode 100644 index 000000000..b86cc2b0c --- /dev/null +++ b/datajson/user.json @@ -0,0 +1,5 @@ +{ + "username":"user-manabie-1", + "password": "123456", + "max_task_per_day": 10 +} diff --git a/db/migrations/1_user.down.sql b/db/migrations/1_user.down.sql new file mode 100644 index 000000000..718c9dd79 --- /dev/null +++ b/db/migrations/1_user.down.sql @@ -0,0 +1,7 @@ +BEGIN; + +ALTER TABLE "public"."users" DROP CONSTRAINT user_PK; + +DROP TABLE IF EXISTS "public"."users"; + +COMMIT; diff --git a/db/migrations/1_user.up.sql b/db/migrations/1_user.up.sql new file mode 100644 index 000000000..24f3dbe1e --- /dev/null +++ b/db/migrations/1_user.up.sql @@ -0,0 +1,11 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS users ( + id varchar(50) NOT NULL, + username varchar(50) NOT NULL, + password varchar(50) NOT NULL, + max_task_per_day INTEGER DEFAULT 5 NOT NULL, + CONSTRAINT user_PK PRIMARY KEY (id) +); + +COMMIT; diff --git a/db/migrations/2_task.down.sql b/db/migrations/2_task.down.sql new file mode 100644 index 000000000..95bb6eb0c --- /dev/null +++ b/db/migrations/2_task.down.sql @@ -0,0 +1,8 @@ +BEGIN; + +ALTER TABLE "public"."tasks" DROP CONSTRAINT task_FK; +ALTER TABLE "public"."tasks" DROP CONSTRAINT task_PK; + +DROP TABLE IF EXISTS "public"."tasks"; + +COMMIT; diff --git a/db/migrations/2_task.up.sql b/db/migrations/2_task.up.sql new file mode 100644 index 000000000..dd0ff9061 --- /dev/null +++ b/db/migrations/2_task.up.sql @@ -0,0 +1,12 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS tasks( + id varchar(50) NOT NULL, + content varchar(50) NOT NULL, + create_date varchar(50) NOT NULL, + user_id varchar(50) NOT NULL, + CONSTRAINT task_PK PRIMARY KEY (id), + CONSTRAINT task_FK FOREIGN KEY (user_id) REFERENCES users(id) +); + +COMMIT; diff --git a/db/migrations/3_alter_user.down.sql b/db/migrations/3_alter_user.down.sql new file mode 100644 index 000000000..e69de29bb diff --git a/db/migrations/3_alter_user.up.sql b/db/migrations/3_alter_user.up.sql new file mode 100644 index 000000000..8be6dbf22 --- /dev/null +++ b/db/migrations/3_alter_user.up.sql @@ -0,0 +1,6 @@ +BEGIN; + +INSERT INTO "public"."users" (id, username, password, max_task_per_day) VALUES('firstUser','manabie' ,'example', 5); +INSERT INTO "public"."users" (id, username, password, max_task_per_day) VALUES('secondUser','manabie-test-1' ,'example', 0); + +COMMIT; diff --git a/db/migrations/4_alter_task.down.sql b/db/migrations/4_alter_task.down.sql new file mode 100644 index 000000000..e69de29bb diff --git a/db/migrations/4_alter_task.up.sql b/db/migrations/4_alter_task.up.sql new file mode 100644 index 000000000..758fb8395 --- /dev/null +++ b/db/migrations/4_alter_task.up.sql @@ -0,0 +1,5 @@ +BEGIN; + +INSERT INTO "public"."tasks" ("id", "content", "create_date", "user_id") VALUES ('1', 'manabie', '2022-06-24', 'firstUser'); + +COMMIT; diff --git a/db/migrations/CHANGELOG.md b/db/migrations/CHANGELOG.md new file mode 100644 index 000000000..a5c088d89 --- /dev/null +++ b/db/migrations/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Added migration data for `tasks` +- Added migration data for `users` +- Added migration table `users` and `tasks` diff --git a/documents/ERD.svg b/documents/ERD.svg new file mode 100644 index 000000000..624ec7367 --- /dev/null +++ b/documents/ERD.svg @@ -0,0 +1,4 @@ + + + +tasksPKid varchar(50) NOT NULLFK1user_id varchar(50) NOT NULLcontent varchar(50)UsersPKid varchar(50) NOT NULL username varchar(50)password varchar(50)max_task_per_day intergercreate_date varchar(50) \ No newline at end of file diff --git a/documents/Flow-AddTask.svg b/documents/Flow-AddTask.svg new file mode 100644 index 000000000..f7818c739 --- /dev/null +++ b/documents/Flow-AddTask.svg @@ -0,0 +1,4 @@ + + + +
alt
alt
ClientAdd Task
Fail case
Fail case
Success case
Success case
API
Validate task per day
Validate task per day
ResponsePOST /tasksAuthResponseTaskDatabaseResponseCheck login by tokenResponseResponseGet  list tasks by user and createDateResponseGet list tasksResponseCreate TaskCreate Task
Text is not SVG - cannot display
\ No newline at end of file diff --git a/documents/Flow-CreateUser.svg b/documents/Flow-CreateUser.svg new file mode 100644 index 000000000..4e0f7961d --- /dev/null +++ b/documents/Flow-CreateUser.svg @@ -0,0 +1,4 @@ + + + +
alt
alt
ClientCreate User
Have user
Have user
APIResponsePOST /usersAuthResponseUserResponseDatabaseResponseCreate UserValidate user with usernameFind user with usernameResponse
Check user is exists
Check user is exists
Not have user
Not have user
Create UserResponse
Text is not SVG - cannot display
\ No newline at end of file diff --git a/documents/Flow-Login.svg b/documents/Flow-Login.svg new file mode 100644 index 000000000..c2b954269 --- /dev/null +++ b/documents/Flow-Login.svg @@ -0,0 +1,4 @@ + + + +
alt
alt
ClientLoginAPIPOST /login UserDatabaseFind user by username and passwordResponseRequest username and password
Fail case
Fail case
ResponseResponse
Success case
Success case
Response
Generate token and save in cookie
Generate token and save in cookie
Text is not SVG - cannot display
\ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..35f793c17 --- /dev/null +++ b/go.mod @@ -0,0 +1,41 @@ +module github.com/manabie-com/togo + +go 1.17 + +require ( + github.com/DATA-DOG/go-sqlmock v1.5.0 + github.com/caarlos0/env v3.5.0+incompatible + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/gin-gonic/gin v1.8.1 + github.com/google/uuid v1.3.0 + github.com/jinzhu/gorm v1.9.16 + github.com/joho/godotenv v1.4.0 + github.com/lib/pq v1.1.1 + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.7.5 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.10.0 // indirect + github.com/goccy/go-json v0.9.7 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect + golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..f440d3643 --- /dev/null +++ b/go.sum @@ -0,0 +1,133 @@ +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs= +github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/integrationtest/CHANGELOG.md b/integrationtest/CHANGELOG.md new file mode 100644 index 000000000..36994c61b --- /dev/null +++ b/integrationtest/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create `intergationtest` diff --git a/integrationtest/integrationtest_test.go b/integrationtest/integrationtest_test.go new file mode 100644 index 000000000..12b470800 --- /dev/null +++ b/integrationtest/integrationtest_test.go @@ -0,0 +1,283 @@ +package integrationtest + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "math/rand" + "net/http" + "strconv" + "testing" + + "github.com/manabie-com/togo/constants" + "github.com/manabie-com/togo/internal/api/handlers" + "github.com/manabie-com/togo/internal/models" + "github.com/manabie-com/togo/internal/usecases/authorization" + "github.com/manabie-com/togo/utils" + + "github.com/jinzhu/gorm" + _ "github.com/jinzhu/gorm/dialects/postgres" + _ "github.com/lib/pq" + "github.com/stretchr/testify/suite" +) + +type IntegrationTestSuite struct { + suite.Suite + host string + port int + dbConn *gorm.DB +} + +func (s *IntegrationTestSuite) SetupSuite() { + utils.LoadEnv("../.env") + + port, err := strconv.Atoi(utils.Env.Port) + if err != nil { + log.Fatal("Can't getenv", err) + } + + s.port = port + s.host = utils.Env.Host +} + +func TestIntegrationSuite(t *testing.T) { + suite.Run(t, &IntegrationTestSuite{}) +} + +//Login: Success case +func (s *IntegrationTestSuite) TestIntegration_Login_Success() { + reqBodyStr := `{"username": "manabie", "password": "example"}` + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:%d/login", s.host, s.port), bytes.NewBuffer([]byte(reqBodyStr))) + s.Require().NoError(err) + + req.Header.Set("Content-Type", "application/json") + + client := http.Client{} + response, err := client.Do(req) + s.Require().NoError(err) + defer response.Body.Close() + s.Require().Equal(http.StatusOK, response.StatusCode) +} + +//Login: Fail case. Fail Find not have user +func (s *IntegrationTestSuite) TestIntegration_Login_Fail_WrongUsername() { + reqBodyStr := `{"username": "username1", "password": "123456"}` + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:%d/login", s.host, s.port), bytes.NewBuffer([]byte(reqBodyStr))) + s.Require().NoError(err) + + req.Header.Set("Content-Type", "application/json") + + client := http.Client{} + response, err := client.Do(req) + s.Require().NoError(err) + defer response.Body.Close() + + s.Require().Equal(response.StatusCode, http.StatusBadRequest) +} + +//Login: Fail case - Username is empty +func (s *IntegrationTestSuite) TestIntegration_Login_Fail_Validate() { + reqBodyStr := `{"username": "", "password": "123456"}` + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:%d/login", s.host, s.port), bytes.NewBuffer([]byte(reqBodyStr))) + s.Require().NoError(err) + + req.Header.Set("Content-Type", "application/json") + + client := http.Client{} + response, err := client.Do(req) + s.Require().NoError(err) + defer response.Body.Close() + + s.Require().Equal(response.StatusCode, http.StatusBadRequest) +} + +// AddTask: Success case +func (s *IntegrationTestSuite) TestIntegration_AddTaskSuccess() { + // get token + repositories := handlers.NewRepositories(s.dbConn) + authUsecase := authorization.NewAuthUseCase(repositories.Auth) + token, err := authUsecase.GenerateToken("firstUser", "10") + s.Require().NoError(err) + + reqBodyStr := `{ + "content": "task_success", + "create_date": "2022-06-24", + "userID": "firstUser" + }` + + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:%d/tasks", s.host, s.port), bytes.NewBuffer([]byte(reqBodyStr))) + s.Require().NoError(err) + req.Header.Set("Content-Type", "application/json") + cookie := &http.Cookie{ + Name: constants.CookieTokenKey, + Value: utils.SafeString(token), + MaxAge: 300, + } + req.AddCookie(cookie) + + client := http.Client{} + response, err := client.Do(req) + s.Require().NoError(err) + defer response.Body.Close() + s.Require().Equal(http.StatusCreated, response.StatusCode) +} + +// AddTask: Fail case - Wrong userID +func (s *IntegrationTestSuite) TestIntegration_AddTaskFail() { + // get token + repositories := handlers.NewRepositories(s.dbConn) + authUsecase := authorization.NewAuthUseCase(repositories.Auth) + token, err := authUsecase.GenerateToken("firstUser1", "5") + s.Require().NoError(err) + + reqBodyStr := `{ + "content": "task_fail", + "create_date": "2022-06-24", + "userID": "firstUser1" + }` + + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:%d/tasks", s.host, s.port), bytes.NewBuffer([]byte(reqBodyStr))) + s.Require().NoError(err) + req.Header.Set("Content-Type", "application/json") + cookie := &http.Cookie{ + Name: constants.CookieTokenKey, + Value: utils.SafeString(token), + MaxAge: 300, + } + req.AddCookie(cookie) + + client := http.Client{} + response, err := client.Do(req) + s.Require().NoError(err) + defer response.Body.Close() + s.Require().Equal(response.StatusCode, http.StatusBadRequest) +} + +// AddTask: Fail case - ValidateMaxTaskPerDay +func (s *IntegrationTestSuite) TestIntegration_AddTaskFail_ValidateMaxTaskPerDay() { + // get token + repositories := handlers.NewRepositories(s.dbConn) + authUsecase := authorization.NewAuthUseCase(repositories.Auth) + token, err := authUsecase.GenerateToken("secondUser", "0") + s.Require().NoError(err) + + reqBodyStr := `{ + "content": "task-fail-1", + "create_date": "2022-06-24", + "userID": "secondUser" + }` + + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:%d/tasks", s.host, s.port), bytes.NewBuffer([]byte(reqBodyStr))) + s.Require().NoError(err) + req.Header.Set("Content-Type", "application/json") + cookie := &http.Cookie{ + Name: constants.CookieTokenKey, + Value: utils.SafeString(token), + MaxAge: 300, + } + req.AddCookie(cookie) + + client := http.Client{} + response, err := client.Do(req) + s.Require().NoError(err) + defer response.Body.Close() + s.Require().Equal(response.StatusCode, http.StatusInternalServerError) +} + +// CreateUser: Success case +func (s *IntegrationTestSuite) TestIntegration_CreateUser_Success() { + username := fmt.Sprintf("manabie-new-%d", rand.Intn(1000)) + reqUser := models.User{ + Username: username, + Password: "123456", + MaxTaskPerDay: 5, + } + + body, err := json.Marshal(reqUser) + s.Require().NoError(err) + + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:%d/users", s.host, s.port), bytes.NewReader(body)) + s.Require().NoError(err) + + client := http.Client{} + response, err := client.Do(req) + s.Require().NoError(err) + defer response.Body.Close() + //s.Require().Equal(http.StatusCreated, response.StatusCode) + + byteResBody, err := ioutil.ReadAll(response.Body) + s.Require().NoError(err) + + data := map[string]interface{}{} + err = json.Unmarshal(byteResBody, &data) + fmt.Println("byteResBody ", string(byteResBody)) + s.Require().NoError(err) + + byteUser, err := json.Marshal(data["data"]) + s.Require().NoError(err) + + user := models.User{} + err = json.Unmarshal(byteUser, &user) + s.Require().NoError(err) + +} + +// CreateUser: Fail case - Conflict User +func (s *IntegrationTestSuite) TestIntegration_CreateUser_Fail_Conflict() { + // get token + repositories := handlers.NewRepositories(s.dbConn) + authUsecase := authorization.NewAuthUseCase(repositories.Auth) + token, err := authUsecase.GenerateToken("firstUser1", "5") + s.Require().NoError(err) + + reqBodyStr := `{ + "username": "manabie", + "password": "123456", + "max_task_per_day": 2 + }` + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:%d/users", s.host, s.port), bytes.NewBuffer([]byte(reqBodyStr))) + s.Require().NoError(err) + cookie := &http.Cookie{ + Name: constants.CookieTokenKey, + Value: utils.SafeString(token), + MaxAge: 300, + } + req.AddCookie(cookie) + + client := http.Client{} + response, err := client.Do(req) + s.Require().NoError(err) + defer response.Body.Close() + s.Require().Equal(http.StatusConflict, response.StatusCode) +} + +// CreateUser: Fail case - Username is empty +func (s *IntegrationTestSuite) TestIntegration_CreateUser_Fail_ValidateUser() { + // get token + repositories := handlers.NewRepositories(s.dbConn) + authUsecase := authorization.NewAuthUseCase(repositories.Auth) + token, err := authUsecase.GenerateToken("firstUser1", "5") + s.Require().NoError(err) + + reqBodyStr := `{ + "username": "", + "password": "123456", + "max_task_per_day": 2 + }` + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:%d/users", s.host, s.port), bytes.NewBuffer([]byte(reqBodyStr))) + s.Require().NoError(err) + cookie := &http.Cookie{ + Name: constants.CookieTokenKey, + Value: utils.SafeString(token), + MaxAge: 300, + } + req.AddCookie(cookie) + + client := http.Client{} + response, err := client.Do(req) + s.Require().NoError(err) + defer response.Body.Close() + s.Require().Equal(http.StatusInternalServerError, response.StatusCode) +} diff --git a/internal/api/CHANGELOG.md b/internal/api/CHANGELOG.md new file mode 100644 index 000000000..154dfb78d --- /dev/null +++ b/internal/api/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create `handlers` and `routes` diff --git a/internal/api/handlers/CHANGELOG.md b/internal/api/handlers/CHANGELOG.md new file mode 100644 index 000000000..c248b9996 --- /dev/null +++ b/internal/api/handlers/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Get token from cookie +- Make handle `UseCase` diff --git a/internal/api/handlers/common/CHANGELOG.md b/internal/api/handlers/common/CHANGELOG.md new file mode 100644 index 000000000..977bfb5e9 --- /dev/null +++ b/internal/api/handlers/common/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Add unit test +- Make create `router` +- Handle func common diff --git a/internal/api/handlers/common/api.go b/internal/api/handlers/common/api.go new file mode 100644 index 000000000..38d40e18b --- /dev/null +++ b/internal/api/handlers/common/api.go @@ -0,0 +1,14 @@ +package common + +import ( + "github.com/manabie-com/togo/internal/api/handlers" + + "github.com/gin-gonic/gin" +) + +func NewHandler(router *gin.Engine, service handlers.MainUseCase) { + apiUser := router.Group("/") + { + apiUser.POST("/login", Login(service)) + } +} diff --git a/internal/api/handlers/common/common.go b/internal/api/handlers/common/common.go new file mode 100644 index 000000000..eb2ddde9f --- /dev/null +++ b/internal/api/handlers/common/common.go @@ -0,0 +1,57 @@ +package common + +import ( + "net/http" + "strconv" + + "github.com/manabie-com/togo/constants" + "github.com/manabie-com/togo/internal/api/handlers" + "github.com/manabie-com/togo/internal/pkg/responses" + "github.com/manabie-com/togo/utils" + + "github.com/gin-gonic/gin" +) + +type Input struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func Login(service handlers.MainUseCase) gin.HandlerFunc { + return func(ctx *gin.Context) { + var inputUser Input + if err := ctx.ShouldBindJSON(&inputUser); err != nil { + responses.ResponseForError(ctx, err, http.StatusBadRequest, "Fail BindJSON user") + return + } + + user, err := service.User.Login(inputUser.Username, inputUser.Password) + if err != nil { + responses.ResponseForError(ctx, err, http.StatusBadRequest, "Fail Login") + return + } + + if user == nil { + responses.ResponseForError(ctx, nil, http.StatusBadRequest, "Fail Login") + return + } + + maxTaskPerDay := strconv.Itoa(user.MaxTaskPerDay) + + token, err := service.Auth.GenerateToken(user.ID, maxTaskPerDay) + if err != nil { + responses.ResponseForError(ctx, err, http.StatusInternalServerError, "Fail GenerateToken") + return + } + + if utils.SafeString(token) == "" { + responses.ResponseForError(ctx, nil, http.StatusInternalServerError, "Fail GenerateToken") + return + } + + //Set Cookie + ctx.SetCookie(constants.CookieTokenKey, utils.SafeString(token), 60*60*24, "/", utils.Env.Host, true, true) + + responses.ResponseForOK(ctx, http.StatusOK, utils.SafeString(token), "Success") + } +} diff --git a/internal/api/handlers/common/common_test.go b/internal/api/handlers/common/common_test.go new file mode 100644 index 000000000..94b8f9120 --- /dev/null +++ b/internal/api/handlers/common/common_test.go @@ -0,0 +1,89 @@ +package common + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/manabie-com/togo/internal/api/handlers" + "github.com/manabie-com/togo/internal/models" + "github.com/manabie-com/togo/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLogin(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + utils.LoadEnv("../../../../.env") + r := SetUpRouter() + service := handlers.HandleService(db) + r.POST("/login", Login(service)) + + { //Success case + user := models.User{ + Username: "manabie-test-2", + Password: "123456", + } + + // Create mock data for test + mock.ExpectExec("INSERT INTO users"). + WithArgs(1, user.Username, user.Password, 5). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err := recordStats(db, user.Username, user.Password) + require.Nil(t, err) + + rows := sqlmock.NewRows([]string{"id", "username", "password", "max_task_per_day"}). + AddRow("1", user.Username, user.Password, 5) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users" WHERE (username = $1 and password = $2) LIMIT 1`)). + WithArgs(user.Username, user.Password). + WillReturnRows(rows) + + jsonValue, _ := json.Marshal(user) + req, _ := http.NewRequest(http.MethodPost, "/login", bytes.NewBuffer(jsonValue)) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + } + + { //Fail case + user := models.User{ + Username: "manabie-test", + Password: "123456", + } + + jsonValue, _ := json.Marshal(user) + req, _ := http.NewRequest(http.MethodPost, "/example", bytes.NewBuffer(jsonValue)) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusNotFound, w.Code) + } + + { //Fail case + user := models.User{ + Username: "manabie-test", + Password: "123456", + } + + jsonValue, _ := json.Marshal(user) + req, _ := http.NewRequest(http.MethodPost, "/login", bytes.NewBuffer(jsonValue)) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusBadRequest, w.Code) + } +} diff --git a/internal/api/handlers/common/setup.go b/internal/api/handlers/common/setup.go new file mode 100644 index 000000000..c8fb6519b --- /dev/null +++ b/internal/api/handlers/common/setup.go @@ -0,0 +1,41 @@ +package common + +import ( + "fmt" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +func setupMock() (*gorm.DB, sqlmock.Sqlmock, error) { + sqlDB, mock, err := sqlmock.New() // mock sql.DB + if err != nil { + return nil, nil, errors.Wrap(err, "Fail connection to database") + } + + db, err := gorm.Open("postgres", sqlDB) // open gorm db + if err != nil { + return nil, nil, err + } + + if db == nil { + return nil, nil, errors.New("Fail connection to database") + } + + return db, mock, nil +} + +func SetUpRouter() *gin.Engine { + router := gin.Default() + return router +} + +func recordStats(db *gorm.DB, username, password string) (err error) { + _, err = db.CommonDB().Exec("INSERT INTO users (id, username,password,max_task_per_day) VALUES (?, ?,?,?,?)", 1, username, password, 5) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("error '%s' was not expected, while inserting a row", err)) + } + return +} diff --git a/internal/api/handlers/common/setup_test.go b/internal/api/handlers/common/setup_test.go new file mode 100644 index 000000000..76b8860bc --- /dev/null +++ b/internal/api/handlers/common/setup_test.go @@ -0,0 +1,54 @@ +package common + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/manabie-com/togo/internal/models" + "github.com/manabie-com/togo/utils" + "github.com/stretchr/testify/require" +) + +func Test_recordStats(t *testing.T) { + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + utils.LoadEnv("../../../../.env") + { + user := &models.User{ + Username: "manabie-test-2", + Password: "123456", + } + + // Create mock data for test + mock.ExpectExec("INSERT INTO users"). + WithArgs(1, user.Username, user.Password, 5). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err = recordStats(db, user.Username, user.Password) + require.Nil(t, err) + } + + { //Fail case + user := &models.User{ + Username: "", + Password: "", + } + + // Create mock data for test + mock.ExpectExec("INSERT INTO users"). + WithArgs(1, user.Username, user.Password, 5). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err = recordStats(db, user.Username, user.Password) + require.Nil(t, err) + } +} diff --git a/internal/api/handlers/handlers.go b/internal/api/handlers/handlers.go new file mode 100644 index 000000000..4e81bcb44 --- /dev/null +++ b/internal/api/handlers/handlers.go @@ -0,0 +1,126 @@ +package handlers + +import ( + "fmt" + + "github.com/manabie-com/togo/constants" + authRepo "github.com/manabie-com/togo/internal/repositories/authorization" + taskRepo "github.com/manabie-com/togo/internal/repositories/task" + userRepo "github.com/manabie-com/togo/internal/repositories/user" + "github.com/manabie-com/togo/utils" + + authService "github.com/manabie-com/togo/internal/usecases/authorization" + taskService "github.com/manabie-com/togo/internal/usecases/task" + userService "github.com/manabie-com/togo/internal/usecases/user" + + "github.com/dgrijalva/jwt-go" + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +type MainUseCase struct { + Auth authService.AuthUseCase + User userService.UserUseCase + Task taskService.TaskUseCase +} + +type MainRepository struct { + Auth authService.AuthRepository + User userService.UserRepository + Task taskService.TaskRepository +} + +var MainUC = &MainUseCase{} + +func SetMainUseCase(muc *MainUseCase) { + MainUC = muc +} + +func NewUseCase(db *gorm.DB) MainUseCase { + // Create repositories. + mainRepositories := NewRepositories(db) + + //Create UseCase + authUseCase := authService.NewAuthUseCase(mainRepositories.Auth) + userUseCase := userService.NewUserUseCase(mainRepositories.User) + taskUseCase := taskService.NewTaskUseCase(mainRepositories.Task) + + return MainUseCase{ + Auth: authUseCase, + User: userUseCase, + Task: taskUseCase, + } +} + +func HandleService(db *gorm.DB) MainUseCase { + return NewUseCase(db) +} + +func NewRepositories(db *gorm.DB) *MainRepository { + return &MainRepository{ + Auth: authRepo.NewAuthRepository(db), + User: userRepo.NewUserRepository(db), + Task: taskRepo.NewTaskRepository(db), + } +} + +type UserInfoFromCtx string +type UserInfoFromToken struct { + ID string + MaxTaskPerDay string +} + +func GetValueCookieFromCtx(ctx *gin.Context, keyCookie string) (*string, error) { + cookie, err := ctx.Request.Cookie(keyCookie) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("Don't have cookie with Key: %s", keyCookie)) + } + + if cookie == nil { + return nil, errors.New("Cookie is nil") + } + + return utils.String(cookie.Value), nil +} + +func GetUserInfoFromToken(ctx *gin.Context) (*UserInfoFromToken, error) { + tokenStr, err := GetValueCookieFromCtx(ctx, constants.CookieTokenKey) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("Don't have cookie with Key1: %s", constants.CookieTokenKey)) + } + + if utils.SafeString(tokenStr) == "" { + return nil, errors.New("Token is empty") + } + + claims := make(jwt.MapClaims) + tkn, err := jwt.ParseWithClaims(utils.SafeString(tokenStr), claims, func(token *jwt.Token) (interface{}, error) { + return []byte(utils.Env.JwtKey), nil + }) + + if err != nil { + return nil, errors.Wrap(err, "Fail ParseWithClaims") + } + + if tkn == nil || !tkn.Valid { + return nil, errors.New("Token is valid") + } + + id, ok := claims["user_id"].(string) + if !ok { + return nil, errors.New("Fail Claims user_id") + } + + maxTaskPerDay, ok := claims["max_task_per_day"].(string) + if !ok { + return nil, errors.New("Fail Claims max_task_per_day") + } + + userInfo := &UserInfoFromToken{ + ID: id, + MaxTaskPerDay: maxTaskPerDay, + } + + return userInfo, nil +} diff --git a/internal/api/handlers/handlers_test.go b/internal/api/handlers/handlers_test.go new file mode 100644 index 000000000..d422a8836 --- /dev/null +++ b/internal/api/handlers/handlers_test.go @@ -0,0 +1,34 @@ +package handlers + +import ( + "testing" + + "github.com/gin-gonic/gin" +) + +func TestGetValueCookieFromCtx(t *testing.T) { + type args struct { + ctx *gin.Context + keyCookie string + } + tests := []struct { + name string + args args + want *string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetValueCookieFromCtx(tt.args.ctx, tt.args.keyCookie) + if (err != nil) != tt.wantErr { + t.Errorf("GetValueCookieFromCtx() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GetValueCookieFromCtx() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/api/handlers/tasks/CHANGELOG.md b/internal/api/handlers/tasks/CHANGELOG.md new file mode 100644 index 000000000..8c2f61d75 --- /dev/null +++ b/internal/api/handlers/tasks/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Add unit test +- Make create `router` +- Handle func `Validate` and api `FindTaskByUser` diff --git a/internal/api/handlers/tasks/api.go b/internal/api/handlers/tasks/api.go new file mode 100644 index 000000000..5293b6994 --- /dev/null +++ b/internal/api/handlers/tasks/api.go @@ -0,0 +1,16 @@ +package tasks + +import ( + "github.com/manabie-com/togo/cmd/middlewares" + "github.com/manabie-com/togo/internal/api/handlers" + + "github.com/gin-gonic/gin" +) + +func NewHandler(router *gin.Engine, service handlers.MainUseCase) { + apiTask := router.Group("/") + apiTask.Use(middlewares.ValidateToken()) + { + apiTask.POST("/tasks", AddTask(service)) + } +} diff --git a/internal/api/handlers/tasks/setup.go b/internal/api/handlers/tasks/setup.go new file mode 100644 index 000000000..7c4a69406 --- /dev/null +++ b/internal/api/handlers/tasks/setup.go @@ -0,0 +1,41 @@ +package tasks + +import ( + "fmt" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +func setupMock() (*gorm.DB, sqlmock.Sqlmock, error) { + sqlDB, mock, err := sqlmock.New() // mock sql.DB + if err != nil { + return nil, nil, errors.Wrap(err, "Fail connection to database") + } + + db, err := gorm.Open("postgres", sqlDB) // open gorm db + if err != nil { + return nil, nil, err + } + + if db == nil { + return nil, nil, errors.New("Fail connection to database") + } + + return db, mock, nil +} + +func SetUpRouter() *gin.Engine { + router := gin.Default() + return router +} + +func recordStatsUser(db *gorm.DB, username, password string) (err error) { + _, err = db.CommonDB().Exec("INSERT INTO users (id, username,password,max_task_per_day) VALUES (?, ?,?,?,?)", 1, username, password, 5) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("error '%s' was not expected, while inserting a row", err)) + } + return +} diff --git a/internal/api/handlers/tasks/setup_test.go b/internal/api/handlers/tasks/setup_test.go new file mode 100644 index 000000000..9e2b036ef --- /dev/null +++ b/internal/api/handlers/tasks/setup_test.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/manabie-com/togo/internal/models" + "github.com/manabie-com/togo/utils" + "github.com/stretchr/testify/require" +) + +func Test_recordStats(t *testing.T) { + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + utils.LoadEnv("../../../../.env") + { + user := &models.User{ + Username: "manabie-test-2", + Password: "123456", + } + + // Create mock data for test + mock.ExpectExec("INSERT INTO users"). + WithArgs(1, user.Username, user.Password, 5). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err = recordStatsUser(db, user.Username, user.Password) + require.Nil(t, err) + } + + { //Fail case + user := &models.User{ + Username: "", + Password: "", + } + + // Create mock data for test + mock.ExpectExec("INSERT INTO users"). + WithArgs(1, user.Username, user.Password, 5). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err = recordStatsUser(db, user.Username, user.Password) + require.Nil(t, err) + } +} diff --git a/internal/api/handlers/tasks/task.go b/internal/api/handlers/tasks/task.go new file mode 100644 index 000000000..8ee93db60 --- /dev/null +++ b/internal/api/handlers/tasks/task.go @@ -0,0 +1,69 @@ +package tasks + +import ( + "net/http" + "strconv" + "time" + + "github.com/manabie-com/togo/internal/api/handlers" + "github.com/manabie-com/togo/internal/models" + "github.com/manabie-com/togo/internal/pkg/responses" + "github.com/manabie-com/togo/internal/repositories/task" + + "github.com/gin-gonic/gin" +) + +func AddTask(service handlers.MainUseCase) gin.HandlerFunc { + return func(ctx *gin.Context) { + userInfo, err := handlers.GetUserInfoFromToken(ctx) + if err != nil { + responses.ResponseForError(ctx, err, http.StatusUnauthorized, "Access denied") + return + } + + // Get task per day of user + maxTaskOfUser, err := strconv.Atoi(userInfo.MaxTaskPerDay) + if err != nil { + responses.ResponseForError(ctx, err, http.StatusInternalServerError, "Fail Parse String to Int") + return + } + createDate := time.Now().Format("2006-01-02") + + // Find all tasks of user with userID and createDate + tasks, err := service.Task.FindTaskByUser(userInfo.ID, createDate) + if err != nil { + responses.ResponseForError(ctx, err, http.StatusBadRequest, "Fail FindTaskByUser") + return + } + + // Check number of task + if IsMaxTaskPerDay(len(tasks), maxTaskOfUser) { + responses.ResponseForError(ctx, nil, http.StatusInternalServerError, "Number tasks of User is maximum") + return + } + + // Mapping Request + input := models.Task{} + if err := ctx.BindJSON(&input); err != nil { + responses.ResponseForError(ctx, err, http.StatusBadRequest, "Fail BindJSON task") + return + } + input.UserID = userInfo.ID + input.CreateDate = createDate + + // New + newTask := task.NewTask(input) + + // Create Task + if err := service.Task.AddTask(newTask); err != nil { + responses.ResponseForError(ctx, err, http.StatusBadRequest, "Fail Add Task") + return + } + + responses.ResponseForOK(ctx, http.StatusCreated, nil, "Success") + } +} + +func IsMaxTaskPerDay(numTaskPerDay int, maxTaskOfUser int) bool { + return numTaskPerDay >= maxTaskOfUser +} diff --git a/internal/api/handlers/tasks/task_test.go b/internal/api/handlers/tasks/task_test.go new file mode 100644 index 000000000..10df65f2c --- /dev/null +++ b/internal/api/handlers/tasks/task_test.go @@ -0,0 +1,454 @@ +package tasks + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "regexp" + "testing" + "time" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/manabie-com/togo/constants" + "github.com/manabie-com/togo/internal/api/handlers" + "github.com/manabie-com/togo/internal/models" + "github.com/manabie-com/togo/internal/usecases/authorization" + "github.com/manabie-com/togo/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAddTask_FailCase(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + utils.LoadEnv("../../../../.env") + r := SetUpRouter() + service := handlers.HandleService(db) + r.POST("/tasks", AddTask(service)) + + //Generate token + repositories := handlers.NewRepositories(db) + authUsecase := authorization.NewAuthUseCase(repositories.Auth) + + { //Fail case - Not found + req, _ := http.NewRequest(http.MethodPost, "/example", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusNotFound, w.Code) + } + + { //Fail case - No Login + req, _ := http.NewRequest(http.MethodPost, "/tasks", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusUnauthorized, w.Code) + } + + { //Fail case - Max Task Per Day + // Create mock data User for test + user := models.User{ + Username: "manabie-test-3", + Password: "123456", + } + mock.ExpectExec("INSERT INTO users"). + WithArgs(1, user.Username, user.Password, 5). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err := recordStatsUser(db, user.Username, user.Password) + require.Nil(t, err) + + input := models.Task{ + Content: "Test Interview", + CreateDate: time.Now().Format("2006-01-02"), + UserID: "1", + } + + rows := sqlmock.NewRows([]string{"id", "content", "create_date", "user_id"}). + AddRow("1", input.Content, input.CreateDate, 1) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "tasks" WHERE (user_id = $1 and create_date = $2)`)). + WithArgs(input.UserID, input.CreateDate). + WillReturnRows(rows) + + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "tasks" ("id","content","create_date","user_id") VALUES ($1,$2,$3,$4) RETURNING "tasks"."id"`)). + WithArgs(input.ID, input.Content, input.CreateDate, input.UserID). + WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) + mock.ExpectCommit() + + //Generate token + token, err := authUsecase.GenerateToken(input.UserID, "0") + require.Nil(t, err) + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/tasks", bytes.NewBuffer(jsonValue)) + cookie := &http.Cookie{ + Name: constants.CookieTokenKey, + Value: utils.SafeString(token), + MaxAge: 300, + } + req.AddCookie(cookie) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusInternalServerError, w.Code) + } + +} + +func TestAddTask_FailCase_NoHaveUserReference(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + utils.LoadEnv("../../../../.env") + r := SetUpRouter() + service := handlers.HandleService(db) + r.POST("/tasks", AddTask(service)) + + //Generate token + repositories := handlers.NewRepositories(db) + authUsecase := authorization.NewAuthUseCase(repositories.Auth) + + { //Fail case - + // Create mock data User for test + user := models.User{ + Username: "manabie-test-3", + Password: "123456", + } + mock.ExpectExec("INSERT INTO users"). + WithArgs(1, user.Username, user.Password, 5). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err := recordStatsUser(db, user.Username, user.Password) + require.Nil(t, err) + + input := models.Task{ + Content: "Test Interview", + CreateDate: time.Now().Format("2006-01-02"), + UserID: "no_have", + } + + rows := sqlmock.NewRows([]string{"id", "content", "create_date", "user_id"}). + AddRow("1", input.Content, input.CreateDate, 1) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "tasks" WHERE (user_id = $1 and create_date = $2)`)). + WithArgs(input.UserID, input.CreateDate). + WillReturnRows(rows) + + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "tasks" ("id","content","create_date","user_id") VALUES ($1,$2,$3,$4) RETURNING "tasks"."id"`)). + WithArgs(input.ID, input.Content, input.CreateDate, input.UserID). + WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) + + //Generate token + token, err := authUsecase.GenerateToken(input.UserID, "0") + require.Nil(t, err) + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/tasks", bytes.NewBuffer(jsonValue)) + cookie := &http.Cookie{ + Name: constants.CookieTokenKey, + Value: utils.SafeString(token), + MaxAge: 300, + } + req.AddCookie(cookie) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusInternalServerError, w.Code) + } +} + +func TestAddTask_FailCase_UserIDEmpty(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + utils.LoadEnv("../../../../.env") + r := SetUpRouter() + service := handlers.HandleService(db) + r.POST("/tasks", AddTask(service)) + + //Generate token + repositories := handlers.NewRepositories(db) + authUsecase := authorization.NewAuthUseCase(repositories.Auth) + + { + // Create mock data User for test + user := models.User{ + Username: "manabie-test-5", + Password: "123456", + } + mock.ExpectExec("INSERT INTO users"). + WithArgs(1, user.Username, user.Password, 5). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err := recordStatsUser(db, user.Username, user.Password) + require.Nil(t, err) + + input := models.Task{ + Content: "Test Interview", + CreateDate: time.Now().Format("2006-01-02"), + UserID: "", + } + + rows := sqlmock.NewRows([]string{"id", "content", "create_date", "user_id"}). + AddRow("1", input.Content, input.CreateDate, 1) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "tasks" WHERE (user_id = $1 and create_date = $2)`)). + WithArgs(input.UserID, input.CreateDate). + WillReturnRows(rows) + + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "tasks" ("id","content","create_date","user_id") VALUES ($1,$2,$3,$4) RETURNING "tasks"."id"`)). + WithArgs(input.ID, input.Content, input.CreateDate, input.UserID). + WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) + + //Generate token + token, err := authUsecase.GenerateToken("2", "0") + require.Nil(t, err) + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/tasks", bytes.NewBuffer(jsonValue)) + cookie := &http.Cookie{ + Name: constants.CookieTokenKey, + Value: utils.SafeString(token), + MaxAge: 300, + } + req.AddCookie(cookie) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusBadRequest, w.Code) + } +} + +func TestAddTask_FailCase_CreateDateEmpty(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + utils.LoadEnv("../../../../.env") + r := SetUpRouter() + service := handlers.HandleService(db) + r.POST("/tasks", AddTask(service)) + + //Generate token + repositories := handlers.NewRepositories(db) + authUsecase := authorization.NewAuthUseCase(repositories.Auth) + + { + // Create mock data User for test + user := models.User{ + Username: "manabie-test-5", + Password: "123456", + } + mock.ExpectExec("INSERT INTO users"). + WithArgs(1, user.Username, user.Password, 5). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err := recordStatsUser(db, user.Username, user.Password) + require.Nil(t, err) + + input := models.Task{ + Content: "Test Interview", + CreateDate: "", + UserID: "1", + } + + rows := sqlmock.NewRows([]string{"id", "content", "create_date", "user_id"}). + AddRow("1", input.Content, input.CreateDate, 1) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "tasks" WHERE (user_id = $1 and create_date = $2)`)). + WithArgs(input.UserID, input.CreateDate). + WillReturnRows(rows) + + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "tasks" ("id","content","create_date","user_id") VALUES ($1,$2,$3,$4) RETURNING "tasks"."id"`)). + WithArgs(input.ID, input.Content, input.CreateDate, input.UserID). + WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) + + //Generate token + token, err := authUsecase.GenerateToken(input.UserID, "0") + require.Nil(t, err) + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/tasks", bytes.NewBuffer(jsonValue)) + cookie := &http.Cookie{ + Name: constants.CookieTokenKey, + Value: utils.SafeString(token), + MaxAge: 300, + } + req.AddCookie(cookie) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusBadRequest, w.Code) + } +} + +func TestAddTask_FailCase_UserIDAndCreateDateEmpty(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + utils.LoadEnv("../../../../.env") + r := SetUpRouter() + service := handlers.HandleService(db) + r.POST("/tasks", AddTask(service)) + + //Generate token + repositories := handlers.NewRepositories(db) + authUsecase := authorization.NewAuthUseCase(repositories.Auth) + + { + // Create mock data User for test + user := models.User{ + Username: "manabie-test-6", + Password: "123456", + } + mock.ExpectExec("INSERT INTO users"). + WithArgs(1, user.Username, user.Password, 5). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err := recordStatsUser(db, user.Username, user.Password) + require.Nil(t, err) + + input := models.Task{ + Content: "Test Interview", + CreateDate: "", + UserID: "", + } + + rows := sqlmock.NewRows([]string{"id", "content", "create_date", "user_id"}). + AddRow("1", input.Content, input.CreateDate, 1) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "tasks" WHERE (user_id = $1 and create_date = $2)`)). + WithArgs(input.UserID, input.CreateDate). + WillReturnRows(rows) + + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "tasks" ("id","content","create_date","user_id") VALUES ($1,$2,$3,$4) RETURNING "tasks"."id"`)). + WithArgs(input.ID, input.Content, input.CreateDate, input.UserID). + WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) + + //Generate token + token, err := authUsecase.GenerateToken("5", "5") + require.Nil(t, err) + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/tasks", bytes.NewBuffer(jsonValue)) + cookie := &http.Cookie{ + Name: constants.CookieTokenKey, + Value: utils.SafeString(token), + MaxAge: 300, + } + req.AddCookie(cookie) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusBadRequest, w.Code) + } +} + +func TestAddTask_Success(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + utils.LoadEnv("../../../../.env") + r := SetUpRouter() + service := handlers.HandleService(db) + r.POST("/tasks", AddTask(service)) + + //Generate token + repositories := handlers.NewRepositories(db) + authUsecase := authorization.NewAuthUseCase(repositories.Auth) + + { //Success case + // Create mock data User for test + user := models.User{ + Username: "manabie-test-3", + Password: "123456", + } + mock.ExpectExec("INSERT INTO users"). + WithArgs(1, user.Username, user.Password, 5). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err := recordStatsUser(db, user.Username, user.Password) + require.Nil(t, err) + + input := models.Task{ + ID: "task-3", + Content: "Test Interview", + CreateDate: time.Now().Format("2006-01-02"), + UserID: "1", + } + + rows := sqlmock.NewRows([]string{"id", "content", "create_date", "user_id"}). + AddRow("task-3", input.Content, input.CreateDate, 1) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "tasks" WHERE (user_id = $1 and create_date = $2)`)). + WithArgs(input.UserID, input.CreateDate). + WillReturnRows(rows) + + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "tasks" ("id","content","create_date","user_id") VALUES ($1,$2,$3,$4) RETURNING "tasks"."id"`)). + WithArgs(input.ID, input.Content, input.CreateDate, input.UserID). + WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) + mock.ExpectCommit() + + //Generate token + token, err := authUsecase.GenerateToken(input.UserID, "6") + require.Nil(t, err) + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/tasks", bytes.NewBuffer(jsonValue)) + cookie := &http.Cookie{ + Name: constants.CookieTokenKey, + Value: utils.SafeString(token), + MaxAge: 300, + } + req.AddCookie(cookie) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusCreated, w.Code) + } +} diff --git a/internal/api/handlers/users/CHANGELOG.md b/internal/api/handlers/users/CHANGELOG.md new file mode 100644 index 000000000..7e6a78b52 --- /dev/null +++ b/internal/api/handlers/users/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Add unit test +- Make create `router` +- Handle api `Create` diff --git a/internal/api/handlers/users/api.go b/internal/api/handlers/users/api.go new file mode 100644 index 000000000..321ec2aae --- /dev/null +++ b/internal/api/handlers/users/api.go @@ -0,0 +1,14 @@ +package users + +import ( + "github.com/manabie-com/togo/internal/api/handlers" + + "github.com/gin-gonic/gin" +) + +func NewHandler(router *gin.Engine, service handlers.MainUseCase) { + apiUser := router.Group("/") + { + apiUser.POST("/users", CreateUser(service)) + } +} diff --git a/internal/api/handlers/users/setup.go b/internal/api/handlers/users/setup.go new file mode 100644 index 000000000..f747d78a6 --- /dev/null +++ b/internal/api/handlers/users/setup.go @@ -0,0 +1,31 @@ +package users + +import ( + "github.com/DATA-DOG/go-sqlmock" + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +func setupMock() (*gorm.DB, sqlmock.Sqlmock, error) { + sqlDB, mock, err := sqlmock.New() // mock sql.DB + if err != nil { + return nil, nil, errors.Wrap(err, "Fail connection to database") + } + + db, err := gorm.Open("postgres", sqlDB) // open gorm db + if err != nil { + return nil, nil, err + } + + if db == nil { + return nil, nil, errors.New("Fail connection to database") + } + + return db, mock, nil +} + +func SetUpRouter() *gin.Engine { + router := gin.Default() + return router +} diff --git a/internal/api/handlers/users/user.go b/internal/api/handlers/users/user.go new file mode 100644 index 000000000..41473128f --- /dev/null +++ b/internal/api/handlers/users/user.go @@ -0,0 +1,41 @@ +package users + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/manabie-com/togo/internal/api/handlers" + "github.com/manabie-com/togo/internal/models" + "github.com/manabie-com/togo/internal/pkg/responses" + "github.com/manabie-com/togo/internal/repositories/user" +) + +func CreateUser(service handlers.MainUseCase) gin.HandlerFunc { + return func(ctx *gin.Context) { + var inputUser models.User + if err := ctx.ShouldBindJSON(&inputUser); err != nil { + responses.ResponseForError(ctx, err, http.StatusInternalServerError, "Fail BindJSON user") + return + } + + isExistUser, err := service.Auth.ValidateUser(inputUser.Username) + if err != nil { + responses.ResponseForError(ctx, err, http.StatusInternalServerError, "Fail ValidateUser") + return + } + + if isExistUser { + responses.ResponseForError(ctx, nil, http.StatusConflict, "User is exists") + return + } + + input := user.New(&inputUser) + + if err := service.User.Create(input); err != nil { + responses.ResponseForError(ctx, nil, http.StatusInternalServerError, "Fail CreateUser") + return + } + + responses.ResponseForOK(ctx, http.StatusCreated, inputUser, "Success") + } +} diff --git a/internal/api/handlers/users/user_test.go b/internal/api/handlers/users/user_test.go new file mode 100644 index 000000000..3049f5645 --- /dev/null +++ b/internal/api/handlers/users/user_test.go @@ -0,0 +1,199 @@ +package users + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/manabie-com/togo/internal/api/handlers" + "github.com/manabie-com/togo/internal/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreateUser_FailCase(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + r := SetUpRouter() + service := handlers.HandleService(db) + r.POST("/users", CreateUser(service)) + + { //Fail case - Not found + input := &models.User{ + ID: "2", + Username: "", + Password: "123456", + MaxTaskPerDay: 5, + } + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users" WHERE (username = $1) ORDER BY "users"."id" ASC LIMIT 1`)). + WithArgs(input.Username). + WillReturnError(fmt.Errorf("")) + + // Create mock data for test + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "users"`)). + WithArgs(input.ID, input.Username, input.Password, input.MaxTaskPerDay) + mock.ExpectCommit() + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/example", bytes.NewBuffer(jsonValue)) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusNotFound, w.Code) + } + + { //Fail case - Username is empty + input := &models.User{ + ID: "2", + Username: "", + Password: "123456", + MaxTaskPerDay: 5, + } + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users" WHERE (username = $1) ORDER BY "users"."id" ASC LIMIT 1`)). + WithArgs(input.Username). + WillReturnError(fmt.Errorf("")) + + // Create mock data for test + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "users"`)). + WithArgs(input.ID, input.Username, input.Password, input.MaxTaskPerDay) + mock.ExpectCommit() + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/users", bytes.NewBuffer(jsonValue)) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusInternalServerError, w.Code) + } + + { //Fail case - Password is empty + input := &models.User{ + ID: "3", + Username: "manabie-fail-1", + Password: "", + MaxTaskPerDay: 5, + } + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users" WHERE (username = $1) ORDER BY "users"."id" ASC LIMIT 1`)). + WithArgs(input.Username). + WillReturnError(fmt.Errorf("")) + + // Create mock data for test + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "users"`)). + WithArgs(input.ID, input.Username, input.Password, input.MaxTaskPerDay) + mock.ExpectCommit() + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/users", bytes.NewBuffer(jsonValue)) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusInternalServerError, w.Code) + } + + { //Fail case - Usernamd Password are empty + input := &models.User{ + ID: "3", + Username: "manabie-fail-1", + Password: "", + MaxTaskPerDay: 5, + } + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users" WHERE (username = $1) ORDER BY "users"."id" ASC LIMIT 1`)). + WithArgs(input.Username). + WillReturnError(fmt.Errorf("invalid password")) + + // Create mock data for test + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "users"`)). + WithArgs(input.ID, input.Username, input.Password, input.MaxTaskPerDay) + mock.ExpectCommit() + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/users", bytes.NewBuffer(jsonValue)) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusInternalServerError, w.Code) + } + + { //Fail case - conflic data + input := &models.User{ + ID: "user-1", + Username: "new-manabie-1", + Password: "123456", + MaxTaskPerDay: 5, + } + rows := sqlmock.NewRows([]string{"id", "username", "password", "max_task_per_day"}). + AddRow("user-3", input.Username, input.Password, 5) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users" WHERE (username = $1) ORDER BY "users"."id" ASC LIMIT 1`)). + WithArgs(input.Username). + WillReturnRows(rows) + + // Create mock data for test + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "users" ("id","username","password","max_task_per_day") VALUES ($1,$2,$3,$4) RETURNING "users"."id"`)). + WithArgs(input.ID, input.Username, input.Password, input.MaxTaskPerDay). + WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) + mock.ExpectCommit() + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/users", bytes.NewBuffer(jsonValue)) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusInternalServerError, w.Code) + } +} + +func TestCreateUser_SuccessCase(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + r := SetUpRouter() + service := handlers.HandleService(db) + r.POST("/users", CreateUser(service)) + + { //Success case + input := &models.User{ + ID: "user-3", + Username: "new-manabie-3", + Password: "123456", + MaxTaskPerDay: 5, + } + + // Create mock data for test + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "users" ("id","username","password","max_task_per_day") VALUES ($1,$2,$3,$4) RETURNING "users"."id"`)). + WithArgs(input.ID, input.Username, input.Password, input.MaxTaskPerDay). + WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) + mock.ExpectCommit() + + jsonValue, _ := json.Marshal(input) + req, _ := http.NewRequest(http.MethodPost, "/users", bytes.NewBuffer(jsonValue)) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusCreated, w.Code) + } + +} diff --git a/internal/api/routes/CHANGELOG.md b/internal/api/routes/CHANGELOG.md new file mode 100644 index 000000000..3a2d20f05 --- /dev/null +++ b/internal/api/routes/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create handle router diff --git a/internal/api/routes/routes.go b/internal/api/routes/routes.go new file mode 100644 index 000000000..63758a593 --- /dev/null +++ b/internal/api/routes/routes.go @@ -0,0 +1,16 @@ +package routes + +import ( + "github.com/manabie-com/togo/internal/api/handlers" + "github.com/manabie-com/togo/internal/api/handlers/common" + "github.com/manabie-com/togo/internal/api/handlers/tasks" + "github.com/manabie-com/togo/internal/api/handlers/users" + + "github.com/gin-gonic/gin" +) + +func SetupRoute(router *gin.Engine, service handlers.MainUseCase) { + common.NewHandler(router, service) + users.NewHandler(router, service) + tasks.NewHandler(router, service) +} diff --git a/internal/api/routes/routes_test.go b/internal/api/routes/routes_test.go new file mode 100644 index 000000000..3040f9bd3 --- /dev/null +++ b/internal/api/routes/routes_test.go @@ -0,0 +1,26 @@ +package routes + +import ( + "testing" + + "github.com/gin-gonic/gin" + "github.com/manabie-com/togo/internal/api/handlers" +) + +func TestSetupRoute(t *testing.T) { + type args struct { + router *gin.Engine + service handlers.MainUseCase + } + tests := []struct { + name string + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + SetupRoute(tt.args.router, tt.args.service) + }) + } +} diff --git a/internal/driver/CHANGELOG.md b/internal/driver/CHANGELOG.md new file mode 100644 index 000000000..2743534c8 --- /dev/null +++ b/internal/driver/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Create file `driver.go` diff --git a/internal/driver/driver.go b/internal/driver/driver.go new file mode 100644 index 000000000..95484b6cf --- /dev/null +++ b/internal/driver/driver.go @@ -0,0 +1,40 @@ +package driver + +import ( + "fmt" + + "github.com/manabie-com/togo/utils" + + "github.com/jinzhu/gorm" + _ "github.com/jinzhu/gorm/dialects/postgres" + "github.com/pkg/errors" +) + +var DB *gorm.DB + +// Connect to Database postgres +func ConnectDatabase() (*gorm.DB, error) { + dsn := "" + switch utils.Env.DBDriver { + case "postgres": + dsn = utils.Env.DSNPostgres + //To Do for another database + default: + return nil, errors.New("cannot find db driver") + } + + database, err := gorm.Open(utils.Env.DBDriver, dsn) + if err != nil { + return nil, errors.Wrap(err, "Fail to Connect Database!") + } + + if err := database.DB().Ping(); err != nil { + return nil, err + } + + DB = database + fmt.Println("Connection DB Complete") + + return database, nil + +} diff --git a/internal/models/CHANGELOG.md b/internal/models/CHANGELOG.md new file mode 100644 index 000000000..67337fc69 --- /dev/null +++ b/internal/models/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Create models `user` and `task` diff --git a/internal/models/task.go b/internal/models/task.go new file mode 100644 index 000000000..2a53dfa22 --- /dev/null +++ b/internal/models/task.go @@ -0,0 +1,8 @@ +package models + +type Task struct { + ID string `json:"id,omitempty"` + Content string `json:"content,omitempty"` + CreateDate string `json:"create_date,omitempty"` + UserID string `json:"user_id,omitempty"` +} diff --git a/internal/models/user.go b/internal/models/user.go new file mode 100644 index 000000000..a100dd40a --- /dev/null +++ b/internal/models/user.go @@ -0,0 +1,8 @@ +package models + +type User struct { + ID string `json:"id,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + MaxTaskPerDay int `json:"max_task_per_day,omitempty"` +} diff --git a/internal/pkg/CHANGELOG.md b/internal/pkg/CHANGELOG.md new file mode 100644 index 000000000..6cf3fc59d --- /dev/null +++ b/internal/pkg/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create respoonse +- Make create `uuid` diff --git a/internal/pkg/id/CHANGELOG.md b/internal/pkg/id/CHANGELOG.md new file mode 100644 index 000000000..19753ce67 --- /dev/null +++ b/internal/pkg/id/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create uuid diff --git a/internal/pkg/id/id.go b/internal/pkg/id/id.go new file mode 100644 index 000000000..1bc9f77c8 --- /dev/null +++ b/internal/pkg/id/id.go @@ -0,0 +1,18 @@ +package id + +import "github.com/google/uuid" + +type Uid = uuid.UUID + +//NewID create a new entity Uid +func NewID() Uid { + return Uid(uuid.New()) +} + +func UUIDIsNil(id *uuid.UUID) bool { + if id == nil { + return false + } + + return *id == uuid.Nil +} diff --git a/internal/pkg/id/id_test.go b/internal/pkg/id/id_test.go new file mode 100644 index 000000000..a34fc46e9 --- /dev/null +++ b/internal/pkg/id/id_test.go @@ -0,0 +1,37 @@ +package id + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +func TestNewID(t *testing.T) { + tests := []struct { + name string + want interface{} + }{ + { + name: "Success case", + want: uuid.UUID{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewID() + require.IsType(t, tt.want, got) + require.NotEqual(t, "", got) + }) + } +} + +func TestUUIDIsNil(t *testing.T) { + { //Success key + id := &uuid.UUID{} + require.Equal(t, true, UUIDIsNil(id)) + } + { //Fail key + require.Equal(t, false, UUIDIsNil(nil)) + } +} diff --git a/internal/pkg/responses/CHANGELOG.md b/internal/pkg/responses/CHANGELOG.md new file mode 100644 index 000000000..b5a204b7a --- /dev/null +++ b/internal/pkg/responses/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create struct for response Ok and errors diff --git a/internal/pkg/responses/responses.go b/internal/pkg/responses/responses.go new file mode 100644 index 000000000..bbd2851ec --- /dev/null +++ b/internal/pkg/responses/responses.go @@ -0,0 +1,38 @@ +package responses + +import ( + "github.com/manabie-com/togo/constants" + + "github.com/gin-gonic/gin" +) + +type ResponseError struct { + Status int + Error error + ErrorDetail string +} + +type ResponseOK struct { + Status int + Data interface{} +} + +func ResponseForError(ctx *gin.Context, err error, status int, errorDetail string) { + e := "" + if err != nil { + e = err.Error() + } + ctx.JSON(status, gin.H{ + constants.ResponseStatus: status, + constants.ResponseError: e, + constants.ResponseErrorDetail: errorDetail, + }) +} + +func ResponseForOK(ctx *gin.Context, status int, data interface{}, message string) { + ctx.JSON(status, gin.H{ + constants.ResponseStatus: status, + constants.ResponseData: data, + constants.ResponseMessage: message, + }) +} diff --git a/internal/repositories/CHANGELOG.md b/internal/repositories/CHANGELOG.md new file mode 100644 index 000000000..c938fb3d6 --- /dev/null +++ b/internal/repositories/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Create folder `user`, `task`, `auth` diff --git a/internal/repositories/authorization/CHANGELOG.md b/internal/repositories/authorization/CHANGELOG.md new file mode 100644 index 000000000..58f1c455d --- /dev/null +++ b/internal/repositories/authorization/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Create unit test case +- Generate token +- Make setup mock diff --git a/internal/repositories/authorization/authorization.go b/internal/repositories/authorization/authorization.go new file mode 100644 index 000000000..0d6d3a150 --- /dev/null +++ b/internal/repositories/authorization/authorization.go @@ -0,0 +1,70 @@ +package authorization + +import ( + "os" + "strconv" + "time" + + "github.com/manabie-com/togo/internal/models" + "github.com/manabie-com/togo/internal/usecases/authorization" + "github.com/manabie-com/togo/utils" + + "github.com/dgrijalva/jwt-go" + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +type repository struct { + DB *gorm.DB +} + +func NewAuthRepository(db *gorm.DB) authorization.AuthRepository { + return &repository{ + DB: db, + } +} + +// ValidateUser implements auth.AuthRepository +func (r *repository) ValidateUser(username string) (bool, error) { + if username == "" { + return false, errors.New("Invalid input") + } + + user := &models.User{} + r.DB.Where("username = ?", username).First(user) + + if user == nil || user.Username == "" { + return false, nil + } + + return true, nil +} + +func (r *repository) GenerateToken(userID, maxTaskPerday string) (*string, error) { + if userID == "" { + return nil, errors.New("Input empty") + } + + // Init a map claim for storing essential info + claims := jwt.MapClaims{} + + timeout, err := strconv.Atoi(os.Getenv("JWT_TIMEOUT")) + if err != nil { + return nil, err + } + + //Set value for token + claims["user_id"] = userID + claims["max_task_per_day"] = maxTaskPerday + + // Must use 'exp' key for storing timeout info + claims["exp"] = time.Now().Add(time.Minute * time.Duration(timeout)).Unix() + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString([]byte(os.Getenv("JWT_KEY"))) + if err != nil { + return nil, err + } + + return utils.String(tokenString), nil +} diff --git a/internal/repositories/authorization/authorization_test.go b/internal/repositories/authorization/authorization_test.go new file mode 100644 index 000000000..3bc80b1bc --- /dev/null +++ b/internal/repositories/authorization/authorization_test.go @@ -0,0 +1,40 @@ +package authorization + +import ( + "testing" + + "github.com/jinzhu/gorm" +) + +func Test_repository_ValidateUser(t *testing.T) { + type fields struct { + DB *gorm.DB + } + type args struct { + username string + } + tests := []struct { + name string + fields fields + args args + want bool + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &repository{ + DB: tt.fields.DB, + } + got, err := r.ValidateUser(tt.args.username) + if (err != nil) != tt.wantErr { + t.Errorf("repository.ValidateUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("repository.ValidateUser() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/repositories/task/CHANGELOG.md b/internal/repositories/task/CHANGELOG.md new file mode 100644 index 000000000..7027eb8b4 --- /dev/null +++ b/internal/repositories/task/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create unit test +- Make create func `FindTaskByUser` and `AddTask` +- Create file `task.go` diff --git a/internal/repositories/task/task.go b/internal/repositories/task/task.go new file mode 100644 index 000000000..0130669c3 --- /dev/null +++ b/internal/repositories/task/task.go @@ -0,0 +1,64 @@ +package task + +import ( + "github.com/manabie-com/togo/internal/models" + "github.com/manabie-com/togo/internal/pkg/id" + "github.com/manabie-com/togo/internal/usecases/task" + + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +type repository struct { + DB *gorm.DB +} + +func NewTaskRepository(db *gorm.DB) task.TaskUseCase { + return &repository{ + DB: db, + } +} + +func NewTask(task models.Task) *models.Task { + taskID := id.NewID().String() + if task.ID != "" { + taskID = task.ID + } + return &models.Task{ + ID: taskID, + Content: task.Content, + CreateDate: task.CreateDate, + UserID: task.UserID, + } +} + +// AddTask implements task.TaskUseCase +func (r *repository) AddTask(task *models.Task) error { + if task == nil { + return errors.New("Invalid input") + } + + if err := r.DB.Create(task).Error; err != nil { + return errors.Wrap(err, "Fail Create Task") + } + + return nil +} + +// FindTaskByUser implements task.TaskUseCase +func (r *repository) FindTaskByUser(userID string, createDate string) ([]models.Task, error) { + if userID == "" || createDate == "" { + return nil, errors.New("Invalid input") + } + + tasks := []models.Task{} + if err := r.DB.Where("user_id = ? and create_date = ?", userID, createDate).Find(&tasks).Error; err != nil { + return nil, errors.Wrap(err, "Fail query task") + } + + if len(tasks) == 0 { + return nil, nil + } + + return tasks, nil +} diff --git a/internal/repositories/task/task_test.go b/internal/repositories/task/task_test.go new file mode 100644 index 000000000..a185ee048 --- /dev/null +++ b/internal/repositories/task/task_test.go @@ -0,0 +1,139 @@ +package task + +import ( + "fmt" + "regexp" + "testing" + "time" + + "github.com/manabie-com/togo/internal/models" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/require" +) + +func Test_repository_AddTask(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + + taskRepository := NewTaskRepository(db) + + { //Success case + input := NewTask(models.Task{ + Content: "Test Interview", + CreateDate: time.Now().Format("2006-01-02"), + UserID: "manabie", + }) + // Create mock data for test + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "tasks" ("id","content","create_date","user_id") VALUES ($1,$2,$3,$4) RETURNING "tasks"."id"`)). + WithArgs(input.ID, input.Content, input.CreateDate, input.UserID). + WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) + mock.ExpectCommit() + + err := taskRepository.AddTask(input) + require.Nil(t, err) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("at least 1 expectation was not met: %s", err) + } + } + + { //Fail case + input := NewTask(models.Task{ + Content: "Test Interview", + CreateDate: time.Now().Format("2006-01-02"), + UserID: "manabie", + }) + // Create mock data for test + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "tasks" ("id","content","create_date","user_id") VALUES ($1,$2,$3,$4) RETURNING "tasks"."id"`)). + WithArgs("", input.Content, input.CreateDate, input.UserID). + WillReturnError(fmt.Errorf("nil task_id")) + mock.ExpectCommit() + + err := taskRepository.AddTask(input) + require.NotNil(t, err) + require.NotNil(t, mock.ExpectationsWereMet()) + } + +} + +func Test_repository_FindTaskByUser(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + + taskRepository := NewTaskRepository(db) + + { //Success case + input := &models.Task{ + Content: "task_manabie_1", + CreateDate: "2022-26-06", + UserID: "manabie_1", + } + // Create mock data for test + mock.ExpectExec("INSERT INTO tasks"). + WithArgs(1, input.Content, input.CreateDate, input.UserID). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Create Data + err := recordStats(db, *input) + require.Nil(t, err) + + rows := sqlmock.NewRows([]string{"id", "content", "create_date", "user_id"}). + AddRow("1", input.Content, input.CreateDate, input.UserID) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "tasks" WHERE (user_id = $1 and create_date = $2)`)). + WithArgs(input.UserID, input.CreateDate). + WillReturnRows(rows) + + tasks, err := taskRepository.FindTaskByUser(input.UserID, input.CreateDate) + require.Nil(t, err) + require.NotNil(t, tasks) + require.Equal(t, 1, len(tasks)) + require.Equal(t, input.UserID, tasks[0].UserID) + require.Equal(t, input.Content, tasks[0].Content) + require.Equal(t, input.CreateDate, tasks[0].CreateDate) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("at least 1 expectation was not met: %s", err) + } + } + + { //fail case + input := &models.Task{ + Content: "fail_task_manabie_1", + CreateDate: "2022-26-06", + UserID: "fail_manabie_1", + } + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "tasks" WHERE (user_id = $1 and create_date = $2)`)). + WithArgs(input.UserID, input.CreateDate). + WillReturnError(fmt.Errorf("invalid task")) + + tasks, err := taskRepository.FindTaskByUser(input.UserID, input.CreateDate) + require.NotNil(t, err) + require.Nil(t, tasks) + require.Equal(t, 0, len(tasks)) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("at least 1 expectation was not met: %s", err) + } + } + +} diff --git a/internal/repositories/task/var_test.go b/internal/repositories/task/var_test.go new file mode 100644 index 000000000..745eb0f56 --- /dev/null +++ b/internal/repositories/task/var_test.go @@ -0,0 +1,37 @@ +package task + +import ( + "fmt" + + "github.com/manabie-com/togo/internal/models" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +func setupMock() (*gorm.DB, sqlmock.Sqlmock, error) { + sqlDB, mock, err := sqlmock.New() // mock sql.DB + if err != nil { + return nil, nil, errors.Wrap(err, "Fail connection to database") + } + + db, err := gorm.Open("postgres", sqlDB) // open gorm db + if err != nil { + return nil, nil, err + } + + if db == nil { + return nil, nil, errors.New("Fail connection to database") + } + + return db, mock, nil +} + +func recordStats(db *gorm.DB, task models.Task) (err error) { + _, err = db.CommonDB().Exec("INSERT INTO tasks (id, content, create_date, user_id) VALUES (?, ?,?,?,?)", 1, task.Content, task.CreateDate, task.UserID) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("error '%s' was not expected, while inserting a row", err)) + } + return +} diff --git a/internal/repositories/user/CHANGELOG.md b/internal/repositories/user/CHANGELOG.md new file mode 100644 index 000000000..76152f6a0 --- /dev/null +++ b/internal/repositories/user/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create unit test +- Make create func `Create`, `Login` and `GetUser` +- Create file `user.go` diff --git a/internal/repositories/user/user.go b/internal/repositories/user/user.go new file mode 100644 index 000000000..ad92e5efc --- /dev/null +++ b/internal/repositories/user/user.go @@ -0,0 +1,64 @@ +package user + +import ( + "github.com/manabie-com/togo/internal/models" + "github.com/manabie-com/togo/internal/pkg/id" + "github.com/manabie-com/togo/internal/usecases/user" + + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +type repository struct { + DB *gorm.DB +} + +func NewUserRepository(db *gorm.DB) user.UserRepository { + return &repository{ + DB: db, + } +} + +func New(user *models.User) *models.User { + userID := id.NewID().String() + if user.ID != "" { + userID = user.ID + } + return &models.User{ + ID: userID, + Username: user.Username, + Password: user.Password, + MaxTaskPerDay: user.MaxTaskPerDay, + } +} + +// Create implements user.UserRepository +func (r *repository) Create(inputUser *models.User) error { + if inputUser == nil || inputUser.Username == "" || inputUser.Password == "" { + return errors.New("Input valid") + } + + if err := r.DB.Create(inputUser).Error; err != nil { + return errors.Wrap(err, "Fail create user") + } + + return nil +} + +// Login implements user.UserRepository +func (r *repository) Login(username, password string) (*models.User, error) { + if username == "" || password == "" { + return nil, errors.New("Input empty") + } + + user := &models.User{} + if err := r.DB.Where("username = ? and password = ?", username, password).Take(user).Error; err != nil { + return nil, errors.Wrap(err, "Fail query user") + } + + if user == nil { + return nil, errors.New("User is not exists") + } + + return user, nil +} diff --git a/internal/repositories/user/user_test.go b/internal/repositories/user/user_test.go new file mode 100644 index 000000000..21400e4fe --- /dev/null +++ b/internal/repositories/user/user_test.go @@ -0,0 +1,66 @@ +package user + +import ( + "fmt" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/manabie-com/togo/internal/models" + "github.com/stretchr/testify/require" +) + +func Test_repository_Create(t *testing.T) { + t.Parallel() + db, mock, err := setupMock() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening stub database connection", err) + } + require.Nil(t, err) + require.NotNil(t, mock) + require.NotNil(t, db) + + defer db.Close() + + userRepository := NewUserRepository(db) + + { //Success case + user := New(&models.User{ + Username: "manabie_1", + Password: "manabie_1", + MaxTaskPerDay: 5, + }) + // Create mock data for test + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "users" ("id","username","password","max_task_per_day") VALUES ($1,$2,$3,$4) RETURNING "users"."id"`)). + WithArgs(user.ID, user.Username, user.Password, user.MaxTaskPerDay). + WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) + mock.ExpectCommit() + + err := userRepository.Create(user) + require.Nil(t, err) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("at least 1 expectation was not met: %s", err) + } + } + + { + //Fail case + user := New(&models.User{ + Username: "manabie_1", + Password: "manabie_1", + MaxTaskPerDay: 5, + }) + // Create mock data for test + mock.ExpectBegin() + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "users" ("id","username","password","max_task_per_day") VALUES ($1,$2,$3,$4) RETURNING "users"."id"`)). + WithArgs("1", user.Username, user.Password, user.MaxTaskPerDay). + WillReturnError(fmt.Errorf("nil user_id")) + mock.ExpectCommit() + + err := userRepository.Create(user) + require.NotNil(t, err) + require.NotNil(t, mock.ExpectationsWereMet()) + } +} diff --git a/internal/repositories/user/var_test.go b/internal/repositories/user/var_test.go new file mode 100644 index 000000000..998c2bcd3 --- /dev/null +++ b/internal/repositories/user/var_test.go @@ -0,0 +1,25 @@ +package user + +import ( + "github.com/DATA-DOG/go-sqlmock" + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +func setupMock() (*gorm.DB, sqlmock.Sqlmock, error) { + sqlDB, mock, err := sqlmock.New() // mock sql.DB + if err != nil { + return nil, nil, errors.Wrap(err, "Fail connection to database") + } + + db, err := gorm.Open("postgres", sqlDB) // open gorm db + if err != nil { + return nil, nil, err + } + + if db == nil { + return nil, nil, errors.New("Fail connection to database") + } + + return db, mock, nil +} diff --git a/internal/usecases/CHANGELOG.md b/internal/usecases/CHANGELOG.md new file mode 100644 index 000000000..124fad9b7 --- /dev/null +++ b/internal/usecases/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create `user`, `task` and `auth` diff --git a/internal/usecases/authorization/CHANGELOG.md b/internal/usecases/authorization/CHANGELOG.md new file mode 100644 index 000000000..24b97e46d --- /dev/null +++ b/internal/usecases/authorization/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Make create func for `authUseCase` +- Implement `AuthUseCase` +- Create file `interface.go` and `auth.go` diff --git a/internal/usecases/authorization/authorization.go b/internal/usecases/authorization/authorization.go new file mode 100644 index 000000000..7db5d9077 --- /dev/null +++ b/internal/usecases/authorization/authorization.go @@ -0,0 +1,26 @@ +package authorization + +// AuthUsecase is the definition for collection of methods related to the `auths` table use case +type AuthUseCase interface { + ValidateUser(username string) (bool, error) + GenerateToken(userID, maxTaskPerday string) (*string, error) +} + +type authUseCaseRepository struct { + repository AuthRepository +} + +// NewAuthUsecase returns a AuthUsecase attached with methods related to the `auths` table use case +func NewAuthUseCase(repository AuthRepository) AuthUseCase { + return &authUseCaseRepository{repository: repository} +} + +// ValidateUser returns a AuthUsecase attached with methods related to the `auths` table use case +func (ar *authUseCaseRepository) ValidateUser(username string) (bool, error) { + return ar.repository.ValidateUser(username) +} + +// GenerateToken returns a AuthUsecase attached with methods related to the `auths` table use case +func (ar *authUseCaseRepository) GenerateToken(userID, maxTaskPerday string) (*string, error) { + return ar.repository.GenerateToken(userID, maxTaskPerday) +} diff --git a/internal/usecases/authorization/interface.go b/internal/usecases/authorization/interface.go new file mode 100644 index 000000000..b779fa78b --- /dev/null +++ b/internal/usecases/authorization/interface.go @@ -0,0 +1,10 @@ +package authorization + +type Common interface { + ValidateUser(username string) (bool, error) + GenerateToken(userID, maxTaskPerday string) (*string, error) +} + +type AuthRepository interface { + Common +} diff --git a/internal/usecases/task/CHANGELOG.md b/internal/usecases/task/CHANGELOG.md new file mode 100644 index 000000000..64dc36dc6 --- /dev/null +++ b/internal/usecases/task/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Implement `TaskRepository` and `TaskUseCase` +- Create file `interface.go` and `task.go` diff --git a/internal/usecases/task/interface.go b/internal/usecases/task/interface.go new file mode 100644 index 000000000..fbd39ce85 --- /dev/null +++ b/internal/usecases/task/interface.go @@ -0,0 +1,18 @@ +package task + +import ( + "github.com/manabie-com/togo/internal/models" +) + +type Reader interface { + FindTaskByUser(username, createDate string) ([]models.Task, error) +} + +type Writer interface { + AddTask(task *models.Task) error +} + +type TaskRepository interface { + Reader + Writer +} diff --git a/internal/usecases/task/task.go b/internal/usecases/task/task.go new file mode 100644 index 000000000..e726428e8 --- /dev/null +++ b/internal/usecases/task/task.go @@ -0,0 +1,28 @@ +package task + +import ( + "github.com/manabie-com/togo/internal/models" +) + +// TaskUseCaseUsecase is the definition for collection of methods related to the `users` table use case +type TaskUseCase interface { + FindTaskByUser(userID, createDate string) ([]models.Task, error) + AddTask(task *models.Task) error +} + +type taskUseCaseRepository struct { + repository TaskRepository +} + +// NewUserUsecase returns a UserUsecase attached with methods related to the `users` table use case +func NewTaskUseCase(repository TaskRepository) TaskUseCase { + return &taskUseCaseRepository{repository: repository} +} + +func (r *taskUseCaseRepository) FindTaskByUser(userID, createDate string) ([]models.Task, error) { + return r.repository.FindTaskByUser(userID, createDate) +} + +func (r *taskUseCaseRepository) AddTask(task *models.Task) error { + return r.repository.AddTask(task) +} diff --git a/internal/usecases/user/CHANGELOG.md b/internal/usecases/user/CHANGELOG.md new file mode 100644 index 000000000..c942a6354 --- /dev/null +++ b/internal/usecases/user/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Implement `UserRepository` and `UserUseCase` +- Create file `interface.go` and `user.go` diff --git a/internal/usecases/user/interface.go b/internal/usecases/user/interface.go new file mode 100644 index 000000000..eda8b3617 --- /dev/null +++ b/internal/usecases/user/interface.go @@ -0,0 +1,18 @@ +package user + +import ( + "github.com/manabie-com/togo/internal/models" +) + +type Reader interface { + Login(username, password string) (*models.User, error) +} + +type Writer interface { + Create(u *models.User) error +} + +type UserRepository interface { + Reader + Writer +} diff --git a/internal/usecases/user/user.go b/internal/usecases/user/user.go new file mode 100644 index 000000000..cd767b4f0 --- /dev/null +++ b/internal/usecases/user/user.go @@ -0,0 +1,30 @@ +package user + +import ( + "github.com/manabie-com/togo/internal/models" +) + +// UserUsecase is the definition for collection of methods related to the `users` table use case +type UserUseCase interface { + Login(username, password string) (*models.User, error) + Create(u *models.User) error +} + +type userUseCaseRepository struct { + repository UserRepository +} + +// NewUserUsecase returns a UserUsecase attached with methods related to the `users` table use case +func NewUserUseCase(repository UserRepository) UserUseCase { + return &userUseCaseRepository{repository: repository} +} + +// Login implements UserUseCase +func (uc *userUseCaseRepository) Login(username, password string) (*models.User, error) { + return uc.repository.Login(username, password) +} + +// Create implements UserUseCase +func (uc *userUseCaseRepository) Create(u *models.User) error { + return uc.repository.Create(u) +} diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md new file mode 100644 index 000000000..523c2843a --- /dev/null +++ b/utils/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +- Create func for Load file and get variable from `.env` diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 000000000..394205a29 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,91 @@ +package utils + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/caarlos0/env" + "github.com/joho/godotenv" +) + +func SafeString(s *string) string { + if s == nil { + return "" + } + + return *s +} + +func String(s string) *string { + return &s +} + +// --Remove Duplicate Data +func RemoveDuplicate(s []string) []string { + inResult := make(map[string]bool) + var result []string + for _, str := range s { + if _, ok := inResult[str]; !ok { + inResult[str] = true + result = append(result, str) + } + } + return result +} + +type Environment struct { + Host string `env:"HOST"` + Port string `env:"PORT"` + JwtKey string `env:"JWT_KEY"` + JwtTimeout string `env:"JWT_TIMEOUT"` + DBDriver string `env:"DB_DRIVER"` + DBHost string `env:"DB_HOST"` + DBPort string `env:"DB_PORT"` + DBUser string `env:"DB_USER"` + DBPassword string `env:"DB_PASSWORD"` + DBName string `env:"DB_NAME"` + DBSslMode string `env:"DB_SSL_MODE"` + DSNPostgres string `env:"DSN_POSTGRES"` +} + +var Env *Environment + +func LoadEnv(file string) { + fileName := getPathfile(file) + err := godotenv.Load(fileName) + if err != nil { + log.Fatalf("Some error occured. Err: %s", err) + } + + if Env == nil { + Env = new(Environment) + } + + getEnvironmentFromEnv(Env) +} + +func GetEnv(key string) string { + value, ok := os.LookupEnv(key) + if !ok { + log.Fatalf("Missing or invalid environment key: '%s'", key) + } + return value +} + +func getEnvironmentFromEnv(object *Environment) { + if object == nil { + return + } + env.Parse(object) +} + +func getPathfile(fileName string) string { + path, err := os.Getwd() + if err != nil { + log.Println(err) + } + curArr := strings.Split(path, "/cmd") + return fmt.Sprintf("%s/%s", curArr[0], fileName) +} diff --git a/utils/utils_test.go b/utils/utils_test.go new file mode 100644 index 000000000..a22edf478 --- /dev/null +++ b/utils/utils_test.go @@ -0,0 +1,52 @@ +package utils + +import ( + "reflect" + "testing" +) + +func TestSafeString(t *testing.T) { + type args struct { + s *string + } + tests := []struct { + name string + args args + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SafeString(tt.args.s); got != tt.want { + t.Errorf("SafeString() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRemoveDuplicate(t *testing.T) { + type args struct { + s []string + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "Success case 1", + args: args{ + []string{"1", "2", "3", "1"}, + }, + want: []string{"1", "2", "3"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := RemoveDuplicate(tt.args.s); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoveDuplicate() = %v, want %v", got, tt.want) + } + }) + } +}