Example use of Jenkins REST API with Golang

Hi!

I’m going to describe how to call Jenkins REST APIs using Go. We will create three Go files:
1. packages/helpers/helpers.go
2. packages/jenkins/jenkins.go
3. main.go

The source codes are available at https://github.com/mohdnaim/jenkins_rest_api

helpers.go contains function helpers. We put functions inside this file to maintain clean and organized codes.

jenkins.go contains functions related to Jenkins. Such functions are IsJobExist() to check whether a Jenkins job or build pipeline exists, CopyJenkinsJob() to copy a Jenkins job and DownloadConfigXML() to download the configurations of a job in XML format.

main.go is our main Go file, the starting point of our program.

main.go

package main

import (
	"fmt"
	"log"
	"strings"

	helpers "./packages/helpers"
	jenkins "./packages/jenkins"
)

func main() {
	// compulsory to set
	jenkinsURL := "http://127.0.0.1/"
	jenkinsUsername := "put your username here"
	jenkinsAPIToken := "put your API token here"

	jenkins.JenkinsDetails = jenkins.Details{jenkinsURL, jenkinsUsername, jenkinsAPIToken}
	xmlFolder := "xml"

	// 1. get all existing projects / jenkins jobs
	allProjectNames := jenkins.GetAllProjectNames()

	// 2. filter out projects that we want
	filteredProjectNames := make([]string, 0)
	for _, projectName := range allProjectNames {
		// do something

		// append to another slice based on condition
		if strings.HasPrefix(projectName, "prefix") {
			filteredProjectNames = append(filteredProjectNames, projectName)
		}
	}

	// 3. for each project, get its config.xml
	for _, projectName := range filteredProjectNames {
		xmlPath := fmt.Sprintf("%s/%s.xml", xmlFolder, projectName)
		if err := jenkins.DownloadConfigXML(projectName, xmlPath); err != nil {
			log.Println("error download config.xml for project:", projectName)
			continue // skip
		}
	}

	// 4. modify its config.xml
	files := helpers.GetFilenamesRecursively(xmlFolder)
	for _, xmlFile := range files {
		log.Println(xmlFile)
	}

	// 4b. rewrite config.xml

	// 5. http request POST updated config.xml
	for _, xmlFile := range files {
		tmpSlice := strings.Split(xmlFile, "/")
		projectName := tmpSlice[len(tmpSlice)-1]
		log.Println(projectName)

		if err := jenkins.PostConfigXML(projectName, xmlFile); err != nil {
			log.Println("error postconfigxml:", projectName)
		}
	}
}

We need to configure the details of the Jenkins instance. So set correct values to the following variables:
jenkinsURL := “http://127.0.0.1/”
jenkinsUsername := “put your username here”
jenkinsAPIToken := “put your API token here”

Line #18 creates a struct containing the three variables above. The struct is used by functions in jenkins.go so before we invoke any function in the jenkins.go module, we need to set the values.

Line #19 sets the folder name we store the XML files – which are the configuration file of Jenkins jobs.

Line #22 – we retrieve the names of all projects exist in the Jenkins instance. If we look at the implementation of the function GetAllProjectNames() in jenkins.go, the function invokes DownloadFileToBytes(). It makes HTTP request to /api/json with the username and password in the request header. The endpoint returns names of project in JSON format. It then converts the JSON format into ‘result’ map. For every name in the map, append the name into ‘allProjectNames’ array.

Line #24 – We filter out projects that we want. We iterate over the allProjectNames array and if a project meets certain criteria, we append the project name into ‘filteredProjectNames’ array which we will use onwards instead of ‘allProjectNames’.

Line #35 – For each project name in ‘filteredProjectNames’, we get its configuration file i.e. the config.xml.

Line #45 – We call a function GetFilenamesRecursively() in helpers.go to read all the file names in ‘xmlFolder’

Line #50 – We do whatever we want with the config.xml file such as renaming the project name, adding build parameters, change permission and so on.
I write a script in Python instead of Go to manipulate the config.xml files because Python has more libraries than Go that can help us to manipulate strings and files.

