├── .dockerignore ├── .env ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── configs └── connection.go ├── controllers ├── auth-controllers │ ├── activation │ │ ├── entity.go │ │ ├── repository.go │ │ └── service.go │ ├── forgot │ │ ├── input.go │ │ ├── repository.go │ │ └── service.go │ ├── login │ │ ├── input.go │ │ ├── repository.go │ │ └── service.go │ ├── register │ │ ├── input.go │ │ ├── repository.go │ │ └── service.go │ ├── resend │ │ ├── input.go │ │ ├── repository.go │ │ └── service.go │ └── reset │ │ ├── entity.go │ │ ├── repository.go │ │ └── service.go └── student-controllers │ ├── create │ ├── entity.go │ ├── repository.go │ └── service.go │ ├── delete │ ├── entity.go │ ├── repository.go │ └── service.go │ ├── result │ ├── entity.go │ ├── repository.go │ └── service.go │ ├── results │ ├── repository.go │ └── service.go │ └── update │ ├── entity.go │ ├── repository.go │ └── service.go ├── docker-compose.yml ├── gin-api.postman_collection.json ├── go.mod ├── go.sum ├── handlers ├── auth-handlers │ ├── activation │ │ └── activation.go │ ├── forgot │ │ └── forgot.go │ ├── login │ │ └── login.go │ ├── register │ │ └── register.go │ ├── resend │ │ └── resend.go │ └── reset │ │ └── reset.go └── student-handlers │ ├── create │ └── create.go │ ├── delete │ └── delete.go │ ├── result │ └── result.go │ ├── results │ └── results.go │ └── update │ └── update.go ├── main.go ├── main_test.go ├── middlewares └── authJwt.go ├── models ├── entity.auth.go └── entity.student.go ├── routes ├── route.auth.go └── route.student.go ├── swagger ├── Dockerfile └── openapi.yml ├── templates ├── template_register.html ├── template_resend.html └── template_reset.html └── utils ├── bcrypt.go ├── dotenv.go ├── html.go ├── httpTest.go ├── json.go ├── jwt.go ├── randomString.go ├── response.go ├── sendgrid.go └── validator.go /.dockerignore: -------------------------------------------------------------------------------- 1 | env 2 | gin-api.postman_collection.json 3 | bin/ 4 | README.md 5 | main 6 | openapi.yml -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ######################### 2 | # APP DEVELOPMENT ENV 3 | ######################### 4 | GO_PORT = 3001 5 | GO_ENV = test 6 | ######################### 7 | # PG DEVELOPMENT ENV 8 | ######################### 9 | DATABASE_URI_PROD = postgres://restuwahyu13:restuwahyu13@db:5432/campus 10 | DATABASE_URI_DEV = postgres://test:test@localhost:5433/test 11 | 12 | ######################### 13 | # JWT DEVELOPMENT ENV 14 | ######################## 15 | JWT_SECRET = 2c46f7651a176169c8d2f7aee60cce3da874501d 16 | 17 | ######################### 18 | # SG DEVELOPMENT ENV 19 | ######################## 20 | SG_API_KEY = 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 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | .env 3 | main -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # ====================== 2 | # GO FIRST STAGE 3 | # ====================== 4 | 5 | FROM golang:latest as builder 6 | USER ${USER} 7 | WORKDIR /usr/src/app 8 | COPY go.mod \ 9 | go.sum ./ 10 | RUN go mod download 11 | COPY . ./ 12 | ENV GO111MODULE="on" \ 13 | GOARCH="amd64" \ 14 | GOOS="linux" \ 15 | CGO_ENABLED="0" 16 | RUN apt-get clean \ 17 | && apt-get remove 18 | 19 | # ====================== 20 | # GO FINAL STAGE 21 | # ====================== 22 | 23 | FROM builder 24 | WORKDIR /usr/src/app 25 | RUN apt-get update \ 26 | && apt-get install -y \ 27 | make \ 28 | vim \ 29 | build-essential 30 | COPY --from=builder . ./usr/src/app 31 | RUN make goprod 32 | EXPOSE 4000 33 | CMD ["./main"] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #================================ 2 | #== DOCKER ENVIRONMENT 3 | #================================ 4 | COMPOSE := @docker-compose 5 | 6 | dcb: 7 | ${COMPOSE} build 8 | 9 | dcuf: 10 | ifdef f 11 | ${COMPOSE} up -d --${f} 12 | endif 13 | 14 | dcubf: 15 | ifdef f 16 | ${COMPOSE} up -d --build --${f} 17 | endif 18 | 19 | dcu: 20 | ${COMPOSE} up -d --build 21 | 22 | dcd: 23 | ${COMPOSE} down 24 | 25 | #================================ 26 | #== GOLANG ENVIRONMENT 27 | #================================ 28 | GO := @go 29 | GIN := @gin 30 | 31 | goinstall: 32 | ${GO} get . 33 | 34 | godev: 35 | ${GIN} -a 4000 -p 3001 -b bin/main run main.go 36 | 37 | goprod: 38 | ${GO} build -o main . 39 | 40 | gotest: 41 | ${GO} test -v 42 | 43 | goformat: 44 | ${GO} fmt ./... -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Golang Gin Framework Fundamental 2 | 3 | Example golang using gin framework everything you need, i create this tutorial special for beginner. 4 | 5 | ### Feature 6 | 7 | - [x] Containerize Application Using Docker 8 | - [x] Protected Route Using JWT 9 | - [x] Integerasi ORM Database Using Gorm 10 | - [x] API Documentation Using Swagger 11 | - [x] Validation Request Using Go Playground Validator 12 | - [x] Integerasi Unit Testing 13 | - [x] And More 14 | 15 | ## Command 16 | 17 | - ### Application Lifecycle 18 | 19 | - Install node modules 20 | 21 | ```sh 22 | $ go get . || go mod || make goinstall 23 | ``` 24 | 25 | - Build application 26 | 27 | ```sh 28 | $ go build -o main || make goprod 29 | ``` 30 | 31 | - Start application in development 32 | 33 | ```sh 34 | $ go run main.go | make godev 35 | ``` 36 | 37 | - Test application 38 | 39 | ```sh 40 | $ go test main.go main_test.go || make gotest 41 | ``` 42 | 43 | * ### Docker Lifecycle 44 | 45 | - Build container 46 | 47 | ```sh 48 | $ docker-compose build | make dcb 49 | ``` 50 | 51 | - Run container with flags 52 | 53 | ```sh 54 | $ docker-compose up -d -- | make dcu f= 55 | ``` 56 | 57 | - Run container build with flags 58 | 59 | ```sh 60 | $ docker-compose up -d --build -- | make dcubf f= 61 | ``` 62 | 63 | - Run container 64 | 65 | ```sh 66 | $ docker-compose up -d --build | make dcu 67 | ``` 68 | 69 | - Stop container 70 | 71 | ```sh 72 | $ docker-compose down | make dcd 73 | ``` 74 | 75 | ### Author 76 | 77 | - [Restu Wahyu Saputra](https://github.com/restuwahyu13) 78 | -------------------------------------------------------------------------------- /configs/connection.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | 6 | model "github.com/restuwahyu13/gin-rest-api/models" 7 | util "github.com/restuwahyu13/gin-rest-api/utils" 8 | "github.com/sirupsen/logrus" 9 | "gorm.io/driver/postgres" 10 | "gorm.io/gorm" 11 | ) 12 | 13 | func Connection() *gorm.DB { 14 | databaseURI := make(chan string, 1) 15 | 16 | if os.Getenv("GO_ENV") != "production" { 17 | databaseURI <- util.GodotEnv("DATABASE_URI_DEV") 18 | } else { 19 | databaseURI <- os.Getenv("DATABASE_URI_PROD") 20 | } 21 | 22 | db, err := gorm.Open(postgres.Open(<-databaseURI), &gorm.Config{}) 23 | 24 | if err != nil { 25 | defer logrus.Info("Connection to Database Failed") 26 | logrus.Fatal(err.Error()) 27 | } 28 | 29 | if os.Getenv("GO_ENV") != "production" { 30 | logrus.Info("Connection to Database Successfully") 31 | } 32 | 33 | err = db.AutoMigrate( 34 | &model.EntityUsers{}, 35 | &model.EntityStudent{}, 36 | ) 37 | 38 | if err != nil { 39 | logrus.Fatal(err.Error()) 40 | } 41 | 42 | return db 43 | } 44 | -------------------------------------------------------------------------------- /controllers/auth-controllers/activation/entity.go: -------------------------------------------------------------------------------- 1 | package activationAuth 2 | 3 | type InputActivation struct { 4 | Email string `json:"email" validate:"required,email"` 5 | Active bool `json:"active"` 6 | Token string `json:"token" validate:"required"` 7 | } 8 | -------------------------------------------------------------------------------- /controllers/auth-controllers/activation/repository.go: -------------------------------------------------------------------------------- 1 | package activationAuth 2 | 3 | import ( 4 | "time" 5 | 6 | model "github.com/restuwahyu13/gin-rest-api/models" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | type Repository interface { 11 | ActivationRepository(input *model.EntityUsers) (*model.EntityUsers, string) 12 | } 13 | 14 | type repository struct { 15 | db *gorm.DB 16 | } 17 | 18 | func NewRepositoryActivation(db *gorm.DB) *repository { 19 | return &repository{db: db} 20 | } 21 | 22 | func (r *repository) ActivationRepository(input *model.EntityUsers) (*model.EntityUsers, string) { 23 | 24 | var users model.EntityUsers 25 | db := r.db.Model(&users) 26 | errorCode := make(chan string, 1) 27 | 28 | users.Email = input.Email 29 | 30 | checkUserAccount := db.Debug().Select("*").Where("email = ?", input.Email).Find(&users) 31 | 32 | if checkUserAccount.RowsAffected < 1 { 33 | errorCode <- "ACTIVATION_NOT_FOUND_404" 34 | return &users, <-errorCode 35 | } 36 | 37 | db.Debug().Select("Active").Where("activation = ?", input.Active).Take(&users) 38 | 39 | if users.Active { 40 | errorCode <- "ACTIVATION_ACTIVE_400" 41 | return &users, <-errorCode 42 | } 43 | 44 | users.Active = input.Active 45 | users.UpdatedAt = time.Now().Local() 46 | 47 | updateActivation := db.Debug().Select("active", "updated_at").Where("email = ?", input.Email).Updates(users) 48 | 49 | if updateActivation.Error != nil { 50 | errorCode <- "ACTIVATION_ACCOUNT_FAILED_403" 51 | return &users, <-errorCode 52 | } else { 53 | errorCode <- "nil" 54 | } 55 | 56 | return &users, <-errorCode 57 | } 58 | -------------------------------------------------------------------------------- /controllers/auth-controllers/activation/service.go: -------------------------------------------------------------------------------- 1 | package activationAuth 2 | 3 | import model "github.com/restuwahyu13/gin-rest-api/models" 4 | 5 | type Service interface { 6 | ActivationService(input *InputActivation) (*model.EntityUsers, string) 7 | } 8 | 9 | type service struct { 10 | repository Repository 11 | } 12 | 13 | func NewServiceActivation(repository Repository) *service { 14 | return &service{repository: repository} 15 | } 16 | 17 | func (s *service) ActivationService(input *InputActivation) (*model.EntityUsers, string) { 18 | users := model.EntityUsers{ 19 | Email: input.Email, 20 | Active: input.Active, 21 | } 22 | 23 | activationResult, activationError := s.repository.ActivationRepository(&users) 24 | 25 | return activationResult, activationError 26 | } 27 | -------------------------------------------------------------------------------- /controllers/auth-controllers/forgot/input.go: -------------------------------------------------------------------------------- 1 | package forgotAuth 2 | 3 | type InputForgot struct { 4 | Email string `json:"email" validate:"required,email"` 5 | } 6 | -------------------------------------------------------------------------------- /controllers/auth-controllers/forgot/repository.go: -------------------------------------------------------------------------------- 1 | package forgotAuth 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | util "github.com/restuwahyu13/gin-rest-api/utils" 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type Repository interface { 10 | ForgotRepository(input *model.EntityUsers) (*model.EntityUsers, string) 11 | } 12 | 13 | type repository struct { 14 | db *gorm.DB 15 | } 16 | 17 | func NewRepositoryForgot(db *gorm.DB) *repository { 18 | return &repository{db: db} 19 | } 20 | 21 | func (r *repository) ForgotRepository(input *model.EntityUsers) (*model.EntityUsers, string) { 22 | 23 | var users model.EntityUsers 24 | db := r.db.Model(&users) 25 | errorCode := make(chan string, 1) 26 | 27 | users.Email = input.Email 28 | users.Password = util.HashPassword(util.RandStringBytes(20)) 29 | 30 | checkUserAccount := db.Debug().Select("*").Where("email = ?", input.Email).Find(&users) 31 | 32 | if checkUserAccount.RowsAffected < 1 { 33 | errorCode <- "FORGOT_NOT_FOUD_404" 34 | return &users, <-errorCode 35 | } 36 | 37 | if !users.Active { 38 | errorCode <- "FORGOT_NOT_ACTIVE_403" 39 | return &users, <-errorCode 40 | } 41 | 42 | changePassword := db.Debug().Select("password", "updated_at").Where("email = ?", input.Email).Updates(users) 43 | 44 | if changePassword.Error != nil { 45 | errorCode <- "FORGOT_PASSWORD_FAILED_403" 46 | return &users, <-errorCode 47 | } else { 48 | errorCode <- "nil" 49 | } 50 | 51 | return &users, <-errorCode 52 | } 53 | -------------------------------------------------------------------------------- /controllers/auth-controllers/forgot/service.go: -------------------------------------------------------------------------------- 1 | package forgotAuth 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | ) 6 | 7 | type Service interface { 8 | ForgotService(input *InputForgot) (*model.EntityUsers, string) 9 | } 10 | 11 | type service struct { 12 | repository Repository 13 | } 14 | 15 | func NewServiceForgot(repository Repository) *service { 16 | return &service{repository: repository} 17 | } 18 | 19 | func (s *service) ForgotService(input *InputForgot) (*model.EntityUsers, string) { 20 | 21 | users := model.EntityUsers{ 22 | Email: input.Email, 23 | } 24 | 25 | resultRegister, errRegister := s.repository.ForgotRepository(&users) 26 | 27 | return resultRegister, errRegister 28 | } 29 | -------------------------------------------------------------------------------- /controllers/auth-controllers/login/input.go: -------------------------------------------------------------------------------- 1 | package loginAuth 2 | 3 | type InputLogin struct { 4 | Email string `json:"email" validate:"required,email"` 5 | Password string `json:"password" validate:"required"` 6 | } 7 | -------------------------------------------------------------------------------- /controllers/auth-controllers/login/repository.go: -------------------------------------------------------------------------------- 1 | package loginAuth 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | util "github.com/restuwahyu13/gin-rest-api/utils" 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type Repository interface { 10 | LoginRepository(input *model.EntityUsers) (*model.EntityUsers, string) 11 | } 12 | 13 | type repository struct { 14 | db *gorm.DB 15 | } 16 | 17 | func NewRepositoryLogin(db *gorm.DB) *repository { 18 | return &repository{db: db} 19 | } 20 | 21 | func (r *repository) LoginRepository(input *model.EntityUsers) (*model.EntityUsers, string) { 22 | 23 | var users model.EntityUsers 24 | db := r.db.Model(&users) 25 | errorCode := make(chan string, 1) 26 | 27 | users.Email = input.Email 28 | users.Password = input.Password 29 | 30 | checkUserAccount := db.Debug().Select("*").Where("email = ?", input.Email).Find(&users) 31 | 32 | if checkUserAccount.RowsAffected < 1 { 33 | errorCode <- "LOGIN_NOT_FOUND_404" 34 | return &users, <-errorCode 35 | } 36 | 37 | if !users.Active { 38 | errorCode <- "LOGIN_NOT_ACTIVE_403" 39 | return &users, <-errorCode 40 | } 41 | 42 | comparePassword := util.ComparePassword(users.Password, input.Password) 43 | 44 | if comparePassword != nil { 45 | errorCode <- "LOGIN_WRONG_PASSWORD_403" 46 | return &users, <-errorCode 47 | } else { 48 | errorCode <- "nil" 49 | } 50 | 51 | return &users, <-errorCode 52 | } 53 | -------------------------------------------------------------------------------- /controllers/auth-controllers/login/service.go: -------------------------------------------------------------------------------- 1 | package loginAuth 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | ) 6 | 7 | type Service interface { 8 | LoginService(input *InputLogin) (*model.EntityUsers, string) 9 | } 10 | 11 | type service struct { 12 | repository Repository 13 | } 14 | 15 | func NewServiceLogin(repository Repository) *service { 16 | return &service{repository: repository} 17 | } 18 | 19 | func (s *service) LoginService(input *InputLogin) (*model.EntityUsers, string) { 20 | 21 | user := model.EntityUsers{ 22 | Email: input.Email, 23 | Password: input.Password, 24 | } 25 | 26 | resultLogin, errLogin := s.repository.LoginRepository(&user) 27 | 28 | return resultLogin, errLogin 29 | } 30 | -------------------------------------------------------------------------------- /controllers/auth-controllers/register/input.go: -------------------------------------------------------------------------------- 1 | package registerAuth 2 | 3 | type InputRegister struct { 4 | Fullname string `json:"fullname" validate:"required,lowercase"` 5 | Email string `json:"email" validate:"required,email"` 6 | Password string `json:"password" validate:"required,gte=8"` 7 | } 8 | -------------------------------------------------------------------------------- /controllers/auth-controllers/register/repository.go: -------------------------------------------------------------------------------- 1 | package registerAuth 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type Repository interface { 9 | RegisterRepository(input *model.EntityUsers) (*model.EntityUsers, string) 10 | } 11 | 12 | type repository struct { 13 | db *gorm.DB 14 | } 15 | 16 | func NewRepositoryRegister(db *gorm.DB) *repository { 17 | return &repository{db: db} 18 | } 19 | 20 | func (r *repository) RegisterRepository(input *model.EntityUsers) (*model.EntityUsers, string) { 21 | 22 | var users model.EntityUsers 23 | db := r.db.Model(&users) 24 | errorCode := make(chan string, 1) 25 | 26 | checkUserAccount := db.Debug().Select("*").Where("email = ?", input.Email).Find(&users) 27 | 28 | if checkUserAccount.RowsAffected > 0 { 29 | errorCode <- "REGISTER_CONFLICT_409" 30 | return &users, <-errorCode 31 | } 32 | 33 | users.Fullname = input.Fullname 34 | users.Email = input.Email 35 | users.Password = input.Password 36 | 37 | addNewUser := db.Debug().Create(&users) 38 | db.Commit() 39 | 40 | if addNewUser.Error != nil { 41 | errorCode <- "REGISTER_FAILED_403" 42 | return &users, <-errorCode 43 | } else { 44 | errorCode <- "nil" 45 | } 46 | 47 | return &users, <-errorCode 48 | } 49 | -------------------------------------------------------------------------------- /controllers/auth-controllers/register/service.go: -------------------------------------------------------------------------------- 1 | package registerAuth 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | ) 6 | 7 | type Service interface { 8 | RegisterService(input *InputRegister) (*model.EntityUsers, string) 9 | } 10 | 11 | type service struct { 12 | repository Repository 13 | } 14 | 15 | func NewServiceRegister(repository Repository) *service { 16 | return &service{repository: repository} 17 | } 18 | 19 | func (s *service) RegisterService(input *InputRegister) (*model.EntityUsers, string) { 20 | 21 | users := model.EntityUsers{ 22 | Fullname: input.Fullname, 23 | Email: input.Email, 24 | Password: input.Password, 25 | } 26 | 27 | resultRegister, errRegister := s.repository.RegisterRepository(&users) 28 | 29 | return resultRegister, errRegister 30 | } 31 | -------------------------------------------------------------------------------- /controllers/auth-controllers/resend/input.go: -------------------------------------------------------------------------------- 1 | package resendAuth 2 | 3 | type InputResend struct { 4 | Email string `json:"email" validate:"required,email"` 5 | } 6 | -------------------------------------------------------------------------------- /controllers/auth-controllers/resend/repository.go: -------------------------------------------------------------------------------- 1 | package resendAuth 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type Repository interface { 9 | ResendRepository(input *model.EntityUsers) (*model.EntityUsers, string) 10 | } 11 | 12 | type repository struct { 13 | db *gorm.DB 14 | } 15 | 16 | func NewRepositoryResend(db *gorm.DB) *repository { 17 | return &repository{db: db} 18 | } 19 | 20 | func (r *repository) ResendRepository(input *model.EntityUsers) (*model.EntityUsers, string) { 21 | 22 | var users model.EntityUsers 23 | db := r.db.Model(&users) 24 | errorCode := make(chan string, 1) 25 | 26 | users.Email = input.Email 27 | 28 | checkUserAccount := db.Debug().Select("*").Where("email = ?", input.Email).Find(&users) 29 | 30 | if checkUserAccount.RowsAffected < 1 { 31 | errorCode <- "RESEND_NOT_FOUD_404" 32 | return &users, <-errorCode 33 | } 34 | 35 | if users.Active { 36 | errorCode <- "RESEND_ACTIVE_403" 37 | return &users, <-errorCode 38 | } else { 39 | errorCode <- "nil" 40 | } 41 | 42 | return &users, <-errorCode 43 | } 44 | -------------------------------------------------------------------------------- /controllers/auth-controllers/resend/service.go: -------------------------------------------------------------------------------- 1 | package resendAuth 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | ) 6 | 7 | type Service interface { 8 | ResendService(input *InputResend) (*model.EntityUsers, string) 9 | } 10 | 11 | type service struct { 12 | repository Repository 13 | } 14 | 15 | func NewServiceResend(repository Repository) *service { 16 | return &service{repository: repository} 17 | } 18 | 19 | func (s *service) ResendService(input *InputResend) (*model.EntityUsers, string) { 20 | 21 | users := model.EntityUsers{ 22 | Email: input.Email, 23 | } 24 | 25 | resultRegister, errRegister := s.repository.ResendRepository(&users) 26 | 27 | return resultRegister, errRegister 28 | } 29 | -------------------------------------------------------------------------------- /controllers/auth-controllers/reset/entity.go: -------------------------------------------------------------------------------- 1 | package resetAuth 2 | 3 | type InputReset struct { 4 | Email string `json:"email" validate:"required,email"` 5 | Password string `json:"password" validate:"required,gte=8"` 6 | Cpassword string `json:"cpassword" validate:"required,gte=8"` 7 | Active bool `json:"active"` 8 | } 9 | -------------------------------------------------------------------------------- /controllers/auth-controllers/reset/repository.go: -------------------------------------------------------------------------------- 1 | package resetAuth 2 | 3 | import ( 4 | "time" 5 | 6 | model "github.com/restuwahyu13/gin-rest-api/models" 7 | util "github.com/restuwahyu13/gin-rest-api/utils" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type Repository interface { 12 | ResetRepository(input *model.EntityUsers) (*model.EntityUsers, string) 13 | } 14 | 15 | type repository struct { 16 | db *gorm.DB 17 | } 18 | 19 | func NewRepositoryReset(db *gorm.DB) *repository { 20 | return &repository{db: db} 21 | } 22 | 23 | func (r *repository) ResetRepository(input *model.EntityUsers) (*model.EntityUsers, string) { 24 | var users model.EntityUsers 25 | db := r.db.Model(&users) 26 | errorCode := make(chan string, 1) 27 | 28 | users.Email = input.Email 29 | users.Password = input.Password 30 | users.Active = input.Active 31 | 32 | checkUserAccount := db.Debug().Select("*").Where("email = ?", input.Email).Find(&users) 33 | 34 | if checkUserAccount.RowsAffected < 1 { 35 | errorCode <- "RESET_NOT_FOUND_404" 36 | return &users, <-errorCode 37 | } 38 | 39 | if !users.Active { 40 | errorCode <- "ACCOUNT_NOT_ACTIVE_403" 41 | return &users, <-errorCode 42 | } 43 | 44 | users.Password = util.HashPassword(input.Password) 45 | users.UpdatedAt = time.Now().Local() 46 | 47 | updateNewPassword := db.Debug().Select("password", "update_at").Where("email = ?", input.Email).Updates(users) 48 | 49 | if updateNewPassword.Error != nil { 50 | errorCode <- "RESET_PASSWORD_FAILED_403" 51 | return &users, <-errorCode 52 | } else { 53 | errorCode <- "nil" 54 | } 55 | 56 | return &users, <-errorCode 57 | } 58 | -------------------------------------------------------------------------------- /controllers/auth-controllers/reset/service.go: -------------------------------------------------------------------------------- 1 | package resetAuth 2 | 3 | import model "github.com/restuwahyu13/gin-rest-api/models" 4 | 5 | type Service interface { 6 | ResetService(input *InputReset) (*model.EntityUsers, string) 7 | } 8 | 9 | type service struct { 10 | repository Repository 11 | } 12 | 13 | func NewServiceReset(repository Repository) *service { 14 | return &service{repository: repository} 15 | } 16 | 17 | func (s *service) ResetService(input *InputReset) (*model.EntityUsers, string) { 18 | 19 | users := model.EntityUsers{ 20 | Email: input.Email, 21 | Password: input.Password, 22 | Active: input.Active, 23 | } 24 | 25 | resetResult, errResult := s.repository.ResetRepository(&users) 26 | 27 | return resetResult, errResult 28 | } 29 | -------------------------------------------------------------------------------- /controllers/student-controllers/create/entity.go: -------------------------------------------------------------------------------- 1 | package createStudent 2 | 3 | type InputCreateStudent struct { 4 | Name string `json:"name" validate:"required,lowercase"` 5 | Npm int `json:"npm" validate:"required,numeric"` 6 | Fak string `json:"fak" validate:"required,lowercase"` 7 | Bid string `json:"bid" validate:"required,lowercase"` 8 | } 9 | -------------------------------------------------------------------------------- /controllers/student-controllers/create/repository.go: -------------------------------------------------------------------------------- 1 | package createStudent 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type Repository interface { 9 | CreateStudentRepository(input *model.EntityStudent) (*model.EntityStudent, string) 10 | } 11 | 12 | type repository struct { 13 | db *gorm.DB 14 | } 15 | 16 | func NewRepositoryCreate(db *gorm.DB) *repository { 17 | return &repository{db: db} 18 | } 19 | 20 | func (r *repository) CreateStudentRepository(input *model.EntityStudent) (*model.EntityStudent, string) { 21 | 22 | var students model.EntityStudent 23 | db := r.db.Model(&students) 24 | errorCode := make(chan string, 1) 25 | 26 | checkStudentExist := db.Debug().Select("*").Where("npm = ?", input.Npm).Find(&students) 27 | 28 | if checkStudentExist.RowsAffected > 0 { 29 | errorCode <- "CREATE_STUDENT_CONFLICT_409" 30 | return &students, <-errorCode 31 | } 32 | 33 | students.Name = input.Name 34 | students.Npm = input.Npm 35 | students.Fak = input.Fak 36 | students.Bid = input.Bid 37 | 38 | addNewStudent := db.Debug().Create(&students) 39 | db.Commit() 40 | 41 | if addNewStudent.Error != nil { 42 | errorCode <- "CREATE_STUDENT_FAILED_403" 43 | return &students, <-errorCode 44 | } else { 45 | errorCode <- "nil" 46 | } 47 | 48 | return &students, <-errorCode 49 | } 50 | -------------------------------------------------------------------------------- /controllers/student-controllers/create/service.go: -------------------------------------------------------------------------------- 1 | package createStudent 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | ) 6 | 7 | type Service interface { 8 | CreateStudentService(input *InputCreateStudent) (*model.EntityStudent, string) 9 | } 10 | 11 | type service struct { 12 | repository Repository 13 | } 14 | 15 | func NewServiceCreate(repository Repository) *service { 16 | return &service{repository: repository} 17 | } 18 | 19 | func (s *service) CreateStudentService(input *InputCreateStudent) (*model.EntityStudent, string) { 20 | 21 | students := model.EntityStudent{ 22 | Name: input.Name, 23 | Npm: input.Npm, 24 | Fak: input.Fak, 25 | Bid: input.Bid, 26 | } 27 | 28 | resultCreateStudent, errCreateStudent := s.repository.CreateStudentRepository(&students) 29 | 30 | return resultCreateStudent, errCreateStudent 31 | } 32 | -------------------------------------------------------------------------------- /controllers/student-controllers/delete/entity.go: -------------------------------------------------------------------------------- 1 | package deleteStudent 2 | 3 | type InputDeleteStudent struct { 4 | ID string `validate:"required,uuid"` 5 | } 6 | -------------------------------------------------------------------------------- /controllers/student-controllers/delete/repository.go: -------------------------------------------------------------------------------- 1 | package deleteStudent 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type Repository interface { 9 | DeleteStudentRepository(input *model.EntityStudent) (*model.EntityStudent, string) 10 | } 11 | 12 | type repository struct { 13 | db *gorm.DB 14 | } 15 | 16 | func NewRepositoryDelete(db *gorm.DB) *repository { 17 | return &repository{db: db} 18 | } 19 | 20 | func (r *repository) DeleteStudentRepository(input *model.EntityStudent) (*model.EntityStudent, string) { 21 | 22 | var students model.EntityStudent 23 | db := r.db.Model(&students) 24 | errorCode := make(chan string, 1) 25 | 26 | checkStudentId := db.Debug().Select("*").Where("id = ?", input.ID).Find(&students) 27 | 28 | if checkStudentId.RowsAffected < 1 { 29 | errorCode <- "DELETE_STUDENT_NOT_FOUND_404" 30 | return &students, <-errorCode 31 | } 32 | 33 | deleteStudentId := db.Debug().Select("*").Where("id = ?", input.ID).Find(&students).Delete(&students) 34 | 35 | if deleteStudentId.Error != nil { 36 | errorCode <- "DELETE_STUDENT_FAILED_403" 37 | return &students, <-errorCode 38 | } else { 39 | errorCode <- "nil" 40 | } 41 | 42 | return &students, <-errorCode 43 | } 44 | -------------------------------------------------------------------------------- /controllers/student-controllers/delete/service.go: -------------------------------------------------------------------------------- 1 | package deleteStudent 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | ) 6 | 7 | type Service interface { 8 | DeleteStudentService(input *InputDeleteStudent) (*model.EntityStudent, string) 9 | } 10 | 11 | type service struct { 12 | repository Repository 13 | } 14 | 15 | func NewServiceDelete(repository Repository) *service { 16 | return &service{repository: repository} 17 | } 18 | 19 | func (s *service) DeleteStudentService(input *InputDeleteStudent) (*model.EntityStudent, string) { 20 | 21 | students := model.EntityStudent{ 22 | ID: input.ID, 23 | } 24 | 25 | resultCreateStudent, errCreateStudent := s.repository.DeleteStudentRepository(&students) 26 | 27 | return resultCreateStudent, errCreateStudent 28 | } 29 | -------------------------------------------------------------------------------- /controllers/student-controllers/result/entity.go: -------------------------------------------------------------------------------- 1 | package resultStudent 2 | 3 | type InputResultStudent struct { 4 | ID string `validate:"required,uuid"` 5 | } 6 | -------------------------------------------------------------------------------- /controllers/student-controllers/result/repository.go: -------------------------------------------------------------------------------- 1 | package resultStudent 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type Repository interface { 9 | ResultStudentRepository(input *model.EntityStudent) (*model.EntityStudent, string) 10 | } 11 | 12 | type repository struct { 13 | db *gorm.DB 14 | } 15 | 16 | func NewRepositoryResult(db *gorm.DB) *repository { 17 | return &repository{db: db} 18 | } 19 | 20 | func (r *repository) ResultStudentRepository(input *model.EntityStudent) (*model.EntityStudent, string) { 21 | 22 | var students model.EntityStudent 23 | db := r.db.Model(&students) 24 | errorCode := make(chan string, 1) 25 | 26 | resultStudents := db.Debug().Select("*").Where("id = ?", input.ID).Find(&students) 27 | 28 | if resultStudents.RowsAffected < 1 { 29 | errorCode <- "RESULT_STUDENT_NOT_FOUND_404" 30 | return &students, <-errorCode 31 | } else { 32 | errorCode <- "nil" 33 | } 34 | 35 | return &students, <-errorCode 36 | } 37 | -------------------------------------------------------------------------------- /controllers/student-controllers/result/service.go: -------------------------------------------------------------------------------- 1 | package resultStudent 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | ) 6 | 7 | type Service interface { 8 | ResultStudentService(input *InputResultStudent) (*model.EntityStudent, string) 9 | } 10 | 11 | type service struct { 12 | repository Repository 13 | } 14 | 15 | func NewServiceResult(repository Repository) *service { 16 | return &service{repository: repository} 17 | } 18 | 19 | func (s *service) ResultStudentService(input *InputResultStudent) (*model.EntityStudent, string) { 20 | 21 | students := model.EntityStudent{ 22 | ID: input.ID, 23 | } 24 | 25 | resultCreateStudent, errCreateStudent := s.repository.ResultStudentRepository(&students) 26 | 27 | return resultCreateStudent, errCreateStudent 28 | } 29 | -------------------------------------------------------------------------------- /controllers/student-controllers/results/repository.go: -------------------------------------------------------------------------------- 1 | package resultsStudent 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type Repository interface { 9 | ResultsStudentRepository() (*[]model.EntityStudent, string) 10 | } 11 | 12 | type repository struct { 13 | db *gorm.DB 14 | } 15 | 16 | func NewRepositoryResults(db *gorm.DB) *repository { 17 | return &repository{db: db} 18 | } 19 | 20 | func (r *repository) ResultsStudentRepository() (*[]model.EntityStudent, string) { 21 | 22 | var students []model.EntityStudent 23 | db := r.db.Model(&students) 24 | errorCode := make(chan string, 1) 25 | 26 | resultsStudents := db.Debug().Select("*").Find(&students) 27 | 28 | if resultsStudents.Error != nil { 29 | errorCode <- "RESULTS_STUDENT_NOT_FOUND_404" 30 | return &students, <-errorCode 31 | } else { 32 | errorCode <- "nil" 33 | } 34 | 35 | return &students, <-errorCode 36 | } 37 | -------------------------------------------------------------------------------- /controllers/student-controllers/results/service.go: -------------------------------------------------------------------------------- 1 | package resultsStudent 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | ) 6 | 7 | type Service interface { 8 | ResultsStudentService() (*[]model.EntityStudent, string) 9 | } 10 | 11 | type service struct { 12 | repository Repository 13 | } 14 | 15 | func NewServiceResults(repository Repository) *service { 16 | return &service{repository: repository} 17 | } 18 | 19 | func (s *service) ResultsStudentService() (*[]model.EntityStudent, string) { 20 | 21 | resultCreateStudent, errCreateStudent := s.repository.ResultsStudentRepository() 22 | 23 | return resultCreateStudent, errCreateStudent 24 | } 25 | -------------------------------------------------------------------------------- /controllers/student-controllers/update/entity.go: -------------------------------------------------------------------------------- 1 | package updateStudent 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type InputUpdateStudent struct { 8 | ID string `validate:"required,uuid"` 9 | Name string `json:"name" validate:"required,lowercase"` 10 | Npm int `json:"npm" validate:"required,number"` 11 | Fak string `json:"fak" validate:"required,lowercase"` 12 | Bid string `json:"bid" validate:"required,lowercase"` 13 | UpdatedAt time.Time 14 | } 15 | -------------------------------------------------------------------------------- /controllers/student-controllers/update/repository.go: -------------------------------------------------------------------------------- 1 | package updateStudent 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type Repository interface { 9 | UpdateStudentRepository(input *model.EntityStudent) (*model.EntityStudent, string) 10 | } 11 | 12 | type repository struct { 13 | db *gorm.DB 14 | } 15 | 16 | func NewRepositoryUpdate(db *gorm.DB) *repository { 17 | return &repository{db: db} 18 | } 19 | 20 | func (r *repository) UpdateStudentRepository(input *model.EntityStudent) (*model.EntityStudent, string) { 21 | 22 | var students model.EntityStudent 23 | db := r.db.Model(&students) 24 | errorCode := make(chan string, 1) 25 | 26 | students.ID = input.ID 27 | 28 | checkStudentId := db.Debug().Select("*").Where("id = ?", input.ID).Find(&students) 29 | 30 | if checkStudentId.RowsAffected < 1 { 31 | errorCode <- "UPDATE_STUDENT_NOT_FOUND_404" 32 | return &students, <-errorCode 33 | } 34 | 35 | students.Name = input.Name 36 | students.Npm = input.Npm 37 | students.Fak = input.Fak 38 | students.Bid = input.Bid 39 | 40 | updateStudent := db.Debug().Select("name", "npm", "fak", "bid", "updated_at").Where("id = ?", input.ID).Updates(students) 41 | 42 | if updateStudent.Error != nil { 43 | errorCode <- "UPDATE_STUDENT_FAILED_403" 44 | return &students, <-errorCode 45 | } else { 46 | errorCode <- "nil" 47 | } 48 | 49 | return &students, <-errorCode 50 | } 51 | -------------------------------------------------------------------------------- /controllers/student-controllers/update/service.go: -------------------------------------------------------------------------------- 1 | package updateStudent 2 | 3 | import ( 4 | model "github.com/restuwahyu13/gin-rest-api/models" 5 | ) 6 | 7 | type Service interface { 8 | UpdateStudentService(input *InputUpdateStudent) (*model.EntityStudent, string) 9 | } 10 | 11 | type service struct { 12 | repository Repository 13 | } 14 | 15 | func NewServiceUpdate(repository Repository) *service { 16 | return &service{repository: repository} 17 | } 18 | 19 | func (s *service) UpdateStudentService(input *InputUpdateStudent) (*model.EntityStudent, string) { 20 | 21 | students := model.EntityStudent{ 22 | ID: input.ID, 23 | Name: input.Name, 24 | Npm: input.Npm, 25 | Fak: input.Fak, 26 | Bid: input.Bid, 27 | } 28 | 29 | resultUpdateStudent, errUpdateStudent := s.repository.UpdateStudentRepository(&students) 30 | 31 | return resultUpdateStudent, errUpdateStudent 32 | } 33 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | ### =================== 4 | ### APP SERVICE 5 | ### =================== 6 | app: 7 | container_name: app 8 | restart: always 9 | build: . 10 | healthcheck: 11 | interval: 120ms 12 | retries: 5 13 | start_period: 60ms 14 | test: | 15 | touch /tmp/healthcheck.txt && 16 | mkdir /tmp/healthcheck && 17 | mv /tmp/healthcheck.txt /tmp/healthcheck && 18 | cd /tmp/healthcheck && 19 | echo "hello my docker container is working fine" >> healthcheck.txt && 20 | cat healthcheck.txt && 21 | rm healthcheck.txt && 22 | cd .. && 23 | rmdir /tmp/healthcheck 24 | cls 25 | timeout: 60ms 26 | env_file: 27 | - .env 28 | ports: 29 | - 4000:4000 30 | networks: 31 | - restapi_network 32 | depends_on: 33 | - db 34 | ### =================== 35 | ### DB SERVICE 36 | ### =================== 37 | db: 38 | image: postgres:12-alpine 39 | restart: always 40 | healthcheck: 41 | interval: 120ms 42 | retries: 5 43 | start_period: 60ms 44 | test: | 45 | touch /tmp/healthcheck.txt && 46 | mkdir /tmp/healthcheck && 47 | mv /tmp/healthcheck.txt /tmp/healthcheck && 48 | cd /tmp/healthcheck && 49 | echo "hello my docker container is working fine" >> healthcheck.txt && 50 | cat healthcheck.txt && 51 | rm healthcheck.txt && 52 | cd .. && 53 | rmdir /tmp/healthcheck 54 | cls 55 | timeout: 60ms 56 | environment: 57 | POSTGRES_DB: campus 58 | POSTGRES_USER: restuwahyu13 59 | POSTGRES_PASSWORD: restuwahyu13 60 | ports: 61 | - 5430:5432 62 | volumes: 63 | - pg-data:/var/lib/postgresql/data 64 | networks: 65 | - restapi_network 66 | ### =================== 67 | ### ADMINER SERVICE 68 | ### =================== 69 | panel: 70 | image: adminer:latest 71 | restart: always 72 | healthcheck: 73 | interval: 120ms 74 | retries: 5 75 | start_period: 60ms 76 | test: | 77 | touch /tmp/healthcheck.txt && 78 | mkdir /tmp/healthcheck && 79 | mv /tmp/healthcheck.txt /tmp/healthcheck && 80 | cd /tmp/healthcheck && 81 | echo "hello my docker container is working fine" >> healthcheck.txt && 82 | cat healthcheck.txt && 83 | rm healthcheck.txt && 84 | cd .. && 85 | rmdir /tmp/healthcheck 86 | cls 87 | timeout: 60ms 88 | ports: 89 | - 8080:8080 90 | networks: 91 | - restapi_network 92 | ### =================== 93 | ### SWAGGER SERVICE 94 | ### =================== 95 | doc: 96 | build: 97 | context: . 98 | dockerfile: swagger/Dockerfile 99 | restart: always 100 | healthcheck: 101 | interval: 120ms 102 | retries: 5 103 | start_period: 60ms 104 | test: | 105 | touch /tmp/healthcheck.txt && 106 | mkdir /tmp/healthcheck && 107 | mv /tmp/healthcheck.txt /tmp/healthcheck && 108 | cd /tmp/healthcheck && 109 | echo "hello my docker container is working fine" >> healthcheck.txt && 110 | cat healthcheck.txt && 111 | rm healthcheck.txt && 112 | cd .. && 113 | rmdir /tmp/healthcheck 114 | cls 115 | timeout: 60ms 116 | environment: 117 | - URL=http://localhost/openapi.yml 118 | - SWAGGER_JSON=swagger/openapi.yml 119 | ports: 120 | - 80:8080 121 | networks: 122 | - restapi_network 123 | ### ======================== 124 | ### VOLUMES PERSISTENT DATA 125 | ### ======================== 126 | volumes: 127 | pg-data: 128 | ### =================================== 129 | ### NETWORKS GROUP FOR ONE SAME SERVICE 130 | ### =================================== 131 | networks: 132 | restapi_network: 133 | -------------------------------------------------------------------------------- /gin-api.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "2ebaceb0-a789-4b28-84a9-8a1e96ebf980", 4 | "name": "gin-api", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "auth", 10 | "item": [ 11 | { 12 | "name": "register", 13 | "request": { 14 | "method": "POST", 15 | "header": [], 16 | "body": { 17 | "mode": "raw", 18 | "raw": "{\n \"fullname\": \"ujang bahri\",\n \"email\": \"ujang13@zetmail.com\",\n \"password\": \"ujang13\"\n}", 19 | "options": { 20 | "raw": { 21 | "language": "json" 22 | } 23 | } 24 | }, 25 | "url": { 26 | "raw": "http://localhost:3001/api/v1/register", 27 | "protocol": "http", 28 | "host": [ 29 | "localhost" 30 | ], 31 | "port": "3001", 32 | "path": [ 33 | "api", 34 | "v1", 35 | "register" 36 | ] 37 | } 38 | }, 39 | "response": [] 40 | }, 41 | { 42 | "name": "login", 43 | "request": { 44 | "method": "POST", 45 | "header": [], 46 | "body": { 47 | "mode": "raw", 48 | "raw": "{\n \"email\": \"aldikhan13@zetmail.com\",\n \"password\": \"aldikhan13\"\n}", 49 | "options": { 50 | "raw": { 51 | "language": "json" 52 | } 53 | } 54 | }, 55 | "url": { 56 | "raw": "http://localhost:3001/api/v1/login", 57 | "protocol": "http", 58 | "host": [ 59 | "localhost" 60 | ], 61 | "port": "3001", 62 | "path": [ 63 | "api", 64 | "v1", 65 | "login" 66 | ] 67 | } 68 | }, 69 | "response": [] 70 | }, 71 | { 72 | "name": "activation", 73 | "request": { 74 | "method": "POST", 75 | "header": [], 76 | "url": { 77 | "raw": "http://localhost:3001/api/v1/activation/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uIjp0cnVlLCJlbWFpbCI6InJlc3R1d2FoeXUxM0B6ZXRtYWlsLmNvbSIsImV4cGlyZWRBdCI6MTYxOTIwMzQwOCwiaWQiOjE2fQ.b8i8VkNyI4ufQ2yF_VjaJSBVPVUi8B6YKZ_qLNuwa3k", 78 | "protocol": "http", 79 | "host": [ 80 | "localhost" 81 | ], 82 | "port": "3001", 83 | "path": [ 84 | "api", 85 | "v1", 86 | "activation", 87 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uIjp0cnVlLCJlbWFpbCI6InJlc3R1d2FoeXUxM0B6ZXRtYWlsLmNvbSIsImV4cGlyZWRBdCI6MTYxOTIwMzQwOCwiaWQiOjE2fQ.b8i8VkNyI4ufQ2yF_VjaJSBVPVUi8B6YKZ_qLNuwa3k" 88 | ] 89 | } 90 | }, 91 | "response": [] 92 | }, 93 | { 94 | "name": "reset", 95 | "request": { 96 | "method": "POST", 97 | "header": [], 98 | "body": { 99 | "mode": "raw", 100 | "raw": "", 101 | "options": { 102 | "raw": { 103 | "language": "json" 104 | } 105 | } 106 | }, 107 | "url": { 108 | "raw": "http://localhost:3001/api/v1/reset-password/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uIjp0cnVlLCJlbWFpbCI6InJlc3R1d2FoeXUxM0B6ZXRtYWlsLmNvbSIsImV4cGlyZWRBdCI6MTYxOTIwMzQwOCwiaWQiOjE2fQ.b8i8VkNyI4ufQ2yF_VjaJSBVPVUi8B6YKZ_qLNuwa3k", 109 | "protocol": "http", 110 | "host": [ 111 | "localhost" 112 | ], 113 | "port": "3001", 114 | "path": [ 115 | "api", 116 | "v1", 117 | "reset-password", 118 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uIjp0cnVlLCJlbWFpbCI6InJlc3R1d2FoeXUxM0B6ZXRtYWlsLmNvbSIsImV4cGlyZWRBdCI6MTYxOTIwMzQwOCwiaWQiOjE2fQ.b8i8VkNyI4ufQ2yF_VjaJSBVPVUi8B6YKZ_qLNuwa3k" 119 | ] 120 | } 121 | }, 122 | "response": [] 123 | }, 124 | { 125 | "name": "resend", 126 | "request": { 127 | "method": "POST", 128 | "header": [], 129 | "body": { 130 | "mode": "raw", 131 | "raw": "{\n \"email\": \"restuwahyu13@zetmail.com\"\n}", 132 | "options": { 133 | "raw": { 134 | "language": "json" 135 | } 136 | } 137 | }, 138 | "url": { 139 | "raw": "http://localhost:3001/api/v1/resend-token", 140 | "protocol": "http", 141 | "host": [ 142 | "localhost" 143 | ], 144 | "port": "3001", 145 | "path": [ 146 | "api", 147 | "v1", 148 | "resend-token" 149 | ] 150 | } 151 | }, 152 | "response": [] 153 | }, 154 | { 155 | "name": "forgot", 156 | "request": { 157 | "method": "POST", 158 | "header": [], 159 | "body": { 160 | "mode": "raw", 161 | "raw": "{\n \"email\": \"restuwahyu13@zetmail.com\"\n}", 162 | "options": { 163 | "raw": { 164 | "language": "json" 165 | } 166 | } 167 | }, 168 | "url": { 169 | "raw": "http://localhost:3001/api/v1/reset-password", 170 | "protocol": "http", 171 | "host": [ 172 | "localhost" 173 | ], 174 | "port": "3001", 175 | "path": [ 176 | "api", 177 | "v1", 178 | "reset-password" 179 | ] 180 | } 181 | }, 182 | "response": [] 183 | } 184 | ] 185 | }, 186 | { 187 | "name": "student", 188 | "item": [ 189 | { 190 | "name": "create", 191 | "request": { 192 | "method": "POST", 193 | "header": [ 194 | { 195 | "key": "Authorization", 196 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uIjp0cnVlLCJlbWFpbCI6ImFsZGlraGFuMTNAemV0bWFpbC5jb20iLCJleHBpcmVkQXQiOjE2MTkyNzkyMDksImlkIjoxMX0.jKxrGfrkNrINdixekYGuDKen24LMGgLCnrXLD3R7Yw8", 197 | "type": "text" 198 | } 199 | ], 200 | "body": { 201 | "mode": "raw", 202 | "raw": "{\n \"name\": \"bagus budiawan\",\n \"npm\": 201543502292,\n \"bid\": \"tehnik informatika\",\n \"fak\": \"mipa\"\n}", 203 | "options": { 204 | "raw": { 205 | "language": "json" 206 | } 207 | } 208 | }, 209 | "url": { 210 | "raw": "http://localhost:3001/api/v1/student", 211 | "protocol": "http", 212 | "host": [ 213 | "localhost" 214 | ], 215 | "port": "3001", 216 | "path": [ 217 | "api", 218 | "v1", 219 | "student" 220 | ] 221 | } 222 | }, 223 | "response": [] 224 | }, 225 | { 226 | "name": "results", 227 | "request": { 228 | "method": "GET", 229 | "header": [ 230 | { 231 | "key": "Authorization", 232 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uIjp0cnVlLCJlbWFpbCI6ImFsZGlraGFuMTNAemV0bWFpbC5jb20iLCJleHBpcmVkQXQiOjE2MTkyNzkyMDksImlkIjoxMX0.jKxrGfrkNrINdixekYGuDKen24LMGgLCnrXLD3R7Yw8", 233 | "type": "text" 234 | } 235 | ], 236 | "url": { 237 | "raw": "http://localhost:3001/api/v1/student", 238 | "protocol": "http", 239 | "host": [ 240 | "localhost" 241 | ], 242 | "port": "3001", 243 | "path": [ 244 | "api", 245 | "v1", 246 | "student" 247 | ], 248 | "query": [ 249 | { 250 | "key": "", 251 | "value": "", 252 | "disabled": true 253 | } 254 | ] 255 | } 256 | }, 257 | "response": [] 258 | }, 259 | { 260 | "name": "result", 261 | "request": { 262 | "method": "GET", 263 | "header": [ 264 | { 265 | "key": "Authorization", 266 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uIjp0cnVlLCJlbWFpbCI6ImFsZGlraGFuMTNAemV0bWFpbC5jb20iLCJleHBpcmVkQXQiOjE2MTkyNzkyMDksImlkIjoxMX0.jKxrGfrkNrINdixekYGuDKen24LMGgLCnrXLD3R7Yw8", 267 | "type": "text" 268 | } 269 | ], 270 | "url": { 271 | "raw": "http://localhost:3001/api/v1/student/3", 272 | "protocol": "http", 273 | "host": [ 274 | "localhost" 275 | ], 276 | "port": "3001", 277 | "path": [ 278 | "api", 279 | "v1", 280 | "student", 281 | "3" 282 | ] 283 | } 284 | }, 285 | "response": [] 286 | }, 287 | { 288 | "name": "delete", 289 | "request": { 290 | "method": "DELETE", 291 | "header": [ 292 | { 293 | "key": "Authorization", 294 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uIjp0cnVlLCJlbWFpbCI6ImFsZGlraGFuMTNAemV0bWFpbC5jb20iLCJleHBpcmVkQXQiOjE2MTkyNzkyMDksImlkIjoxMX0.jKxrGfrkNrINdixekYGuDKen24LMGgLCnrXLD3R7Yw8", 295 | "type": "text" 296 | } 297 | ], 298 | "url": { 299 | "raw": "http://localhost:3001/api/v1/student/6", 300 | "protocol": "http", 301 | "host": [ 302 | "localhost" 303 | ], 304 | "port": "3001", 305 | "path": [ 306 | "api", 307 | "v1", 308 | "student", 309 | "6" 310 | ] 311 | } 312 | }, 313 | "response": [] 314 | }, 315 | { 316 | "name": "update", 317 | "request": { 318 | "method": "PUT", 319 | "header": [ 320 | { 321 | "key": "Authorization", 322 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uIjp0cnVlLCJlbWFpbCI6ImFsZGlraGFuMTNAemV0bWFpbC5jb20iLCJleHBpcmVkQXQiOjE2MTkyNzkyMDksImlkIjoxMX0.jKxrGfrkNrINdixekYGuDKen24LMGgLCnrXLD3R7Yw8", 323 | "type": "text" 324 | } 325 | ], 326 | "body": { 327 | "mode": "raw", 328 | "raw": "{\n \"name\": \"bagus budiawan\",\n \"npm\": 201543502292,\n \"bid\": \"tehnik informatika\",\n \"fak\": \"mipa\"\n}", 329 | "options": { 330 | "raw": { 331 | "language": "json" 332 | } 333 | } 334 | }, 335 | "url": { 336 | "raw": "http://localhost:3001/api/v1/student/51", 337 | "protocol": "http", 338 | "host": [ 339 | "localhost" 340 | ], 341 | "port": "3001", 342 | "path": [ 343 | "api", 344 | "v1", 345 | "student", 346 | "51" 347 | ] 348 | } 349 | }, 350 | "response": [] 351 | } 352 | ] 353 | } 354 | ] 355 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/restuwahyu13/gin-rest-api 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 8 | github.com/gin-contrib/cors v1.3.1 9 | github.com/gin-contrib/gzip v0.0.3 10 | github.com/gin-gonic/gin v1.7.1 11 | github.com/go-playground/assert/v2 v2.0.1 // indirect 12 | github.com/go-playground/validator/v10 v10.5.0 13 | github.com/gofrs/uuid v4.0.0+incompatible // indirect 14 | github.com/golang/protobuf v1.5.2 // indirect 15 | github.com/google/uuid v1.2.0 16 | github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79 // indirect 17 | github.com/jackc/pgproto3/v2 v2.0.7 // indirect 18 | github.com/jackc/pgx/v4 v4.11.0 // indirect 19 | github.com/joho/godotenv v1.3.0 20 | github.com/json-iterator/go v1.1.10 // indirect 21 | github.com/lib/pq v1.6.0 // indirect 22 | github.com/restuwahyu13/go-playground-converter v0.0.5 23 | github.com/restuwahyu13/go-supertest v0.0.3 24 | github.com/sendgrid/rest v2.6.3+incompatible 25 | github.com/sendgrid/sendgrid-go v3.8.0+incompatible 26 | github.com/sirupsen/logrus v1.8.1 27 | github.com/smartystreets/assertions v1.2.0 // indirect 28 | github.com/smartystreets/goconvey v1.6.4 // indirect 29 | github.com/ugorji/go v1.2.5 // indirect 30 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b 31 | golang.org/x/mod v0.4.2 // indirect 32 | golang.org/x/net v0.0.0-20210414194228-064579744ee0 // indirect 33 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 34 | gopkg.in/yaml.v2 v2.4.0 // indirect 35 | gorm.io/driver/postgres v1.0.8 36 | gorm.io/gorm v1.21.7 37 | syreclabs.com/go/faker v1.2.3 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /handlers/auth-handlers/activation/activation.go: -------------------------------------------------------------------------------- 1 | package handlerActivation 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | activationAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/activation" 8 | util "github.com/restuwahyu13/gin-rest-api/utils" 9 | gpc "github.com/restuwahyu13/go-playground-converter" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type handler struct { 14 | service activationAuth.Service 15 | } 16 | 17 | func NewHandlerActivation(service activationAuth.Service) *handler { 18 | return &handler{service: service} 19 | } 20 | 21 | func (h *handler) ActivationHandler(ctx *gin.Context) { 22 | 23 | var input activationAuth.InputActivation 24 | 25 | config := gpc.ErrorConfig{ 26 | Options: []gpc.ErrorMetaConfig{ 27 | gpc.ErrorMetaConfig{ 28 | Tag: "required", 29 | Field: "Email", 30 | Message: "email is required on body", 31 | }, 32 | gpc.ErrorMetaConfig{ 33 | Tag: "email", 34 | Field: "Email", 35 | Message: "email format is not valid", 36 | }, 37 | gpc.ErrorMetaConfig{ 38 | Tag: "required", 39 | Field: "Token", 40 | Message: "accessToken is required on params", 41 | }, 42 | }, 43 | } 44 | 45 | errResponse, errCount := util.GoValidator(input, config.Options) 46 | 47 | if errCount > 0 { 48 | util.ValidatorErrorResponse(ctx, http.StatusBadRequest, http.MethodPost, errResponse) 49 | return 50 | } 51 | 52 | token := ctx.Param("token") 53 | resultToken, errToken := util.VerifyToken(token, "JWT_SECRET") 54 | 55 | if errToken != nil { 56 | defer logrus.Error(errToken.Error()) 57 | util.APIResponse(ctx, "Verified activation token failed", http.StatusBadRequest, http.MethodPost, nil) 58 | return 59 | } 60 | 61 | result := util.DecodeToken(resultToken) 62 | input.Email = result.Claims.Email 63 | input.Active = true 64 | 65 | _, errActivation := h.service.ActivationService(&input) 66 | 67 | switch errActivation { 68 | 69 | case "ACTIVATION_NOT_FOUND_404": 70 | util.APIResponse(ctx, "User account is not exist", http.StatusNotFound, http.MethodPost, nil) 71 | return 72 | 73 | case "ACTIVATION_ACTIVE_400": 74 | util.APIResponse(ctx, "User account hash been active please login", http.StatusBadRequest, http.MethodPost, nil) 75 | return 76 | 77 | case "ACTIVATION_ACCOUNT_FAILED_403": 78 | util.APIResponse(ctx, "Activation account failed", http.StatusForbidden, http.MethodPost, nil) 79 | return 80 | 81 | default: 82 | util.APIResponse(ctx, "Activation account success", http.StatusOK, http.MethodPost, nil) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /handlers/auth-handlers/forgot/forgot.go: -------------------------------------------------------------------------------- 1 | package handlerForgot 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | forgotAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/forgot" 8 | util "github.com/restuwahyu13/gin-rest-api/utils" 9 | gpc "github.com/restuwahyu13/go-playground-converter" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type handler struct { 14 | service forgotAuth.Service 15 | } 16 | 17 | func NewHandlerForgot(service forgotAuth.Service) *handler { 18 | return &handler{service: service} 19 | } 20 | 21 | func (h *handler) ForgotHandler(ctx *gin.Context) { 22 | 23 | var input forgotAuth.InputForgot 24 | ctx.ShouldBindJSON(&input) 25 | 26 | config := gpc.ErrorConfig{ 27 | Options: []gpc.ErrorMetaConfig{ 28 | gpc.ErrorMetaConfig{ 29 | Tag: "required", 30 | Field: "Email", 31 | Message: "email is required on body", 32 | }, 33 | gpc.ErrorMetaConfig{ 34 | Tag: "email", 35 | Field: "Email", 36 | Message: "email format is not valid", 37 | }, 38 | }, 39 | } 40 | 41 | errResponse, errCount := util.GoValidator(input, config.Options) 42 | 43 | if errCount > 0 { 44 | util.ValidatorErrorResponse(ctx, http.StatusBadRequest, http.MethodPost, errResponse) 45 | return 46 | } 47 | 48 | forgotResult, errForgot := h.service.ForgotService(&input) 49 | 50 | switch errForgot { 51 | 52 | case "FORGOT_NOT_FOUD_404": 53 | util.APIResponse(ctx, "Email is not never registered", http.StatusNotFound, http.MethodPost, nil) 54 | return 55 | 56 | case "FORGOT_NOT_ACTIVE_403": 57 | util.APIResponse(ctx, "User account is not active", http.StatusForbidden, http.MethodPost, nil) 58 | return 59 | 60 | case "FORGOT_PASSWORD_FAILED_403": 61 | util.APIResponse(ctx, "Forgot password failed", http.StatusForbidden, http.MethodPost, nil) 62 | return 63 | 64 | default: 65 | accessTokenData := map[string]interface{}{"id": forgotResult.ID, "email": forgotResult.Email} 66 | accessToken, errToken := util.Sign(accessTokenData, "JWT_SECRET", 5) 67 | 68 | if errToken != nil { 69 | defer logrus.Error(errToken.Error()) 70 | util.APIResponse(ctx, "Generate accessToken failed", http.StatusBadRequest, http.MethodPost, nil) 71 | return 72 | } 73 | 74 | _, errorEmail := util.SendGridMail(forgotResult.Fullname, forgotResult.Email, "Reset Password", "template_reset", accessToken) 75 | 76 | if errorEmail != nil { 77 | util.APIResponse(ctx, "Sending email reset password failed", http.StatusBadRequest, http.MethodPost, nil) 78 | return 79 | } 80 | 81 | util.APIResponse(ctx, "Forgot password successfully", http.StatusOK, http.MethodPost, nil) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /handlers/auth-handlers/login/login.go: -------------------------------------------------------------------------------- 1 | package handlerLogin 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | loginAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/login" 8 | util "github.com/restuwahyu13/gin-rest-api/utils" 9 | gpc "github.com/restuwahyu13/go-playground-converter" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type handler struct { 14 | service loginAuth.Service 15 | } 16 | 17 | func NewHandlerLogin(service loginAuth.Service) *handler { 18 | return &handler{service: service} 19 | } 20 | 21 | func (h *handler) LoginHandler(ctx *gin.Context) { 22 | 23 | var input loginAuth.InputLogin 24 | ctx.ShouldBindJSON(&input) 25 | 26 | config := gpc.ErrorConfig{ 27 | Options: []gpc.ErrorMetaConfig{ 28 | gpc.ErrorMetaConfig{ 29 | Tag: "required", 30 | Field: "Email", 31 | Message: "email is required on body", 32 | }, 33 | gpc.ErrorMetaConfig{ 34 | Tag: "email", 35 | Field: "Email", 36 | Message: "email format is not valid", 37 | }, 38 | gpc.ErrorMetaConfig{ 39 | Tag: "required", 40 | Field: "Password", 41 | Message: "password is required on body", 42 | }, 43 | }, 44 | } 45 | 46 | errResponse, errCount := util.GoValidator(&input, config.Options) 47 | 48 | if errCount > 0 { 49 | util.ValidatorErrorResponse(ctx, http.StatusBadRequest, http.MethodPost, errResponse) 50 | return 51 | } 52 | 53 | resultLogin, errLogin := h.service.LoginService(&input) 54 | 55 | switch errLogin { 56 | 57 | case "LOGIN_NOT_FOUND_404": 58 | util.APIResponse(ctx, "User account is not registered", http.StatusNotFound, http.MethodPost, nil) 59 | return 60 | 61 | case "LOGIN_NOT_ACTIVE_403": 62 | util.APIResponse(ctx, "User account is not active", http.StatusForbidden, http.MethodPost, nil) 63 | return 64 | 65 | case "LOGIN_WRONG_PASSWORD_403": 66 | util.APIResponse(ctx, "Username or password is wrong", http.StatusForbidden, http.MethodPost, nil) 67 | return 68 | 69 | default: 70 | accessTokenData := map[string]interface{}{"id": resultLogin.ID, "email": resultLogin.Email} 71 | accessToken, errToken := util.Sign(accessTokenData, "JWT_SECRET", 24*60*1) 72 | 73 | if errToken != nil { 74 | defer logrus.Error(errToken.Error()) 75 | util.APIResponse(ctx, "Generate accessToken failed", http.StatusBadRequest, http.MethodPost, nil) 76 | return 77 | } 78 | 79 | util.APIResponse(ctx, "Login successfully", http.StatusOK, http.MethodPost, map[string]string{"accessToken": accessToken}) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /handlers/auth-handlers/register/register.go: -------------------------------------------------------------------------------- 1 | package handlerRegister 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | registerAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/register" 8 | util "github.com/restuwahyu13/gin-rest-api/utils" 9 | gpc "github.com/restuwahyu13/go-playground-converter" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type handler struct { 14 | service registerAuth.Service 15 | } 16 | 17 | func NewHandlerRegister(service registerAuth.Service) *handler { 18 | return &handler{service: service} 19 | } 20 | 21 | func (h *handler) RegisterHandler(ctx *gin.Context) { 22 | 23 | var input registerAuth.InputRegister 24 | ctx.ShouldBindJSON(&input) 25 | 26 | config := gpc.ErrorConfig{ 27 | Options: []gpc.ErrorMetaConfig{ 28 | gpc.ErrorMetaConfig{ 29 | Tag: "required", 30 | Field: "Fullname", 31 | Message: "fullname is required on body", 32 | }, 33 | gpc.ErrorMetaConfig{ 34 | Tag: "lowercase", 35 | Field: "Fullname", 36 | Message: "fullname must be using lowercase", 37 | }, 38 | gpc.ErrorMetaConfig{ 39 | Tag: "required", 40 | Field: "Email", 41 | Message: "email is required on body", 42 | }, 43 | gpc.ErrorMetaConfig{ 44 | Tag: "email", 45 | Field: "Email", 46 | Message: "email format is not valid", 47 | }, 48 | gpc.ErrorMetaConfig{ 49 | Tag: "required", 50 | Field: "Password", 51 | Message: "password is required on body", 52 | }, 53 | gpc.ErrorMetaConfig{ 54 | Tag: "gte", 55 | Field: "Password", 56 | Message: "password minimum must be 8 character", 57 | }, 58 | }, 59 | } 60 | 61 | errResponse, errCount := util.GoValidator(input, config.Options) 62 | 63 | if errCount > 0 { 64 | util.ValidatorErrorResponse(ctx, http.StatusBadRequest, http.MethodPost, errResponse) 65 | return 66 | } 67 | 68 | resultRegister, errRegister := h.service.RegisterService(&input) 69 | 70 | switch errRegister { 71 | 72 | case "REGISTER_CONFLICT_409": 73 | util.APIResponse(ctx, "Email already exist", http.StatusConflict, http.MethodPost, nil) 74 | return 75 | 76 | case "REGISTER_FAILED_403": 77 | util.APIResponse(ctx, "Register new account failed", http.StatusForbidden, http.MethodPost, nil) 78 | return 79 | 80 | default: 81 | accessTokenData := map[string]interface{}{"id": resultRegister.ID, "email": resultRegister.Email} 82 | accessToken, errToken := util.Sign(accessTokenData, util.GodotEnv("JWT_SECRET"), 60) 83 | 84 | if errToken != nil { 85 | defer logrus.Error(errToken.Error()) 86 | util.APIResponse(ctx, "Generate accessToken failed", http.StatusBadRequest, http.MethodPost, nil) 87 | return 88 | } 89 | 90 | _, errSendMail := util.SendGridMail(resultRegister.Fullname, resultRegister.Email, "Activation Account", "template_register", accessToken) 91 | 92 | if errSendMail != nil { 93 | defer logrus.Error(errSendMail.Error()) 94 | util.APIResponse(ctx, "Sending email activation failed", http.StatusBadRequest, http.MethodPost, nil) 95 | return 96 | } 97 | 98 | util.APIResponse(ctx, "Register new account successfully", http.StatusCreated, http.MethodPost, nil) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /handlers/auth-handlers/resend/resend.go: -------------------------------------------------------------------------------- 1 | package handlerResend 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | resendAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/resend" 8 | util "github.com/restuwahyu13/gin-rest-api/utils" 9 | gpc "github.com/restuwahyu13/go-playground-converter" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type handler struct { 14 | service resendAuth.Service 15 | } 16 | 17 | func NewHandlerResend(service resendAuth.Service) *handler { 18 | return &handler{service: service} 19 | } 20 | 21 | func (h *handler) ResendHandler(ctx *gin.Context) { 22 | 23 | var input resendAuth.InputResend 24 | ctx.ShouldBindJSON(&input) 25 | 26 | config := gpc.ErrorConfig{ 27 | Options: []gpc.ErrorMetaConfig{ 28 | gpc.ErrorMetaConfig{ 29 | Tag: "required", 30 | Field: "Email", 31 | Message: "email is required on body", 32 | }, 33 | gpc.ErrorMetaConfig{ 34 | Tag: "email", 35 | Field: "Email", 36 | Message: "email format is not valid", 37 | }, 38 | }, 39 | } 40 | 41 | errResponse, errCount := util.GoValidator(input, config.Options) 42 | 43 | if errCount > 0 { 44 | util.ValidatorErrorResponse(ctx, http.StatusBadRequest, http.MethodPost, errResponse) 45 | return 46 | } 47 | 48 | resendResult, errResend := h.service.ResendService(&input) 49 | 50 | switch errResend { 51 | 52 | case "RESEND_NOT_FOUD_404": 53 | util.APIResponse(ctx, "Email is not never registered", http.StatusNotFound, http.MethodPost, nil) 54 | return 55 | 56 | case "RESEND_ACTIVE_403": 57 | util.APIResponse(ctx, "User account hash been active", http.StatusForbidden, http.MethodPost, nil) 58 | return 59 | 60 | default: 61 | accessTokenData := map[string]interface{}{"id": resendResult.ID, "email": resendResult.Email} 62 | accessToken, errToken := util.Sign(accessTokenData, "JWT_SECRET", 5) 63 | 64 | if errToken != nil { 65 | defer logrus.Error(errToken.Error()) 66 | util.APIResponse(ctx, "Generate accessToken failed", http.StatusBadRequest, http.MethodPost, nil) 67 | return 68 | } 69 | 70 | _, errorSendEmail := util.SendGridMail(resendResult.Fullname, resendResult.Email, "Resend New Activation", "template_resend", accessToken) 71 | 72 | if errorSendEmail != nil { 73 | defer logrus.Error(errorSendEmail.Error()) 74 | util.APIResponse(ctx, "Sending email resend activation failed", http.StatusBadRequest, http.MethodPost, nil) 75 | return 76 | } 77 | 78 | util.APIResponse(ctx, "Resend new activation token successfully", http.StatusOK, http.MethodPost, nil) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /handlers/auth-handlers/reset/reset.go: -------------------------------------------------------------------------------- 1 | package handlerReset 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | resetAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/reset" 8 | util "github.com/restuwahyu13/gin-rest-api/utils" 9 | gpc "github.com/restuwahyu13/go-playground-converter" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type handler struct { 14 | service resetAuth.Service 15 | } 16 | 17 | func NewHandlerReset(service resetAuth.Service) *handler { 18 | return &handler{service: service} 19 | } 20 | 21 | func (h *handler) ResetHandler(ctx *gin.Context) { 22 | 23 | var input resetAuth.InputReset 24 | ctx.ShouldBindJSON(&input) 25 | 26 | config := gpc.ErrorConfig{ 27 | Options: []gpc.ErrorMetaConfig{ 28 | gpc.ErrorMetaConfig{ 29 | Tag: "required", 30 | Field: "Email", 31 | Message: "email is required on body", 32 | }, 33 | gpc.ErrorMetaConfig{ 34 | Tag: "email", 35 | Field: "Email", 36 | Message: "email format is not valid", 37 | }, 38 | gpc.ErrorMetaConfig{ 39 | Tag: "required", 40 | Field: "Password", 41 | Message: "password is required on body", 42 | }, 43 | gpc.ErrorMetaConfig{ 44 | Tag: "gte", 45 | Field: "Password", 46 | Message: "password minimum must be 8 character", 47 | }, 48 | gpc.ErrorMetaConfig{ 49 | Tag: "required", 50 | Field: "Cpassword", 51 | Message: "cpassword is required on body", 52 | }, 53 | gpc.ErrorMetaConfig{ 54 | Tag: "gte", 55 | Field: "Cpassword", 56 | Message: "cpassword minimum must be 8 character", 57 | }, 58 | }, 59 | } 60 | 61 | errResponse, errCount := util.GoValidator(input, config.Options) 62 | 63 | if errCount > 0 { 64 | util.ValidatorErrorResponse(ctx, http.StatusBadRequest, http.MethodPost, errResponse) 65 | return 66 | } 67 | 68 | token := ctx.Param("token") 69 | resultToken, errToken := util.VerifyToken(token, "JWT_SECRET") 70 | 71 | if errToken != nil { 72 | defer logrus.Error(errToken.Error()) 73 | util.APIResponse(ctx, "Verified activation token failed", http.StatusBadRequest, http.MethodPost, nil) 74 | return 75 | } 76 | 77 | if input.Cpassword != input.Password { 78 | util.APIResponse(ctx, "Confirm Password is not match with Password", http.StatusBadRequest, http.MethodPost, nil) 79 | return 80 | } 81 | 82 | result := util.DecodeToken(resultToken) 83 | input.Email = result.Claims.Email 84 | input.Active = true 85 | 86 | _, errReset := h.service.ResetService(&input) 87 | 88 | switch errReset { 89 | 90 | case "RESET_NOT_FOUND_404": 91 | util.APIResponse(ctx, "User account is not exist", http.StatusNotFound, http.MethodPost, nil) 92 | return 93 | 94 | case "ACCOUNT_NOT_ACTIVE_403": 95 | util.APIResponse(ctx, "User account is not active", http.StatusForbidden, http.MethodPost, nil) 96 | return 97 | 98 | case "RESET_PASSWORD_FAILED_403": 99 | util.APIResponse(ctx, "Change new password failed", http.StatusForbidden, http.MethodPost, nil) 100 | return 101 | 102 | default: 103 | util.APIResponse(ctx, "Change new password successfully", http.StatusOK, http.MethodPost, nil) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /handlers/student-handlers/create/create.go: -------------------------------------------------------------------------------- 1 | package handlerCreateStudent 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | createStudent "github.com/restuwahyu13/gin-rest-api/controllers/student-controllers/create" 8 | util "github.com/restuwahyu13/gin-rest-api/utils" 9 | gpc "github.com/restuwahyu13/go-playground-converter" 10 | ) 11 | 12 | type handler struct { 13 | service createStudent.Service 14 | } 15 | 16 | func NewHandlerCreateStudent(service createStudent.Service) *handler { 17 | return &handler{service: service} 18 | } 19 | 20 | func (h *handler) CreateStudentHandler(ctx *gin.Context) { 21 | 22 | var input createStudent.InputCreateStudent 23 | ctx.ShouldBindJSON(&input) 24 | 25 | config := gpc.ErrorConfig{ 26 | Options: []gpc.ErrorMetaConfig{ 27 | gpc.ErrorMetaConfig{ 28 | Tag: "required", 29 | Field: "Name", 30 | Message: "name is required on body", 31 | }, 32 | gpc.ErrorMetaConfig{ 33 | Tag: "lowercase", 34 | Field: "Name", 35 | Message: "name must be using lowercase", 36 | }, 37 | gpc.ErrorMetaConfig{ 38 | Tag: "required", 39 | Field: "Npm", 40 | Message: "npm is required on body", 41 | }, 42 | gpc.ErrorMetaConfig{ 43 | Tag: "numeric", 44 | Field: "Npm", 45 | Message: "npm must be number format", 46 | }, 47 | gpc.ErrorMetaConfig{ 48 | Tag: "required", 49 | Field: "Fak", 50 | Message: "fak is required on body", 51 | }, 52 | gpc.ErrorMetaConfig{ 53 | Tag: "lowercase", 54 | Field: "Fak", 55 | Message: "fak must be using lowercase", 56 | }, 57 | gpc.ErrorMetaConfig{ 58 | Tag: "required", 59 | Field: "Bid", 60 | Message: "bid is required on body", 61 | }, 62 | gpc.ErrorMetaConfig{ 63 | Tag: "lowercase", 64 | Field: "Bid", 65 | Message: "bid must be using lowercase", 66 | }, 67 | }, 68 | } 69 | 70 | errResponse, errCount := util.GoValidator(&input, config.Options) 71 | 72 | if errCount > 0 { 73 | util.ValidatorErrorResponse(ctx, http.StatusBadRequest, http.MethodPost, errResponse) 74 | return 75 | } 76 | 77 | _, errCreateStudent := h.service.CreateStudentService(&input) 78 | 79 | switch errCreateStudent { 80 | 81 | case "CREATE_STUDENT_CONFLICT_409": 82 | util.APIResponse(ctx, "Npm student already exist", http.StatusConflict, http.MethodPost, nil) 83 | return 84 | 85 | case "CREATE_STUDENT_FAILED_403": 86 | util.APIResponse(ctx, "Create new student account failed", http.StatusForbidden, http.MethodPost, nil) 87 | return 88 | 89 | default: 90 | util.APIResponse(ctx, "Create new student account successfully", http.StatusCreated, http.MethodPost, nil) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /handlers/student-handlers/delete/delete.go: -------------------------------------------------------------------------------- 1 | package handlerDeleteStudent 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | deleteStudent "github.com/restuwahyu13/gin-rest-api/controllers/student-controllers/delete" 8 | util "github.com/restuwahyu13/gin-rest-api/utils" 9 | gpc "github.com/restuwahyu13/go-playground-converter" 10 | ) 11 | 12 | type handler struct { 13 | service deleteStudent.Service 14 | } 15 | 16 | func NewHandlerDeleteStudent(service deleteStudent.Service) *handler { 17 | return &handler{service: service} 18 | } 19 | 20 | func (h *handler) DeleteStudentHandler(ctx *gin.Context) { 21 | 22 | var input deleteStudent.InputDeleteStudent 23 | input.ID = ctx.Param("id") 24 | 25 | config := gpc.ErrorConfig{ 26 | Options: []gpc.ErrorMetaConfig{ 27 | gpc.ErrorMetaConfig{ 28 | Tag: "required", 29 | Field: "ID", 30 | Message: "id is required on param", 31 | }, 32 | gpc.ErrorMetaConfig{ 33 | Tag: "uuid", 34 | Field: "ID", 35 | Message: "params must be uuid format", 36 | }, 37 | }, 38 | } 39 | 40 | errResponse, errCount := util.GoValidator(&input, config.Options) 41 | 42 | if errCount > 0 { 43 | util.ValidatorErrorResponse(ctx, http.StatusBadRequest, http.MethodDelete, errResponse) 44 | return 45 | } 46 | 47 | _, errDeleteStudent := h.service.DeleteStudentService(&input) 48 | 49 | switch errDeleteStudent { 50 | 51 | case "DELETE_STUDENT_NOT_FOUND_404": 52 | util.APIResponse(ctx, "Student data is not exist or deleted", http.StatusForbidden, http.MethodDelete, nil) 53 | return 54 | 55 | case "DELETE_STUDENT_FAILED_403": 56 | util.APIResponse(ctx, "Delete student data failed", http.StatusForbidden, http.MethodDelete, nil) 57 | return 58 | 59 | default: 60 | util.APIResponse(ctx, "Delete student data successfully", http.StatusOK, http.MethodDelete, nil) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /handlers/student-handlers/result/result.go: -------------------------------------------------------------------------------- 1 | package handlerResultStudent 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | resultStudent "github.com/restuwahyu13/gin-rest-api/controllers/student-controllers/result" 8 | util "github.com/restuwahyu13/gin-rest-api/utils" 9 | gpc "github.com/restuwahyu13/go-playground-converter" 10 | ) 11 | 12 | type handler struct { 13 | service resultStudent.Service 14 | } 15 | 16 | func NewHandlerResultStudent(service resultStudent.Service) *handler { 17 | return &handler{service: service} 18 | } 19 | 20 | func (h *handler) ResultStudentHandler(ctx *gin.Context) { 21 | 22 | var input resultStudent.InputResultStudent 23 | input.ID = ctx.Param("id") 24 | 25 | config := gpc.ErrorConfig{ 26 | Options: []gpc.ErrorMetaConfig{ 27 | gpc.ErrorMetaConfig{ 28 | Tag: "required", 29 | Field: "ID", 30 | Message: "id is required on param", 31 | }, 32 | gpc.ErrorMetaConfig{ 33 | Tag: "uuid", 34 | Field: "ID", 35 | Message: "params must be uuid format", 36 | }, 37 | }, 38 | } 39 | 40 | errResponse, errCount := util.GoValidator(&input, config.Options) 41 | 42 | if errCount > 0 { 43 | util.ValidatorErrorResponse(ctx, http.StatusBadRequest, http.MethodGet, errResponse) 44 | return 45 | } 46 | 47 | resultStudent, errResultStudent := h.service.ResultStudentService(&input) 48 | 49 | switch errResultStudent { 50 | 51 | case "RESULT_STUDENT_NOT_FOUND_404": 52 | util.APIResponse(ctx, "Student data is not exist or deleted", http.StatusNotFound, http.MethodGet, nil) 53 | return 54 | 55 | default: 56 | util.APIResponse(ctx, "Result Student data successfully", http.StatusOK, http.MethodGet, resultStudent) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /handlers/student-handlers/results/results.go: -------------------------------------------------------------------------------- 1 | package handlerResultsStudent 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | resultsStudent "github.com/restuwahyu13/gin-rest-api/controllers/student-controllers/results" 8 | util "github.com/restuwahyu13/gin-rest-api/utils" 9 | ) 10 | 11 | type handler struct { 12 | service resultsStudent.Service 13 | } 14 | 15 | func NewHandlerResultsStudent(service resultsStudent.Service) *handler { 16 | return &handler{service: service} 17 | } 18 | 19 | func (h *handler) ResultsStudentHandler(ctx *gin.Context) { 20 | 21 | resultsStudent, errResultsStudent := h.service.ResultsStudentService() 22 | 23 | switch errResultsStudent { 24 | 25 | case "RESULTS_STUDENT_NOT_FOUND_404": 26 | util.APIResponse(ctx, "Students data is not exists", http.StatusConflict, http.MethodPost, nil) 27 | 28 | default: 29 | util.APIResponse(ctx, "Results Students data successfully", http.StatusOK, http.MethodPost, resultsStudent) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /handlers/student-handlers/update/update.go: -------------------------------------------------------------------------------- 1 | package handlerUpdateStudent 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | updateStudent "github.com/restuwahyu13/gin-rest-api/controllers/student-controllers/update" 8 | util "github.com/restuwahyu13/gin-rest-api/utils" 9 | gpc "github.com/restuwahyu13/go-playground-converter" 10 | ) 11 | 12 | type handler struct { 13 | service updateStudent.Service 14 | } 15 | 16 | func NewHandlerUpdateStudent(service updateStudent.Service) *handler { 17 | return &handler{service: service} 18 | } 19 | 20 | func (h *handler) UpdateStudentHandler(ctx *gin.Context) { 21 | 22 | var input updateStudent.InputUpdateStudent 23 | input.ID = ctx.Param("id") 24 | ctx.ShouldBindJSON(&input) 25 | 26 | config := gpc.ErrorConfig{ 27 | Options: []gpc.ErrorMetaConfig{ 28 | gpc.ErrorMetaConfig{ 29 | Tag: "required", 30 | Field: "ID", 31 | Message: "id is required on param", 32 | }, 33 | gpc.ErrorMetaConfig{ 34 | Tag: "uuid", 35 | Field: "ID", 36 | Message: "params must be uuid format", 37 | }, 38 | gpc.ErrorMetaConfig{ 39 | Tag: "required", 40 | Field: "Name", 41 | Message: "name is required on body", 42 | }, 43 | gpc.ErrorMetaConfig{ 44 | Tag: "lowercase", 45 | Field: "Name", 46 | Message: "name must be using lowercase", 47 | }, 48 | gpc.ErrorMetaConfig{ 49 | Tag: "required", 50 | Field: "Npm", 51 | Message: "npm is required on body", 52 | }, 53 | gpc.ErrorMetaConfig{ 54 | Tag: "number", 55 | Field: "Npm", 56 | Message: "npm must be number format", 57 | }, 58 | gpc.ErrorMetaConfig{ 59 | Tag: "required", 60 | Field: "Fak", 61 | Message: "fak is required on body", 62 | }, 63 | gpc.ErrorMetaConfig{ 64 | Tag: "lowercase", 65 | Field: "Fak", 66 | Message: "fak must be using lowercase", 67 | }, 68 | gpc.ErrorMetaConfig{ 69 | Tag: "required", 70 | Field: "Bid", 71 | Message: "bid is required on body", 72 | }, 73 | gpc.ErrorMetaConfig{ 74 | Tag: "lowercase", 75 | Field: "Bid", 76 | Message: "bid must be using lowercase", 77 | }, 78 | }, 79 | } 80 | 81 | errResponse, errCount := util.GoValidator(&input, config.Options) 82 | 83 | if errCount > 0 { 84 | util.ValidatorErrorResponse(ctx, http.StatusBadRequest, http.MethodGet, errResponse) 85 | return 86 | } 87 | 88 | _, errUpdateStudent := h.service.UpdateStudentService(&input) 89 | 90 | switch errUpdateStudent { 91 | 92 | case "UPDATE_STUDENT_NOT_FOUND_404": 93 | util.APIResponse(ctx, "Student data is not exist or deleted", http.StatusNotFound, http.MethodPost, nil) 94 | 95 | case "UPDATE_STUDENT_FAILED_403": 96 | util.APIResponse(ctx, "Update student data failed", http.StatusForbidden, http.MethodPost, nil) 97 | 98 | default: 99 | util.APIResponse(ctx, "Update student data sucessfully", http.StatusOK, http.MethodPost, nil) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | helmet "github.com/danielkov/gin-helmet" 7 | "github.com/gin-contrib/cors" 8 | "github.com/gin-contrib/gzip" 9 | "github.com/gin-gonic/gin" 10 | config "github.com/restuwahyu13/gin-rest-api/configs" 11 | route "github.com/restuwahyu13/gin-rest-api/routes" 12 | util "github.com/restuwahyu13/gin-rest-api/utils" 13 | ) 14 | 15 | func main() { 16 | /** 17 | @description Setup Server 18 | */ 19 | router := SetupRouter() 20 | /** 21 | @description Run Server 22 | */ 23 | log.Fatal(router.Run(":" + util.GodotEnv("GO_PORT"))) 24 | } 25 | 26 | func SetupRouter() *gin.Engine { 27 | /** 28 | @description Setup Database Connection 29 | */ 30 | db := config.Connection() 31 | /** 32 | @description Init Router 33 | */ 34 | router := gin.Default() 35 | /** 36 | @description Setup Mode Application 37 | */ 38 | if util.GodotEnv("GO_ENV") != "production" && util.GodotEnv("GO_ENV") != "test" { 39 | gin.SetMode(gin.DebugMode) 40 | } else if util.GodotEnv("GO_ENV") == "test" { 41 | gin.SetMode(gin.TestMode) 42 | } else { 43 | gin.SetMode(gin.ReleaseMode) 44 | } 45 | /** 46 | @description Setup Middleware 47 | */ 48 | router.Use(cors.New(cors.Config{ 49 | AllowOrigins: []string{"*"}, 50 | AllowMethods: []string{"*"}, 51 | AllowHeaders: []string{"*"}, 52 | AllowWildcard: true, 53 | })) 54 | router.Use(helmet.Default()) 55 | router.Use(gzip.Gzip(gzip.BestCompression)) 56 | /** 57 | @description Init All Route 58 | */ 59 | route.InitAuthRoutes(db, router) 60 | route.InitStudentRoutes(db, router) 61 | 62 | return router 63 | } 64 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/go-playground/assert/v2" 11 | util "github.com/restuwahyu13/gin-rest-api/utils" 12 | "github.com/restuwahyu13/go-supertest/supertest" 13 | . "github.com/smartystreets/goconvey/convey" 14 | "syreclabs.com/go/faker" 15 | ) 16 | 17 | var router = SetupRouter() 18 | var accessToken string 19 | var studentId interface{} 20 | 21 | func TestLoginHandler(t *testing.T) { 22 | 23 | Convey("Auth Login Handler Group", t, func() { 24 | 25 | Convey("Login User Account Is Not Registered", func() { 26 | payload := gin.H{ 27 | "email": "anto13@zetmail.com", 28 | "password": "qwerty12", 29 | } 30 | 31 | test := supertest.NewSuperTest(router, t) 32 | 33 | test.Post("/api/v1/login") 34 | test.Send(payload) 35 | test.Set("Content-Type", "application/json") 36 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 37 | 38 | response := util.Parse(rr.Body.Bytes()) 39 | t.Log(response) 40 | 41 | assert.Equal(t, http.StatusNotFound, rr.Code) 42 | assert.Equal(t, http.MethodPost, req.Method) 43 | assert.Equal(t, "User account is not registered", response.Message) 44 | }) 45 | }) 46 | 47 | Convey("Login Failed User Account Is Not Active", func() { 48 | payload := gin.H{ 49 | "email": "carmelo_marquardt@weissnat.info", 50 | "password": "testing13", 51 | } 52 | 53 | test := supertest.NewSuperTest(router, t) 54 | 55 | test.Post("/api/v1/login") 56 | test.Send(payload) 57 | test.Set("Content-Type", "application/json") 58 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 59 | 60 | response := util.Parse(rr.Body.Bytes()) 61 | t.Log(response) 62 | 63 | assert.Equal(t, http.StatusForbidden, rr.Code) 64 | assert.Equal(t, http.MethodPost, response.Method) 65 | assert.Equal(t, "User account is not active", response.Message) 66 | }) 67 | }) 68 | 69 | Convey("Login Error Username Or Password Is Wrong", func() { 70 | payload := gin.H{ 71 | "email": "eduardo.wehner@greenholtadams.net", 72 | "password": "testing", 73 | } 74 | 75 | test := supertest.NewSuperTest(router, t) 76 | 77 | test.Post("/api/v1/login") 78 | test.Send(payload) 79 | test.Set("Content-Type", "application/json") 80 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 81 | 82 | response := util.Parse(rr.Body.Bytes()) 83 | t.Log(response) 84 | 85 | assert.Equal(t, http.StatusForbidden, rr.Code) 86 | assert.Equal(t, http.MethodPost, req.Method) 87 | assert.Equal(t, "Username or password is wrong", response.Message) 88 | }) 89 | }) 90 | 91 | Convey("Login Success", func() { 92 | payload := gin.H{ 93 | "email": "eduardo.wehner@greenholtadams.net", 94 | "password": "qwerty12345", 95 | } 96 | 97 | test := supertest.NewSuperTest(router, t) 98 | 99 | test.Post("/api/v1/login") 100 | test.Send(payload) 101 | test.Set("Content-Type", "application/json") 102 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 103 | 104 | response := util.Parse(rr.Body.Bytes()) 105 | t.Log(response) 106 | 107 | assert.Equal(t, http.StatusOK, rr.Code) 108 | assert.Equal(t, http.MethodPost, req.Method) 109 | assert.Equal(t, "Login successfully", response.Message) 110 | 111 | var token map[string]interface{} 112 | encoded := util.Strigify(response.Data) 113 | _ = json.Unmarshal(encoded, &token) 114 | 115 | accessToken = token["accessToken"].(string) 116 | }) 117 | }) 118 | }) 119 | } 120 | 121 | func TestRegisterHandler(t *testing.T) { 122 | 123 | Convey("Auth Register Handler Group", t, func() { 124 | 125 | Convey("Register New Account", func() { 126 | payload := gin.H{ 127 | "fullname": faker.Internet().Email(), 128 | "email": faker.Internet().Email(), 129 | "password": "testing13", 130 | } 131 | 132 | test := supertest.NewSuperTest(router, t) 133 | 134 | test.Post("/api/v1/register") 135 | test.Send(payload) 136 | test.Set("Content-Type", "application/json") 137 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 138 | 139 | response := util.Parse(rr.Body.Bytes()) 140 | t.Log(response) 141 | 142 | assert.Equal(t, http.StatusCreated, rr.Code) 143 | assert.Equal(t, http.MethodPost, req.Method) 144 | assert.Equal(t, "Register new account successfully", response.Message) 145 | }) 146 | }) 147 | 148 | }) 149 | } 150 | 151 | func TestForgotHandler(t *testing.T) { 152 | 153 | Convey("Auth Forgot Password Handler Group", t, func() { 154 | 155 | Convey("Forgot Password If Email Not Exist", func() { 156 | 157 | payload := gin.H{ 158 | "email": "santosi131@zetmail.com", 159 | } 160 | 161 | test := supertest.NewSuperTest(router, t) 162 | 163 | test.Post("/api/v1/forgot-password") 164 | test.Send(payload) 165 | test.Set("Content-Type", "application/json") 166 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 167 | 168 | response := util.Parse(rr.Body.Bytes()) 169 | t.Log(response) 170 | 171 | assert.Equal(t, http.StatusNotFound, rr.Code) 172 | assert.Equal(t, http.MethodPost, req.Method) 173 | assert.Equal(t, "Email is not never registered", response.Message) 174 | }) 175 | }) 176 | 177 | Convey("Forgot Password If Account Is Not Active", func() { 178 | 179 | payload := gin.H{ 180 | "email": "santoso13@zetmail.com", 181 | } 182 | 183 | test := supertest.NewSuperTest(router, t) 184 | 185 | test.Post("/api/v1/forgot-password") 186 | test.Send(payload) 187 | test.Set("Content-Type", "application/json") 188 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 189 | 190 | response := util.Parse(rr.Body.Bytes()) 191 | t.Log(response) 192 | 193 | assert.Equal(t, http.StatusForbidden, rr.Code) 194 | assert.Equal(t, http.MethodPost, req.Method) 195 | assert.Equal(t, "User account is not active", response.Message) 196 | }) 197 | }) 198 | 199 | Convey("Forgot Password To Get New Password", func() { 200 | 201 | payload := gin.H{ 202 | "email": "samsul1@zetmail.com", 203 | } 204 | 205 | test := supertest.NewSuperTest(router, t) 206 | 207 | test.Post("/api/v1/forgot-password") 208 | test.Send(payload) 209 | test.Set("Content-Type", "application/json") 210 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 211 | 212 | response := util.Parse(rr.Body.Bytes()) 213 | t.Log(response) 214 | 215 | assert.Equal(t, http.StatusOK, rr.Code) 216 | assert.Equal(t, http.MethodPost, req.Method) 217 | assert.Equal(t, "Forgot password successfully", response.Message) 218 | }) 219 | }) 220 | 221 | }) 222 | } 223 | 224 | func TestResendHandler(t *testing.T) { 225 | 226 | Convey("Auth Resend Token Handler Group", t, func() { 227 | 228 | Convey("Resend New Token If Email Not Exist", func() { 229 | 230 | payload := gin.H{ 231 | "email": "santosi131@zetmail.com", 232 | } 233 | 234 | test := supertest.NewSuperTest(router, t) 235 | 236 | test.Post("/api/v1/resend-token") 237 | test.Send(payload) 238 | test.Set("Content-Type", "application/json") 239 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 240 | 241 | response := util.Parse(rr.Body.Bytes()) 242 | t.Log(response) 243 | 244 | assert.Equal(t, http.StatusNotFound, rr.Code) 245 | assert.Equal(t, http.MethodPost, req.Method) 246 | assert.Equal(t, "Email is not never registered", response.Message) 247 | }) 248 | }) 249 | 250 | Convey("Resend Token If Account Is Active", func() { 251 | 252 | payload := gin.H{ 253 | "email": "restuwahyu13@zetmail.com", 254 | } 255 | 256 | test := supertest.NewSuperTest(router, t) 257 | 258 | test.Post("/api/v1/resend-token") 259 | test.Send(payload) 260 | test.Set("Content-Type", "application/json") 261 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 262 | 263 | response := util.Parse(rr.Body.Bytes()) 264 | t.Log(response) 265 | 266 | assert.Equal(t, http.StatusForbidden, rr.Code) 267 | assert.Equal(t, http.MethodPost, req.Method) 268 | assert.Equal(t, "User account hash been active", response.Message) 269 | }) 270 | }) 271 | 272 | Convey("Forgot Password To Get New Password", func() { 273 | 274 | payload := gin.H{ 275 | "email": "santoso13@zetmail.com", 276 | } 277 | 278 | test := supertest.NewSuperTest(router, t) 279 | 280 | test.Post("/api/v1/resend-token") 281 | test.Send(payload) 282 | test.Set("Content-Type", "application/json") 283 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 284 | 285 | response := util.Parse(rr.Body.Bytes()) 286 | t.Log(response) 287 | 288 | assert.Equal(t, http.StatusOK, rr.Code) 289 | assert.Equal(t, http.MethodPost, req.Method) 290 | assert.Equal(t, "Resend new activation token successfully", response.Message) 291 | }) 292 | }) 293 | 294 | }) 295 | } 296 | 297 | func TestResetHandler(t *testing.T) { 298 | 299 | Convey("Auth Reset Password Handler Group", t, func() { 300 | 301 | Convey("Reset Old Password To New Password", func() { 302 | payload := gin.H{ 303 | "email": "eduardo.wehner@greenholtadams.net", 304 | "password": "qwerty12345", 305 | "cpassword": "qwerty12345", 306 | } 307 | 308 | test := supertest.NewSuperTest(router, t) 309 | 310 | test.Post("/api/v1/change-password/" + accessToken) 311 | test.Send(payload) 312 | test.Set("Content-Type", "application/json") 313 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 314 | 315 | response := util.Parse(rr.Body.Bytes()) 316 | t.Log(response) 317 | 318 | assert.Equal(t, http.StatusOK, rr.Code) 319 | assert.Equal(t, http.MethodPost, req.Method) 320 | assert.Equal(t, "Change new password successfully", response.Message) 321 | }) 322 | }) 323 | 324 | }) 325 | } 326 | 327 | func TestCreateStudentHandler(t *testing.T) { 328 | 329 | Convey("Test Handler Create Student Group", t, func() { 330 | 331 | Convey("Create New Student Is Conflict", func() { 332 | 333 | payload := gin.H{ 334 | "name": "bagus budiawan", 335 | "npm": 201543502292, 336 | "fak": "mipa", 337 | "bid": "tehnik informatika", 338 | } 339 | 340 | test := supertest.NewSuperTest(router, t) 341 | 342 | test.Post("/api/v1/student") 343 | test.Send(payload) 344 | test.Set("Content-Type", "application/json") 345 | test.Set("Authorization", "Bearer "+accessToken) 346 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 347 | 348 | response := util.Parse(rr.Body.Bytes()) 349 | t.Log(response) 350 | 351 | assert.Equal(t, http.StatusConflict, rr.Code) 352 | assert.Equal(t, http.MethodPost, req.Method) 353 | assert.Equal(t, "Npm student already exist", response.Message) 354 | }) 355 | }) 356 | 357 | Convey("Create New Student", func() { 358 | 359 | payload := gin.H{ 360 | "name": faker.Internet().FreeEmail(), 361 | "npm": faker.RandomInt(25, 50), 362 | "fak": "mipa", 363 | "bid": "tehnik informatika", 364 | } 365 | 366 | test := supertest.NewSuperTest(router, t) 367 | 368 | test.Post("/api/v1/student") 369 | test.Send(payload) 370 | test.Set("Content-Type", "application/json") 371 | test.Set("Authorization", "Bearer "+accessToken) 372 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 373 | 374 | response := util.Parse(rr.Body.Bytes()) 375 | t.Log(response) 376 | 377 | assert.Equal(t, http.StatusCreated, rr.Code) 378 | assert.Equal(t, http.MethodPost, req.Method) 379 | assert.Equal(t, "Create new student account successfully", response.Message) 380 | }) 381 | }) 382 | }) 383 | } 384 | 385 | func TestResultsStudentHandler(t *testing.T) { 386 | 387 | Convey("Test Handler Results Student By ID Group", t, func() { 388 | 389 | Convey("Results All Student", func() { 390 | 391 | test := supertest.NewSuperTest(router, t) 392 | 393 | test.Get("/api/v1/student") 394 | test.Send(nil) 395 | test.Set("Content-Type", "application/json") 396 | test.Set("Authorization", "Bearer "+accessToken) 397 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 398 | 399 | response := util.Parse(rr.Body.Bytes()) 400 | t.Log(response) 401 | 402 | var objects []map[string]interface{} 403 | encoded := util.Strigify(response.Data) 404 | _ = json.Unmarshal(encoded, &objects) 405 | 406 | studentId = objects[0]["ID"] 407 | 408 | assert.Equal(t, http.StatusOK, rr.Code) 409 | assert.Equal(t, http.MethodGet, req.Method) 410 | assert.Equal(t, "Results Students data successfully", response.Message) 411 | }) 412 | }) 413 | }) 414 | } 415 | 416 | func TestResultStudentHandler(t *testing.T) { 417 | 418 | Convey("Test Handler Result Student By ID Group", t, func() { 419 | 420 | Convey("Result Specific Student If StudentID Is Not Exist", func() { 421 | 422 | ID := "00f85d71-083b-4089-9d20-bb1054df4575" 423 | 424 | test := supertest.NewSuperTest(router, t) 425 | 426 | test.Get("/api/v1/student/" + ID) 427 | test.Send(nil) 428 | test.Set("Content-Type", "application/json") 429 | test.Set("Authorization", "Bearer "+accessToken) 430 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 431 | 432 | response := util.Parse(rr.Body.Bytes()) 433 | t.Log(response) 434 | 435 | assert.Equal(t, http.StatusNotFound, rr.Code) 436 | assert.Equal(t, http.MethodGet, req.Method) 437 | assert.Equal(t, "Student data is not exist or deleted", response.Message) 438 | }) 439 | }) 440 | 441 | Convey("Result Specific Student By ID", func() { 442 | 443 | ID := studentId 444 | 445 | test := supertest.NewSuperTest(router, t) 446 | 447 | test.Get("/api/v1/student/" + ID.(string)) 448 | test.Send(nil) 449 | test.Set("Content-Type", "application/json") 450 | test.Set("Authorization", "Bearer "+accessToken) 451 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 452 | 453 | response := util.Parse(rr.Body.Bytes()) 454 | t.Log(response) 455 | 456 | assert.Equal(t, http.StatusOK, rr.Code) 457 | assert.Equal(t, http.MethodGet, req.Method) 458 | 459 | mapping := make(map[string]interface{}) 460 | encode := util.Strigify(response.Data) 461 | _: 462 | json.Unmarshal(encode, &mapping) 463 | 464 | assert.Equal(t, ID, mapping["ID"]) 465 | assert.Equal(t, "Result Student data successfully", response.Message) 466 | }) 467 | }) 468 | }) 469 | } 470 | 471 | func TestDeleteStudentHandler(t *testing.T) { 472 | 473 | Convey("Test Handler Delete Student By ID Group", t, func() { 474 | 475 | Convey("Delete Specific Student If StudentID Is Not Exist", func() { 476 | 477 | ID := "00f85d71-083b-4089-9d20-bb1054df4575" 478 | 479 | test := supertest.NewSuperTest(router, t) 480 | 481 | test.Delete("/api/v1/student/" + ID) 482 | test.Send(nil) 483 | test.Set("Content-Type", "application/json") 484 | test.Set("Authorization", "Bearer "+accessToken) 485 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 486 | 487 | response := util.Parse(rr.Body.Bytes()) 488 | t.Log(response) 489 | 490 | assert.Equal(t, http.StatusForbidden, rr.Code) 491 | assert.Equal(t, http.MethodDelete, req.Method) 492 | assert.Equal(t, "Student data is not exist or deleted", response.Message) 493 | }) 494 | }) 495 | 496 | Convey("Delete Specific Student By ID", func() { 497 | 498 | ID := studentId 499 | 500 | test := supertest.NewSuperTest(router, t) 501 | 502 | test.Delete("/api/v1/student/" + ID.(string)) 503 | test.Send(nil) 504 | test.Set("Content-Type", "application/json") 505 | test.Set("Authorization", "Bearer "+accessToken) 506 | test.End(func(req *http.Request, rr *httptest.ResponseRecorder) { 507 | 508 | response := util.Parse(rr.Body.Bytes()) 509 | t.Log(response) 510 | 511 | assert.Equal(t, http.StatusOK, rr.Code) 512 | assert.Equal(t, http.MethodDelete, req.Method) 513 | assert.Equal(t, "Delete student data successfully", response.Message) 514 | }) 515 | }) 516 | }) 517 | } 518 | -------------------------------------------------------------------------------- /middlewares/authJwt.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | util "github.com/restuwahyu13/gin-rest-api/utils" 8 | ) 9 | 10 | type UnathorizatedError struct { 11 | Status string `json:"status"` 12 | Code int `json:"code"` 13 | Method string `json:"method"` 14 | Message string `json:"message"` 15 | } 16 | 17 | func Auth() gin.HandlerFunc { 18 | 19 | return gin.HandlerFunc(func(ctx *gin.Context) { 20 | 21 | var errorResponse UnathorizatedError 22 | 23 | errorResponse.Status = "Forbidden" 24 | errorResponse.Code = http.StatusForbidden 25 | errorResponse.Method = ctx.Request.Method 26 | errorResponse.Message = "Authorization is required for this endpoint" 27 | 28 | if ctx.GetHeader("Authorization") == "" { 29 | ctx.JSON(http.StatusForbidden, errorResponse) 30 | defer ctx.AbortWithStatus(http.StatusForbidden) 31 | } 32 | 33 | token, err := util.VerifyTokenHeader(ctx, "JWT_SECRET") 34 | 35 | errorResponse.Status = "Unathorizated" 36 | errorResponse.Code = http.StatusUnauthorized 37 | errorResponse.Method = ctx.Request.Method 38 | errorResponse.Message = "accessToken invalid or expired" 39 | 40 | if err != nil { 41 | ctx.JSON(http.StatusUnauthorized, errorResponse) 42 | defer ctx.AbortWithStatus(http.StatusUnauthorized) 43 | } else { 44 | // global value result 45 | ctx.Set("user", token.Claims) 46 | // return to next method if token is exist 47 | ctx.Next() 48 | } 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /models/entity.auth.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/google/uuid" 7 | util "github.com/restuwahyu13/gin-rest-api/utils" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type EntityUsers struct { 12 | ID string `gorm:"primaryKey;"` 13 | Fullname string `gorm:"type:varchar(255);unique;not null"` 14 | Email string `gorm:"type:varchar(255);unique;not null"` 15 | Password string `gorm:"type:varchar(255);not null"` 16 | Active bool `gorm:"type:bool;default:false"` 17 | CreatedAt time.Time 18 | UpdatedAt time.Time 19 | } 20 | 21 | func (entity *EntityUsers) BeforeCreate(db *gorm.DB) error { 22 | entity.ID = uuid.New().String() 23 | entity.Password = util.HashPassword(entity.Password) 24 | entity.CreatedAt = time.Now().Local() 25 | return nil 26 | } 27 | 28 | func (entity *EntityUsers) BeforeUpdate(db *gorm.DB) error { 29 | entity.UpdatedAt = time.Now().Local() 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /models/entity.student.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/google/uuid" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | type EntityStudent struct { 11 | ID string `gorm:"primaryKey;"` 12 | Name string `gorm:"type:varchar(255);not null"` 13 | Npm int `gorm:"type:bigint;unique;not null"` 14 | Fak string `gorm:"type:varchar(255);not null"` 15 | Bid string `gorm:"type:varchar(255);not null"` 16 | CreatedAt time.Time 17 | UpdatedAt time.Time 18 | } 19 | 20 | func (entity *EntityStudent) BeforeCreate(db *gorm.DB) error { 21 | entity.ID = uuid.New().String() 22 | entity.CreatedAt = time.Now().Local() 23 | return nil 24 | } 25 | 26 | func (entity *EntityStudent) BeforeUpdate(db *gorm.DB) error { 27 | entity.UpdatedAt = time.Now().Local() 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /routes/route.auth.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | activationAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/activation" 6 | forgotAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/forgot" 7 | loginAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/login" 8 | registerAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/register" 9 | resendAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/resend" 10 | resetAuth "github.com/restuwahyu13/gin-rest-api/controllers/auth-controllers/reset" 11 | handlerActivation "github.com/restuwahyu13/gin-rest-api/handlers/auth-handlers/activation" 12 | handlerForgot "github.com/restuwahyu13/gin-rest-api/handlers/auth-handlers/forgot" 13 | handlerLogin "github.com/restuwahyu13/gin-rest-api/handlers/auth-handlers/login" 14 | handlerRegister "github.com/restuwahyu13/gin-rest-api/handlers/auth-handlers/register" 15 | handlerResend "github.com/restuwahyu13/gin-rest-api/handlers/auth-handlers/resend" 16 | handlerReset "github.com/restuwahyu13/gin-rest-api/handlers/auth-handlers/reset" 17 | "gorm.io/gorm" 18 | ) 19 | 20 | func InitAuthRoutes(db *gorm.DB, route *gin.Engine) { 21 | 22 | /** 23 | @description All Handler Auth 24 | */ 25 | LoginRepository := loginAuth.NewRepositoryLogin(db) 26 | loginService := loginAuth.NewServiceLogin(LoginRepository) 27 | loginHandler := handlerLogin.NewHandlerLogin(loginService) 28 | 29 | registerRepository := registerAuth.NewRepositoryRegister(db) 30 | registerService := registerAuth.NewServiceRegister(registerRepository) 31 | registerHandler := handlerRegister.NewHandlerRegister(registerService) 32 | 33 | activationRepository := activationAuth.NewRepositoryActivation(db) 34 | activationService := activationAuth.NewServiceActivation(activationRepository) 35 | activationHandler := handlerActivation.NewHandlerActivation(activationService) 36 | 37 | resendRepository := resendAuth.NewRepositoryResend(db) 38 | resendService := resendAuth.NewServiceResend(resendRepository) 39 | resendHandler := handlerResend.NewHandlerResend(resendService) 40 | 41 | forgotRepository := forgotAuth.NewRepositoryForgot(db) 42 | forgotService := forgotAuth.NewServiceForgot(forgotRepository) 43 | forgotHandler := handlerForgot.NewHandlerForgot(forgotService) 44 | 45 | resetRepository := resetAuth.NewRepositoryReset(db) 46 | resetService := resetAuth.NewServiceReset(resetRepository) 47 | resetHandler := handlerReset.NewHandlerReset(resetService) 48 | 49 | /** 50 | @description All Auth Route 51 | */ 52 | groupRoute := route.Group("/api/v1") 53 | groupRoute.POST("/register", registerHandler.RegisterHandler) 54 | groupRoute.POST("/login", loginHandler.LoginHandler) 55 | groupRoute.POST("/activation/:token", activationHandler.ActivationHandler) 56 | groupRoute.POST("/resend-token", resendHandler.ResendHandler) 57 | groupRoute.POST("/forgot-password", forgotHandler.ForgotHandler) 58 | groupRoute.POST("/change-password/:token", resetHandler.ResetHandler) 59 | 60 | } 61 | -------------------------------------------------------------------------------- /routes/route.student.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | createStudent "github.com/restuwahyu13/gin-rest-api/controllers/student-controllers/create" 6 | deleteStudent "github.com/restuwahyu13/gin-rest-api/controllers/student-controllers/delete" 7 | resultStudent "github.com/restuwahyu13/gin-rest-api/controllers/student-controllers/result" 8 | resultsStudent "github.com/restuwahyu13/gin-rest-api/controllers/student-controllers/results" 9 | updateStudent "github.com/restuwahyu13/gin-rest-api/controllers/student-controllers/update" 10 | handlerCreateStudent "github.com/restuwahyu13/gin-rest-api/handlers/student-handlers/create" 11 | handlerDeleteStudent "github.com/restuwahyu13/gin-rest-api/handlers/student-handlers/delete" 12 | handlerResultStudent "github.com/restuwahyu13/gin-rest-api/handlers/student-handlers/result" 13 | handlerResultsStudent "github.com/restuwahyu13/gin-rest-api/handlers/student-handlers/results" 14 | handlerUpdateStudent "github.com/restuwahyu13/gin-rest-api/handlers/student-handlers/update" 15 | middleware "github.com/restuwahyu13/gin-rest-api/middlewares" 16 | "gorm.io/gorm" 17 | ) 18 | 19 | func InitStudentRoutes(db *gorm.DB, route *gin.Engine) { 20 | 21 | /** 22 | @description All Handler Student 23 | */ 24 | createStudentRepository := createStudent.NewRepositoryCreate(db) 25 | createStudentService := createStudent.NewServiceCreate(createStudentRepository) 26 | createStudentHandler := handlerCreateStudent.NewHandlerCreateStudent(createStudentService) 27 | 28 | resultsStudentRepository := resultsStudent.NewRepositoryResults(db) 29 | resultsStudentService := resultsStudent.NewServiceResults(resultsStudentRepository) 30 | resultsStudentHandler := handlerResultsStudent.NewHandlerResultsStudent(resultsStudentService) 31 | 32 | resultStudentRepository := resultStudent.NewRepositoryResult(db) 33 | resultStudentService := resultStudent.NewServiceResult(resultStudentRepository) 34 | resultStudentHandler := handlerResultStudent.NewHandlerResultStudent(resultStudentService) 35 | 36 | deleteStudentRepository := deleteStudent.NewRepositoryDelete(db) 37 | deleteStudentService := deleteStudent.NewServiceDelete(deleteStudentRepository) 38 | deleteStudentHandler := handlerDeleteStudent.NewHandlerDeleteStudent(deleteStudentService) 39 | 40 | updateStudentRepository := updateStudent.NewRepositoryUpdate(db) 41 | updateStudentService := updateStudent.NewServiceUpdate(updateStudentRepository) 42 | updateStudentHandler := handlerUpdateStudent.NewHandlerUpdateStudent(updateStudentService) 43 | 44 | /** 45 | @description All Student Route 46 | */ 47 | groupRoute := route.Group("/api/v1").Use(middleware.Auth()) 48 | groupRoute.POST("/student", createStudentHandler.CreateStudentHandler) 49 | groupRoute.GET("/student", resultsStudentHandler.ResultsStudentHandler) 50 | groupRoute.GET("/student/:id", resultStudentHandler.ResultStudentHandler) 51 | groupRoute.DELETE("/student/:id", deleteStudentHandler.DeleteStudentHandler) 52 | groupRoute.PUT("/student/:id", updateStudentHandler.UpdateStudentHandler) 53 | } 54 | -------------------------------------------------------------------------------- /swagger/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swaggerapi/swagger-ui:latest 2 | USER ${USER} 3 | COPY ./swagger/openapi.yml /usr/share/nginx/html -------------------------------------------------------------------------------- /swagger/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | version: 1.0.0 4 | title: Go Gin Framework Rest API 5 | description: Example go using gin framework implementation rest api 6 | contact: 7 | name: Restu Wahyu Saputra 8 | url: https://github.com/restuwahyu13 9 | email: restuwahyu13@gmail.com 10 | license: 11 | name: MIT 12 | url: https://github.com/restuwahyu13/gin-rest-api/blob/main/README.md 13 | servers: 14 | - url: 'http://{host}:4000/api/v1' 15 | description: go service api server endpoint application 16 | variables: 17 | host: 18 | default: localhost 19 | enum: 20 | - localhost 21 | paths: 22 | ############################ 23 | ## REGISTER AUTH ENDPOINT 24 | ############################ 25 | /register: 26 | post: 27 | tags: 28 | - Auth Endpoint 29 | summary: Auth Register 30 | description: register new users account 31 | requestBody: 32 | required: true 33 | content: 34 | application/json: 35 | schema: 36 | type: object 37 | properties: 38 | fullname: 39 | type: string 40 | example: restu wahyu saputra 41 | email: 42 | type: string 43 | format: email 44 | example: restuwahyu13@zetmail.com 45 | password: 46 | type: string 47 | format: password 48 | example: qwerty123456789 49 | required: 50 | - fullname 51 | - email 52 | - password 53 | responses: 54 | '200': 55 | description: Register new account successfully 56 | content: 57 | application/json: 58 | schema: 59 | type: object 60 | properties: 61 | statusCode: 62 | type: integer 63 | format: number 64 | method: 65 | type: string 66 | message: 67 | type: string 68 | required: 69 | - statusCode 70 | - method 71 | - message 72 | '400': 73 | description: | 74 | Parsing json data failed, 75 | Generate accessToken failed, 76 | Sending email activation failed 77 | '403': 78 | description: Register new account failed" 79 | '409': 80 | description: Email already exist 81 | ############################ 82 | ## LOGIN AUTH ENDPOINT 83 | ############################ 84 | /login: 85 | post: 86 | tags: 87 | - Auth Endpoint 88 | summary: Auth Login 89 | description: login user account 90 | requestBody: 91 | required: true 92 | content: 93 | application/json: 94 | schema: 95 | type: object 96 | properties: 97 | email: 98 | type: string 99 | format: email 100 | example: restuwahyu13@zetmail.com 101 | password: 102 | type: string 103 | format: password 104 | example: qwerty123456789 105 | required: 106 | - email 107 | - password 108 | responses: 109 | '200': 110 | description: Login successfully 111 | content: 112 | application/json: 113 | schema: 114 | type: object 115 | properties: 116 | statusCode: 117 | type: integer 118 | format: number 119 | method: 120 | type: string 121 | message: 122 | type: string 123 | data: 124 | type: object 125 | required: 126 | - statusCode 127 | - method 128 | - message 129 | - data 130 | '400': 131 | description: | 132 | Parsing json data failed, 133 | Generate accessToken failed 134 | '403': 135 | description: | 136 | User account is not active, 137 | Username or password is wrong 138 | '404': 139 | description: User account is not registered 140 | ############################ 141 | ## ACTIVATION AUTH ENDPOINT 142 | ############################ 143 | /activation/{token}: 144 | post: 145 | tags: 146 | - Auth Endpoint 147 | summary: Auth Activation 148 | description: activation user account 149 | parameters: 150 | - in: path 151 | name: token 152 | required: true 153 | schema: 154 | type: string 155 | example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uIjp0cnVlLCJlbWFpbCI6ImFsZGlraGFuMTNAemV0bWFpbC5jb20iLCJleHBpcmVkQXQiOjE2MTkyNzkyMDksImlkIjoxMX0.jKxrGfrkNrINdixekYGuDKen24LMGgLCnrXLD3R7Yw8 156 | responses: 157 | '200': 158 | description: Activation account success 159 | content: 160 | application/json: 161 | schema: 162 | type: object 163 | properties: 164 | statusCode: 165 | type: integer 166 | format: number 167 | method: 168 | type: string 169 | message: 170 | type: string 171 | required: 172 | - statusCode 173 | - method 174 | - message 175 | '400': 176 | description: | 177 | Parsing json data failed, 178 | User account hash been active please login 179 | '403': 180 | description: Activation account failed 181 | '404': 182 | description: User account is not exist 183 | ############################# 184 | ## RESEND TOKEN AUTH ENDPOINT 185 | ############################# 186 | /resend-token: 187 | post: 188 | tags: 189 | - Auth Endpoint 190 | summary: Auth Resend 191 | description: resend new activation token 192 | requestBody: 193 | required: true 194 | content: 195 | application/json: 196 | schema: 197 | type: object 198 | properties: 199 | email: 200 | type: string 201 | format: email 202 | example: restuwahyu13@zetmail.com 203 | required: 204 | - email 205 | responses: 206 | '200': 207 | description: Resend new activation token successfully 208 | content: 209 | application/json: 210 | schema: 211 | type: object 212 | properties: 213 | statusCode: 214 | type: integer 215 | format: number 216 | method: 217 | type: string 218 | message: 219 | type: string 220 | required: 221 | - statusCode 222 | - method 223 | - message 224 | '400': 225 | description: | 226 | Parsing json data failed, 227 | Generate accessToken failed, 228 | Sending email resend activation failed 229 | '403': 230 | description: User account is not active 231 | '404': 232 | description: Email is not never registered 233 | ################################# 234 | ## FORGOT PASSWORD AUTH ENDPOINT 235 | ################################# 236 | /forgot-password: 237 | post: 238 | tags: 239 | - Auth Endpoint 240 | summary: Auth Forgot 241 | description: forgot password account 242 | requestBody: 243 | required: true 244 | content: 245 | application/json: 246 | schema: 247 | type: object 248 | properties: 249 | email: 250 | type: string 251 | format: email 252 | example: restuwahyu13@zetmail.com 253 | required: 254 | - email 255 | responses: 256 | '200': 257 | description: Forgot password successfully 258 | content: 259 | application/json: 260 | schema: 261 | type: object 262 | properties: 263 | statusCode: 264 | type: integer 265 | format: number 266 | method: 267 | type: string 268 | message: 269 | type: string 270 | required: 271 | - statusCode 272 | - method 273 | - message 274 | '400': 275 | description: | 276 | Parsing json data failed, 277 | Generate accessToken failed, 278 | Sending email reset password failed 279 | '403': 280 | description: | 281 | User account is not active, 282 | Forgot password failed 283 | '404': 284 | description: Email is not never registered 285 | ################################# 286 | ## CHANGE PASSWORD AUTH ENDPOINT 287 | ################################# 288 | /change-password/{token}: 289 | post: 290 | tags: 291 | - Auth Endpoint 292 | summary: Auth Login 293 | description: login user account 294 | parameters: 295 | - in: path 296 | name: token 297 | required: true 298 | schema: 299 | type: string 300 | example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uIjp0cnVlLCJlbWFpbCI6ImFsZGlraGFuMTNAemV0bWFpbC5jb20iLCJleHBpcmVkQXQiOjE2MTkyNzkyMDksImlkIjoxMX0.jKxrGfrkNrINdixekYGuDKen24LMGgLCnrXLD3R7Yw8 301 | responses: 302 | '200': 303 | description: Change new password successfully 304 | content: 305 | application/json: 306 | schema: 307 | type: object 308 | properties: 309 | statusCode: 310 | type: integer 311 | format: number 312 | method: 313 | type: string 314 | message: 315 | type: string 316 | required: 317 | - statusCode 318 | - method 319 | - message 320 | '400': 321 | description: | 322 | Parsing json data failed, 323 | Verified activation token failed, 324 | Confirm Password is not match with Password 325 | '403': 326 | description: | 327 | User account is not active, 328 | Change new password failed 329 | '404': 330 | description: User account is not exist 331 | ################################# 332 | ## STUDENT ENDPOINT TERITORY 333 | ################################# 334 | /student: 335 | ################################# 336 | ## CREATE STUDENT ENDPOINT 337 | ################################# 338 | post: 339 | tags: 340 | - Student Endpoint 341 | summary: Create Student 342 | description: create new student account 343 | security: 344 | - bearerAuth: [] 345 | requestBody: 346 | required: true 347 | content: 348 | application/json: 349 | schema: 350 | type: object 351 | properties: 352 | name: 353 | type: string 354 | example: restu wahyu saputra 355 | npm: 356 | type: integer 357 | format: number 358 | example: 201543502291 359 | fak: 360 | type: string 361 | example: mipa 362 | bid: 363 | type: string 364 | example: tehnik informatika 365 | required: 366 | - name 367 | - npm 368 | - fak 369 | - bid 370 | responses: 371 | '200': 372 | description: Create new student account successfully 373 | content: 374 | application/json: 375 | schema: 376 | type: object 377 | properties: 378 | statusCode: 379 | type: integer 380 | format: number 381 | method: 382 | type: string 383 | message: 384 | type: string 385 | required: 386 | - statusCode 387 | - method 388 | - message 389 | '400': 390 | description: Parsing json data failed 391 | '401': 392 | description: accessToken invalid or expired 393 | '403': 394 | description: | 395 | Authorization is required for this endpoint, 396 | Create new student account failed 397 | '409': 398 | description: CREATE_STUDENT_CONFLICT_409 399 | ################################# 400 | ## RESULTS STUDENT ENDPOINT 401 | ################################# 402 | get: 403 | tags: 404 | - Student Endpoint 405 | summary: Results All Student 406 | description: results all student account exists 407 | security: 408 | - bearerAuth: [] 409 | responses: 410 | '200': 411 | description: Results Students data successfully 412 | content: 413 | application/json: 414 | schema: 415 | type: object 416 | properties: 417 | statusCode: 418 | type: integer 419 | format: number 420 | method: 421 | type: string 422 | message: 423 | type: string 424 | data: 425 | type: array 426 | required: 427 | - statusCode 428 | - method 429 | - message 430 | - data 431 | '401': 432 | description: accessToken invalid or expired 433 | '403': 434 | description: Authorization is required for this endpoint 435 | '404': 436 | description: Students data is not exists 437 | ################################### 438 | ## STUDENT ENDPOINT TERITORY BY ID 439 | ################################### 440 | /student/{id}: 441 | ################################# 442 | ## RESULT STUDENT ENDPOINT 443 | ################################# 444 | get: 445 | tags: 446 | - Student Endpoint 447 | summary: Result Student 448 | description: result specific student by id 449 | security: 450 | - bearerAuth: [] 451 | parameters: 452 | - in: path 453 | name: id 454 | required: true 455 | schema: 456 | type: string 457 | format: uuid 458 | example: b78bf89d-7748-42c0-b971-3bb7d28d167e 459 | responses: 460 | '200': 461 | description: Result Student data successfully 462 | content: 463 | application/json: 464 | schema: 465 | type: object 466 | properties: 467 | statusCode: 468 | type: integer 469 | format: number 470 | method: 471 | type: string 472 | message: 473 | type: string 474 | data: 475 | type: array 476 | required: 477 | - statusCode 478 | - method 479 | - message 480 | - data 481 | '401': 482 | description: accessToken invalid or expired 483 | '403': 484 | description: Authorization is required for this endpoint 485 | '404': 486 | description: Student data is not exist or deleted 487 | ################################# 488 | ## DELETE STUDENT ENDPOINT 489 | ################################# 490 | delete: 491 | tags: 492 | - Student Endpoint 493 | summary: Delete Student 494 | description: delete specific student by id 495 | security: 496 | - bearerAuth: [] 497 | parameters: 498 | - in: path 499 | name: id 500 | required: true 501 | schema: 502 | type: string 503 | format: uuid 504 | example: b78bf89d-7748-42c0-b971-3bb7d28d167e 505 | responses: 506 | '200': 507 | description: Delete student data successfully 508 | content: 509 | application/json: 510 | schema: 511 | type: object 512 | properties: 513 | statusCode: 514 | type: integer 515 | format: number 516 | method: 517 | type: string 518 | message: 519 | type: string 520 | required: 521 | - statusCode 522 | - method 523 | - message 524 | '401': 525 | description: accessToken invalid or expired 526 | '403': 527 | description: | 528 | Authorization is required for this endpoint, 529 | Delete student data failed 530 | '404': 531 | description: Student data is not exist or deleted 532 | ################################# 533 | ## UPDATE STUDENT ENDPOINT 534 | ################################# 535 | put: 536 | tags: 537 | - Student Endpoint 538 | summary: Update Student 539 | description: update specific student by id 540 | security: 541 | - bearerAuth: [] 542 | parameters: 543 | - in: path 544 | name: id 545 | required: true 546 | schema: 547 | type: string 548 | format: uuid 549 | example: b78bf89d-7748-42c0-b971-3bb7d28d167e 550 | requestBody: 551 | required: true 552 | content: 553 | application/json: 554 | schema: 555 | type: object 556 | properties: 557 | name: 558 | type: string 559 | example: restu wahyu saputra 560 | npm: 561 | type: integer 562 | format: number 563 | example: 201543502291 564 | fak: 565 | type: string 566 | example: mipa 567 | bid: 568 | type: string 569 | example: tehnik informatika 570 | required: 571 | - name 572 | - npm 573 | - fak 574 | - bid 575 | responses: 576 | '200': 577 | description: Update student data sucessfully 578 | content: 579 | application/json: 580 | schema: 581 | type: object 582 | properties: 583 | statusCode: 584 | type: integer 585 | format: number 586 | method: 587 | type: string 588 | message: 589 | type: string 590 | required: 591 | - statusCode 592 | - method 593 | - message 594 | '400': 595 | description: Parsing json data failed 596 | '401': 597 | description: accessToken invalid or expired 598 | '403': 599 | description: | 600 | Authorization is required for this endpoint, 601 | Update student data failed 602 | '404': 603 | description: Student data is not exist or deleted 604 | ################################# 605 | ################################# 606 | ## COMPONENTS AUTH TERITORY 607 | ################################# 608 | ################################# 609 | components: 610 | securitySchemes: 611 | bearerAuth: 612 | type: http 613 | scheme: bearer 614 | bearerFormat: JWT 615 | -------------------------------------------------------------------------------- /templates/template_register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Email Template 5 | 6 | 7 | 123 | 124 | 125 | 126 |
127 |
128 | 131 |
132 |

