[Tutorial Inisialisasi Project Golang & Build Golang] #39

Open
opened 2024-01-11 08:18:33 +07:00 by sindhu · 0 comments
Owner

Referensi

1. Membuat project baru
Setelah clone empty project dari repository, maka buat folder baru dengan nama backend

mkdir backend
cd backend
go mod init com.sismedika.com.absensi

printf '// +build tools\npackage tools\nimport (_ "github.com/99designs/gqlgen"\n _ "github.com/99designs/gqlgen/graphql/introspection")' | gofmt > tools.go

go mod tidy
go run github.com/99designs/gqlgen init
go run server.go

Setelah dirun bisa dan dapat diakses maka bisa lanjut ke tahap selanjutnya.

2. Membuat File RSA Private Key
pembuatan rsa key dapat mengikuti tutorial dibawah ini
https://developers.yubico.com/PIV/Guides/Generating_keys_using_OpenSSL.html
// openssl genrsa -out private.pem 1024
// openssl rsa -in private.pem -outform PEM -pubout out public.pem

3. Membuat database absensi di devone
Buat database di devone. Setelah itu buat user untuk akses didatabase.
Lalu grant all hak akses untuk database.

4. Membuat config.yaml

privatekey: **di isi dengan private key yang berekstensi .pem**
dbuser: **di sesuaikan dengan user yang memiliki grant all di database terkait** 
dbpass: **di sesuaikan dengan password pada database terkait**
dbhost: **diisi host pada server database**
dbport: **diisi port pada server database**
dbname: **diisi database yang digunakan**

5. Membuat fungsi di folder pkg/config/config.go
jika di root folder sudah ada maka buat folder baru dengan nama pkg/config. lalu buat nama config.go

code seperti dibawah ini :

package config

import (
	"log"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

type Reader interface {
	Get(key string) string
}

type viperConfigReader struct {
	viper *viper.Viper
}

var Data *viperConfigReader

func (v viperConfigReader) Get(key string) string {
	return v.viper.GetString(key)
}

func (v viperConfigReader) GetInt(key string) int {
	return v.viper.GetInt(key)
}

func Load() {
	v := viper.New()
	v.AddConfigPath(".")
	v.SetConfigName("config")
	v.SetConfigType("yaml")
	v.AutomaticEnv()

	err := v.ReadInConfig()
	if err != nil {
		return
	}
	Data = &viperConfigReader{
		viper: v,
	}
	v.WatchConfig()
	v.OnConfigChange(func(e fsnotify.Event) {
		log.Println("config file changed", e.Name)
	})
	return
}

6. Install github.com/fsnotify/fsnotify
cara install go get github.com/fsnotify/fsnotify

7. Install github.com/spf13/viper
cara install go get github.com/spf13/viper

8. Install golang.org/x/crypto/bcrypt
cara install go get golang.org/x/crypto/bcrypt

9. Install github.com/dgrijalva/jwt-go
cara install go get github.com/dgrijalva/jwt-go

10. Membuat fungsi di pkg/crypt/crypt.go
jika file sudah ada di root, maka buat folder crypt lalu buat file crypt.go.
code seperti dibawah ini :

package crypt

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"log"
	"time"

	"com.sismedika.com.absensi/pkg/config"
	"golang.org/x/crypto/bcrypt"
)