Line #52 – Finally after we make the change at line #50, we submit the change by executing POST request to Jenkins.

helpers.go

package helpers

import (
	"os"
	"path/filepath"
)

// StringInSlice ...
func StringInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

// GetFilenamesRecursively ...
func GetFilenamesRecursively(path string) []string {
	var files []string
	err := filepath.Walk("xml", func(path string, info os.FileInfo, err error) error {
		files = append(files, path)
		return nil
	})
	if err != nil {
		panic(err)
	}
	return files
}

jenkins.go

package jenkins

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"reflect"
)

// Details ...
type Details struct {
	URL      string
	Username string
	APIToken string
}

// JenkinsDetails ...
var JenkinsDetails = Details{}

// IsJobExist ...
func IsJobExist(projectName string) bool {
	fullURL := fmt.Sprintf("%sjob/%s", JenkinsDetails.URL, projectName)
	request, err := http.NewRequest("GET", fullURL, nil)
	request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		log.Println("project doesn't exist:", projectName)
		return false
	}
	if resp.StatusCode == 200 {
		// log.Println("project exists:", projectName)
		return true
	}
	log.Println("project doesn't exist:", projectName)
	return false
}

// CopyJenkinsJob ...
func CopyJenkinsJob(srcJob string, dstJob string) error {
	fullURL := fmt.Sprintf("%s%s?name=%s&mode=copy&from=%s", JenkinsDetails.URL, "createItem", dstJob, srcJob)
	request, err := http.NewRequest("POST", fullURL, nil)
	request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		return err
	}
	if resp.StatusCode == 200 {
		return nil
	}
	return fmt.Errorf("error copying job: %s", srcJob)
}

// DownloadConfigXML ...
func DownloadConfigXML(projectName string, dstFilename string) error {
	fullURL := fmt.Sprintf("%sjob/%s/config.xml", JenkinsDetails.URL, projectName)
	if DownloadFile(fullURL, dstFilename) == nil {
		return nil
	}
	return fmt.Errorf("error downloading file %s", fullURL)
}

// PostConfigXML ...
func PostConfigXML(projectName string, filename string) error {
	// read content of file
	data, err := os.Open(filename)
	if err != nil {
		log.Fatal(err)
		return err
	}

	fullURL := fmt.Sprintf("%sjob/%s/config.xml", JenkinsDetails.URL, projectName)
	request, err := http.NewRequest("POST", fullURL, data)
	request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
	client := &http.Client{}

	// perform the request
	resp, err := client.Do(request)
	if err != nil {
		return err
	}
	if resp.StatusCode == 200 {
		// log.Printf("success postConfigXML: %s %s %s", projectName, filename, resp.Status)
		return nil
	}
	return fmt.Errorf("error postConfigXML: %s %s %s", projectName, filename, resp.Status)
}

// DownloadFile ...
func DownloadFile(url string, filepath string) error {
	// Create the file
	out, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer out.Close()

	// Get the data
	request, err := http.NewRequest("GET", url, nil)
	request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Write the body to file
	_, err = io.Copy(out, resp.Body)
	if err != nil {
		return err
	}

	return nil
}

// DownloadFileToBytes ...
func DownloadFileToBytes(url string) ([]byte, error) {
	request, err := http.NewRequest("GET", url, nil)
	request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err) // writes to standard error
	}
	return bodyBytes, nil
}

// GetAllProjectNames ...
func GetAllProjectNames() []string {
	allProjectNames := make([]string, 0)
	allProjectsURL := fmt.Sprintf("%sapi/json?pretty=true", JenkinsDetails.URL)
	if respBytes, err := DownloadFileToBytes(allProjectsURL); err == nil {
		// json --> map
		var result map[string]interface{}
		json.Unmarshal([]byte(respBytes), &result)

		// if map has slice 'jobs', iterate over it
		if jobs, keyIsPresent := result["jobs"]; keyIsPresent && reflect.TypeOf(jobs).Kind() == reflect.Slice {
			jobs2 := result["jobs"].([]interface{})
			for _, job := range jobs2 {
				// log.Println(job)
				_map, _ := job.(map[string]interface{}) // assert to map
				allProjectNames = append(allProjectNames, _map["name"].(string))
			}
		}
	}
	return allProjectNames
}

