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 + -- Fork this repo and show us your development progress via a PR +- Feature Create User + -### Interesting facts about Manabie +- Feature Add Task + -- 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. + + +### 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 @@ + + + +altaltClientAdd TaskFail caseFail caseSuccess caseSuccess caseAPIValidate task per dayValidate task per dayResponsePOST /tasksAuthResponseTaskDatabaseResponseCheck login by tokenResponseResponseGet list tasks by user and createDateResponseGet list tasksResponseCreate TaskCreate TaskText 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 @@ + + + +altaltClientCreate UserHave userHave userAPIResponsePOST /usersAuthResponseUserResponseDatabaseResponseCreate UserValidate user with usernameFind user with usernameResponseCheck user is existsCheck user is existsNot have userNot have userCreate UserResponseText 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 @@ + + + +altaltClientLoginAPIPOST /login UserDatabaseFind user by username and passwordResponseRequest username and passwordFail caseFail caseResponseResponseSuccess caseSuccess caseResponseGenerate token and save in cookieGenerate token and save in cookieText 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) + } + }) + } +}