// openssl genrsa -out private.pem 1024
// openssl rsa -in private.pem -outform PEM -pubout out public.pem
func EncryptPassword(password string) (string, error) {
	pemData, err := ioutil.ReadFile(config.Data.Get("privatekey"))
	if err != nil {
		log.Printf("read key file: %s", err)
		return "", fmt.Errorf(("INTERNAL_SERVER_ERROR"))
	}
	block, _ := pem.Decode(pemData)
	if block == nil {
		log.Printf("bad key data: %s", "not PEM-encoded")
		return "", fmt.Errorf(("INTERNAL_SERVER_ERROR"))
	}
	if got, want := block.Type, "RSA PRIVATE KEY"; got != want {
		log.Printf("unknown key type %q, want %q", got, want)
		return "", fmt.Errorf(("INTERNAL_SERVER_ERROR"))
	}
	// Decode the RSA private key
	priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		log.Printf("bad private key: %s", err)
		return "", fmt.Errorf(("INTERNAL_SERVER_ERROR"))
	}

	passEncrypted, err := rsa.EncryptPKCS1v15(rand.Reader, &priv.PublicKey, []byte(password))
	if err != nil {
		log.Printf("decrypt: %s\n", err)
		return "", fmt.Errorf(("DECRYPTION_FAILED"))
	}

	return base64.StdEncoding.EncodeToString(passEncrypted), nil
}

func DecryptPassword(passEncoded string) ([]byte, error) {
	/// decrypt password
	/// openssl genrsa -traditional -out private.pem 1024
	/// openssl rsa -in private.pem -outform PEM -pubout -out public.pem
	pemData, err := ioutil.ReadFile(config.Data.Get("privatekey"))
	if err != nil {
		log.Printf("read key file: %s", err)
		return nil, fmt.Errorf(("INTERNAL_SERVER_ERROR"))
	}
	block, _ := pem.Decode(pemData)
	if block == nil {
		log.Printf("bad key data: %s", "not PEM-encoded")
		return nil, fmt.Errorf(("INTERNAL_SERVER_ERROR"))
	}
	if got, want := block.Type, "RSA PRIVATE KEY"; got != want {
		log.Printf("unknown key type %q, want %q", got, want)
		return nil, fmt.Errorf(("INTERNAL_SERVER_ERROR"))
	}
	// Decode the RSA private key
	priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		log.Printf("bad private key: %s", err)
		return nil, fmt.Errorf(("INTERNAL_SERVER_ERROR"))
	}

	var passDecrypted []byte
	var passDecoded []byte
	passDecoded, err = base64.StdEncoding.DecodeString(passEncoded)
	if err != nil {
		log.Printf("base64 decode: %s\n", err)
		return nil, fmt.Errorf(("BASE64_DECODE_FAILED"))
	}

	passDecrypted, err = rsa.DecryptPKCS1v15(rand.Reader, priv, []byte(passDecoded))
	if err != nil {
		log.Printf("decrypt: %s\n", err)
		return nil, fmt.Errorf(("DECRYPTION_FAILED"))
	}

	return passDecrypted, nil
}

func CheckPasswordHash(hash, password string) bool {
	start := time.Now()
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	log.Printf("CompareHashAndPassword execution took %s", time.Since(start))
	return err == nil
}

11. Membuat fungsi pkg/database/database.go
Jika di root folder sudah ada database.go, maka moving folder di pkg/database.
lalu code nya seperti ini :

package database

import (
	"bytes"
	"database/sql"
	"fmt"
	"log"
	"strings"

	"com.sismedika.com.absensi/pkg/config"
	_ "github.com/go-sql-driver/mysql"
)

var Handle *sql.DB

func InitDB() {
	dsn := config.Data.Get("DBuser") + ":" + config.Data.Get("DBpass") + "@tcp(" + config.Data.Get("DBhost") + ":" + config.Data.Get("DBport") + ")/" + config.Data.Get("DBname")
	handle, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Panic(err)
	}

	if err = handle.Ping(); err != nil {
		log.Panic(err)
	}
	Handle = handle
}