Golang binary search implementation

package main

import (
	"fmt"
	"math"
)

func binarySearch(arr []int, val int) int {
	var first = 0
	var last = len(arr) - 1
	for first <= last {
		var midpoint = int(math.Floor(float64((first + last) / 2)))
		if arr[midpoint] == val {
			return midpoint
		}
		if val < arr[midpoint] {
			last = midpoint - 1
		} else {
			first = midpoint + 1
		}
	}
	return -1
}

func main() {
	arr := []int{1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39}
	fmt.Println(binarySearch(arr, 35))
}

Golang queue data structure implementation

package main

import (
	"container/list"
	"errors"
	"fmt"
)

// Queue is a list data structure that follows First-In-First-Out (FIFO) methodology
type Queue struct {
	size  int       // size of the queue
	items list.List // holds the elements
}

// enqueue() method inserts new element at the back of the queue
func (queue *Queue) enqueue(str string) (bool, error) {
	if queue.items.Len() >= queue.size {
		return false, errors.New("Overflow")
	}
	queue.items.PushBack(str)
	return true, nil
}

// dequeue() method removes element at the front of the queue
func (queue *Queue) dequeue() (string, error) {
	if queue.items.Len() == 0 {
		return "", errors.New("Empty")
	}
	ret := queue.items.Front().Value.(string)
	queue.items.Remove(queue.items.Front())
	return ret, nil
}

func main() {
	queue := &Queue{size: 5}

	fmt.Println(queue.enqueue("1"))
	fmt.Println(queue.dequeue())
	fmt.Println(queue.dequeue())

	fmt.Println(queue.enqueue("1"))
	fmt.Println(queue.enqueue("2"))
	fmt.Println(queue.enqueue("3"))
	fmt.Println(queue.enqueue("4"))
	fmt.Println(queue.enqueue("5"))
	fmt.Println(queue.enqueue("6"))
}

Golang stack implementation

package main

import (
	"container/list"
	"errors"
	"fmt"
)

// Stack is a list that can only perform two operations: push and pop
type Stack struct {
	items list.List
}

// push() method inserts new element at the top of the stack
func (stack *Stack) push(str string) {
	stack.items.PushBack(str)
}

// pop() method removes the element at the top of the stack
func (stack *Stack) pop() (string, error) {
	if stack.items.Back() == nil {
		return "", errors.New("Empty")
	}
	lastVal := stack.items.Back().Value.(string) // get the value of last element
	stack.items.Remove(stack.items.Back())       // remove the last element
	return lastVal, nil
}

func main() {
	stack := &Stack{}
	stack.push("1")
	stack.push("2")
	fmt.Println(stack.pop())
	fmt.Println(stack.pop())
	fmt.Println(stack.pop())
}

Golang singly linked list

package main

import "fmt"

// Node can store two values, 'id' and 'name'.
// Another value, 'ptr' is a pointer to another node
type Node struct {
	id   int
	name string
	ptr  *Node
}

// LinkedList struct
type LinkedList struct {
	head *Node
}

// LinkedList method to append a node to tail
func (linkedlist *LinkedList) append(newnode *Node) *LinkedList {
	if linkedlist.head == nil {
		linkedlist.head = newnode
		newnode.ptr = nil
		return linkedlist
	} // else
	// initialization; condition; increment
	for n := linkedlist.head; n != nil; n = n.ptr {
		if n.ptr == nil {
			n.ptr = newnode
			return linkedlist
		}
	}
	return linkedlist
}

// LinkedList method to print the nodes details
func (linkedlist *LinkedList) print() {
	for n := linkedlist.head; n != nil; n = n.ptr {
		fmt.Println(n.id, n.name, n.ptr)
	}
}

