From 9f272786c6989eeba8731bbfbc87839f9672cd84 Mon Sep 17 00:00:00 2001 From: ajikamaludin Date: Fri, 15 Jul 2022 13:39:07 +0700 Subject: [PATCH] setup database done --- README.md | 27 ++++++++++- api/v1/health/status.go | 18 +++++++ config.yaml | 6 +++ configs/configs.go | 9 +++- example.sql | 31 ++++++++++++ go.mod | 5 +- go.sum | 2 + pkg/v1/config/config.go | 23 +++++++++ pkg/v1/postgres/custom.main.go | 74 +++++++++++++++++++++++++++++ pkg/v1/postgres/postgres.go | 41 ++++++++++++++++ pkg/v1/utils/constants/constants.go | 4 ++ pkg/v1/utils/converter/converter.go | 16 +++++++ 12 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 example.sql create mode 100644 pkg/v1/postgres/custom.main.go create mode 100644 pkg/v1/postgres/postgres.go create mode 100644 pkg/v1/utils/converter/converter.go diff --git a/README.md b/README.md index 442dced..8d4a827 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,11 @@ g.Go(func() error { return router.NewHTTPServer(configs, logger) }) - `go run .` run grpc and http server - test `curl localhost:8080/api/v1/health/status`, reponse ``` -{"success":true,"code":"0000","desc":"SUCCESS"} +{ + "success":true, + "code":"0000", + "desc":"SUCCESS" +} ``` - GENERATE API DOCS: - `mkdir swagger` @@ -91,4 +95,23 @@ func CreateSwagger(gwmux *http.ServeMux) { http.ServeFile(w, r, "swagger/docs.json") }) } -``` \ No newline at end of file +``` + +### Implement DB_Connection (With PostgreSQL) +- Execute `example.sql` in db, you know how to do it +- add `config.yaml` with line below +``` +postgres: + host: 127.0.0.1 + port: 5432 + dbname: test + username: postgres + password: mysecretpassword +``` +- changes `pkg/v1/config/config.go` to add new config line in config struct and and validateConfigData rule +- `go get github.com/lib/pq` +- create `pkg/v1/postgres` dir, create `postgres.go` file in there implement string conn and test connection +- create `pkg/v1/utils/converter` dir, create `converter.go` file in there, to convert camelcase to snake_case +- create `pkg/v1/postgres/custom.main.go` to implement all query to database table custom +- changes `configs/configs.go` to bundle pg connection +- how to use custom.main.go call function from custom main in api status, check `api/v1/health/status.go` \ No newline at end of file diff --git a/api/v1/health/status.go b/api/v1/health/status.go index 5beaaee..7ee3eb8 100644 --- a/api/v1/health/status.go +++ b/api/v1/health/status.go @@ -2,13 +2,31 @@ package health import ( "context" + "errors" + "github.com/ajikamaludin/go-grpc_basic/pkg/v1/postgres" "github.com/ajikamaludin/go-grpc_basic/pkg/v1/utils/constants" hlpb "github.com/ajikamaludin/go-grpc_basic/proto/v1/health" "github.com/golang/protobuf/ptypes/empty" ) func (s *Server) Status(ctx context.Context, req *empty.Empty) (*hlpb.Response, error) { + // select db + rows, err := s.config.Pg.CustomMainSelect(&postgres.CustomMain{UserId: "abc"}) + + if err != nil { + s.logger.Errorf("[HEALTH][GET] ERROR %v", err) + } + + if len(rows) <= 0 { + s.logger.Errorf("[HEALTH][GET] EMPTY ROW") + return nil, errors.New("Invoke empty rows") + } else { + for _, v := range rows { + s.logger.Infof("[HEALTH][GET][ROW] %v", v) + } + } + return &hlpb.Response{ Success: true, Code: constants.SuccessCode, diff --git a/config.yaml b/config.yaml index 3539fb1..7e960b0 100644 --- a/config.yaml +++ b/config.yaml @@ -5,3 +5,9 @@ server: port: 5566 rest: port: 8080 +postgres: + host: 127.0.0.1 + port: 5432 + dbname: test + username: aji + password: eta diff --git a/configs/configs.go b/configs/configs.go index 33bf512..1ddaa3c 100644 --- a/configs/configs.go +++ b/configs/configs.go @@ -4,12 +4,14 @@ import ( "log" "github.com/ajikamaludin/go-grpc_basic/pkg/v1/config" + "github.com/ajikamaludin/go-grpc_basic/pkg/v1/postgres" "github.com/sirupsen/logrus" ) // bundle/wrap another service access here type Configs struct { Config *config.Config + Pg *postgres.Conn } func New() (*Configs, *logrus.Logger, error) { @@ -32,9 +34,14 @@ func New() (*Configs, *logrus.Logger, error) { logger.Info("[CONFIG] Setup complete") - // TODO: pg letter + // Test Pg Con + pg, err := postgres.New(config) + if err != nil { + return nil, nil, err + } return &Configs{ Config: config, + Pg: pg, }, logger, nil } diff --git a/example.sql b/example.sql new file mode 100644 index 0000000..73b6d3c --- /dev/null +++ b/example.sql @@ -0,0 +1,31 @@ +CREATE DATABASE test; +CREATE SCHEMA custom; + +CREATE TABLE IF NOT EXISTS custom.main ( + user_id VARCHAR (50) PRIMARY KEY, + pass VARCHAR (256) NOT NUll, + del_flag BOOLEAN default FALSE, + description VARCHAR(50), + cre_id VARCHAR (50) NOT NULL, + cre_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + mod_id VARCHAR (50) NOT NULL, + mod_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + mod_ts INT NOT NULL +); + +CREATE OR REPLACE FUNCTION custom.trigger_set_timestamp() +RETURNS TRIGGER AS $$ +BEGIN + NEW.mod_time = now(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE TRIGGER set_timestamp +BEFORE UPDATE ON custom.main +FOR EACH ROW +EXECUTE PROCEDURE custom.trigger_set_timestamp(); + +INSERT INTO custom.main (user_id, pass, cre_id, mod_id, mod_ts) VALUES ('abc', 'password', 1, 1, 1); +SELECT * FROM custom.main; +UPDATE custom.main SET pass = 'pass', mod_ts = 3; \ No newline at end of file diff --git a/go.mod b/go.mod index 7f3369a..a794709 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,10 @@ require ( golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f ) -require github.com/felixge/httpsnoop v1.0.1 // indirect +require ( + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/lib/pq v1.10.6 // indirect +) require ( github.com/golang/protobuf v1.5.2 diff --git a/go.sum b/go.sum index cbcf63d..10209c3 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= +github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/pkg/v1/config/config.go b/pkg/v1/config/config.go index 1fdd48e..a378bef 100644 --- a/pkg/v1/config/config.go +++ b/pkg/v1/config/config.go @@ -21,6 +21,13 @@ type Config struct { Port int `yaml:"port"` } `yaml:"rest"` } `yaml:"server"` + Postgres struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + Dbname string `yaml:"dbname"` + Username string `yaml:"username"` + Password string `yaml:"password"` + } `yaml:"postgres"` } func New() (*Config, error) { @@ -100,5 +107,21 @@ func validateConfigData(config *Config) error { return errors.New("server.rest.port is empty") } + if config.Postgres.Host == "" { + return errors.New("postgres.host is empty") + } + if config.Postgres.Port == 0 { + return errors.New("postgres.port is empty") + } + if config.Postgres.Dbname == "" { + return errors.New("postgres.dbname is empty") + } + if config.Postgres.Username == "" { + return errors.New("postgres.user is empty") + } + if config.Postgres.Password == "" { + return errors.New("postgres.pass is empty") + } + return nil } diff --git a/pkg/v1/postgres/custom.main.go b/pkg/v1/postgres/custom.main.go new file mode 100644 index 0000000..20b6274 --- /dev/null +++ b/pkg/v1/postgres/custom.main.go @@ -0,0 +1,74 @@ +package postgres + +import ( + "database/sql" + "fmt" + "reflect" + "strings" + "time" + + "github.com/ajikamaludin/go-grpc_basic/pkg/v1/utils/constants" + "github.com/ajikamaludin/go-grpc_basic/pkg/v1/utils/converter" +) + +type CustomMain struct { + UserId string `db:"user_id,omitempty"` + Pass string `db:"pass,omitempty"` + DelFlag bool `db:"del_flag,omitempty"` + Description sql.NullString `db:"description,omitempty"` + CreId string `db:"cre_id,omitempty"` + CreTime time.Time `db:"cre_time,omitempty"` + ModId string `db:"mod_id,omitempty"` + ModTime time.Time `db:"mod_time,omitempty"` + ModTs int `db:"mod_td,omitempty"` +} + +func (c *Conn) CustomMainSelect(filter *CustomMain) ([]*CustomMain, error) { + //create query syntax + qsyntax := fmt.Sprintf(`SELECT * FROM %s`, constants.Table_Custom_Main) + + fil := reflect.ValueOf(*filter) + + for i := 0; i < fil.NumField(); i++ { + if !fil.Field(i).IsZero() { + if i == 0 { + qsyntax = fmt.Sprintf(`%s WHERE`, qsyntax) + } + qsyntax = fmt.Sprintf(`%s %s = '%v'`, qsyntax, converter.CamelToSnakeCase(fil.Type().Field(i).Name), fil.Field(i)) + } + } + + qsyntax = strings.TrimRight(qsyntax, "AND") + qsyntax = fmt.Sprintf(`%s;`, qsyntax) + + fmt.Println(qsyntax) + db, err := sql.Open(POSTGRES, c.Conn) + if err != nil { + return nil, err + } + + rows, err := db.Query(qsyntax) + defer db.Close() + if err != nil { + return nil, err + } + + defer rows.Close() + + var rowsScanArr []*CustomMain + + for rows.Next() { + var rowsScan CustomMain + err := rows.Scan(&rowsScan.UserId, &rowsScan.Pass, + &rowsScan.DelFlag, &rowsScan.Description, &rowsScan.CreId, + &rowsScan.CreTime, &rowsScan.ModId, &rowsScan.ModTime, &rowsScan.ModTs) + + if err != nil { + return nil, err + } + + rowsScanArr = append(rowsScanArr, &rowsScan) + } + + return rowsScanArr, nil +} diff --git a/pkg/v1/postgres/postgres.go b/pkg/v1/postgres/postgres.go new file mode 100644 index 0000000..906b3cd --- /dev/null +++ b/pkg/v1/postgres/postgres.go @@ -0,0 +1,41 @@ +package postgres + +import ( + "database/sql" + "fmt" + + "github.com/ajikamaludin/go-grpc_basic/pkg/v1/config" + _ "github.com/lib/pq" +) + +const ( + POSTGRES string = "postgres" +) + +type Conn struct { + Conn string +} + +func New(config *config.Config) (*Conn, error) { + conn := fmt.Sprintf("host=%v port=%v user=%v password=%v dbname=%v sslmode=disable", + config.Postgres.Host, + config.Postgres.Port, + config.Postgres.Username, + config.Postgres.Password, + config.Postgres.Dbname) + db, err := sql.Open(POSTGRES, conn) + + if err != nil { + return nil, err + } + + err = db.Ping() + defer db.Close() + if err != nil { + return nil, err + } + + return &Conn{ + Conn: conn, + }, nil +} diff --git a/pkg/v1/utils/constants/constants.go b/pkg/v1/utils/constants/constants.go index 90da946..299b4c1 100644 --- a/pkg/v1/utils/constants/constants.go +++ b/pkg/v1/utils/constants/constants.go @@ -8,3 +8,7 @@ const ( SuccessCode = `0000` SuccesDesc = `SUCCESS` ) + +const ( + Table_Custom_Main = "custom.main" +) diff --git a/pkg/v1/utils/converter/converter.go b/pkg/v1/utils/converter/converter.go new file mode 100644 index 0000000..fd93abc --- /dev/null +++ b/pkg/v1/utils/converter/converter.go @@ -0,0 +1,16 @@ +package converter + +import ( + "regexp" + "strings" +) + +func CamelToSnakeCase(str string) string { + var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") + var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") + + snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}") + snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") + + return strings.ToLower(snake) +}