func LogSQL(query string, args ...interface{}) {
	var buffer bytes.Buffer
	nArgs := len(args)
	// Break the string by question marks, iterate over its parts and for each
	// question mark - append an argument and format the argument according to
	// it's type, taking into consideration NULL values and quoting strings.
	for i, part := range strings.Split(query, "?") {
		buffer.WriteString(part)
		if i < nArgs {
			switch a := args[i].(type) {
			case int:
				buffer.WriteString(fmt.Sprintf("%d", a))
			case int64:
				buffer.WriteString(fmt.Sprintf("%d", a))
			case bool:
				buffer.WriteString(fmt.Sprintf("%t", a))
			case sql.NullBool:
				if a.Valid {
					buffer.WriteString(fmt.Sprintf("%t", a.Bool))
				} else {
					buffer.WriteString("NULL")
				}
			case sql.NullInt64:
				if a.Valid {
					buffer.WriteString(fmt.Sprintf("%d", a.Int64))
				} else {
					buffer.WriteString("NULL")
				}
			case sql.NullString:
				if a.Valid {
					buffer.WriteString(fmt.Sprintf("%q", a.String))
				} else {
					buffer.WriteString("NULL")
				}
			case sql.NullFloat64:
				if a.Valid {
					buffer.WriteString(fmt.Sprintf("%f", a.Float64))
				} else {
					buffer.WriteString("NULL")
				}
			default:
				buffer.WriteString(fmt.Sprintf("%q", a))
			}
		}
	}
	log.Print(buffer.String())
}

12. Membuat fungsi pkg/jwt/jwt.go
Jika di folder root sudah ada jwt.go maka moving folder ke pkg/jwt.
lalu code seperti ini

package jwt

import (
	"fmt"
	"log"
	"time"

	"com.sismedika.com.absensi/pkg/config"
	"github.com/dgrijalva/jwt-go"
)

// var conf = config.Data

// GenerateToken generates a jwt token and assign a username to it's claims and return it
func GenerateToken(user_id int, username string, staff_id int) (string, int64, error) {
	token := jwt.New(jwt.SigningMethodHS256)
	/* Create a map to store our claims */
	claims := token.Claims.(jwt.MapClaims)
	/* Set token claims */
	expired := time.Now().Add(time.Minute * time.Duration(config.Data.GetInt("tokenExpiration"))).Unix()
	claims["user_id"] = user_id
	claims["user_name"] = username
	claims["staff_id"] = staff_id
	claims["exp"] = expired
	tokenString, err := token.SignedString([]byte(config.Data.Get("secretkey")))
	if err != nil {
		return "", 0, fmt.Errorf("GENERATE_TOKEN_FAILED")
	}
	return tokenString, expired, nil
}

// ParseToken parses a jwt token and returns the username in it's claims
func ParseToken(tokenStr string) (int, string, int, error) {
	claims := jwt.MapClaims{}

	token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
		return []byte(config.Data.Get("secretkey")), nil
	})
	if err != nil {
		v, _ := err.(*jwt.ValidationError)

		if v.Errors == jwt.ValidationErrorExpired {
			log.Printf("parse token 1: %v\n", err)
			return 0, "", 0, fmt.Errorf("TOKEN_EXPIRED")
		}

		log.Printf("parse token 2: %v\n", err)
		return 0, "", 0, fmt.Errorf("TOKEN_INVALID")
	}

	if !token.Valid {
		log.Printf("token invalid: %v\n", err)
		return 0, "", 0, fmt.Errorf(("TOKEN_INVALID"))
	}

	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		if !ok {
			return 0, "", 0, fmt.Errorf(("TOKEN_INVALID"))
		}
		// type of user_id is float64: https://stackoverflow.com/questions/70705673/panic-interface-conversion-interface-is-float64-not-int64
		if claims["user_id"] == nil {
			return 0, "", 0, fmt.Errorf(("TOKEN_INVALID"))
		}
		if claims["user_name"] == nil {
			return 0, "", 0, fmt.Errorf(("TOKEN_INVALID"))
		}
		if claims["staff_id"] == nil {
			return 0, "", 0, fmt.Errorf(("TOKEN_INVALID"))
		}
		user_id := int(claims["user_id"].(float64))
		user_name := claims["user_name"].(string)
		staff_id := int(claims["practitioner_id"].(float64))
		return user_id, user_name, staff_id, nil
	} else {
		log.Printf("token claims: %v\n", err)
		return 0, "", 0, fmt.Errorf(("TOKEN_INVALID"))
	}
}