// LinkedList method to insert a new node before a particular node
func (linkedlist *LinkedList) insertBefore(name string, newNode *Node) bool {
	if linkedlist.head == nil {
		return false
	}

	ptrNode := linkedlist.head // points to first node
	for ptrNode.ptr != nil {   // while() loop
		if ptrNode.ptr.name == name {
			newNode.ptr = ptrNode.ptr // new node points to the next node
			ptrNode.ptr = newNode     // current node points to the new node
			return true
		}
		ptrNode = ptrNode.ptr // points to next node
	}
	return false
}

func main() {
	// create a linked list object
	linkedlist := &LinkedList{}

	// create nodes
	nodeA := &Node{1, "A", nil}
	nodeB := &Node{2, "B", nil}
	nodeC := &Node{3, "C", nil}
	nodeD := &Node{4, "D", nil}
	nodeE := &Node{5, "E", nil}

	// append the nodes to the linked list
	linkedlist.append(nodeA)
	linkedlist.append(nodeB)
	linkedlist.append(nodeC)
	linkedlist.append(nodeD)
	linkedlist.append(nodeE)

	// print the linked list
	linkedlist.print()

	// create a new node
	nodeF := &Node{6, "F", nil}
	// insert the new node before node "B"
	linkedlist.insertBefore("B", nodeF)
	// print the linked list
	linkedlist.print()
}

Export multi-line environment variable, replace a variable in file with the former using Go

There are times you want to replace a string in a file with a multi-line environment variable.

For example, you create a Jenkins pipeline and have a multi-line / text build parameter. You want to replace a string in file with the multi-line build parameter.

Using Linux utility such as sed and awk would be complicated because you need to escape some characters in the multi-line environment variable such as ‘=’, ‘/’, ‘\’ and so on.

There are reasons why Python 2.7 is installed by default in Linux distros like Ubuntu and RHEL, and there are reasons why sysadmins prefer to use Go instead of Python.

In this article I show an example how to replace a string with the multi-line environment variable from Jenkins using Go.

1. Write the multi-line environment variable to a file

#!/bin/bash
echo -e "$my_variable" > my_variable

2. Create a Go script to replace a string with the variable

package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strings"
)

func main() {

}

3. Write codes to parse command line arguments

	var filepath, replace, sSourceTextFile string
	flag.StringVar(&filepath, "file", "myfile.txt", "source file containing text to replace")
	flag.StringVar(&replace, "replace", "foo", "string to replace")
	flag.StringVar(&source, "source", "variable", "source file containing multi-line string/text to be replaced with")
	flag.Parse()

The codes above add command line arguments to the Go script.
The first argument is filepath, which points to the file path having the string that we want to replace
The second argument is the string that we want to replace
The third argument is the file containing the multi-line environment variable that we want to replace the string with

4. Read the file into memory

	// read the source file into memory
	input, err := ioutil.ReadFile(filepath)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

5. Read the file containing multi-line environment variable into memory

	// read the source text file into memory
	text, err := ioutil.ReadFile(source)
	if err != nil {
		fmt.Print(err)
		os.Exit(1)
	}

6. Perform the string replacement

	output := bytes.Replace(input, []byte(replace), []byte(text), -1)

7. Write the output into the same file

	if err = ioutil.WriteFile(filepath, output, 0666); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

8. Compile the codes
go build script-name.go

9. Run/test
./script-name -filepath=myfile.txt -replace=replaceme -source=my_variable

Echo framework + GORM = Blazing fast Golang server-side application. Authentication example

In this article I want to show example of login, logout and registration implementation using Go Echo framework and GORM (Go Object Relational Mapper) with PostgreSQL. We use JWT (JSON Web Token) to authenticate users.

Firstly we create main.go:

package main

