replace redis with mysql
This commit is contained in:
25
README.md
25
README.md
@@ -4,17 +4,28 @@ Sample of hexagonal architecture to handle login logic and user CRUD
|
||||
How to run
|
||||
---
|
||||
#### Set Environment Variable
|
||||
This application support 2 kind of database Redis and MongoDB to prove our ports is completely agnostic from the implementation.
|
||||
This application support 2 kind of database MySQL and MongoDB to prove our ports is completely agnostic from the implementation.
|
||||
By default it will connect into our mongo DB database with default host & port `localhost:27017` and collection `local`
|
||||
To connect into different database we need to set database information in environment variable
|
||||
To connect into different database we need to set database information in environment variable
|
||||
|
||||
```go
|
||||
1. MongoDB
|
||||
|
||||
```cli
|
||||
set mongo_url=mongodb://localhost:27017/local
|
||||
set mongo_timeout=30
|
||||
set mongo_db=local
|
||||
set url_db=mongo
|
||||
```
|
||||
|
||||
2. MySQL
|
||||
|
||||
```cli
|
||||
set mysql_url=root:Password.1@tcp(127.0.0.1:3306)/tes
|
||||
set mysql_timeout=10
|
||||
set mysql_db=tes
|
||||
set url_db=mysql
|
||||
```
|
||||
|
||||
After setting the database information we only need to run the main.go file
|
||||
`go run main.go`
|
||||
|
||||
@@ -91,8 +102,8 @@ We also can make sure that our _**Domain Logic**_ are testable without any of th
|
||||
#### The service that we are going to build
|
||||
|
||||
So we have our service which is a user management and login and it will connect to serializer which will either serialize the data into json or message pack before serving it through REST API
|
||||
And then on the other side we have our repository which will either choose to use MongoDB or Redis based on how we start the application from command line.
|
||||
So basically our API will be able to accept JSON or message pack format and also our repository is able to use both MongoDB and Redis and it won't really affect our service
|
||||
And then on the other side we have our repository which will either choose to use MongoDB or MySQL based on how we start the application from command line.
|
||||
So basically our API will be able to accept JSON or message pack format and also our repository is able to use both MongoDB and MySQL and it won't really affect our service
|
||||
|
||||
Project Structure
|
||||
---
|
||||
@@ -106,8 +117,8 @@ contains data models
|
||||
contains **Port** interface for repository adapter
|
||||
- **mongodb**
|
||||
contains mongo **Adapter** that implement UserRepository interface. This package will store mongo client and connect to mongoDB database to handle database query or command
|
||||
- **redis**
|
||||
contains redis **Adapter** that implement UserRepository interface. This package will store redis client and connect to redis server to handle database query or data manipulation
|
||||
- **mysql**
|
||||
contains MySQL **Adapter** that implement UserRepository interface. This package will store MySQL client and connect to MySQL server to handle database query or data manipulation
|
||||
4. **serializer**
|
||||
contains **Port** interface for decode and encode serializer. It will be used in our API to decode and encode data.
|
||||
- **json**
|
||||
|
||||
@@ -30,10 +30,20 @@ import (
|
||||
===================================
|
||||
TO SET DATABASE INFO FROM TERMINAL
|
||||
===================================
|
||||
=======
|
||||
MongoDB
|
||||
=======
|
||||
set mongo_url=mongodb://localhost:27017/local
|
||||
set mongo_timeout=10
|
||||
set mongo_db=local
|
||||
set url_db=mongo
|
||||
=======
|
||||
MySQL
|
||||
=======
|
||||
set mysql_url=root:Password.1@tcp(127.0.0.1:3306)/tes
|
||||
set mysql_timeout=10
|
||||
set mysql_db=tes
|
||||
set url_db=mysql
|
||||
*/
|
||||
|
||||
var (
|
||||
|
||||
@@ -32,10 +32,20 @@ import (
|
||||
===================================
|
||||
TO SET DATABASE INFO FROM TERMINAL
|
||||
===================================
|
||||
=======
|
||||
MongoDB
|
||||
=======
|
||||
set mongo_url=mongodb://localhost:27017/local
|
||||
set mongo_timeout=10
|
||||
set mongo_db=local
|
||||
set url_db=mongo
|
||||
=======
|
||||
MySQL
|
||||
=======
|
||||
set mysql_url=root:Password.1@tcp(127.0.0.1:3306)/tes
|
||||
set mysql_timeout=10
|
||||
set mysql_db=tes
|
||||
set url_db=mysql
|
||||
*/
|
||||
|
||||
var (
|
||||
|
||||
@@ -30,7 +30,7 @@ func (u *userService) GetAll() ([]m.User, error) {
|
||||
}
|
||||
|
||||
func (u *userService) GetById(id string) (*m.User, error) {
|
||||
res, e := u.userRepo.GetBy(map[string]interface{}{"_id": id})
|
||||
res, e := u.userRepo.GetBy(map[string]interface{}{"ID": id})
|
||||
if e != nil {
|
||||
return res, e
|
||||
}
|
||||
@@ -62,14 +62,14 @@ func (u *userService) Update(user *m.User) error {
|
||||
if user.Password != "" {
|
||||
user.Password = repo.EncryptPassword(user.Password)
|
||||
}
|
||||
return u.userRepo.Update(user, map[string]interface{}{"_id": user.ID})
|
||||
return u.userRepo.Update(user)
|
||||
|
||||
}
|
||||
func (u *userService) Delete(user *m.User) error {
|
||||
if user.ID == "" {
|
||||
return errs.Wrap(helper.ErrUserNotFound, "service.User.Delete")
|
||||
}
|
||||
if e := u.userRepo.Delete(map[string]interface{}{"_id": user.ID}); e != nil {
|
||||
if e := u.userRepo.Delete(user); e != nil {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string `json:"ID" bson:"_id" msgpack:"_id"`
|
||||
Username string `json:"Username" bson:"Username" msgpack:"Username"`
|
||||
Email string `json:"Email" bson:"Email" msgpack:"Email"`
|
||||
Password string `json:"Password" bson:"Password" msgpack:"Password"`
|
||||
Name string `json:"Name" bson:"Name" msgpack:"Name"`
|
||||
Address string `json:"Address" bson:"Address" msgpack:"Address"`
|
||||
IsActive bool `json:"IsActive" bson:"IsActive" msgpack:"IsActive"`
|
||||
ID string `json:"ID" bson:"_id" msgpack:"_id" db:"ID"`
|
||||
Name string `json:"Name" bson:"Name" msgpack:"Name" db:"Name"`
|
||||
Username string `json:"Username" bson:"Username" msgpack:"Username" db:"Username"`
|
||||
Email string `json:"Email" bson:"Email" msgpack:"Email" db:"Email"`
|
||||
Password string `json:"Password" bson:"Password" msgpack:"Password" db:"Password"`
|
||||
Address string `json:"Address" bson:"Address" msgpack:"Address" db:"Address"`
|
||||
IsActive bool `json:"IsActive" bson:"IsActive" msgpack:"IsActive" db:"IsActive"`
|
||||
}
|
||||
|
||||
func NewUser() *User {
|
||||
@@ -33,24 +29,26 @@ func NewUserDefaultData() *User {
|
||||
return user
|
||||
}
|
||||
|
||||
func (user *User) FormingUserData(data map[string]string) {
|
||||
user.ID = data["ID"]
|
||||
user.Username = data["Username"]
|
||||
user.Email = data["Email"]
|
||||
user.Password = data["Password"]
|
||||
user.Name = data["Name"]
|
||||
user.Address = data["Address"]
|
||||
user.IsActive, _ = strconv.ParseBool(data["IsActive"])
|
||||
}
|
||||
|
||||
func (user *User) GetMapFormat() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"ID": user.ID,
|
||||
"Name": user.Name,
|
||||
"Username": user.Username,
|
||||
"Email": user.Email,
|
||||
"Password": user.Password,
|
||||
"Name": user.Name,
|
||||
"Address": user.Address,
|
||||
"IsActive": user.IsActive,
|
||||
}
|
||||
}
|
||||
|
||||
func (user *User) SplitByField() []interface{} {
|
||||
return []interface{}{
|
||||
user.ID,
|
||||
user.Name,
|
||||
user.Username,
|
||||
user.Email,
|
||||
user.Password,
|
||||
user.Address,
|
||||
user.IsActive,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,34 +6,46 @@ import (
|
||||
"strconv"
|
||||
|
||||
repo "github.com/rinosukmandityo/hexagonal-login/repositories"
|
||||
mr "github.com/rinosukmandityo/hexagonal-login/repositories/mongodb"
|
||||
rr "github.com/rinosukmandityo/hexagonal-login/repositories/redis"
|
||||
mg "github.com/rinosukmandityo/hexagonal-login/repositories/mongodb"
|
||||
mr "github.com/rinosukmandityo/hexagonal-login/repositories/mysql"
|
||||
)
|
||||
|
||||
func ChooseRepo() repo.UserRepository {
|
||||
switch os.Getenv("url_db") {
|
||||
case "redis":
|
||||
redisURL := os.Getenv("redis_url")
|
||||
repo, e := rr.NewUserRedisRepository(redisURL)
|
||||
case "mysql":
|
||||
url := os.Getenv("mysql_url")
|
||||
if url == "" {
|
||||
url = "root:Password.1@tcp(127.0.0.1:3306)/tes"
|
||||
}
|
||||
db := os.Getenv("mysql_db")
|
||||
if db == "" {
|
||||
db = "tes"
|
||||
}
|
||||
timeout, _ := strconv.Atoi(os.Getenv("mysql_timeout"))
|
||||
if timeout == 0 {
|
||||
timeout = 10
|
||||
}
|
||||
repo, e := mr.NewUserRepository(url, db, timeout)
|
||||
if e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
|
||||
return repo
|
||||
default:
|
||||
mongoURL := os.Getenv("mongo_url")
|
||||
if mongoURL == "" {
|
||||
mongoURL = "mongodb://localhost:27017/local"
|
||||
url := os.Getenv("mongo_url")
|
||||
if url == "" {
|
||||
url = "mongodb://localhost:27017/local"
|
||||
}
|
||||
mongoDB := os.Getenv("mongo_db")
|
||||
if mongoDB == "" {
|
||||
mongoDB = "local"
|
||||
db := os.Getenv("mongo_db")
|
||||
if db == "" {
|
||||
db = "local"
|
||||
}
|
||||
mongoTimeout, _ := strconv.Atoi(os.Getenv("mongo_timeout"))
|
||||
if mongoTimeout == 0 {
|
||||
mongoTimeout = 10
|
||||
timeout, _ := strconv.Atoi(os.Getenv("mongo_timeout"))
|
||||
if timeout == 0 {
|
||||
timeout = 10
|
||||
}
|
||||
|
||||
repo, e := mr.NewUserMongoRepository(mongoURL, mongoDB, mongoTimeout)
|
||||
repo, e := mg.NewUserMongoRepository(url, db, timeout)
|
||||
if e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
|
||||
@@ -66,11 +66,15 @@ func (r *userMongoRepository) GetBy(filter map[string]interface{}) (*m.User, err
|
||||
ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
|
||||
defer cancel()
|
||||
c := r.client.Database(r.database).Collection(res.TableName())
|
||||
if _, ok := filter["ID"]; ok {
|
||||
filter["_id"] = filter["ID"]
|
||||
delete(filter, "ID")
|
||||
}
|
||||
if e := c.FindOne(ctx, filter).Decode(res); e != nil {
|
||||
if e == mongo.ErrNoDocuments {
|
||||
return res, errors.Wrap(helper.ErrUserNotFound, "repository.User.GetById")
|
||||
return res, errors.Wrap(helper.ErrUserNotFound, "repository.User.GetBy")
|
||||
}
|
||||
return res, errors.Wrap(e, "repository.User.GetById")
|
||||
return res, errors.Wrap(e, "repository.User.GetBy")
|
||||
}
|
||||
return res, nil
|
||||
|
||||
@@ -86,10 +90,11 @@ func (r *userMongoRepository) Store(data *m.User) error {
|
||||
return nil
|
||||
|
||||
}
|
||||
func (r *userMongoRepository) Update(data *m.User, filter map[string]interface{}) error {
|
||||
func (r *userMongoRepository) Update(data *m.User) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
|
||||
defer cancel()
|
||||
c := r.client.Database(r.database).Collection(data.TableName())
|
||||
filter := map[string]interface{}{"_id": data.ID}
|
||||
if res, e := c.UpdateOne(ctx, filter, bson.M{"$set": data}, options.Update().SetUpsert(false)); e != nil {
|
||||
return errors.Wrap(e, "repository.User.Update")
|
||||
} else {
|
||||
@@ -101,9 +106,10 @@ func (r *userMongoRepository) Update(data *m.User, filter map[string]interface{}
|
||||
return nil
|
||||
|
||||
}
|
||||
func (r *userMongoRepository) Delete(filter map[string]interface{}) error {
|
||||
func (r *userMongoRepository) Delete(data *m.User) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
|
||||
defer cancel()
|
||||
filter := map[string]interface{}{"_id": data.ID}
|
||||
c := r.client.Database(r.database).Collection(new(m.User).TableName())
|
||||
if res, e := c.DeleteOne(ctx, filter); e != nil {
|
||||
return errors.Wrap(e, "repository.User.Delete")
|
||||
|
||||
88
repositories/mysql/query_construct.go
Normal file
88
repositories/mysql/query_construct.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
m "github.com/rinosukmandityo/hexagonal-login/models"
|
||||
)
|
||||
|
||||
func constructUpdateQuery(data *m.User, filter map[string]interface{}) (string, []interface{}) {
|
||||
// "UPDATE <tablename> SET field1=?, field2=? WHERE filter1=?"
|
||||
dataMap := data.GetMapFormat()
|
||||
q := fmt.Sprintf("UPDATE %s SET", data.TableName())
|
||||
values := []interface{}{}
|
||||
for k, v := range dataMap {
|
||||
q += fmt.Sprintf(" %s=?,", k)
|
||||
values = append(values, v)
|
||||
}
|
||||
q = strings.TrimSuffix(q, ",")
|
||||
q += " WHERE"
|
||||
for k, v := range filter {
|
||||
q += fmt.Sprintf(" %s=?,", k)
|
||||
values = append(values, v)
|
||||
}
|
||||
q = strings.TrimSuffix(q, ",")
|
||||
|
||||
return q, values
|
||||
}
|
||||
|
||||
func constructDeleteQuery(filter map[string]interface{}) (string, []interface{}) {
|
||||
// "DELETE <tablename> WHERE filter1=?"
|
||||
q := fmt.Sprintf("DELETE FROM %s WHERE", new(m.User).TableName())
|
||||
values := []interface{}{}
|
||||
for k, v := range filter {
|
||||
q += fmt.Sprintf(" %s=?,", k)
|
||||
values = append(values, v)
|
||||
}
|
||||
q = strings.TrimSuffix(q, ",")
|
||||
|
||||
return q, values
|
||||
}
|
||||
|
||||
func constructStoreQuery(data *m.User) (string, []interface{}) {
|
||||
// "INSERT INTO <tablename> VALUES(?, ?, ?, ?)"
|
||||
dataFields := data.SplitByField()
|
||||
q := fmt.Sprintf("INSERT INTO %s VALUES(", data.TableName())
|
||||
values := []interface{}{}
|
||||
for _, v := range dataFields {
|
||||
q += "?,"
|
||||
values = append(values, v)
|
||||
}
|
||||
q = strings.TrimSuffix(q, ",") + ")"
|
||||
|
||||
return q, values
|
||||
}
|
||||
|
||||
func constructGetBy(filter map[string]interface{}) (string, []interface{}) {
|
||||
// SELECT * FROM <tablename> WHERE filter1=filtervalue
|
||||
q := fmt.Sprintf("SELECT * FROM %s WHERE", new(m.User).TableName())
|
||||
dataFields := []interface{}{}
|
||||
for k, v := range filter {
|
||||
q += fmt.Sprintf(" %s=?,", k)
|
||||
dataFields = append(dataFields, v)
|
||||
}
|
||||
q = strings.TrimSuffix(q, ",")
|
||||
|
||||
return q, dataFields
|
||||
}
|
||||
|
||||
func constructGetAll() string {
|
||||
return "select * from users"
|
||||
}
|
||||
|
||||
func constructAuth(filter map[string]interface{}) (string, []interface{}) {
|
||||
q := fmt.Sprintf("SELECT * FROM %s WHERE", new(m.User).TableName())
|
||||
count := 0
|
||||
dataFields := []interface{}{}
|
||||
for k, v := range filter {
|
||||
if count == 0 {
|
||||
q += fmt.Sprintf(" %s=?", k)
|
||||
} else {
|
||||
q += fmt.Sprintf(" OR %s=?", k)
|
||||
}
|
||||
dataFields = append(dataFields, v)
|
||||
count++
|
||||
}
|
||||
return q, dataFields
|
||||
}
|
||||
216
repositories/mysql/user_mysql_repo.go
Normal file
216
repositories/mysql/user_mysql_repo.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/rinosukmandityo/hexagonal-login/helper"
|
||||
m "github.com/rinosukmandityo/hexagonal-login/models"
|
||||
repo "github.com/rinosukmandityo/hexagonal-login/repositories"
|
||||
|
||||
"database/sql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type newsMySQLRepository struct {
|
||||
url string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func newUserClient(URL string) (*sql.DB, error) {
|
||||
db, e := sql.Open("mysql", URL)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
if e = db.Ping(); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return db, e
|
||||
}
|
||||
|
||||
func (r *newsMySQLRepository) createNewTable() error {
|
||||
schema := `CREATE TABLE ` + new(m.User).TableName() + ` (
|
||||
ID VARCHAR(30) NOT NULL UNIQUE,
|
||||
Name VARCHAR(30),
|
||||
Username VARCHAR(30) NOT NULL,
|
||||
Email VARCHAR(50),
|
||||
Password VARCHAR(50),
|
||||
Address VARCHAR(50),
|
||||
IsActive boolean
|
||||
);`
|
||||
db, e := sqlx.Connect("mysql", r.url)
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.CreateTable")
|
||||
}
|
||||
defer db.Close()
|
||||
res, e := db.Exec(schema)
|
||||
if res != nil && e == nil {
|
||||
fmt.Println("Table 'Users' created")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewUserRepository(URL, DB string, timeout int) (repo.UserRepository, error) {
|
||||
repo := &newsMySQLRepository{
|
||||
url: fmt.Sprintf("%s?parseTime=true", URL),
|
||||
timeout: time.Duration(timeout) * time.Second,
|
||||
}
|
||||
repo.createNewTable()
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
func (r *newsMySQLRepository) GetAll() ([]m.User, error) {
|
||||
res := []m.User{}
|
||||
db, e := sqlx.Connect("mysql", r.url)
|
||||
if e != nil {
|
||||
return res, errors.Wrap(e, "repository.User.GetAll")
|
||||
}
|
||||
defer db.Close()
|
||||
q := constructGetAll()
|
||||
|
||||
if e = db.Select(&res, q); e != nil {
|
||||
return res, errors.Wrap(e, "repository.User.GetAll")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
func (r *newsMySQLRepository) GetBy(filter map[string]interface{}) (*m.User, error) {
|
||||
res := new(m.User)
|
||||
db, e := sqlx.Connect("mysql", r.url)
|
||||
if e != nil {
|
||||
return res, errors.Wrap(e, "repository.User.GetBy")
|
||||
}
|
||||
defer db.Close()
|
||||
q, dataFields := constructGetBy(filter)
|
||||
|
||||
if e = db.Get(res, q, dataFields...); e != nil {
|
||||
return res, errors.Wrap(e, "repository.User.GetBy")
|
||||
}
|
||||
return res, nil
|
||||
|
||||
}
|
||||
func (r *newsMySQLRepository) Store(data *m.User) error {
|
||||
db, e := newUserClient(r.url)
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Store")
|
||||
}
|
||||
defer db.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
|
||||
defer cancel()
|
||||
conn, e := db.Conn(ctx)
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Store")
|
||||
}
|
||||
|
||||
q, dataField := constructStoreQuery(data)
|
||||
stmt, e := conn.PrepareContext(ctx, q)
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Store")
|
||||
}
|
||||
defer stmt.Close()
|
||||
if _, e := stmt.Exec(dataField...); e != nil {
|
||||
return errors.Wrap(e, "repository.User.Store")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *newsMySQLRepository) Update(data *m.User) error {
|
||||
db, e := newUserClient(r.url)
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Update")
|
||||
}
|
||||
defer db.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
|
||||
defer cancel()
|
||||
conn, e := db.Conn(ctx)
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Update")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
filter := map[string]interface{}{"ID": data.ID}
|
||||
q, dataField := constructUpdateQuery(data, filter)
|
||||
stmt, e := conn.PrepareContext(ctx, q)
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Update")
|
||||
}
|
||||
defer stmt.Close()
|
||||
if res, e := stmt.Exec(dataField...); e != nil {
|
||||
return errors.Wrap(e, "repository.User.Update")
|
||||
} else {
|
||||
count, e := res.RowsAffected()
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Update")
|
||||
}
|
||||
if count == 0 {
|
||||
return errors.Wrap(helper.ErrUserNotFound, "repository.User.Update")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
func (r *newsMySQLRepository) Delete(data *m.User) error {
|
||||
db, e := newUserClient(r.url)
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Delete")
|
||||
}
|
||||
defer db.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
|
||||
defer cancel()
|
||||
conn, e := db.Conn(ctx)
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Delete")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
filter := map[string]interface{}{"ID": data.ID}
|
||||
q, dataFields := constructDeleteQuery(filter)
|
||||
stmt, e := conn.PrepareContext(ctx, q)
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Delete")
|
||||
}
|
||||
defer stmt.Close()
|
||||
if res, e := stmt.Exec(dataFields...); e != nil {
|
||||
return errors.Wrap(e, "repository.User.Delete")
|
||||
} else {
|
||||
count, e := res.RowsAffected()
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Delete")
|
||||
}
|
||||
if count == 0 {
|
||||
return errors.Wrap(helper.ErrUserNotFound, "repository.User.Delete")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *newsMySQLRepository) Authenticate(username, password string) (bool, *m.User, error) {
|
||||
res := new(m.User)
|
||||
db, e := sqlx.Connect("mysql", r.url)
|
||||
if e != nil {
|
||||
return false, res, errors.Wrap(e, "repository.User.Authenticate")
|
||||
}
|
||||
defer db.Close()
|
||||
q, dataFields := constructAuth(map[string]interface{}{"Username": username, "Email": username})
|
||||
|
||||
if e = db.Get(res, q, dataFields...); e != nil {
|
||||
return false, res, errors.Wrap(e, "repository.User.Authenticate")
|
||||
}
|
||||
if res.ID == "" {
|
||||
return false, res, errors.Wrap(helper.ErrUserNotFound, "repository.User.Authenticate")
|
||||
}
|
||||
|
||||
if !repo.IsPasswordMatch(password, res.Password) {
|
||||
return false, res, errors.New("Password does not match")
|
||||
}
|
||||
|
||||
return true, res, nil
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package redis
|
||||
|
||||
func FormingData(data map[string]string) map[string]interface{} {
|
||||
res := map[string]interface{}{}
|
||||
for k, v := range data {
|
||||
res[k] = v
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func generateKey(code string) string {
|
||||
return fmt.Sprintf("login<>%s", code)
|
||||
}
|
||||
|
||||
func generateUsernameKey(code string) string {
|
||||
return fmt.Sprintf("login<>username<>%s", code)
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"github.com/rinosukmandityo/hexagonal-login/helper"
|
||||
m "github.com/rinosukmandityo/hexagonal-login/models"
|
||||
repo "github.com/rinosukmandityo/hexagonal-login/repositories"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type userRedisRepository struct {
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
func newUserRedisClient(redisURL string) (*redis.Client, error) {
|
||||
opt, e := redis.ParseURL(redisURL)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
client := redis.NewClient(opt)
|
||||
if _, e = client.Ping().Result(); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return client, e
|
||||
}
|
||||
|
||||
func NewUserRedisRepository(redisURL string) (repo.UserRepository, error) {
|
||||
repo := &userRedisRepository{}
|
||||
client, e := newUserRedisClient(redisURL)
|
||||
if e != nil {
|
||||
return nil, errors.Wrap(e, "repository.NewUserRedisRepository")
|
||||
}
|
||||
repo.client = client
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
func (r *userRedisRepository) GetAll() ([]m.User, error) {
|
||||
return []m.User{}, nil
|
||||
}
|
||||
|
||||
func (r *userRedisRepository) GetBy(filter map[string]interface{}) (*m.User, error) {
|
||||
key := generateKey(filter["_id"].(string))
|
||||
data, e := r.client.HGetAll(key).Result()
|
||||
user := new(m.User)
|
||||
if e != nil {
|
||||
return user, errors.Wrap(e, "repository.User.GetById")
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return user, errors.Wrap(helper.ErrUserNotFound, "repository.User.GetById")
|
||||
}
|
||||
user.FormingUserData(data)
|
||||
return user, nil
|
||||
}
|
||||
func (r *userRedisRepository) Store(data *m.User) error {
|
||||
key := generateKey(data.ID)
|
||||
redisData := data.GetMapFormat()
|
||||
if _, e := r.client.HMSet(key, redisData).Result(); e != nil {
|
||||
return errors.Wrap(e, "repository.User.Store")
|
||||
}
|
||||
keyUsername := generateUsernameKey(data.Username)
|
||||
if _, e := r.client.HMSet(keyUsername, redisData).Result(); e != nil {
|
||||
return errors.Wrap(e, "repository.User.Store")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
func (r *userRedisRepository) Update(data *m.User, filter map[string]interface{}) error {
|
||||
key := generateKey(filter["_id"].(string))
|
||||
redisData := data.GetMapFormat()
|
||||
if _, e := r.client.HMSet(key, redisData).Result(); e != nil {
|
||||
return errors.Wrap(e, "repository.User.Update")
|
||||
}
|
||||
keyUsername := generateUsernameKey(filter["username"].(string))
|
||||
if _, e := r.client.HMSet(keyUsername, redisData).Result(); e != nil {
|
||||
return errors.Wrap(e, "repository.User.Update")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
func (r *userRedisRepository) Delete(filter map[string]interface{}) error {
|
||||
key := generateKey(filter["_id"].(string))
|
||||
data, e := r.client.HGetAll(key).Result()
|
||||
if e != nil {
|
||||
return errors.Wrap(e, "repository.User.Delete")
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return errors.Wrap(errors.New("User Not Found"), "repository.User.Delete")
|
||||
}
|
||||
if _, e := r.client.HDel(key).Result(); e != nil {
|
||||
return errors.Wrap(e, "repository.User.Delete")
|
||||
}
|
||||
keyUsername := generateUsernameKey(data["username"])
|
||||
if _, e := r.client.HDel(keyUsername).Result(); e != nil {
|
||||
return errors.Wrap(e, "repository.User.Delete")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *userRedisRepository) Authenticate(username, password string) (bool, *m.User, error) {
|
||||
user := new(m.User)
|
||||
key := generateUsernameKey(username)
|
||||
data, e := r.client.HGetAll(key).Result()
|
||||
if e != nil {
|
||||
return false, user, errors.Wrap(e, "repository.User.Authenticate")
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return false, user, errors.Wrap(errors.New("User Not Found"), "repository.User.Authenticate")
|
||||
}
|
||||
if !repo.IsPasswordMatch(password, user.Password) {
|
||||
return false, user, errors.New("Password does not match")
|
||||
}
|
||||
user.FormingUserData(data)
|
||||
|
||||
return true, user, nil
|
||||
}
|
||||
@@ -8,7 +8,7 @@ type UserRepository interface {
|
||||
GetAll() ([]m.User, error)
|
||||
GetBy(filter map[string]interface{}) (*m.User, error)
|
||||
Store(data *m.User) error
|
||||
Update(data *m.User, filter map[string]interface{}) error
|
||||
Delete(filter map[string]interface{}) error
|
||||
Update(data *m.User) error
|
||||
Delete(data *m.User) error
|
||||
Authenticate(username, password string) (bool, *m.User, error)
|
||||
}
|
||||
|
||||
@@ -22,10 +22,20 @@ import (
|
||||
===================================
|
||||
TO SET DATABASE INFO FROM TERMINAL
|
||||
===================================
|
||||
=======
|
||||
MongoDB
|
||||
=======
|
||||
set mongo_url=mongodb://localhost:27017/local
|
||||
set mongo_timeout=10
|
||||
set mongo_db=local
|
||||
set url_db=mongo
|
||||
=======
|
||||
MySQL
|
||||
=======
|
||||
set mysql_url=root:Password.1@tcp(127.0.0.1:3306)/tes
|
||||
set mysql_timeout=10
|
||||
set mysql_db=tes
|
||||
set url_db=mysql
|
||||
*/
|
||||
|
||||
var (
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
package services_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rinosukmandityo/hexagonal-login/logic"
|
||||
m "github.com/rinosukmandityo/hexagonal-login/models"
|
||||
@@ -21,10 +23,20 @@ import (
|
||||
===================================
|
||||
TO SET DATABASE INFO FROM TERMINAL
|
||||
===================================
|
||||
=======
|
||||
MongoDB
|
||||
=======
|
||||
set mongo_url=mongodb://localhost:27017/local
|
||||
set mongo_timeout=10
|
||||
set mongo_db=local
|
||||
set url_db=mongo
|
||||
=======
|
||||
MySQL
|
||||
=======
|
||||
set mysql_url=root:Password.1@tcp(127.0.0.1:3306)/tes
|
||||
set mysql_timeout=10
|
||||
set mysql_db=tes
|
||||
set url_db=mysql
|
||||
*/
|
||||
|
||||
var (
|
||||
@@ -66,6 +78,7 @@ func init() {
|
||||
}
|
||||
|
||||
func TestUserService(t *testing.T) {
|
||||
// t.Run("Delete All", DeleteAll)
|
||||
t.Run("Insert User", InsertUser)
|
||||
t.Run("Update User", UpdateUser)
|
||||
t.Run("Delete User", DeleteUser)
|
||||
@@ -85,6 +98,7 @@ func InsertUser(t *testing.T) {
|
||||
}(data)
|
||||
}
|
||||
wg.Wait()
|
||||
time.Sleep(time.Second * 1)
|
||||
|
||||
t.Run("Case 1: Save data", func(t *testing.T) {
|
||||
for _, data := range testdata {
|
||||
@@ -98,6 +112,7 @@ func InsertUser(t *testing.T) {
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
time.Sleep(time.Second * 1)
|
||||
for _, data := range testdata {
|
||||
res, e := userService.GetById(data.ID)
|
||||
if e != nil || res.ID == "" {
|
||||
@@ -172,3 +187,11 @@ func GetUser(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteAll(t *testing.T) {
|
||||
for i := 1; i <= 4; i++ {
|
||||
data := UserTestData()[0]
|
||||
data.ID = fmt.Sprintf("userid0%d", i)
|
||||
userService.Delete(&data)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user