13. Membuat model graphql staff
Membuat file staff.graphqls, lalu code nya seperti ini

# model staff
type Staff {
    staff_id: ID!
    nip: String!
    name: String!
    email: String!
    phone_number: String
    is_active: String
    is_login: Boolean
    token: String
    id_google_sign_in: String!
    display_name_google_sign_in: String
    created_at: String
    last_updated_at: String
}

# model token response ketika login
type TokenResponse {
  token: String
  message: String
}

# query search
extend type Query {
  searchStaffByEmail(email: String!): Staff!
  staffGetByStaffId(staff_id: ID!): Staff!
  staffListBySearch(search:String, page:Int, maxPerPage: Int): [Staff!]!
}

# mutation untuk perubahan data
extend type Mutation {
  generateToken(email: String!, id_google_sign_in:String!): TokenResponse!
  loginAttendance(email:String!, id_google_sign_in:String!) : Staff!
}

Setelah itu run generate, go run github.com/99designs/gqlgen generate

14. Memindahkan generated.go, model, resolver ke folder generated, model, resolver

  • pindahkan generated.go ke dalam folder graph/generated/generated.go.
  • pindahkan staff.graphqls ke dalam folder graph/graphqls/staff.graphqls.
  • pindahkan models_gen.go ke dalam folder graph/model/models_gen.go.
  • pindahkan resolver.go, schema.resolver.go, staff.resolver.go ke folder graph/resolver

15. Memindahkan tools.go di root ke folder tools/tools.go

16. Memodifikasi gqlgen karena file sudah dipindahkan
Setelah dipindahkan pada langkah ke 14, maka perlu melakukan perubahan settingan di gqlgqn.yml.
lalu perubahan code seperti ini :