import (
	"app1/helper"
	"app1/models"
	"fmt"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/postgres"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

func main() {
	configuration := helper.GetConfig()

	gormParameters := fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=disable", configuration.DbHost, configuration.DbPort, configuration.DbName, configuration.DbUsername, configuration.DbPassword)
	gormDB, err := gorm.Open("postgres", gormParameters)
	if err != nil {
		panic("failed to connect database")
	}
	helper.GormDB = gormDB

	// Migrate the schema (tables): User, Authentication
	helper.GormDB.AutoMigrate(&helper.User{})
	helper.GormDB.AutoMigrate(&helper.Authentication{})
	helper.GormDB.Model(&helper.Authentication{}).AddForeignKey("user_id", "users(id)", "CASCADE", "CASCADE")

	echoFramework := echo.New()
	echoFramework.Use(middleware.Logger()) // log
	echoFramework.Use(middleware.CORS())   // CORS from Any Origin, Any Method

	echoGroupUseJWT := echoFramework.Group("/api/v1")
	echoGroupUseJWT.Use(middleware.JWT([]byte(configuration.EncryptionKey)))
	echoGroupNoJWT := echoFramework.Group("/api/v1")

	// /api/v1/users : logged in users
	echoGroupUseJWT.POST("/users/logout", models.Logout)

	// /api/v1/users : public accessible
	echoGroupNoJWT.POST("/users", models.CreateUser)
	echoGroupNoJWT.POST("/users/login", models.Login)

	defer helper.GormDB.Close()
	echoFramework.Logger.Fatal(echoFramework.Start(":1323"))
}

Secondly we create globals.go:

package helper

import (
	"regexp"
	"time"

	"github.com/jinzhu/gorm"
)

type Configuration struct {
	EncryptionKey string
	DbHost        string
	DbPort        string
	DbName        string
	DbUsername    string
	DbPassword    string
}

var configuration Configuration

type ModelBase struct {
	ID        int       `gorm:"primary_key"`
	CreatedAt time.Time `json:"-"`
	UpdatedAt time.Time `json:"-"`
}

type User struct {
	ModelBase        // replaces gorm.Model
	Email     string `gorm:"not null; unique"`
	Password  string `gorm:"not null" json:"-"`
	Name      string `gorm:"not null; type:varchar(100)"` // unique_index
}

type Authentication struct {
	ModelBase
	User   User `gorm:"foreignkey:UserID; not null"`
	UserID int
	Token  string `gorm:"type:varchar(200); not null"`
}


type CustomHTTPSuccess struct {
	Data string `json:"data"`
}

type ErrorType struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

type CustomHTTPError struct {
	Error ErrorType `json:"error"`
}

var GormDB *gorm.DB

func init() {
	configuration = Configuration{
		EncryptionKey: "F61L8L7CUCGN0NK6336I8TFP9Y2ZOS43",
		DbHost:        "localhost",
		DbPort:        "5432",
		DbName:        "postgres",
		DbUsername:    "postgres",
		DbPassword:    "postgres",
	}
}

func GetConfig() Configuration {
	return configuration
}

func ValidateEmail(email string) (matchedString bool) {
	re := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
	matchedString = re.MatchString(email)
	return
}

Finally our implementation of the authentication methods in user.go:

package models

import (
	"app1/helper"
	"fmt"
	"net/http"
	"strconv"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/labstack/echo"
)

func CreateUser(c echo.Context) error {
	m := echo.Map{}
	if err := c.Bind(&m); err != nil {
		// return err
	}
	name := m["name"].(string)
	email := m["email"].(string)
	password := m["password"].(string)
	confirmPassword := m["confirm_password"].(string)

	if password == "" || confirmPassword == "" || name == "" || email == "" {
		return echo.NewHTTPError(http.StatusBadRequest, "Please enter name, email and password")
	}

	if password != confirmPassword {
		return echo.NewHTTPError(http.StatusBadRequest, "Confirm password is not same to password provided")
	}

	if helper.ValidateEmail(email) == false {
		return echo.NewHTTPError(http.StatusBadRequest, "Please enter valid email")
	}

	if bCheckUserExists(email) == true {
		return echo.NewHTTPError(http.StatusBadRequest, "Email provided already exists")
	}

	configuration := helper.GetConfig()

	enc, _ := helper.EncryptString(password, configuration.EncryptionKey)

	user1 := helper.User{Name: name, Email: email, Password: enc}
	// globals.GormDB.NewRecord(user) // => returns `true` as primary key is blank
	helper.GormDB.Create(&user1)

	token := jwt.New(jwt.SigningMethodHS256)

	claims := token.Claims.(jwt.MapClaims)
	claims["name"] = user1.Name
	claims["email"] = user1.Email

	t, err := token.SignedString([]byte(configuration.EncryptionKey)) // "secret" >> EncryptionKey
	if err != nil {
		return err
	}

	authentication := helper.Authentication{}
	if helper.GormDB.First(&authentication, "user_id =?", user1.ID).RecordNotFound() {
		// insert
		helper.GormDB.Create(&helper.Authentication{User: user1, Token: t})
	} else {
		authentication.User = user1
		authentication.Token = t
		helper.GormDB.Save(&authentication)
	}

	return c.JSON(http.StatusOK, map[string]string{
		"token": t,
	})
}

func bCheckUserExists(email string) bool {
	user1 := helper.User{}
	if helper.GormDB.Where(&helper.User{Email: email}).First(&user1).RecordNotFound() {
		return false
	}
	return true
}

func ValidateUser(email, password string, c echo.Context) (bool, error) {
	fmt.Println("validate")
	var user1 helper.User
	if helper.GormDB.First(&user1, "email =?", email).RecordNotFound() {
		return false, nil
	}

	configuration := helper.GetConfig()

	decrypted, _ := helper.DecryptString(user1.Password, configuration.EncryptionKey)

	if password == decrypted {
		return true, nil
	}
	return false, nil
}

func Login(c echo.Context) error {
	m := echo.Map{}
	if err := c.Bind(&m); err != nil {
		// return err
	}
	email := m["email"].(string)
	password := m["password"].(string)

	var user1 helper.User
	if helper.GormDB.First(&user1, "email =?", email).RecordNotFound() {
		_error := helper.CustomHTTPError{
			Error: helper.ErrorType{
				Code:    http.StatusBadRequest,
				Message: "Invalid email & password",
			},
		}
		return c.JSONPretty(http.StatusBadGateway, _error, "  ")
	}

	configuration := helper.GetConfig()
	decrypted, _ := helper.DecryptString(user1.Password, configuration.EncryptionKey)

	if password == decrypted {
		token := jwt.New(jwt.SigningMethodHS256)

		claims := token.Claims.(jwt.MapClaims)
		claims["name"] = user1.Name
		claims["email"] = user1.Email
		claims["id"] = user1.ModelBase.ID

		t, err := token.SignedString([]byte(configuration.EncryptionKey)) // "secret" >> EncryptionKey
		if err != nil {
			return err
		}

		authentication := helper.Authentication{}
		if helper.GormDB.First(&authentication, "user_id =?", user1.ID).RecordNotFound() {
			// insert
			helper.GormDB.Create(&helper.Authentication{User: user1, Token: t})
		} else {
			// update
			authentication.User = user1
			authentication.Token = t
			helper.GormDB.Save(&authentication)
		}

		return c.JSON(http.StatusOK, map[string]string{
			"token": t,
		})
	} else {
		_error := helper.CustomHTTPError{
			Error: helper.ErrorType{
				Code:    http.StatusBadRequest,
				Message: "Invalid email & password",
			},
		}
		return c.JSONPretty(http.StatusBadGateway, _error, "  ")
	}
}

func Logout(c echo.Context) error {
	tokenRequester := c.Get("user").(*jwt.Token)
	claims := tokenRequester.Claims.(jwt.MapClaims)
	fRequesterID := claims["id"].(float64)
	iRequesterID := int(fRequesterID)
	sRequesterID := strconv.Itoa(iRequesterID)

	requester := helper.User{}
	if helper.GormDB.First(&requester, "id =?", sRequesterID).RecordNotFound() {
		return echo.ErrUnauthorized
	}

	authentication := helper.Authentication{}
	if helper.GormDB.First(&authentication, "user_id =?", requester.ModelBase.ID).RecordNotFound() {
		return echo.ErrUnauthorized
	}
	helper.GormDB.Delete(&authentication)
	return c.String(http.StatusAccepted, "")
}

 

Encryption and decryption functions are missing. Drop me an email to get them 🙂