Hello Dear {{.To}}

133 |

Kepada user YTH silahkan konfirmasi account anda:

134 |
135 | 136 | 145 |
146 |
147 |
148 |
149 | 150 | 151 | -------------------------------------------------------------------------------- /templates/template_resend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Email Template 5 | 6 | 7 | 110 | 111 | 112 |
113 |
114 | 117 |
118 |

Hello Dear {{.To}}

119 |

Kepada user YTH silahkan konfirmasi account anda:

120 |
121 | 124 | 133 |
134 |
135 |
136 |
137 | 138 | 139 | -------------------------------------------------------------------------------- /templates/template_reset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Email Template 5 | 6 | 7 | 110 | 111 | 112 |
113 |
114 | 117 |
118 |

Hello Dear {{.To}}

119 |

Kepada user YTH Berikut adalah konfirmasi pemulihan account anda:

120 |
121 | 124 | 133 |
134 |
135 |
136 |
137 | 138 | 139 | -------------------------------------------------------------------------------- /utils/bcrypt.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "golang.org/x/crypto/bcrypt" 6 | ) 7 | 8 | func HashPassword(password string) string { 9 | pw := []byte(password) 10 | result, err := bcrypt.GenerateFromPassword(pw, bcrypt.DefaultCost) 11 | if err != nil { 12 | logrus.Fatal(err.Error()) 13 | } 14 | return string(result) 15 | } 16 | 17 | func ComparePassword(hashPassword string, password string) error { 18 | pw := []byte(password) 19 | hw := []byte(hashPassword) 20 | err := bcrypt.CompareHashAndPassword(hw, pw) 21 | return err 22 | } 23 | -------------------------------------------------------------------------------- /utils/dotenv.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/joho/godotenv" 7 | ) 8 | 9 | func GodotEnv(key string) string { 10 | env := make(chan string, 1) 11 | 12 | if os.Getenv("GO_ENV") != "production" { 13 | godotenv.Load(".env") 14 | env <- os.Getenv(key) 15 | } else { 16 | env <- os.Getenv(key) 17 | } 18 | 19 | return <-env 20 | } 21 | -------------------------------------------------------------------------------- /utils/html.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "text/template" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | type BodyRequest struct { 12 | To string 13 | Token string 14 | } 15 | 16 | func ParseHtml(fileName string, data map[string]string) string { 17 | html, errParse := template.ParseFiles("templates/" + fileName + ".html") 18 | 19 | if errParse != nil { 20 | defer fmt.Println("parser file html failed") 21 | logrus.Fatal(errParse.Error()) 22 | } 23 | 24 | body := BodyRequest{To: data["to"], Token: data["token"]} 25 | 26 | buf := new(bytes.Buffer) 27 | errExecute := html.Execute(buf, body) 28 | 29 | if errExecute != nil { 30 | defer fmt.Println("execute html file failed") 31 | logrus.Fatal(errExecute.Error()) 32 | } 33 | 34 | return buf.String() 35 | } 36 | -------------------------------------------------------------------------------- /utils/httpTest.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "net/http" 7 | "net/http/httptest" 8 | ) 9 | 10 | func HttpTestRequest(method, url string, payload []byte) (*httptest.ResponseRecorder, *http.Request, error) { 11 | 12 | request := make(chan *http.Request, 1) 13 | errors := make(chan error, 1) 14 | 15 | if binary.Size(payload) > 0 { 16 | req, err := http.NewRequest(method, url, bytes.NewBuffer(payload)) 17 | request <- req 18 | errors <- err 19 | } else { 20 | req, err := http.NewRequest(method, url, nil) 21 | request <- req 22 | errors <- err 23 | } 24 | 25 | rr := httptest.NewRecorder() 26 | 27 | return rr, <-request, <-errors 28 | } 29 | -------------------------------------------------------------------------------- /utils/json.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | type Response struct { 10 | StatusCode int `json:"statusCode"` 11 | Method string `json:"method"` 12 | Message string `json:"message"` 13 | Data interface{} `json:"data"` 14 | } 15 | 16 | func Strigify(payload interface{}) []byte { 17 | response, _ := json.Marshal(payload) 18 | return response 19 | } 20 | 21 | func Parse(payload []byte) Response { 22 | var jsonResponse Response 23 | err := json.Unmarshal(payload, &jsonResponse) 24 | 25 | if err != nil { 26 | logrus.Fatal(err.Error()) 27 | } 28 | 29 | return jsonResponse 30 | } 31 | -------------------------------------------------------------------------------- /utils/jwt.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | "time" 7 | 8 | "github.com/dgrijalva/jwt-go" 9 | "github.com/gin-gonic/gin" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type MetaToken struct { 14 | ID string 15 | Email string 16 | ExpiredAt time.Time 17 | Authorization bool 18 | } 19 | 20 | type AccessToken struct { 21 | Claims MetaToken 22 | } 23 | 24 | func Sign(Data map[string]interface{}, SecrePublicKeyEnvName string, ExpiredAt time.Duration) (string, error) { 25 | 26 | expiredAt := time.Now().Add(time.Duration(time.Minute) * ExpiredAt).Unix() 27 | 28 | jwtSecretKey := GodotEnv(SecrePublicKeyEnvName) 29 | 30 | claims := jwt.MapClaims{} 31 | claims["exp"] = expiredAt 32 | claims["authorization"] = true 33 | 34 | for i, v := range Data { 35 | claims[i] = v 36 | } 37 | 38 | to := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 39 | accessToken, err := to.SignedString([]byte(jwtSecretKey)) 40 | 41 | if err != nil { 42 | logrus.Error(err.Error()) 43 | return accessToken, err 44 | } 45 | 46 | return accessToken, nil 47 | } 48 | 49 | func VerifyTokenHeader(ctx *gin.Context, SecrePublicKeyEnvName string) (*jwt.Token, error) { 50 | tokenHeader := ctx.GetHeader("Authorization") 51 | accessToken := strings.SplitAfter(tokenHeader, "Bearer")[1] 52 | jwtSecretKey := GodotEnv(SecrePublicKeyEnvName) 53 | 54 | token, err := jwt.Parse(strings.Trim(accessToken, " "), func(token *jwt.Token) (interface{}, error) { 55 | return []byte(jwtSecretKey), nil 56 | }) 57 | 58 | if err != nil { 59 | logrus.Error(err.Error()) 60 | return nil, err 61 | } 62 | 63 | return token, nil 64 | } 65 | 66 | func VerifyToken(accessToken, SecrePublicKeyEnvName string) (*jwt.Token, error) { 67 | jwtSecretKey := GodotEnv(SecrePublicKeyEnvName) 68 | 69 | token, err := jwt.Parse(accessToken, func(token *jwt.Token) (interface{}, error) { 70 | return []byte(jwtSecretKey), nil 71 | }) 72 | 73 | if err != nil { 74 | logrus.Error(err.Error()) 75 | return nil, err 76 | } 77 | 78 | return token, nil 79 | } 80 | 81 | func DecodeToken(accessToken *jwt.Token) AccessToken { 82 | var token AccessToken 83 | stringify, _ := json.Marshal(&accessToken) 84 | json.Unmarshal([]byte(stringify), &token) 85 | 86 | return token 87 | } 88 | -------------------------------------------------------------------------------- /utils/randomString.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "math/rand" 4 | 5 | var letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 6 | 7 | func RandStringBytes(n int) string { 8 | b := make([]byte, n) 9 | for i := range b { 10 | b[i] = letterBytes[rand.Intn(len(letterBytes))] 11 | } 12 | return string(b) 13 | } 14 | -------------------------------------------------------------------------------- /utils/response.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | type Responses struct { 8 | StatusCode int `json:"statusCode"` 9 | Method string `json:"method"` 10 | Message string `json:"message"` 11 | Data interface{} `json:"data"` 12 | } 13 | 14 | type ErrorResponse struct { 15 | StatusCode int `json:"statusCode"` 16 | Method string `json:"method"` 17 | Error interface{} `json:"error"` 18 | } 19 | 20 | func APIResponse(ctx *gin.Context, Message string, StatusCode int, Method string, Data interface{}) { 21 | 22 | jsonResponse := Responses{ 23 | StatusCode: StatusCode, 24 | Method: Method, 25 | Message: Message, 26 | Data: Data, 27 | } 28 | 29 | if StatusCode >= 400 { 30 | ctx.JSON(StatusCode, jsonResponse) 31 | defer ctx.AbortWithStatus(StatusCode) 32 | } else { 33 | ctx.JSON(StatusCode, jsonResponse) 34 | } 35 | } 36 | 37 | func ValidatorErrorResponse(ctx *gin.Context, StatusCode int, Method string, Error interface{}) { 38 | errResponse := ErrorResponse{ 39 | StatusCode: StatusCode, 40 | Method: Method, 41 | Error: Error, 42 | } 43 | 44 | ctx.JSON(StatusCode, errResponse) 45 | defer ctx.AbortWithStatus(StatusCode) 46 | } 47 | -------------------------------------------------------------------------------- /utils/sendgrid.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/sendgrid/rest" 5 | "github.com/sendgrid/sendgrid-go" 6 | "github.com/sendgrid/sendgrid-go/helpers/mail" 7 | ) 8 | 9 | func SendGridMail(name, email, subject, fileName, token string) (*rest.Response, error) { 10 | from := mail.NewEmail("admin", "admin@unindra.com") 11 | to := mail.NewEmail(name, email) 12 | subjectMail := subject 13 | template := ParseHtml(fileName, map[string]string{ 14 | "to": email, 15 | "token": token, 16 | }) 17 | 18 | message := mail.NewSingleEmail(from, subjectMail, to, "", template) 19 | client := sendgrid.NewSendClient(GodotEnv("SG_API_KEY")) 20 | return client.Send(message) 21 | } 22 | -------------------------------------------------------------------------------- /utils/validator.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/go-playground/validator/v10" 5 | gpc "github.com/restuwahyu13/go-playground-converter" 6 | ) 7 | 8 | func GoValidator(s interface{}, config []gpc.ErrorMetaConfig) (interface{}, int) { 9 | var validate *validator.Validate 10 | validators := gpc.NewValidator(validate) 11 | bind := gpc.NewBindValidator(validators) 12 | 13 | errResponse, errCount := bind.BindValidator(s, config) 14 | return errResponse, errCount 15 | } 16 | --------------------------------------------------------------------------------