# Where are all the schema files located? globs are supported eg  src/**/*.graphqls
schema:
  - graph/graphqls/*.graphqls

# Where should the generated server code go?
exec:
  filename: graph/generated/generated.go
  package: generated

# Uncomment to enable federation
# federation:
#   filename: graph/federation.go
#   package: graph

# Where should any generated models go?
model:
  filename: graph/model/models_gen.go
  package: model

# Where should the resolver implementations go?
resolver:
  layout: follow-schema
  dir: graph/resolver
  package: resolver
  filename_template: "{name}.resolvers.go"
  # Optional: turn on to not generate template comments above resolvers
  # omit_template_comment: false

# Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models
# struct_tag: json

# Optional: turn on to use []Thing instead of []*Thing
# omit_slice_element_pointers: false

# Optional: turn on to omit Is<Name>() methods to interface and unions
# omit_interface_checks : true

# Optional: turn on to skip generation of ComplexityRoot struct content and Complexity function
# omit_complexity: false

# Optional: turn on to not generate any file notice comments in generated files
# omit_gqlgen_file_notice: false

# Optional: turn on to exclude the gqlgen version in the generated file notice. No effect if `omit_gqlgen_file_notice` is true.
# omit_gqlgen_version_in_file_notice: false

# Optional: turn off to make struct-type struct fields not use pointers
# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing }
# struct_fields_always_pointers: true

# Optional: turn off to make resolvers return values instead of pointers for structs
# resolvers_always_return_pointers: true

# Optional: turn on to return pointers instead of values in unmarshalInput
# return_pointers_in_unmarshalinput: false

# Optional: wrap nullable input fields with Omittable
# nullable_input_omittable: true

# Optional: set to speed up generation time by not performing a final validation pass.
# skip_validation: true

# Optional: set to skip running `go mod tidy` when generating server code
# skip_mod_tidy: true

# gqlgen will search for any type names in the schema in these go packages
# if they match it will use them, otherwise it will generate them.
autobind:
#  - "com.sismedika.com.absensi/graph/model"

# This section declares type mapping between the GraphQL and go type systems
#
# The first line in each type will be used as defaults for resolver arguments and
# modelgen, the others will be allowed when binding to fields. Configure them to
# your liking
models:
  ID:
    model:
      - github.com/99designs/gqlgen/graphql.ID
      - github.com/99designs/gqlgen/graphql.Int
      - github.com/99designs/gqlgen/graphql.Int64
      - github.com/99designs/gqlgen/graphql.Int32
  Int:
    model:
      - github.com/99designs/gqlgen/graphql.Int
      - github.com/99designs/gqlgen/graphql.Int64
      - github.com/99designs/gqlgen/graphql.Int32

17. Generate ulang gql gen
lalu generate ulang go run github.com/99designs/gqlgen generate

build
env GOOS=linux GOARCH=amd64 go build server.go

### Referensi - https://gqlgen.com/ **1. Membuat project baru** Setelah clone empty project dari repository, maka buat folder baru dengan nama backend ```golang mkdir backend cd backend go mod init com.sismedika.com.absensi printf '// +build tools\npackage tools\nimport (_ "github.com/99designs/gqlgen"\n _ "github.com/99designs/gqlgen/graphql/introspection")' | gofmt > tools.go go mod tidy go run github.com/99designs/gqlgen init go run server.go ``` Setelah dirun bisa dan dapat diakses maka bisa lanjut ke tahap selanjutnya. **2. Membuat File RSA Private Key** pembuatan rsa key dapat mengikuti tutorial dibawah ini https://developers.yubico.com/PIV/Guides/Generating_keys_using_OpenSSL.html // openssl genrsa -out private.pem 1024 // openssl rsa -in private.pem -outform PEM -pubout out public.pem **3. Membuat database absensi di devone** Buat database di devone. Setelah itu buat user untuk akses didatabase. Lalu grant all hak akses untuk database. **4. Membuat config.yaml** ```golang privatekey: **di isi dengan private key yang berekstensi .pem** dbuser: **di sesuaikan dengan user yang memiliki grant all di database terkait** dbpass: **di sesuaikan dengan password pada database terkait** dbhost: **diisi host pada server database** dbport: **diisi port pada server database** dbname: **diisi database yang digunakan** ``` **5. Membuat fungsi di folder pkg/config/config.go** jika di root folder sudah ada maka buat folder baru dengan nama pkg/config. lalu buat nama config.go code seperti dibawah ini : ```golang package config import ( "log" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) type Reader interface { Get(key string) string } type viperConfigReader struct { viper *viper.Viper } var Data *viperConfigReader func (v viperConfigReader) Get(key string) string { return v.viper.GetString(key) } func (v viperConfigReader) GetInt(key string) int { return v.viper.GetInt(key) } func Load() { v := viper.New() v.AddConfigPath(".") v.SetConfigName("config") v.SetConfigType("yaml") v.AutomaticEnv() err := v.ReadInConfig() if err != nil { return } Data = &viperConfigReader{ viper: v, } v.WatchConfig() v.OnConfigChange(func(e fsnotify.Event) { log.Println("config file changed", e.Name) }) return } ``` **6. Install github.com/fsnotify/fsnotify** cara install go get github.com/fsnotify/fsnotify **7. Install github.com/spf13/viper** cara install go get github.com/spf13/viper **8. Install golang.org/x/crypto/bcrypt** cara install go get golang.org/x/crypto/bcrypt **9. Install github.com/dgrijalva/jwt-go** cara install go get github.com/dgrijalva/jwt-go **10. Membuat fungsi di pkg/crypt/crypt.go** jika file sudah ada di root, maka buat folder crypt lalu buat file crypt.go. code seperti dibawah ini : ```golang package crypt import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/pem" "fmt" "io/ioutil" "log" "time" "com.sismedika.com.absensi/pkg/config" "golang.org/x/crypto/bcrypt" ) // openssl genrsa -out private.pem 1024 // openssl rsa -in private.pem -outform PEM -pubout out public.pem func EncryptPassword(password string) (string, error) { pemData, err := ioutil.ReadFile(config.Data.Get("privatekey")) if err != nil { log.Printf("read key file: %s", err) return "", fmt.Errorf(("INTERNAL_SERVER_ERROR")) } block, _ := pem.Decode(pemData) if block == nil { log.Printf("bad key data: %s", "not PEM-encoded") return "", fmt.Errorf(("INTERNAL_SERVER_ERROR")) } if got, want := block.Type, "RSA PRIVATE KEY"; got != want { log.Printf("unknown key type %q, want %q", got, want) return "", fmt.Errorf(("INTERNAL_SERVER_ERROR")) } // Decode the RSA private key priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { log.Printf("bad private key: %s", err) return "", fmt.Errorf(("INTERNAL_SERVER_ERROR")) } passEncrypted, err := rsa.EncryptPKCS1v15(rand.Reader, &priv.PublicKey, []byte(password)) if err != nil { log.Printf("decrypt: %s\n", err) return "", fmt.Errorf(("DECRYPTION_FAILED")) } return base64.StdEncoding.EncodeToString(passEncrypted), nil } func DecryptPassword(passEncoded string) ([]byte, error) { /// decrypt password /// openssl genrsa -traditional -out private.pem 1024 /// openssl rsa -in private.pem -outform PEM -pubout -out public.pem pemData, err := ioutil.ReadFile(config.Data.Get("privatekey")) if err != nil { log.Printf("read key file: %s", err) return nil, fmt.Errorf(("INTERNAL_SERVER_ERROR")) } block, _ := pem.Decode(pemData) if block == nil { log.Printf("bad key data: %s", "not PEM-encoded") return nil, fmt.Errorf(("INTERNAL_SERVER_ERROR")) } if got, want := block.Type, "RSA PRIVATE KEY"; got != want { log.Printf("unknown key type %q, want %q", got, want) return nil, fmt.Errorf(("INTERNAL_SERVER_ERROR")) } // Decode the RSA private key priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { log.Printf("bad private key: %s", err) return nil, fmt.Errorf(("INTERNAL_SERVER_ERROR")) } var passDecrypted []byte var passDecoded []byte passDecoded, err = base64.StdEncoding.DecodeString(passEncoded) if err != nil { log.Printf("base64 decode: %s\n", err) return nil, fmt.Errorf(("BASE64_DECODE_FAILED")) } passDecrypted, err = rsa.DecryptPKCS1v15(rand.Reader, priv, []byte(passDecoded)) if err != nil { log.Printf("decrypt: %s\n", err) return nil, fmt.Errorf(("DECRYPTION_FAILED")) } return passDecrypted, nil } func CheckPasswordHash(hash, password string) bool { start := time.Now() err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) log.Printf("CompareHashAndPassword execution took %s", time.Since(start)) return err == nil } ``` **11. Membuat fungsi pkg/database/database.go** Jika di root folder sudah ada database.go, maka moving folder di pkg/database. lalu code nya seperti ini : ```golang package database import ( "bytes" "database/sql" "fmt" "log" "strings" "com.sismedika.com.absensi/pkg/config" _ "github.com/go-sql-driver/mysql" ) var Handle *sql.DB func InitDB() { dsn := config.Data.Get("DBuser") + ":" + config.Data.Get("DBpass") + "@tcp(" + config.Data.Get("DBhost") + ":" + config.Data.Get("DBport") + ")/" + config.Data.Get("DBname") handle, err := sql.Open("mysql", dsn) if err != nil { log.Panic(err) } if err = handle.Ping(); err != nil { log.Panic(err) } Handle = handle } func LogSQL(query string, args ...interface{}) { var buffer bytes.Buffer nArgs := len(args) // Break the string by question marks, iterate over its parts and for each // question mark - append an argument and format the argument according to // it's type, taking into consideration NULL values and quoting strings. for i, part := range strings.Split(query, "?") { buffer.WriteString(part) if i < nArgs { switch a := args[i].(type) { case int: buffer.WriteString(fmt.Sprintf("%d", a)) case int64: buffer.WriteString(fmt.Sprintf("%d", a)) case bool: buffer.WriteString(fmt.Sprintf("%t", a)) case sql.NullBool: if a.Valid { buffer.WriteString(fmt.Sprintf("%t", a.Bool)) } else { buffer.WriteString("NULL") } case sql.NullInt64: if a.Valid { buffer.WriteString(fmt.Sprintf("%d", a.Int64)) } else { buffer.WriteString("NULL") } case sql.NullString: if a.Valid { buffer.WriteString(fmt.Sprintf("%q", a.String)) } else { buffer.WriteString("NULL") } case sql.NullFloat64: if a.Valid { buffer.WriteString(fmt.Sprintf("%f", a.Float64)) } else { buffer.WriteString("NULL") } default: buffer.WriteString(fmt.Sprintf("%q", a)) } } } log.Print(buffer.String()) } ``` **12. Membuat fungsi pkg/jwt/jwt.go** Jika di folder root sudah ada jwt.go maka moving folder ke pkg/jwt. lalu code seperti ini ```golang package jwt import ( "fmt" "log" "time" "com.sismedika.com.absensi/pkg/config" "github.com/dgrijalva/jwt-go" ) // var conf = config.Data // GenerateToken generates a jwt token and assign a username to it's claims and return it func GenerateToken(user_id int, username string, staff_id int) (string, int64, error) { token := jwt.New(jwt.SigningMethodHS256) /* Create a map to store our claims */ claims := token.Claims.(jwt.MapClaims) /* Set token claims */ expired := time.Now().Add(time.Minute * time.Duration(config.Data.GetInt("tokenExpiration"))).Unix() claims["user_id"] = user_id claims["user_name"] = username claims["staff_id"] = staff_id claims["exp"] = expired tokenString, err := token.SignedString([]byte(config.Data.Get("secretkey"))) if err != nil { return "", 0, fmt.Errorf("GENERATE_TOKEN_FAILED") } return tokenString, expired, nil } // ParseToken parses a jwt token and returns the username in it's claims func ParseToken(tokenStr string) (int, string, int, error) { claims := jwt.MapClaims{} token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) { return []byte(config.Data.Get("secretkey")), nil }) if err != nil { v, _ := err.(*jwt.ValidationError) if v.Errors == jwt.ValidationErrorExpired { log.Printf("parse token 1: %v\n", err) return 0, "", 0, fmt.Errorf("TOKEN_EXPIRED") } log.Printf("parse token 2: %v\n", err) return 0, "", 0, fmt.Errorf("TOKEN_INVALID") } if !token.Valid { log.Printf("token invalid: %v\n", err) return 0, "", 0, fmt.Errorf(("TOKEN_INVALID")) } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { if !ok { return 0, "", 0, fmt.Errorf(("TOKEN_INVALID")) } // type of user_id is float64: https://stackoverflow.com/questions/70705673/panic-interface-conversion-interface-is-float64-not-int64 if claims["user_id"] == nil { return 0, "", 0, fmt.Errorf(("TOKEN_INVALID")) } if claims["user_name"] == nil { return 0, "", 0, fmt.Errorf(("TOKEN_INVALID")) } if claims["staff_id"] == nil { return 0, "", 0, fmt.Errorf(("TOKEN_INVALID")) } user_id := int(claims["user_id"].(float64)) user_name := claims["user_name"].(string) staff_id := int(claims["practitioner_id"].(float64)) return user_id, user_name, staff_id, nil } else { log.Printf("token claims: %v\n", err) return 0, "", 0, fmt.Errorf(("TOKEN_INVALID")) } } ``` **13. Membuat model graphql staff** Membuat file staff.graphqls, lalu code nya seperti ini ```gql # model staff type Staff { staff_id: ID! nip: String! name: String! email: String! phone_number: String is_active: String is_login: Boolean token: String id_google_sign_in: String! display_name_google_sign_in: String created_at: String last_updated_at: String } # model token response ketika login type TokenResponse { token: String message: String } # query search extend type Query { searchStaffByEmail(email: String!): Staff! staffGetByStaffId(staff_id: ID!): Staff! staffListBySearch(search:String, page:Int, maxPerPage: Int): [Staff!]! } # mutation untuk perubahan data extend type Mutation { generateToken(email: String!, id_google_sign_in:String!): TokenResponse! loginAttendance(email:String!, id_google_sign_in:String!) : Staff! } Setelah itu run generate, go run github.com/99designs/gqlgen generate ``` **14. Memindahkan generated.go, model, resolver ke folder generated, model, resolver** - pindahkan generated.go ke dalam folder graph/generated/generated.go. - pindahkan staff.graphqls ke dalam folder graph/graphqls/staff.graphqls. - pindahkan models_gen.go ke dalam folder graph/model/models_gen.go. - pindahkan resolver.go, schema.resolver.go, staff.resolver.go ke folder graph/resolver **15. Memindahkan tools.go di root ke folder tools/tools.go** **16. Memodifikasi gqlgen karena file sudah dipindahkan** Setelah dipindahkan pada langkah ke 14, maka perlu melakukan perubahan settingan di gqlgqn.yml. lalu perubahan code seperti ini : ```yaml # Where are all the schema files located? globs are supported eg src/**/*.graphqls schema: - graph/graphqls/*.graphqls # Where should the generated server code go? exec: filename: graph/generated/generated.go package: generated # Uncomment to enable federation # federation: # filename: graph/federation.go # package: graph # Where should any generated models go? model: filename: graph/model/models_gen.go package: model # Where should the resolver implementations go? resolver: layout: follow-schema dir: graph/resolver package: resolver filename_template: "{name}.resolvers.go" # Optional: turn on to not generate template comments above resolvers # omit_template_comment: false # Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models # struct_tag: json # Optional: turn on to use []Thing instead of []*Thing # omit_slice_element_pointers: false # Optional: turn on to omit Is<Name>() methods to interface and unions # omit_interface_checks : true # Optional: turn on to skip generation of ComplexityRoot struct content and Complexity function # omit_complexity: false # Optional: turn on to not generate any file notice comments in generated files # omit_gqlgen_file_notice: false # Optional: turn on to exclude the gqlgen version in the generated file notice. No effect if `omit_gqlgen_file_notice` is true. # omit_gqlgen_version_in_file_notice: false # Optional: turn off to make struct-type struct fields not use pointers # e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing } # struct_fields_always_pointers: true # Optional: turn off to make resolvers return values instead of pointers for structs # resolvers_always_return_pointers: true # Optional: turn on to return pointers instead of values in unmarshalInput # return_pointers_in_unmarshalinput: false # Optional: wrap nullable input fields with Omittable # nullable_input_omittable: true # Optional: set to speed up generation time by not performing a final validation pass. # skip_validation: true # Optional: set to skip running `go mod tidy` when generating server code # skip_mod_tidy: true # gqlgen will search for any type names in the schema in these go packages # if they match it will use them, otherwise it will generate them. autobind: # - "com.sismedika.com.absensi/graph/model" # This section declares type mapping between the GraphQL and go type systems # # The first line in each type will be used as defaults for resolver arguments and # modelgen, the others will be allowed when binding to fields. Configure them to # your liking models: ID: model: - github.com/99designs/gqlgen/graphql.ID - github.com/99designs/gqlgen/graphql.Int - github.com/99designs/gqlgen/graphql.Int64 - github.com/99designs/gqlgen/graphql.Int32 Int: model: - github.com/99designs/gqlgen/graphql.Int - github.com/99designs/gqlgen/graphql.Int64 - github.com/99designs/gqlgen/graphql.Int32 ``` **17. Generate ulang gql gen** lalu generate ulang go run github.com/99designs/gqlgen generate build env GOOS=linux GOARCH=amd64 go build server.go
sindhu changed title from [Tutorial Inisialisasi Project Golang] to [Tutorial Inisialisasi Project Golang & Build Golang] 2024-04-22 12:04:47 +07:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sindhu/belajar#39