Full Code of gwuah/postmates for AI

master 9317d13f54ff cached
65 files
109.9 KB
41.4k tokens
214 symbols
1 requests
Download .txt
Repository: gwuah/postmates
Branch: master
Commit: 9317d13f54ff
Files: 65
Total size: 109.9 KB

Directory structure:
gitextract_qfqtsf5j/

├── .dockerignore
├── .gitignore
├── API_DOCS.md
├── Dockerfile
├── Dockerfile.wait
├── README.md
├── database/
│   ├── couriers.yml
│   ├── customers.yml
│   ├── models/
│   │   ├── courier.go
│   │   ├── customer.go
│   │   ├── delivery.go
│   │   ├── order.go
│   │   ├── product.go
│   │   ├── tripPoint.go
│   │   └── vehicle.go
│   ├── postgres/
│   │   └── postgres.go
│   ├── products.yml
│   ├── redis/
│   │   └── redis.go
│   ├── seeds.go
│   └── vehicles.yml
├── demo/
│   ├── customer__closest_couriers.js
│   ├── customer__delivery_request.js
│   ├── electrons.js
│   └── package.json
├── docker-compose.yml
├── go.mod
├── go.sum
├── handler/
│   ├── auth.go
│   ├── base.go
│   ├── courier.go
│   ├── customer.go
│   ├── delivery.go
│   ├── handler.go
│   ├── order.go
│   └── ws.go
├── lib/
│   ├── billing/
│   │   └── billing.go
│   ├── eta/
│   │   └── eta.go
│   ├── sms/
│   │   └── sms.go
│   └── ws/
│       ├── connection.go
│       ├── hub.go
│       └── room.go
├── main.go
├── middleware/
│   ├── cors.go
│   └── jwt.go
├── plg/
│   └── handy.go
├── repository/
│   ├── courier.go
│   ├── customer.go
│   ├── delivery.go
│   ├── order.go
│   ├── product.go
│   ├── repository.go
│   └── tripPoint.go
├── server/
│   └── server.go
├── services/
│   ├── base.go
│   ├── courier.go
│   ├── delivery.go
│   ├── dispatch.go
│   ├── order.go
│   └── services.go
├── shared/
│   └── types.go
└── utils/
    ├── geo/
    │   └── geo.go
    ├── jwt/
    │   └── jwt.go
    ├── secure/
    │   └── secure.go
    ├── utils.go
    └── validator/
        └── validator.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
Dockerfile
docker-compose.yml
./postmates
./postmates-app


================================================
FILE: .gitignore
================================================
.env
node_modules/
main

================================================
FILE: API_DOCS.md
================================================
# Docs

### Courier can initiate connection @

`/courier/realtime/:id`

### Customer can initiate connection @

`/customer/realtime/:id`

see `handler/ws.go` and `handler/handler.go` for more details with regards to realtime connections.

### Get Delivery Quote

`/v1/get-delivery-cost`

**method:** POST

**data params:**

```
{
    origin: {
        latitude: 5.677474538991623,
        longitude: -0.24460022375167725
    },
    destination: {
        latitude: 5.6796946725653745,
        longitude: -0.2447180449962616
    }
}
```

**response:**

```
{
    data: {
        estimate: {
            1: {
                productId: 1,
                price: 5
            }
        },
        distance: 2.3166666666666664,
        duration: 319
    },
    message: "success"
}
```

### Rate Delivery (Customer)

`/v1/customer-rate-trip`

**method:** POST

**data params:**

```
{
    message: "Good Service",
    deliveryId: 1,
    customerId: 1,
    rating: 5
}
```

**response:**

```
{
    message: "success"
    data: true
}
```

### Rate Delivery (Courier)

`/v1/courier-rate-trip`

**method:** POST

**data params:**

```
{
    message: "Good Service",
    deliveryId: 1,
    courierId: 1
    rating: 5
}
```

**response:**

```
{
    message: "success"
    data: true
}
```


================================================
FILE: Dockerfile
================================================
FROM golang:1.14-alpine AS main-env
# install gcc for uber/h3-go. see https://github.com/uber/h3/issues/354
RUN apk add build-base


RUN mkdir /app
ARG PORT=8080
ENV PORT=${PORT}
ADD . /app/


WORKDIR /app
RUN cd /app
# Attempt to cache the module retrieval
RUN go mod download
RUN go build -o postmates-app

FROM alpine

WORKDIR /app
COPY --from=main-env /app/postmates-app /app
COPY --from=main-env /app/database /app/database
COPY .env /app/.env

EXPOSE $PORT

CMD ["/app/postmates-app"]

================================================
FILE: Dockerfile.wait
================================================
FROM alpine


# Add docker-compose-wait tool -------------------
ENV WAIT_VERSION 2.7.3
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/$WAIT_VERSION/wait /wait
RUN chmod +x /wait

CMD ["/wait"]

================================================
FILE: README.md
================================================
# Postmates

This is the heart of a delivery service. Features include geo-indexing, order-dispatch, proximity-searching, ETA, trip estimates, etc <br/>
We use google maps for features such as distance-matrix and directions <br/>
Find more documentation [here](https://github.com/gwuah/postmates/blob/master/API_DOCS.md)

# Inbuilt Features

- [x] geo-indexing
- [x] geo-radius search
- [x] ETA
- [x] order creation
- [x] order dispatch
- [x] order acceptance
- [x] order order rejection
- [x] customer login/signup
- [x] customer ratings

# Requirements

- Postgres
- Redis
- Uber H3

# Architecture

- We use websocket connections for realtime communications with courier and customers. The ws connections are store in-memory in a concurrency safe manner.
- Couriers are indexed using uber's h3 geo-indexing library and grouped in redis.
- When you perform a radius search(closest couriers), we use h3 to calculate all indices 2 levels at resolution 8, see image below. Then we query our courier index, powered by redis to find all the couriers in those locations. Then, we make a request using their lng/lats and the customer's lng/lat to google maps to get the distance and duration from the customer, then we sort that result, and then dispatch the order to these couriers in order of those closest the origin of the request. (See [image](https://github.com/gwuah/postmates/blob/master/img/radius.png)
- The couriers send location updates every 3 seconds. This allows us to know their locations in almost realtime.
- The dispatch logic gives a courier 5 seconds to accept an order, after which it is sent to the next closest/available courier. If none of the available couriers accept the request, the process starts all over again, till someone finally accepts it.

## Project Setup

1. Clone the repo and make a copy of .env.sample as .env & update the env vars.

```bash
git clone https://github.com/gwuah/postmates.git
cp .env.sample .env
```

2. Run the app using either :

```bash
go run main.go
```

```bash
go build main.go
./main
```

# Demo

- `cd demo` and run `yarn` to install all required dependencies.
- run `node electrons.js` to initiate 3 couriers instances that are constantly sending location updates every 3 seconds
- run `node customer__delivery_request.js` to instantiate a customer that will create a delivery request.
- pay attention to the logs.


================================================
FILE: database/couriers.yml
================================================
- Griffith Awuah
- Andy Osei
- Yaw Manu


================================================
FILE: database/customers.yml
================================================
- Alicia Keys


================================================
FILE: database/models/courier.go
================================================
package models

type Courier struct {
	Model
	FirstName  string   `json:"firstName"`
	LastName   string   `json:"lastName"`
	MiddleName string   `json:"middleName"`
	Longitude  float64  `json:"longitude"`
	Latitude   float64  `json:"latitude"`
	State      State    `json:"state"`
	Vehicle    *Vehicle `json:"vehicle,omitempty"`
	// Deliveries []Delivery `json:"deliveries"`
	PhotoUrl string `json:"photoUrl"`
	Rating   int    `json:"rating"`
}


================================================
FILE: database/models/customer.go
================================================
package models

import (
	"time"
)

type Model struct {
	ID        uint       `gorm:"primary_key" json:"id"`
	CreatedAt time.Time  `json:"createdAt"`
	UpdatedAt time.Time  `json:"updatedAt"`
	DeletedAt *time.Time `sql:"index" json:"deletedAt"`
}

type Customer struct {
	Model
	State     State   `json:"state"`
	Phone     string  `gorm:"not null;unique" json:"phone"`
	FirstName string  `json:"firstName"`
	LastName  string  `json:"lastName"`
	Email     string  `json:"email"`
	Longitude float64 `json:"longitude"`
	Latitude  float64 `json:"latitude"`
	Code      int     `json:"code"`
	Active    bool    `json:"active" gorm:"default=false"`
	Token     string  `json:"-"`
	Rating    int     `json:"rating"`
}


================================================
FILE: database/models/delivery.go
================================================
package models

type State string

const (
	// delivery state types
	Pending         State = "pending"
	PendingPickup         = "pending_pickup"
	NearingPickup         = "nearing_pickup"
	AtPickup              = "at_pickup"
	DeliveryOngoing       = "delivery_ongoing"
	NearingDropoff        = "nearing_dropoff"
	AtDropOff             = "at_dropoff"
	Completed             = "completed"
	Cancelled             = "cancelled"

	// courier state types
	AwaitingDispatch = "awaiting_dispatch"
	Dispatched       = "dispatched"
	OnTrip           = "on_trip"
	Offline          = "offline"

	// vehicle state
	Inactive = "inactive"

	// customer state
	Searching = "searching"
	AtRest    = "atRest"
)

type Delivery struct {
	Model
	State                 State        `json:"state"`
	OriginLongitude       float64      `json:"originLongitude"`
	OriginLatitude        float64      `json:"originLatitude"`
	DestinationLongitude  float64      `json:"destinationLongitude"`
	DestinationLatitude   float64      `json:"destinationLatitude"`
	FinalCost             float64      `json:"finalCost"`
	InitialCost           float64      `json:"initialCost"`
	Completed             bool         `json:"completed"`
	Notes                 string       `json:"notes"`
	CustomerID            uint         `json:"customerId"`
	Customer              Customer     `json:"customer"`
	ProductID             uint         `json:"productId"`
	Product               Product      `json:"product"`
	CourierID             *uint        `json:"courierId,omitempty"`
	Courier               *Courier     `json:"courier,omitempty"`
	TripPoints            []*TripPoint `json:"tripPoints,omitempty"`
	CourierRating         float64      `json:"courierRating"`
	CourierRatingMessage  string       `json:"courierRatingMessage"`
	CustomerRating        float64      `json:"customerRating"`
	CustomerRatingMessage string       `json:"customerRatingMessage"`
}


================================================
FILE: database/models/order.go
================================================
package models

type Order struct {
	Model
	// Deliveries []Delivery `json:"deliveries"`
	CourierID uint    `json:"courierId"`
	Courier   Courier `json:"courier"`
	Completed bool    `json:"completed"`
	State     State   `json:"state"`
}


================================================
FILE: database/models/product.go
================================================
package models

type Product struct {
	Model
	Name string `json:"name"`
}


================================================
FILE: database/models/tripPoint.go
================================================
package models

type TripPoint struct {
	Model
	DeliveryID uint      `json:"deliveryId"`
	Delivery   *Delivery `json:"delivery"`
	Longitude  float64   `json:"longitude"`
	Latitude   float64   `json:"latitude"`
	State      State     `json:"state"`
}


================================================
FILE: database/models/vehicle.go
================================================
package models

type VehicleType string

const (
	Motor VehicleType = "motor"
	Car               = "car"
)

type Vehicle struct {
	Model

	RegNumber    string      `gorm:"not null;unique" json:"regNumber"`
	VehicleModel string      `json:"vehicleModel"`
	Type         VehicleType `json:"vehicleType"`
	CourierID    uint        `json:"courierId"`

	State  State `json:"state"`
	Active bool  `json:"active" gorm:"default=false"`
}


================================================
FILE: database/postgres/postgres.go
================================================
package postgres

import (
	"fmt"
	"os"

	"gorm.io/driver/postgres"

	"gorm.io/gorm"
)

type Config struct {
	Host     string
	Port     string
	Password string
	User     string
	DBName   string
	SSLMode  string
	DBurl    string
}

func SetupDatabase(db *gorm.DB, models ...interface{}) error {
	err := db.AutoMigrate(models...)
	return err
}

func New(config *Config) (*gorm.DB, error) {
	var (
		db  *gorm.DB
		err error
	)

	dsn := fmt.Sprintf(
		"host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
		config.Host, config.Port, config.User, config.Password, config.DBName, config.SSLMode,
	)

	if os.Getenv("ENV") == "staging" || os.Getenv("ENV") == "production" {
		db, err = gorm.Open(postgres.Open(config.DBurl), &gorm.Config{})
	} else {
		db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
	}

	if err != nil {
		return nil, err
	}

	return db, nil
}


================================================
FILE: database/products.yml
================================================
- Express
- Pool


================================================
FILE: database/redis/redis.go
================================================
package redis

import (
	"net/url"
	"os"

	"github.com/go-redis/redis"
)

type Config struct {
	Addr     string
	Password string
	DB       int
	DBurl    string
}

func New(config *Config) *redis.Client {
	if os.Getenv("ENV") == "staging" || os.Getenv("ENV") == "production" {
		parsedURL, _ := url.Parse(config.DBurl)
		password, _ := parsedURL.User.Password()
		return redis.NewClient(&redis.Options{
			Addr:     parsedURL.Host,
			Password: password,
		})
	}

	return redis.NewClient(&redis.Options{
		Addr:     config.Addr,
		Password: config.Password,
		DB:       config.DB,
	})
}


================================================
FILE: database/seeds.go
================================================
package database

import (
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/utils"
	"github.com/kylelemons/go-gypsy/yaml"
	"gorm.io/gorm"
)

type SeedFn func(db *gorm.DB, path string)

func RunSeeds(db *gorm.DB, seeds []SeedFn) {
	path, err := os.Getwd()
	if err != nil {
		panic(err)
	}

	for _, seed := range seeds {
		seed(db, path)
	}
}

func SeedProducts(DB *gorm.DB, path string) {
	config, err := yaml.ReadFile(path + "/database/products.yml")
	if err != nil {
		panic(err)
	}

	productList, ok := config.Root.(yaml.List)
	if !ok {
		panic("failed to parse product.yml")
	}

	for i := 0; i < productList.Len(); i++ {
		productName := strings.ToLower(fmt.Sprintf("%s", productList.Item(i)))
		var product models.Product

		if err := DB.Where("name = ?", productName).First(&product).Error; err != nil {
			if err == gorm.ErrRecordNotFound {
				DB.Create(&models.Product{Name: productName})
			} else {
				log.Printf("Product [ %s ] lookup failed", productName)
				log.Println(err)
			}
		}
	}
}

func SeedCouriers(DB *gorm.DB, path string) {
	config, err := yaml.ReadFile(path + "/database/couriers.yml")
	if err != nil {
		panic(err)
	}

	courierList, ok := config.Root.(yaml.List)
	if !ok {
		panic("failed to parse product.yml")
	}

	for i := 0; i < courierList.Len(); i++ {
		name := strings.ToLower(fmt.Sprintf("%s", courierList.Item(i)))
		firstName := strings.Split(name, " ")[0]
		lastName := strings.Split(name, " ")[1]

		var courier models.Courier

		if err := DB.Where("first_name = ? AND last_name = ?", firstName, lastName).First(&courier).Error; err != nil {
			if err == gorm.ErrRecordNotFound {
				DB.Create(&models.Courier{FirstName: firstName, LastName: lastName})
			} else {
				log.Printf("Courier [ %s ] lookup failed", name)
				log.Println(err)
			}
		}
	}
}

func SeedCustomers(DB *gorm.DB, path string) {
	config, err := yaml.ReadFile(path + "/database/customers.yml")
	if err != nil {
		panic(err)
	}

	customerList, ok := config.Root.(yaml.List)
	if !ok {
		panic("failed to parse product.yml")
	}

	for i := 0; i < customerList.Len(); i++ {
		name := strings.ToLower(fmt.Sprintf("%s", customerList.Item(i)))
		firstName := strings.Split(name, " ")[0]
		lastName := strings.Split(name, " ")[1]

		var customer models.Customer

		if err := DB.Where("first_name = ? AND last_name = ?", firstName, lastName).First(&customer).Error; err != nil {
			if err == gorm.ErrRecordNotFound {
				DB.Create(&models.Customer{FirstName: firstName, LastName: lastName, Active: true})
			} else {
				log.Printf("Customer [ %s ] lookup failed", name)
				log.Println(err)
			}
		}
	}
}

func SeedVehicles(DB *gorm.DB, path string) {
	config, err := yaml.ReadFile(path + "/database/vehicles.yml")
	if err != nil {
		panic(err)
	}

	c, ok := config.Root.(yaml.List)

	if !ok {
		panic("failed to parse vehicles.yml")
	}

	cd, ok := c.Item(0).(yaml.Map)

	if !ok {
		panic("failed to parse vehicles.yml")
	}

	l, ok := cd["data"].(yaml.List)

	if !ok {
		panic("failed to parse vehicles.yml")
	}
	for _, v := range l {

		value, ok := v.(yaml.Map)

		if !ok {
			panic("failed to parse vehicles.yml")
		}

		courierId := fmt.Sprintf("%v", value["courierId"])
		vehicleModel := fmt.Sprintf("%v", value["vehicleModel"])
		regNumber := fmt.Sprintf("%v", value["regNumber"])
		Type := fmt.Sprintf("%v", value["type"])

		vehicle := models.Vehicle{
			CourierID:    uint(utils.ConvertToUint64(courierId)),
			VehicleModel: vehicleModel,
			RegNumber:    regNumber,
			Type:         utils.ConvertToVehicleType(Type),
			Active:       false,
		}

		if err := DB.Where("reg_number = ?", vehicle.RegNumber).First(&models.Vehicle{}).Error; err != nil {
			if err == gorm.ErrRecordNotFound {
				DB.Create(&vehicle)
			} else {
				log.Printf("Vehicle [ %s ] lookup failed", vehicle.RegNumber)
				log.Println(err)
			}
		}

	}

}


================================================
FILE: database/vehicles.yml
================================================
- data:
    - vehicleModel: yahama
      regNumber: GX 4888-10
      type: motor
      courierId: 1
    - vehicleModel: yahama
      regNumber: GM 44-10
      type: motor
      courierId: 2
    - vehicleModel: kia morning
      regNumber: GE 4993-10
      type: Car
      courierId: 3


================================================
FILE: demo/customer__closest_couriers.js
================================================
const WebSocket = require("ws");

function connect(id) {
  console.log(`Customer ${id} initating a connection ... `);
  let ws = new WebSocket(`ws://localhost:8080/v1/customer/realtime/${id}`);

  ws.on("open", (e) => {
    console.log("connection successful");

    setInterval(() => {
      ws.send(
        JSON.stringify({
          meta: {
            type: "GetClosestCouriers",
          },
          id: id,
          origin: {
            latitude: 5.6796946725653745,
            longitude: -0.2447180449962616,
          },
        })
      );
    }, 2000);
  });

  ws.on("message", function (data) {
    console.log(data);
  });

  ws.on("error", function (data) {
    console.log("Error connecting");
  });
}

function parseMessage(message) {}

function main() {
  connect(process.argv[2]);
}

main();


================================================
FILE: demo/customer__delivery_request.js
================================================
const WebSocket = require("ws");

function connect(id) {
  console.log(`Customer ${id} initating a connection ... `);
  let ws = new WebSocket(`ws://localhost:8080/v1/customer/realtime/${id}`);

  ws.on("open", (e) => {
    console.log("connection successful");

    setTimeout(() => {
      ws.send(
        JSON.stringify({
          meta: {
            type: "DeliveryRequest",
          },
          productId: 1,
          customerID: 1,
          notes: "Handle it carefully",
          origin: {
            latitude: 5.6796946725653745,
            longitude: -0.2447180449962616,
          },
          destination: {
            longitude: 2.4345545,
            latitude: 4.054594095,
          },
        })
      );
    }, 1000);
  });

  ws.on("message", function (data) {
    let parsed = JSON.parse(data);
    console.log(JSON.stringify(parsed, null, 4));
  });

  ws.on("error", function (data) {
    console.log("Error connecting");
  });
}

function main() {
  connect("PostMaster");
}

main();


================================================
FILE: demo/electrons.js
================================================
const WebSocket = require("ws");

const outsideScope = {
  latitude: 5.698188535023582,
  longitude: -0.239341780857103,
};

const defaultCabPositions = [
  {
    longitude: -0.2475990969444747,
    latitude: 5.684136332305188,
    color: "blue",
  },
  {
    longitude: -0.2397266058667604,
    latitude: 5.683835847589247,
    color: "blue",
  },
  {
    longitude: -0.24460022375167725,
    latitude: 5.677474538991623,
    color: "blue",
  },
];

class Courier {
  constructor(id, coord) {
    this.appState = {
      id,
      coord,
      state: "awaiting_dispatch",
      courier: null,
      current_delivery: null,
    };
  }

  _initialization() {
    this.ws = new WebSocket(
      `ws://localhost:8080/v1/courier/realtime/${this.appState.id}`
    );

    this.ws.on("message", (msg) => {
      console.log("new message");
      this.handleMessage(msg);
    });

    this.ws.on("error", (data) => {
      console.log("Error connecting", data);
    });

    console.log(`Courier ${this.appState.id} has been instantiated.`);

    this._sendLocationUpdate();
  }

  _handleNewDelivery(parsed) {
    console.log(`NewDeliveryRequest Recieved ${this.appState.id} `);
    if (this.appState.id == "2") {
      console.log(
        `ID(${this.appState.id}) >>> `,
        JSON.stringify(parsed, null, 4)
      );
      this.appState.current_delivery = parsed.delivery;
      this.ws.send(
        JSON.stringify({
          meta: {
            type: "AcceptDelivery",
          },
          deliveryId: parsed.delivery.id,
        })
      );
    }
  }

  _sendLocationUpdate() {
    const deliveryId = this.appState.current_delivery
      ? this.appState.current_delivery.id
      : null;

    setInterval(() => {
      this.ws.send(
        JSON.stringify({
          meta: {
            type: "LocationUpdate",
          },
          id: this.appState.id,
          latitude: this.appState.coord.latitude,
          longitude: this.appState.coord.longitude,
          state: this.appState.state,
          deliveryId,
        })
      );
    }, 3000);
  }

  handleMessage(message) {
    let parsed = JSON.parse(message);
    switch (parsed.meta.type) {
      case "NewDelivery":
        this._handleNewDelivery(parsed);
        break;
    }
  }
}

function main() {
  var c1 = new Courier("1", defaultCabPositions[0]);
  c1._initialization();
  var c2 = new Courier("2", defaultCabPositions[1]);
  c2._initialization();
  var c3 = new Courier("3", defaultCabPositions[2]);
  c3._initialization();
}

main();


================================================
FILE: demo/package.json
================================================
{
  "name": "demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ws": "^7.3.1"
  }
}


================================================
FILE: docker-compose.yml
================================================
version: '3.7'
services:
  postgres:
    image: "postgres:13.1"
    hostname: postgres
    container_name: postmates-postgres
    env_file: .env
    environment:
      - POSTGRES_PASSWORD=${DB_PASS}
      - POSTGRES_DB=${DB_NAME}
      - POSTGRES_USER=${DB_USER}
    ports:
      - "5432:5432"

  redis:
    image: redis:5.0.10-alpine
    container_name: postmates-redis
    ports:
      - "6379:6379"

  postmates:
    build:
      dockerfile: ./Dockerfile
      context: .
      args:
        PORT: ${PORT}
    container_name: postmates-app
    ports:
      - "9000:${PORT}"
    env_file: .env
    depends_on:
      - postgres
      - redis
      - waiter

  waiter:
    build:
      dockerfile: ./Dockerfile.wait
      context: .
    container_name: postmates-waiter
    depends_on: 
      - postgres
      - redis

    environment:
      - WAIT_HOSTS=postgres:5432, redis:6379
      - WAIT_HOSTS_TIMEOUT=300
      - WAIT_SLEEP_INTERVAL=30
      - WAIT_HOST_CONNECT_TIMEOUT=30
      - WAIT_AFTER_HOSTS=0

================================================
FILE: go.mod
================================================
module github.com/gwuah/postmates

go 1.14

// +heroku goVersion go1.14.2

require (
	github.com/dgrijalva/jwt-go v3.2.0+incompatible
	github.com/gin-gonic/gin v1.6.3
	github.com/go-playground/validator/v10 v10.3.0
	github.com/go-redis/redis v6.15.9+incompatible
	github.com/gorilla/websocket v1.4.2
	github.com/joho/godotenv v1.3.0
	github.com/json-iterator/go v1.1.10 // indirect
	github.com/kylelemons/go-gypsy v1.0.0
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.1 // indirect
	github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d
	github.com/onsi/ginkgo v1.14.2 // indirect
	github.com/onsi/gomega v1.10.3 // indirect
	github.com/uber/h3-go v3.0.1+incompatible
	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
	google.golang.org/protobuf v1.25.0 // indirect
	googlemaps.github.io/maps v1.2.3
	gorm.io/driver/postgres v1.0.0
	gorm.io/gorm v1.20.2
)


================================================
FILE: go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o=
github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.6.4 h1:S7T6cx5o2OqmxdHaXLH1ZeD1SbI8jBznyYE9Ec0RCQ8=
github.com/jackc/pgconn v1.6.4/go.mod h1:w2pne1C2tZgP+TvjqLpOigGzNqjBgQW9dUw/4Chex78=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.2 h1:q1Hsy66zh4vuNsajBUF2PNqfAMMfxU5mk594lPE9vjY=
github.com/jackc/pgproto3/v2 v2.0.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
github.com/jackc/pgtype v1.4.2 h1:t+6LWm5eWPLX1H5Se702JSBcirq6uWa4jiG4wV1rAWY=
github.com/jackc/pgtype v1.4.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.8.1 h1:SUbCLP2pXvf/Sr/25KsuI4aTxiFYIvpfk4l6aTSdyCw=
github.com/jackc/pgx/v4 v4.8.1/go.mod h1:4HOLxrl8wToZJReD04/yB20GDwf4KBYETvlHciCnwW0=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/go-gypsy v1.0.0 h1:7/wQ7A3UL1bnqRMnZ6T8cwCOArfZCxFmb1iTxaOOo1s=
github.com/kylelemons/go-gypsy v1.0.0/go.mod h1:chkXM0zjdpXOiqkCW1XcCHDfjfk14PH2KKkQWxfJUcU=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/uber/h3-go v3.0.1+incompatible h1:RVpBm8qd7mM94YuIhNQfCXpVj6mPY6gNsVstDg1FvjY=
github.com/uber/h3-go v3.0.1+incompatible/go.mod h1:66a2M4rQlf+dtkTWj3bHoLFgDT/Rt4kLT8dMuEQVQvw=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
googlemaps.github.io/maps v1.2.3 h1:zChNy7zFReU4ovIw5btSPks47imSE/OhAtn9Rn8T1wg=
googlemaps.github.io/maps v1.2.3/go.mod h1:cCq0JKYAnnCRSdiaBi7Ex9CW15uxIAk7oPi8V/xEh6s=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.0.0 h1:Yh4jyFQ0a7F+JPU0Gtiam/eKmpT/XFc1FKxotGqc6FM=
gorm.io/driver/postgres v1.0.0/go.mod h1:wtMFcOzmuA5QigNsgEIb7O5lhvH1tHAF1RbWmLWV4to=
gorm.io/gorm v1.9.19 h1:NMrwpxOZIHWJEFzZ0MM8PdYlcXyKLaXTHWfpDEDdBNg=
gorm.io/gorm v1.9.19/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.2 h1:bZzSEnq7NDGsrd+n3evOOedDrY5oLM5QPlCjZJUK2ro=
gorm.io/gorm v1.20.2/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=


================================================
FILE: handler/auth.go
================================================
package handler

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/gwuah/postmates/database/models"
)

func (h *Handler) Refresh(c *gin.Context) {
	token := c.Param("token")
	if token == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": "token not found",
		})
		return
	}

	customer := new(models.Customer)
	result := h.DB.Where("token = ?", token).First(customer)
	if result.Error != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failed to fetch customer",
		})
		return
	}

	newToken, err := h.JWT.GenerateToken(customer)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failed to generate token",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": "success",
		"token":   newToken,
	})
	return
}


================================================
FILE: handler/base.go
================================================
package handler

import (
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"github.com/gwuah/postmates/shared"
	myValidator "github.com/gwuah/postmates/utils/validator"
)

func (h *Handler) handleCustomerRating(c *gin.Context) {
	var data shared.CustomerRatingRequest
	if err := c.ShouldBindJSON(&data); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failure",
			"err":     err,
		})
		return
	}

	response, err := h.Services.RateDelivery(shared.RatingRequest{
		IsCustomerRating: true,
		CustomerRating:   data,
	})

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failure",
			"err":     err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": "success",
		"data":    response,
	})

}

func (h *Handler) handleCourierRating(c *gin.Context) {
	var data shared.CourierRatingRequest
	if err := c.ShouldBindJSON(&data); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failure",
			"err":     err,
		})
		return
	}

	response, err := h.Services.RateDelivery(shared.RatingRequest{
		IsCustomerRating: false,
		CourierRating:    data,
	})

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failure",
			"err":     err,
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": "success",
		"data":    response,
	})

}

func (h *Handler) GetDeliveryCost(c *gin.Context) {
	var quoteRequest shared.GetDeliveryCostRequest
	if err := c.ShouldBindJSON(&quoteRequest); err != nil {
		for _, fieldErr := range err.(validator.ValidationErrors) {
			c.JSON(http.StatusBadRequest, gin.H{
				"message": myValidator.FieldError{Err: fieldErr}.String(),
			})
			return
		}
	}

	response, err := h.Services.GetDeliveryCost(quoteRequest)
	if err != nil {
		log.Println(err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failure",
			"err":     err,
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": "success",
		"data":    response,
	})

}


================================================
FILE: handler/courier.go
================================================
package handler

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"github.com/gwuah/postmates/shared"
	myValidator "github.com/gwuah/postmates/utils/validator"
)

type closestCourierResponse struct {
	Couriers []string `json:"couriers"`
}

func (h *Handler) GetClosestCouriers(c *gin.Context) {
	var data shared.GetClosestCouriersRequest
	if err := c.ShouldBindJSON(&data); err != nil {
		for _, fieldErr := range err.(validator.ValidationErrors) {
			c.JSON(http.StatusBadRequest, gin.H{
				"message": myValidator.FieldError{Err: fieldErr}.String(),
			})
			return
		}
	}

	couriersWithEta, err := h.Services.GetClosestCouriers(data.Origin, 2)

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failure",
			"err":     err,
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": "success",
		"data":    couriersWithEta,
	})

}


================================================
FILE: handler/customer.go
================================================
package handler

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/lib/sms"
	"github.com/gwuah/postmates/utils"
	"gorm.io/gorm"
)

type CreateCustomerRequest struct {
	Phone string `json:"phone" validate:"required"`
}

type LoginRequest struct {
	Phone string `json:"phone" validate:"required"`
	Code  int    `json:"code" validate:"required"`
}

func (h *Handler) ListCustomers(c *gin.Context) {
	var customers []models.Customer

	if err := h.DB.Find(&customers).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failed to retrieve customers",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message":   "success",
		"customers": customers,
	})
}

func (h *Handler) ViewCustomer(c *gin.Context) {
	id := c.Param("id")
	if id == "" {
		c.JSON(http.StatusNotFound, gin.H{
			"message": "customer id not found",
		})
	}

	customer := new(models.Customer)
	result := h.DB.First(customer, id)
	if result.Error != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failed To retrieve customer",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message":  "success",
		"customer": customer,
	})
	return
}

func (h *Handler) sendSMS(customer models.Customer, token string) {
	response, err := h.SMS.SendTextMessage(sms.Message{
		To:  utils.GeneratePhoneNumber(customer.Phone),
		Sms: fmt.Sprintf("Your electra code: %s", token),
	})

	if err != nil {
		log.Println(err)
		return
	}

	fmt.Println(response)
}

func (h *Handler) SignupCustomer(c *gin.Context) {
	var data CreateCustomerRequest
	if err := c.ShouldBindJSON(&data); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": "failed to parse request",
		})
	}

	existingCustomer, err := h.Repo.FindCustomerByPhone(data.Phone)

	if err != nil && err != gorm.ErrRecordNotFound {
		log.Println(err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "Request failed",
		})
		return
	}

	code := utils.GenerateOTP()

	if existingCustomer != nil {

		_, err = h.Repo.UpdateCustomer(existingCustomer.ID, map[string]interface{}{
			"Code": code,
		})

		go h.sendSMS(*existingCustomer, code)

		c.JSON(http.StatusOK, gin.H{
			"message": "customer already exists, token has been sent",
		})

	} else {

		record, err := h.Repo.CreateCustomerWithPhoneAndCode(data.Phone, utils.ConvertToInt(code))

		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"message": "Customer Creation failed",
			})
			return
		}

		go h.sendSMS(*record, code)

		c.JSON(http.StatusOK, gin.H{
			"message": "new customer",
			"data": gin.H{
				"customer": record,
			},
		})

	}
}

func (h *Handler) LoginCustomer(c *gin.Context) {

	var req LoginRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": "failed to parse request",
		})
		return
	}

	query := fmt.Sprintf("phone = '%s' AND code = '%d'", req.Phone, req.Code)
	customer, err := h.Repo.FindCustomerByQuery(query)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{
			"message": "wrong token or phone number",
		})
		return
	}

	token, err := h.JWT.GenerateToken(customer)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failed to generate token",
		})
		return
	}

	refreshToken := h.Sec.Token(token)

	_, err = h.Repo.UpdateCustomer(customer.ID, map[string]interface{}{
		"Token": token,
	})

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failed to store refresh token",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message":      "success",
		"token":        token,
		"refreshToken": refreshToken,
	})
	return
}


================================================
FILE: handler/delivery.go
================================================
package handler

import (
	"encoding/json"
	"log"

	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/lib/ws"
	"github.com/gwuah/postmates/shared"
)

type CourierWithEta struct {
	Courier  *shared.User
	Duration float64
}

func (h *Handler) acceptDelivery(message []byte, ws *ws.WSConnection) {

	var data shared.AcceptDelivery
	err := json.Unmarshal(message, &data)

	if err != nil {
		log.Println("failed to parse message", err)
		return
	}

	err = h.Services.AcceptDelivery(data, ws)
	if err != nil {
		log.Println("failed to accept delivery", err)
		return
	}

}

func (h *Handler) processDeliveryRequest(message []byte, ws *ws.WSConnection) {
	var data shared.DeliveryRequest

	err := json.Unmarshal(message, &data)
	if err != nil {
		log.Println("failed to parse message", err)
		return
	}

	_, err = h.Repo.UpdateCustomer(data.CustomerID, map[string]interface{}{
		"State": models.Searching,
	})

	if err != nil {
		log.Println("failed to update customer", err)
		return
	}

	product, err := h.Repo.FindProduct(data.ProductId)
	if err != nil {
		log.Printf("failed to find product with id (%d)", data.ProductId)
		log.Println(err)
		return
	}

	if product.Name == "express" {

		delivery, err := h.Services.CreateDelivery(data)
		if err != nil {
			log.Println("failed to create delivery", err)
			return
		}

		err = h.Services.DispatchDelivery(data, delivery, ws)
		if err != nil {
			log.Println("failed to dispatch delivery", err)
			return
		}

	} else {

	}
}

func (h *Handler) handleDeliveryCancellation(message []byte, ws *ws.WSConnection) {
	var data shared.CancelDeliveryRequest
	err := json.Unmarshal(message, &data)
	if err != nil {
		log.Println("failed to parse message", err)
		return

	}

	ws.SendMessage([]byte("Delivery Cancelled"))

}


================================================
FILE: handler/handler.go
================================================
package handler

import (
	"os"

	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis"
	"github.com/gwuah/postmates/lib/billing"
	"github.com/gwuah/postmates/lib/eta"
	"github.com/gwuah/postmates/lib/sms"
	"github.com/gwuah/postmates/lib/ws"
	"github.com/gwuah/postmates/repository"
	"github.com/gwuah/postmates/services"
	"github.com/gwuah/postmates/utils/jwt"
	"github.com/gwuah/postmates/utils/secure"
	"gorm.io/gorm"
)

type Handler struct {
	DB                   *gorm.DB
	Repo                 *repository.Repository
	JWT                  jwt.Service
	Sec                  *secure.Service
	Services             *services.Services
	maxMessageTypeLength int
	Hub                  *ws.Hub
	RedisDB              *redis.Client
	SMS                  *sms.SMS
	Eta                  *eta.Eta
}

func New(DB *gorm.DB, jwt jwt.Service, sec *secure.Service, redisDB *redis.Client) *Handler {
	SMS := sms.New(os.Getenv("TERMII_API_KEY"))
	eta := eta.New(os.Getenv("GMAPS_TOKEN"))
	billing := billing.New()
	hub := ws.NewHub()
	go hub.Run()
	repo := repository.New(DB, redisDB)
	services := services.New(repo, eta, hub, billing)

	return &Handler{
		DB:                   DB,
		Repo:                 repo,
		JWT:                  jwt,
		Services:             services,
		maxMessageTypeLength: 30,
		Hub:                  hub,
		RedisDB:              redisDB,
		SMS:                  SMS,
		Eta:                  eta,
		Sec:                  sec,
	}
}

func (h *Handler) Register(v1 *gin.RouterGroup) {
	v1.GET("/customer/realtime/:id", h.handleConnection("customer"))
	v1.GET("/courier/realtime/:id", h.handleConnection("courier"))
	v1.POST("/get-closest-couriers", h.GetClosestCouriers)
	v1.POST("/get-delivery-cost", h.GetDeliveryCost)
	v1.POST("/customer-rate-trip", h.handleCustomerRating)
	v1.POST("/courier-rate-trip", h.handleCourierRating)

	v1.GET("/refresh/:token", h.Refresh)

	//  middleware.JWT(h.JWT)
	customers := v1.Group("/customers")
	customers.GET("/", h.ListCustomers)
	customers.GET("/:id", h.ViewCustomer)
	customers.POST("/signup", h.SignupCustomer)
	customers.POST("/login", h.LoginCustomer)

}


================================================
FILE: handler/order.go
================================================
package handler

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/gwuah/postmates/utils"
)

func (h *Handler) GetOrder(c *gin.Context) {
	id := c.Param("id")

	if id == "" {
		c.JSON(http.StatusNotFound, gin.H{
			"message": "Order ID not found",
		})
	}

	id64 := utils.ConvertToUint64(id)

	order, err := h.Repo.FindDelivery(uint(id64), true)

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "failed To Retrieve Order",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": "success",
		"order":   order,
	})
	return

}


================================================
FILE: handler/ws.go
================================================
package handler

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"github.com/gwuah/postmates/lib/ws"
	"github.com/gwuah/postmates/shared"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

var MESSAGE_TYPES = map[string]string{
	"DeliveryRequest": "DeliveryRequest",
	"CancelDelivery":  "CancelDelivery",
	"GetEstimate":     "GetEstimate",
	"LocationUpdate":  "LocationUpdate",
	"AcceptDelivery":  "AcceptDelivery",
}

func (h *Handler) handleConnection(entity string) func(c *gin.Context) {
	return func(c *gin.Context) {
		id := c.Param("id")
		// this is unsafe, in future we have to set a static list of accepted origins
		upgrader.CheckOrigin = func(r *http.Request) bool { return true }
		conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)

		if err != nil {
			log.Println("failed to setup websocket conn ..", err)
			return
		}

		wsConnection := &ws.WSConnection{
			Hub:                   h.Hub,
			Send:                  make(chan []byte),
			Conn:                  conn,
			Id:                    id,
			Entity:                entity,
			ProcessMessage:        h.processIncomingMessage,
			IsActive:              true,
			DeliveryAcceptanceAck: make(chan bool),
		}

		h.Hub.Register <- wsConnection

		go wsConnection.ReadPump()
		go wsConnection.WritePump()

	}
}

func (h *Handler) processIncomingMessage(message []byte, ws *ws.WSConnection) {
	switch string(h.getTypeOfMessage(message)) {
	case MESSAGE_TYPES["DeliveryRequest"]:
		h.processDeliveryRequest(message, ws)
	case MESSAGE_TYPES["CancelDelivery"]:
		h.handleDeliveryCancellation(message, ws)
	case MESSAGE_TYPES["LocationUpdate"]:
		h.handleLocationUpdate(message, ws)
	case MESSAGE_TYPES["AcceptDelivery"]:
		h.acceptDelivery(message, ws)
	default:
		log.Printf("No handler available for request %s", h.getTypeOfMessage(message))
	}
}

func (h *Handler) getTypeOfMessage(message []byte) []byte {
	// this method pre-parses the message and extracts the type of message from the payload
	// this is done to speedup parsing and reduce size of marshalled/unmarshalled payload
	// custom algorithm, ask @gwuah for explanation
	start := 16
	end := start + h.maxMessageTypeLength + 2
	payload := message[start:end]

	numberOfQuotesSeen := 0
	head := []byte{}

	for i := 0; i < len(payload); i++ {
		value := payload[i]

		if numberOfQuotesSeen == 2 {
			break
		}

		if value == byte('"') {
			numberOfQuotesSeen++
		}

		head = append(head, value)

	}

	return head[1 : len(head)-1]
}

func (h *Handler) handleLocationUpdate(message []byte, ws *ws.WSConnection) {
	var data shared.UserLocationUpdate
	err := json.Unmarshal(message, &data)
	if err != nil {
		log.Println("failed to parse message", err)
		return
	}

	err = h.Services.HandleLocationUpdate(data)
	if err != nil {
		log.Println("failed to handle location update", err)
		return
	}
}


================================================
FILE: lib/billing/billing.go
================================================
package billing

import (
	"math"

	"github.com/gwuah/postmates/utils/geo"
)

const (
	BASE_PRICE = 5
)

type Billing struct {
}

func New() *Billing {
	return &Billing{}
}

func (b *Billing) GetDeliveryCost(distance float64) float64 {
	fare := (13 * geo.ConvertMetresToKM(distance)) / 12.5
	if fare < BASE_PRICE {
		return BASE_PRICE
	}
	return math.Ceil(fare)
}


================================================
FILE: lib/eta/eta.go
================================================
package eta

import (
	"context"
	"fmt"
	"log"

	"github.com/gwuah/postmates/shared"

	"googlemaps.github.io/maps"
)

type Eta struct {
	token string
	gmaps *maps.Client
}

type DistanceFromOrigin float64
type DurationFromOrigin float64

func New(googleAPIKey string) *Eta {

	if googleAPIKey == "" {
		log.Fatal("gmaps token required")
	}

	gmapsClient, err := maps.NewClient(maps.WithAPIKey(googleAPIKey))
	if err != nil {
		log.Fatal("failed to initialize gmaps", err)
	}

	return &Eta{
		token: googleAPIKey,
		gmaps: gmapsClient,
	}
}

func (eta *Eta) GMAPS__distanceMatrixBase(origins []shared.Coord, destinations []shared.Coord) (*maps.DistanceMatrixResponse, error) {

	modifiedOrigins := []string{}
	modifiedDestinations := []string{}

	for _, coord := range origins {
		modifiedOrigins = append(modifiedOrigins, fmt.Sprintf("%f,%f", coord.Latitude, coord.Longitude))
	}

	for _, coord := range destinations {
		modifiedDestinations = append(modifiedDestinations, fmt.Sprintf("%f,%f", coord.Latitude, coord.Longitude))
	}

	r := &maps.DistanceMatrixRequest{
		Origins:      modifiedOrigins,
		Destinations: modifiedDestinations,
	}

	resp, err := eta.gmaps.DistanceMatrix(context.Background(), r)

	if err != nil {
		return nil, err
	}

	return resp, nil

}

func (eta *Eta) GMAPS__getDistanceAndDuration1to1(origin shared.Coord, destination shared.Coord) (int, float64, error) {

	resp, err := eta.GMAPS__distanceMatrixBase([]shared.Coord{origin}, []shared.Coord{destination})

	if err != nil {
		return 0, 0, err
	}

	distance := resp.Rows[0].Elements[0].Distance.Meters
	duration := resp.Rows[0].Elements[0].Duration.Minutes()

	return distance, duration, nil

}

func (eta *Eta) GMAPS__getDistanceAndDurationManyTo1(origins []shared.Coord, destination shared.Coord) (*maps.DistanceMatrixResponse, error) {

	resp, err := eta.GMAPS__distanceMatrixBase(origins, []shared.Coord{destination})

	if err != nil {
		return nil, err
	}

	return resp, nil
}


================================================
FILE: lib/sms/sms.go
================================================
package sms

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

type SMS struct {
	key      string
	senderID string
}

type Message struct {
	To      string `json:"to"`
	From    string `json:"from"`
	Sms     string `json:"sms"`
	Type    string `json:"type"`
	Channel string `json:"channel"`
	ApiKey  string `json:"api_key"`
}

type Response struct {
	MessageId string  `json:"message_id"`
	Message   string  `json:"message"`
	Balance   float64 `json:"balance"`
	User      string  `json:"user"`
}

const API_ENDPOINT = "https://termii.com/api/sms/send"

func New(apiKey string) *SMS {
	if apiKey == "" {
		log.Fatal("termii api key required")
	}
	return &SMS{apiKey, os.Getenv("TERMII_SENDER_ID")}
}

func (s *SMS) SendTextMessage(msg Message) (*Response, error) {
	msg.ApiKey = s.key
	msg.Type = "plain"
	msg.Channel = "generic"
	msg.From = s.senderID

	body, err := json.Marshal(msg)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", API_ENDPOINT, bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)

	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	payload, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return nil, err
	}

	var response Response

	err = json.Unmarshal(payload, &response)

	if err != nil {
		return nil, err
	}

	return &response, nil
}


================================================
FILE: lib/ws/connection.go
================================================
package ws

import (
	"fmt"
	"log"
	"time"

	"github.com/gorilla/websocket"
)

const (
	writeWait      = 10 * time.Second
	pongWait       = 60 * time.Second
	pingPeriod     = (pongWait * 9) / 10
	maxMessageSize = 1024
)

type WSConnection struct {
	Id                    string
	Hub                   *Hub
	Room                  string
	Conn                  *websocket.Conn
	Send                  chan []byte
	ProcessMessage        func(msg []byte, ws *WSConnection)
	Entity                string
	IsActive              bool
	DeliveryAcceptanceAck chan bool
}

func (w *WSConnection) Deactivate() {
	close(w.Send)
	w.IsActive = false
}

func (w *WSConnection) ReadPump() {
	defer func() {
		log.Println("Unregistering", w.Id)
		w.Hub.unregister <- w
		w.Conn.Close()
	}()
	w.Conn.SetReadLimit(maxMessageSize)
	w.Conn.SetReadDeadline(time.Now().Add(pongWait))
	w.Conn.SetPongHandler(func(string) error { w.Conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })

	for {
		_, message, err := w.Conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Printf("error: %v", err)
			}
			break
		}

		go func() {
			w.ProcessMessage(message, w)
		}()

	}
}

func (w *WSConnection) WritePump() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		w.Conn.Close()
	}()
	for {
		select {
		case message, ok := <-w.Send:
			w.Conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				// The Hub closed the channel.
				w.Conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}

			err := w.Conn.WriteMessage(websocket.TextMessage, message)

			if err != nil {
				log.Println("failed to Send message to client", err)
			}

		case <-ticker.C:
			w.Conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := w.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

func (w *WSConnection) GetIdBasedOnType() string {
	if w.Entity == "courier" {
		return fmt.Sprintf("courier_%s", w.Id)
	} else {
		return fmt.Sprintf("customer_%s", w.Id)
	}
}

func (w *WSConnection) JoinRoom(name string) {
	w.Hub.joinRoomQueue <- RoomRequest{name: name, w: w}
}

func (w *WSConnection) LeaveRoom(name string) {
	w.Hub.leaveRoomQueue <- RoomRequest{name: name, w: w}
}

func (w *WSConnection) SendMessage(message []byte) {
	if w.IsActive {
		w.Send <- message
	} else {
		log.Println("Can't send message to closed socket conn", w.Id, w.Entity)
	}
}

func (w *WSConnection) AckDeliveryAcceptance(status bool) {
	if w.IsActive {
		w.DeliveryAcceptanceAck <- status
	} else {
		log.Println("Can't send message to closed socket conn", w.Id, w.Entity)
	}
}


================================================
FILE: lib/ws/hub.go
================================================
package ws

import (
	"fmt"
	"sync"
)

type Hub struct {
	customers       map[string]*WSConnection
	couriers        map[string]*WSConnection
	broadcast       chan []byte
	Register        chan *WSConnection
	unregister      chan *WSConnection
	rooms           map[string]*Room
	createRoomQueue chan RoomRequest
	joinRoomQueue   chan RoomRequest
	leaveRoomQueue  chan RoomRequest
	gil             sync.Mutex
}

func NewHub() *Hub {
	return &Hub{
		broadcast:  make(chan []byte),
		Register:   make(chan *WSConnection),
		unregister: make(chan *WSConnection),

		customers: make(map[string]*WSConnection),
		couriers:  make(map[string]*WSConnection),

		rooms:           make(map[string]*Room),
		createRoomQueue: make(chan RoomRequest),
		joinRoomQueue:   make(chan RoomRequest),
		leaveRoomQueue:  make(chan RoomRequest),

		gil: sync.Mutex{},
	}
}

func (h *Hub) GetSize(entities string) int {
	h.gil.Lock()
	defer h.gil.Unlock()

	if entities == "couriers" {
		return len(h.couriers)
	} else {
		return len(h.customers)
	}
}

func (h *Hub) GetCourier(id string) *WSConnection {
	// in future, we can refactor this so every entity has their own mutex
	h.gil.Lock()
	defer h.gil.Unlock()
	return h.couriers[id]
}

func (h *Hub) GetCustomer(id uint) *WSConnection {
	// in future, we can refactor this so every entity has their own mutex
	h.gil.Lock()
	defer h.gil.Unlock()
	return h.customers[fmt.Sprintf("%d", id)]
}

func (h *Hub) createRoom(name string) {
	if _, roomExists := h.rooms[name]; roomExists {
		return
	}

	h.rooms[name] = NewRoom(name)
}

func (h *Hub) Run() {
	for {
		select {
		case conn := <-h.Register:
			h.gil.Lock()

			if conn.Entity == "courier" {
				h.couriers[conn.Id] = conn
			} else {
				h.customers[conn.Id] = conn
			}

			h.gil.Unlock()

		case conn := <-h.unregister:
			h.gil.Lock()

			if conn.Entity == "courier" {
				if _, ok := h.couriers[conn.Id]; ok {
					delete(h.couriers, conn.Id)
					conn.Deactivate()
				}
			} else {
				if _, ok := h.customers[conn.Id]; ok {
					delete(h.customers, conn.Id)
					conn.Deactivate()
				}
			}

			h.gil.Unlock()

		case request := <-h.createRoomQueue:
			h.createRoom(request.name)

		case request := <-h.joinRoomQueue:
			room := h.rooms[request.name]
			room.joinQueue <- request

		case request := <-h.leaveRoomQueue:
			room := h.rooms[request.name]
			room.leaveQueue <- request
		}
	}
}


================================================
FILE: lib/ws/room.go
================================================
package ws

type RoomRequest struct {
	w    *WSConnection
	name string
}

type Room struct {
	name       string
	broadcast  chan []byte
	members    map[string]*WSConnection
	joinQueue  chan RoomRequest
	leaveQueue chan RoomRequest
	done       chan bool
}

func NewRoom(name string) *Room {
	room := &Room{
		name:       name,
		broadcast:  make(chan []byte),
		leaveQueue: make(chan RoomRequest),
		members:    make(map[string]*WSConnection),
		joinQueue:  make(chan RoomRequest),
		done:       make(chan bool),
	}

	go room.run()

	return room
}

func (room *Room) run() {
	for {
		select {

		case request := <-room.joinQueue:
			room.members[request.w.GetIdBasedOnType()] = request.w
		case request := <-room.leaveQueue:
			if _, ok := room.members[request.w.GetIdBasedOnType()]; ok {
				delete(room.members, request.w.GetIdBasedOnType())
				close(request.w.Send)
			}
		case message := <-room.broadcast:
			for id, client := range room.members {
				select {
				case client.Send <- message:
				default:
					close(client.Send)
					delete(room.members, id)
				}
			}

		case <-room.done:
			return
		}
	}
}

func (room *Room) close() {
	room.done <- true
}


================================================
FILE: main.go
================================================
package main

import (
	"crypto/sha1"
	"fmt"
	"log"
	"os"

	"github.com/gwuah/postmates/database"
	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/database/postgres"
	"github.com/gwuah/postmates/database/redis"
	"github.com/gwuah/postmates/handler"
	"github.com/gwuah/postmates/server"
	"github.com/gwuah/postmates/utils/jwt"
	"github.com/gwuah/postmates/utils/secure"
	"github.com/joho/godotenv"
)

func main() {
	ENV := os.Getenv("ENV")
	if ENV == "" {
		err := godotenv.Load()

		if err != nil {
			log.Fatal("Error loading .env file", err)
		}
	}

	db, err := postgres.New(&postgres.Config{
		User:     os.Getenv("DB_USER"),
		Password: os.Getenv("DB_PASS"),
		DBName:   os.Getenv("DB_NAME"),
		SSLMode:  os.Getenv("DB_SSLMODE"),
		Host:     os.Getenv("DB_HOST"),
		Port:     os.Getenv("DB_PORT"),
		DBurl:    os.Getenv("DATABASE_URL"),
	})

	if err != nil {
		log.Fatal("failed To Connect To Postgresql database", err)
	}

	err = postgres.SetupDatabase(db,
		&models.Customer{},
		&models.Delivery{},
		&models.Courier{},
		&models.Order{},
		&models.Vehicle{},
		&models.TripPoint{},
	)

	if err != nil {
		log.Fatal("failed To Setup Tables", err)
	}

	database.RunSeeds(db, []database.SeedFn{
		database.SeedProducts,
		database.SeedCouriers,
		database.SeedCustomers,
		database.SeedVehicles,
	})

	sec := secure.New(1, sha1.New())

	jwt, err := jwt.New("HS256", os.Getenv("JWT_SECRET"), 15, 64)

	if err != nil {
		log.Fatal(err)
	}
	
	redisDB := redis.New(&redis.Config{
		Addr:     os.Getenv("REDIS_ADDRESS"),
		Password: os.Getenv("REDIS_PASSWORD"),
	})

	s := server.New()
	h := handler.New(db, jwt, sec, redisDB)

	routes := s.Group("/v1")
	h.Register(routes)

	server.Start(&s, &server.Config{
		Port: fmt.Sprintf(":%s", os.Getenv("PORT")),
	})
}


================================================
FILE: middleware/cors.go
================================================
package middleware

import "github.com/gin-gonic/gin"

func CORS() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")

		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}

		c.Next()
	}
}


================================================
FILE: middleware/jwt.go
================================================
package middleware

import (
	"net/http"

	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
)

type TokenParser interface {
	ParseToken(string) (*jwt.Token, error)
}

func JWT(tokenParser TokenParser) gin.HandlerFunc {
	return func(c *gin.Context) {
		token, err := tokenParser.ParseToken(c.Request.Header.Get("Authorization"))
		if err != nil || !token.Valid {
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		claims := token.Claims.(jwt.MapClaims)
		phone := claims["phone"].(string)
		email := claims["email"].(string)

		c.Set("phone", phone)
		c.Set("email", email)

		c.Next()
	}
}


================================================
FILE: plg/handy.go
================================================
package plg

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/shared"
	"gorm.io/gorm"
)

func S(db *gorm.DB) {

	var data shared.GetDeliveryCostRequest
	err := json.Unmarshal([]byte(
		`{
			"origin": {
				"latitude": 5.677474538991623,
				"longitude": -0.24460022375167725
			},
			"destination": {
				"latitude": 5.6796946725653745,
				"longitude": -0.2447180449962616
			}
		}`,
	), &data)

	if err != nil {
		log.Fatal("error", err)
	}

	for i := 0; i < 10; i++ {
		cId := uint(1)
		delivery := models.Delivery{
			OriginLatitude:       data.Origin.Latitude,
			OriginLongitude:      data.Origin.Longitude,
			DestinationLatitude:  data.Destination.Latitude,
			DestinationLongitude: data.Destination.Longitude,
			Notes:                "Hello",
			CustomerID:           1,
			State:                models.Pending,
			CourierID:            &cId,
			CustomerRating:       1,
			ProductID:            1,
		}

		if err := db.Create(&delivery).Error; err != nil {
			log.Fatal("error", err)
		}

	}

	fmt.Println("seed complete")
}

func C(db *gorm.DB) {

	var data shared.GetDeliveryCostRequest
	err := json.Unmarshal([]byte(
		`{
			"origin": {
				"latitude": 5.677474538991623,
				"longitude": -0.24460022375167725
			},
			"destination": {
				"latitude": 5.6796946725653745,
				"longitude": -0.2447180449962616
			}
		}`,
	), &data)

	if err != nil {
		log.Fatal("error", err)
	}

	for i := 0; i < 10; i++ {
		cId := uint(1)
		delivery := models.Delivery{
			OriginLatitude:       data.Origin.Latitude,
			OriginLongitude:      data.Origin.Longitude,
			DestinationLatitude:  data.Destination.Latitude,
			DestinationLongitude: data.Destination.Longitude,
			Notes:                "Hello",
			CustomerID:           1,
			State:                models.Pending,
			CourierID:            &cId,
			CourierRating:        1,
			ProductID:            1,
		}

		if err := db.Create(&delivery).Error; err != nil {
			log.Fatal("error", err)
		}

	}

	fmt.Println("seed complete")
}


================================================
FILE: repository/courier.go
================================================
package repository

import (
	"encoding/json"
	"fmt"

	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/shared"
	"github.com/uber/h3-go"
	"gorm.io/gorm/clause"
)

func (r *Repository) FindCourier(id uint) (*models.Courier, error) {
	courier := models.Courier{}

	if err := r.DB.Preload(clause.Associations).First(&courier, id).Error; err != nil {
		return nil, err
	}

	return &courier, nil
}

func (r *Repository) UpdateCourier(id uint, data map[string]interface{}) (*models.Courier, error) {
	courier := models.Courier{}

	if err := r.DB.Model(&courier).Where("id = ?", id).Updates(data).Error; err != nil {
		return nil, err
	}

	return &courier, nil
}

func (r *Repository) GetCourierFromRedis(id string) (*shared.User, error) {
	var user shared.User
	key := fmt.Sprintf("courier_%s", id)

	result, err := r.RedisDB.Get(key).Result()

	if err != nil {
		return nil, err
	}

	err = json.Unmarshal([]byte(result), &user)

	if err != nil {
		return nil, err
	}

	return &user, nil

}

func (r *Repository) InsertCourierIntoRedis(user *shared.User) error {
	stringifiedUser, err := json.Marshal(user)
	if err != nil {
		return err
	}

	key := fmt.Sprintf("courier_%s", user.Id)
	_, err = r.RedisDB.Set(key, stringifiedUser, 0).Result()

	if err != nil {
		return err
	}
	return nil

}

func (r *Repository) RemoveCourierFromIndex(index h3.H3Index, user *shared.User) error {
	key := fmt.Sprintf("courier_index_%d", index)
	_, err := r.RedisDB.LRem(key, 0, user.Id).Result()
	if err != nil {
		return err
	}
	return nil

}

func (r *Repository) InsertCourierIntoIndex(index h3.H3Index, user *shared.User) error {
	key := fmt.Sprintf("courier_index_%d", index)
	_, err := r.RedisDB.LPush(key, user.Id).Result()
	if err != nil {
		return err
	}
	return nil

}

func (r *Repository) GetCouriersInIndex(index h3.H3Index) ([]string, error) {
	key := fmt.Sprintf("courier_index_%d", index)
	couriersIds, err := r.RedisDB.LRange(key, 0, -1).Result()
	if err != nil {
		return nil, err
	}
	return couriersIds, nil

}

func (r *Repository) GetAllCouriers(ids []string) ([]*shared.User, error) {
	couriers := []*shared.User{}

	for _, id := range ids {
		courier, _ := r.GetCourierFromRedis(id)
		couriers = append(couriers, courier)
	}

	return couriers, nil
}


================================================
FILE: repository/customer.go
================================================
package repository

import (
	"github.com/gwuah/postmates/database/models"
)

func (r *Repository) FindCustomerByQuery(query string) (*models.Customer, error) {

	var customer models.Customer

	if err := r.DB.Where(query).First(&customer).Error; err != nil {
		return nil, err
	}

	return &customer, nil
}

func (r *Repository) FindCustomerByPhone(phone string) (*models.Customer, error) {

	customer := models.Customer{}
	err := r.DB.Where("phone = ?", phone).First(&customer).Error

	if err != nil {
		return nil, err
	}

	return &customer, nil
}

func (r *Repository) CreateCustomerWithPhoneAndCode(phone string, code int) (*models.Customer, error) {

	customer := models.Customer{Phone: phone, Code: code}

	if err := r.DB.Create(&customer).Error; err != nil {
		return nil, err
	}

	return &customer, nil
}

func (r *Repository) UpdateCustomer(id uint, data map[string]interface{}) (*models.Customer, error) {
	var customer models.Customer

	if err := r.DB.Model(&customer).Where("id = ?", id).Updates(data).Error; err != nil {
		return nil, err
	}

	return &customer, nil
}


================================================
FILE: repository/delivery.go
================================================
package repository

import (
	"fmt"

	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/shared"
	"gorm.io/gorm/clause"
)

func (r *Repository) CreateDelivery(data shared.DeliveryRequest) (*models.Delivery, error) {
	delivery := models.Delivery{
		OriginLatitude:       data.Origin.Latitude,
		OriginLongitude:      data.Origin.Longitude,
		DestinationLatitude:  data.Destination.Latitude,
		DestinationLongitude: data.Destination.Longitude,
		Notes:                data.Notes,
		ProductID:            data.ProductId,
		CustomerID:           data.CustomerID,
		State:                models.Pending,
	}

	if err := r.DB.Create(&delivery).Error; err != nil {
		return nil, err
	}

	return &delivery, nil
}

func (r *Repository) FindDelivery(id uint, loadAssociations bool) (*models.Delivery, error) {

	var delivery models.Delivery

	if err := r.DB.First(&delivery, id).Error; err != nil {
		return nil, err
	}

	if loadAssociations {
		r.DB.Preload(clause.Associations).Find(&delivery)
	}

	return &delivery, nil
}

func (r *Repository) UpdateDelivery(id uint, data map[string]interface{}) (*models.Delivery, error) {
	var delivery models.Delivery

	if err := r.DB.Model(&delivery).Where("id = ?", id).Updates(data).Error; err != nil {
		return nil, err
	}

	return &delivery, nil
}

func (r *Repository) DeliveryCount(condition string) (int64, error) {
	var count int64
	if err := r.DB.Model(&models.Delivery{}).Where(condition).Count(&count).Error; err != nil {
		return 0, err
	}
	return count, nil
}

func (r *Repository) DeliverySum(condition string, field string) (int64, error) {
	var count int64
	response := r.DB.Model(&models.Delivery{}).Where(condition).Select(fmt.Sprintf("sum(%s)", field))

	if err := response.Error; err != nil {
		return 0, err
	}

	response.Scan(&count)
	return count, nil
}


================================================
FILE: repository/order.go
================================================
package repository

import (
	"errors"

	"github.com/gwuah/postmates/database/models"
	"gorm.io/gorm/clause"
)

type CreateOrderSchema struct {
	Phone string `json:"phone" validate:"required"`
}

func (r *Repository) CreateOrder() (*models.Order, error) {
	order := models.Order{}

	if err := r.DB.Create(&order).Error; err != nil {
		return nil, err
	}

	return &order, nil

}

func (r *Repository) FindOrder(id uint) (*models.Order, error) {

	order := models.Order{Courier: models.Courier{}}

	if err := r.DB.First(&order, id).Error; err != nil {
		return nil, err
	}

	if order.ID == 0 {
		return nil, errors.New("order doesn't exist")
	}

	r.DB.Preload(clause.Associations).Find(&order)

	return &order, nil
}

func (r *Repository) UpdateOrder(id uint, data map[string]interface{}) (*models.Order, error) {
	order := models.Order{}

	if err := r.DB.Model(&order).Where("id = ?", id).Updates(data).Error; err != nil {
		return nil, err
	}

	return &order, nil
}


================================================
FILE: repository/product.go
================================================
package repository

import (
	"errors"

	"github.com/gwuah/postmates/database/models"
)

func (r *Repository) FindProduct(id uint) (*models.Product, error) {

	product := models.Product{}

	if err := r.DB.First(&product, id).Error; err != nil {
		return nil, err
	}

	if product.ID == 0 {
		return nil, errors.New("Product Doesn't Exist")
	}

	return &product, nil
}

func (r *Repository) FindAllProducts() ([]models.Product, error) {

	products := []models.Product{}

	if err := r.DB.Find(&products).Error; err != nil {
		return nil, err
	}

	return products, nil
}


================================================
FILE: repository/repository.go
================================================
// This is basically our data layer.
package repository

import (
	"github.com/go-redis/redis"
	"gorm.io/gorm"
)

type Repository struct {
	DB      *gorm.DB
	RedisDB *redis.Client
}

func New(db *gorm.DB, redisDB *redis.Client) *Repository {
	return &Repository{db, redisDB}
}


================================================
FILE: repository/tripPoint.go
================================================
package repository

import (
	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/shared"
)

func (r *Repository) CreateTripPoint(data shared.UserLocationUpdate) (*models.TripPoint, error) {
	tripPoint := models.TripPoint{
		Latitude:   data.Latitude,
		Longitude:  data.Longitude,
		DeliveryID: data.DeliveryId,
		State:      data.State,
	}

	if err := r.DB.Create(&tripPoint).Error; err != nil {
		return nil, err
	}

	return &tripPoint, nil
}


================================================
FILE: server/server.go
================================================
package server

import (
	"log"
	"net/http"
	"os"
	"os/signal"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/gwuah/postmates/middleware"
	"github.com/gwuah/postmates/utils/validator"
)

type Config struct {
	Port  string
	Debug bool
}

type Server struct {
	*gin.Engine
}

func healthCheck(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"status": "OK",
	})
}

func New() Server {
	binding.Validator = new(validator.DefaultValidator)
	server := gin.Default()
	server.Use(middleware.CORS())
	server.GET("/", healthCheck)
	return Server{server}
}

func Start(e *Server, cfg *Config) {

	s := &http.Server{
		Addr:    cfg.Port,
		Handler: e.Engine,
	}

	quit := make(chan os.Signal)
	signal.Notify(quit, os.Interrupt)

	go func() {
		<-quit
		if err := s.Close(); err != nil {
			log.Println("failed To ShutDown Server", err)
		}
		log.Println("Shut Down Server")
	}()

	if err := s.ListenAndServe(); err != nil {
		if err == http.ErrServerClosed {
			log.Println("Server Closed After Interruption")
		} else {
			log.Println("Unexpected Server Shutdown. err:", err)
		}
	}
}


================================================
FILE: services/base.go
================================================
package services

import (
	"errors"
	"fmt"
	"strings"

	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/shared"
)

type PricePerProduct struct {
	ProductId uint    `json:"productId"`
	Price     float64 `json:"price"`
}

type GetDeliveryCostResponse struct {
	Estimates map[uint]PricePerProduct `json:"estimate"`
	Distance  float64                  `json:"distance"`
	Duration  float64                  `json:"duration"`
}

func (s *Services) RateDelivery(data shared.RatingRequest) (bool, error) {

	if data.IsCustomerRating {
		delivery, err := s.repo.UpdateDelivery(data.CustomerRating.DeliveryId, map[string]interface{}{
			"CustomerRating":        data.CustomerRating.Rating,
			"CustomerRatingMessage": data.CustomerRating.Message,
		})
		if err != nil {
			return false, err
		}

		delivery, err = s.repo.FindDelivery(data.CustomerRating.DeliveryId, false)
		if err != nil {
			return false, err
		}

		if delivery.State != models.Completed {
			return false, errors.New("delivery not completed")
		}

		condition := fmt.Sprintf("courier_id = %d AND state = %s", *delivery.CourierID, models.Completed)

		totalTrips, err := s.repo.DeliveryCount(condition)
		if err != nil {
			return false, err
		}

		totalRatings, err := s.repo.DeliverySum(condition, "customer_rating")
		if err != nil {
			return false, err
		}

		averageRating := totalRatings / totalTrips

		_, err = s.repo.UpdateCourier(*delivery.CourierID, map[string]interface{}{
			"Rating": averageRating,
		})

		if err != nil {
			return false, err
		}

	} else {

		delivery, err := s.repo.UpdateDelivery(data.CourierRating.DeliveryId, map[string]interface{}{
			"CourierRating":        data.CourierRating.Rating,
			"CourierRatingMessage": data.CourierRating.Message,
		})

		if err != nil {
			return false, err
		}

		delivery, err = s.repo.FindDelivery(data.CourierRating.DeliveryId, false)
		if err != nil {
			return false, err
		}

		condition := fmt.Sprintf("customer_id = %d", delivery.CustomerID)

		totalTrips, err := s.repo.DeliveryCount(condition)
		if err != nil {
			return false, err
		}

		totalRatings, err := s.repo.DeliverySum(condition, "courier_rating")
		if err != nil {
			return false, err
		}

		averageRating := totalRatings / totalTrips

		_, err = s.repo.UpdateCustomer(delivery.CustomerID, map[string]interface{}{
			"Rating": averageRating,
		})

		if err != nil {
			return false, err
		}

	}

	return true, nil
}

func (s *Services) GetDeliveryCost(data shared.GetDeliveryCostRequest) (*GetDeliveryCostResponse, error) {
	products, err := s.repo.FindAllProducts()

	if err != nil {

		return nil, errors.New("failed to load products")
	}

	duration, distance, err := s.eta.GMAPS__getDistanceAndDuration1to1(data.Origin, data.Destination)

	if err != nil {
		return nil, err
	}

	response := GetDeliveryCostResponse{
		Estimates: make(map[uint]PricePerProduct),
		Duration:  float64(duration),
		Distance:  float64(distance),
	}

	for _, product := range products {
		switch strings.ToLower(product.Name) {
		case "express":
			response.Estimates[product.ID] = PricePerProduct{
				ProductId: product.ID,
				Price:     s.billing.GetDeliveryCost(float64(distance)),
			}
			break

		}
	}

	return &response, nil

}


================================================
FILE: services/courier.go
================================================
package services


================================================
FILE: services/delivery.go
================================================
package services

import (
	"encoding/json"
	"log"

	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/lib/ws"
	"github.com/gwuah/postmates/shared"
	"github.com/gwuah/postmates/utils"
)

func (s *Services) CreateDelivery(data shared.DeliveryRequest) (*models.Delivery, error) {
	delivery, err := s.repo.CreateDelivery(data)
	if err != nil {
		return nil, err
	}
	return delivery, nil
}

func (s *Services) AcceptDelivery(data shared.AcceptDelivery, courierWS *ws.WSConnection) error {
	courierFromRedis, err := s.repo.GetCourierFromRedis(courierWS.Id)
	if err != nil {
		log.Printf("failed to retrieve courier %s from redis", courierWS.Id)
		return nil
	}

	delivery, err := s.repo.FindDelivery(data.DeliveryId, false)
	if err != nil {
		return err
	}

	duration, distance, err := s.eta.GMAPS__getDistanceAndDuration1to1(shared.Coord{
		Latitude:  courierFromRedis.Latitude,
		Longitude: courierFromRedis.Longitude,
	}, shared.Coord{
		Latitude:  delivery.OriginLatitude,
		Longitude: delivery.OriginLongitude,
	})
	if err != nil {
		log.Printf("failed to get courier %s ETA", courierWS.Id)
		return nil
	}

	_, err = s.repo.UpdateDelivery(data.DeliveryId, map[string]interface{}{
		"State":     models.PendingPickup,
		"CourierID": courierWS.Id,
	})
	if err != nil {
		return err
	}

	_, err = s.repo.UpdateCourier(uint(utils.ConvertToUint64(courierWS.Id)), map[string]interface{}{
		"State": models.Dispatched,
	})
	if err != nil {
		return err
	}

	go func() {
		courierWS.AckDeliveryAcceptance(true)
	}()

	delivery, err = s.repo.FindDelivery(data.DeliveryId, true)
	if err != nil {
		return err
	}

	courier, err := s.repo.FindCourier(*delivery.CourierID)
	if err != nil {
		return err
	}

	customer := s.hub.GetCustomer(delivery.CustomerID)
	if customer != nil {
		go func() {
			courier.Latitude = courierFromRedis.Latitude
			courier.Longitude = courierFromRedis.Longitude

			acceptanceDataStruct := shared.DeliveryAcceptedPayload{
				Meta: shared.Meta{
					Type: "DeliveryAccepted",
				},
				Courier:          *courier,
				Delivery:         *delivery,
				DistanceToPickup: float64(distance),
				DurationToPickup: float64(duration),
			}

			acceptanceData, err := json.Marshal(acceptanceDataStruct)
			if err != nil {
				return
			}

			customer.SendMessage(acceptanceData)
		}()
	}
	return nil
}

func (s *Services) CancelDelivery(data shared.CancelDeliveryRequest) bool {
	return true
}


================================================
FILE: services/dispatch.go
================================================
package services

import (
	"encoding/json"
	"errors"
	"log"
	"sort"
	"time"

	"github.com/go-redis/redis"
	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/lib/ws"
	"github.com/gwuah/postmates/shared"
	"github.com/gwuah/postmates/utils/geo"
)

func (s *Services) HandleLocationUpdate(params shared.UserLocationUpdate) error {

	switch params.State {
	case models.AwaitingDispatch:
		_, err := s.indexCourierLocation(params)
		return err
	case models.Dispatched, models.OnTrip:
		_, err := s.indexCourierLocation(params)
		if err != nil {
			return err
		}
		_, err = s.repo.CreateTripPoint(params)
		if err != nil {
			return err
		}
		err = s.relayCoordsToCustomer(params)
		if err != nil {
			return err
		}
	}

	return nil
}

func (s *Services) relayCoordsToCustomer(params shared.UserLocationUpdate) error {
	delivery, err := s.repo.FindDelivery(params.DeliveryId, false)
	if err != nil {
		return err
	}

	redisCourier, err := s.repo.GetCourierFromRedis(string(params.Id))
	if err != nil {
		return err
	}

	duration, distance, err := s.eta.GMAPS__getDistanceAndDuration1to1(shared.Coord{
		Latitude:  redisCourier.Latitude,
		Longitude: redisCourier.Longitude,
	}, shared.Coord{
		Latitude:  delivery.OriginLatitude,
		Longitude: delivery.OriginLongitude,
	})

	if err != nil {
		return nil
	}

	data, err := json.Marshal(shared.CourierLocation{
		Meta: shared.Meta{
			Type: "CourierLocationUpdate",
		},
		Coord:            redisCourier.Coord,
		DistanceToPickup: float64(distance),
		DurationToPickup: float64(duration),
	})

	if err != nil {
		return nil
	}

	if customerConn := s.hub.GetCustomer(delivery.CustomerID); customerConn != nil {
		customerConn.SendMessage(data)
	}

	return nil
}

func (s *Services) indexCourierLocation(param shared.UserLocationUpdate) (*shared.User, error) {
	newIndex := geo.CoordToIndex(param.Coord)

	courier, err := s.repo.GetCourierFromRedis(param.Id)

	if err == redis.Nil {
		courier = &shared.User{
			Id: param.Id,
		}
	}

	if err != redis.Nil && err != nil {
		return nil, err
	}

	oldIndex := courier.LastKnownIndex

	courier.Coord = param.Coord
	courier.LastKnownIndex = newIndex

	err = s.repo.InsertCourierIntoRedis(courier)

	if err != nil {
		return nil, err
	}

	if oldIndex != newIndex {
		err = s.repo.RemoveCourierFromIndex(oldIndex, courier)
		if err != nil {
			return nil, err
		}

		err = s.repo.InsertCourierIntoIndex(newIndex, courier)
		if err != nil {
			return nil, err
		}

	}

	return courier, nil
}

func (s *Services) GetClosestCouriers(destination shared.Coord, steps int) ([]shared.CourierWithEta, error) {
	var e []shared.CourierWithEta

	rings := geo.GetRingsFromOrigin(destination, steps)

	couriersIds := []string{}

	for _, index := range rings {
		ids, err := s.repo.GetCouriersInIndex(index)

		if err != nil {
			log.Printf("failed to load couriers in courier_index %d", index)
			continue
		}

		if len(ids) > 0 {
			couriersIds = append(couriersIds, ids...)
		}
	}

	if len(couriersIds) == 0 {
		return e, errors.New("no couriers available")
	}

	couriers, err := s.repo.GetAllCouriers(couriersIds)

	if err != nil {
		return e, err
	}

	origins := []shared.Coord{}

	for _, courier := range couriers {
		origins = append(origins, shared.Coord{
			Latitude:  courier.Latitude,
			Longitude: courier.Longitude,
		})
	}

	response, err := s.eta.GMAPS__getDistanceAndDurationManyTo1(origins, destination)

	if err != nil {
		return e, err
	}

	for key, dt := range response.Rows {
		courier := couriers[key]
		e = append(e, shared.CourierWithEta{
			Courier:  courier,
			Duration: dt.Elements[0].Duration.Minutes(),
			Distance: float64(dt.Elements[0].Distance.Meters),
		})
	}

	sort.Slice(e, func(i, j int) bool {
		return e[i].Duration < e[j].Duration
	})

	return e, nil

}

func (s *Services) DispatchDelivery(data shared.DeliveryRequest, delivery *models.Delivery, ws *ws.WSConnection) error {

	foundCourierForOrder := false

	if s.hub.GetSize("couriers") == 0 {
		res, err := json.Marshal(shared.NoCourierAvailable{
			Meta: shared.Meta{
				Type: "NoCourierAvailable",
			},
			Message: "there are no couriers available",
		})

		if err != nil {
			return err
		}

		ws.SendMessage(res)

		return nil
	}

dispatchLogic:

	e, err := s.GetClosestCouriers(data.Origin, 2)

	if err != nil {
		return nil
	}

	delivery, err = s.repo.FindDelivery(delivery.ID, true)
	if err != nil {
		return nil
	}

	ticker := time.NewTicker(5 * time.Second)

courierLoop:
	for _, courier := range e {
		conn := s.hub.GetCourier(courier.Courier.Id)
		if conn == nil {
			continue
		}

		convertedDeliveryRequest, err := json.Marshal(shared.NewDelivery{
			Meta: shared.Meta{
				Type: "NewDelivery",
			},
			Delivery:         delivery,
			DistanceToPickup: courier.Distance,
			DurationToPickup: courier.Duration,
		})

		if err != nil {
			return nil
		}

		conn.SendMessage(convertedDeliveryRequest)

		select {
		case <-ticker.C:
			// move to next courier in queue
		case <-conn.DeliveryAcceptanceAck:
			// delivery has been accepted, exit
			ticker.Stop()
			foundCourierForOrder = true
			break courierLoop
		}

	}

	if !foundCourierForOrder {
		goto dispatchLogic
	}

	return nil
}


================================================
FILE: services/order.go
================================================
package services


================================================
FILE: services/services.go
================================================
package services

import (
	"github.com/gwuah/postmates/lib/billing"
	"github.com/gwuah/postmates/lib/eta"
	"github.com/gwuah/postmates/lib/ws"
	"github.com/gwuah/postmates/repository"
)

type Services struct {
	repo    *repository.Repository
	eta     *eta.Eta
	hub     *ws.Hub
	billing *billing.Billing
}

func New(repo *repository.Repository, eta *eta.Eta, hub *ws.Hub, billing *billing.Billing) *Services {
	return &Services{repo: repo, eta: eta, hub: hub, billing: billing}
}


================================================
FILE: shared/types.go
================================================
package shared

import (
	"errors"

	"github.com/gwuah/postmates/database/models"
	"github.com/uber/h3-go"
)

var (
	MAPBOX_ERROR = errors.New("Mapbox Request failed")
)

type Meta struct {
	Type string `json:"type"`
}

type Coord struct {
	Longitude float64 `json:"longitude" validate:"required"`
	Latitude  float64 `json:"latitude" validate:"required"`
}

type User struct {
	Id             string     `json:"id"`
	LastKnownIndex h3.H3Index `json:"lastKnownIndex"`
	Coord
}

type UserLocationUpdate struct {
	Id         string       `json:"id"`
	State      models.State `json:"state"`
	DeliveryId uint         `json:"deliveryId"`
	Coord
}

type DeliveryRequest struct {
	Meta        Meta   `json:"meta"`
	Origin      Coord  `json:"origin"`
	Destination Coord  `json:"destination"`
	ProductId   uint   `json:"productId"`
	Notes       string `json:"notes"`
	CustomerID  uint   `json:"customerId"`
}

type CancelDeliveryRequest struct {
	Meta   Meta `json:"meta"`
	TripId uint `json:"tripId"`
}

type GetClosestCouriersRequest struct {
	Origin Coord `json:"origin"`
}

type NewDelivery struct {
	Meta             Meta             `json:"meta"`
	Delivery         *models.Delivery `json:"delivery"`
	DistanceToPickup float64          `json:"distanceToPickup"`
	DurationToPickup float64          `json:"durationToPickup"`
}

type AcceptDelivery struct {
	Meta       Meta `json:"meta"`
	DeliveryId uint `json:"deliveryId"`
}

type CourierWithEta struct {
	Courier  *User   `json:"courier"`
	Distance float64 `json:"distance"`
	Duration float64 `json:"duration"`
}

type DeliveryAcceptedPayload struct {
	Meta             Meta            `json:"meta"`
	Courier          models.Courier  `json:"courier"`
	Delivery         models.Delivery `json:"delivery"`
	DistanceToPickup float64         `json:"distanceToPickup"`
	DurationToPickup float64         `json:"durationToPickup"`
}

type NoCourierAvailable struct {
	Meta    Meta   `json:"meta"`
	Message string `json:"message"`
}

type CourierLocation struct {
	Meta Meta `json:"meta"`
	Coord
	DistanceToPickup float64 `json:"distanceToPickup"`
	DurationToPickup float64 `json:"durationToPickup"`
}

type PricePerProduct struct {
	ProductId uint `json:"productId"`
	Price     int  `json:"price"`
}

type GetDeliveryCostRequest struct {
	Origin      Coord `json:"origin" validate:"required"`
	Destination Coord `json:"destination" validate:"required"`
}

type BaseRating struct {
	DeliveryId uint   `json:"deliveryId" validate:"required"`
	Rating     int    `json:"rating" validate:"required"`
	Message    string `json:"message"`
}

type CustomerRatingRequest struct {
	BaseRating
	CustomerId uint `json:"customerId" validate:"required"`
}

type CourierRatingRequest struct {
	BaseRating
	CourierId uint `json:"courierId" validate:"required"`
}

type RatingRequest struct {
	IsCustomerRating bool
	CustomerRating   CustomerRatingRequest
	CourierRating    CourierRatingRequest
}


================================================
FILE: utils/geo/geo.go
================================================
package geo

import (
	"github.com/gwuah/postmates/shared"
	"github.com/uber/h3-go"
)

func CoordToIndex(param shared.Coord) h3.H3Index {
	return h3.FromGeo(h3.GeoCoord{
		Latitude:  param.Latitude,
		Longitude: param.Longitude,
	}, 8)
}

func GetRingsFromOrigin(coord shared.Coord, steps int) []h3.H3Index {
	return h3.KRing(CoordToIndex(coord), steps)
}

func ConvertMetresToKM(distance float64) float64 {
	return distance / 1000
}


================================================
FILE: utils/jwt/jwt.go
================================================
package jwt

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/gwuah/postmates/database/models"
)

var minSecretLen = 128

// New generates new JWT service necessary for auth middleware
func New(algo, secret string, ttlMinutes, minSecretLength int) (Service, error) {
	if minSecretLength > 0 {
		minSecretLen = minSecretLength
	}
	if len(secret) < minSecretLen {
		return Service{}, fmt.Errorf("jwt secret length is %v, which is less than required %v", len(secret), minSecretLen)
	}
	signingMethod := jwt.GetSigningMethod(algo)
	if signingMethod == nil {
		return Service{}, fmt.Errorf("invalid jwt signing method: %s", algo)
	}

	return Service{
		key:  []byte(secret),
		algo: signingMethod,
		ttl:  time.Duration(ttlMinutes) * time.Minute,
	}, nil
}

// Service provides a Json-Web-Token authentication implementation
type Service struct {
	// Secret key used for signing.
	key []byte

	// Duration for which the jwt token is valid.
	ttl time.Duration

	// JWT signing algorithm
	algo jwt.SigningMethod
}

// ParseToken parses token from Authorization header
func (s Service) ParseToken(authHeader string) (*jwt.Token, error) {
	parts := strings.SplitN(authHeader, " ", 2)
	if !(len(parts) == 2 && parts[0] == "Bearer") {
		return nil, errors.New("token not passed to authorization header")
	}

	return jwt.Parse(parts[1], func(token *jwt.Token) (interface{}, error) {
		if s.algo != token.Method {
			return nil, errors.New("failed to parse token")
		}
		return s.key, nil
	})

}

// GenerateToken generates new JWT token and populates it with user data
func (s Service) GenerateToken(customer *models.Customer) (string, error) {
	return jwt.NewWithClaims(s.algo, jwt.MapClaims{
		"phone": customer.Phone,
		"exp":   time.Now().Add(s.ttl).Unix(),
	}).SignedString(s.key)

}


================================================
FILE: utils/secure/secure.go
================================================
package secure

import (
	"fmt"
	"hash"
	"strconv"
	"time"

	"github.com/nbutton23/zxcvbn-go"
	"golang.org/x/crypto/bcrypt"
)

// New initializes security service
func New(minPWStr int, h hash.Hash) *Service {
	return &Service{minPWStr: minPWStr, h: h}
}

// Service holds security related methods
type Service struct {
	minPWStr int
	h        hash.Hash
}

// Password checks whether password is secure enough using zxcvbn library
func (s *Service) Password(pass string, inputs ...string) bool {
	pwStrength := zxcvbn.PasswordStrength(pass, inputs)
	return pwStrength.Score >= s.minPWStr
}

// Hash hashes the password using bcrypt
func (*Service) Hash(password string) string {
	hashedPW, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	return string(hashedPW)
}

// HashMatchesPassword matches hash with password. Returns true if hash and password match.
func (*Service) HashMatchesPassword(hash, password string) bool {
	return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
}

// Token generates new unique token
func (s *Service) Token(str string) string {
	s.h.Reset()
	fmt.Fprintf(s.h, "%s%s", str, strconv.Itoa(time.Now().Nanosecond()))
	return fmt.Sprintf("%x", s.h.Sum(nil))
}


================================================
FILE: utils/utils.go
================================================
package utils

import (
	"crypto/rand"
	"fmt"
	"io"
	"strconv"
	"strings"

	"github.com/gwuah/postmates/database/models"
	"github.com/gwuah/postmates/shared"
)

var table = [...]byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}

func GeneratePhoneNumber(phone string) string {
	return fmt.Sprintf("233%s", phone[1:])
}

func GenerateOTP() string {
	max := 4
	b := make([]byte, max)
	n, err := io.ReadAtLeast(rand.Reader, b, max)
	if n != max {
		panic(err)
	}
	for i := 0; i < len(b); i++ {
		b[i] = table[int(b[i])%len(table)]
	}
	return string(b)
}

func StringifyLngLat(props shared.Coord) string {
	return fmt.Sprintf("%f,%f", props.Longitude, props.Latitude)
}

func ConvertToUint64(num string) uint64 {
	id64, _ := strconv.ParseUint(num, 10, 64)
	return id64
}

func ConvertToInt(num string) int {
	id64, _ := strconv.ParseInt(num, 10, 64)
	return int(id64)
}

func ConvertToVehicleType(id string) models.VehicleType {
	switch strings.ToLower(id) {
	case "motor":
		return models.Motor
	case "car":
		return models.Car
	default:
		return models.Motor
	}
}


================================================
FILE: utils/validator/validator.go
================================================
package validator

import (
	"fmt"
	"reflect"
	"strings"
	"sync"

	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
)

type FieldError struct {
	Err validator.FieldError
}

func (q FieldError) String() string {
	var sb strings.Builder

	sb.WriteString("validation failed on field '" + q.Err.Field() + "'")
	sb.WriteString(", condition: " + q.Err.ActualTag())

	// Print condition parameters, e.g. oneof=red blue -> { red blue }
	if q.Err.Param() != "" {
		sb.WriteString(" { " + q.Err.Param() + " }")
	}

	if q.Err.Value() != nil && q.Err.Value() != "" {
		sb.WriteString(fmt.Sprintf(", actual: %v", q.Err.Value()))
	}

	return sb.String()
}

// DefaultValidator ...
type DefaultValidator struct {
	once     sync.Once
	validate *validator.Validate
}

var _ binding.StructValidator = &DefaultValidator{}

func (v *DefaultValidator) ValidateStruct(obj interface{}) error {

	if kindOfData(obj) == reflect.Struct {

		v.lazyinit()

		if err := v.validate.Struct(obj); err != nil {
			return err
		}
	}

	return nil
}

func (v *DefaultValidator) Engine() interface{} {
	v.lazyinit()
	return v.validate
}

func (v *DefaultValidator) lazyinit() {
	v.once.Do(func() {
		v.validate = validator.New()
		v.validate.SetTagName("validate")

		// add any custom validations etc. here
	})
}

func kindOfData(data interface{}) reflect.Kind {

	value := reflect.ValueOf(data)
	valueType := value.Kind()

	if valueType == reflect.Ptr {
		valueType = value.Elem().Kind()
	}
	return valueType
}
Download .txt
gitextract_qfqtsf5j/

├── .dockerignore
├── .gitignore
├── API_DOCS.md
├── Dockerfile
├── Dockerfile.wait
├── README.md
├── database/
│   ├── couriers.yml
│   ├── customers.yml
│   ├── models/
│   │   ├── courier.go
│   │   ├── customer.go
│   │   ├── delivery.go
│   │   ├── order.go
│   │   ├── product.go
│   │   ├── tripPoint.go
│   │   └── vehicle.go
│   ├── postgres/
│   │   └── postgres.go
│   ├── products.yml
│   ├── redis/
│   │   └── redis.go
│   ├── seeds.go
│   └── vehicles.yml
├── demo/
│   ├── customer__closest_couriers.js
│   ├── customer__delivery_request.js
│   ├── electrons.js
│   └── package.json
├── docker-compose.yml
├── go.mod
├── go.sum
├── handler/
│   ├── auth.go
│   ├── base.go
│   ├── courier.go
│   ├── customer.go
│   ├── delivery.go
│   ├── handler.go
│   ├── order.go
│   └── ws.go
├── lib/
│   ├── billing/
│   │   └── billing.go
│   ├── eta/
│   │   └── eta.go
│   ├── sms/
│   │   └── sms.go
│   └── ws/
│       ├── connection.go
│       ├── hub.go
│       └── room.go
├── main.go
├── middleware/
│   ├── cors.go
│   └── jwt.go
├── plg/
│   └── handy.go
├── repository/
│   ├── courier.go
│   ├── customer.go
│   ├── delivery.go
│   ├── order.go
│   ├── product.go
│   ├── repository.go
│   └── tripPoint.go
├── server/
│   └── server.go
├── services/
│   ├── base.go
│   ├── courier.go
│   ├── delivery.go
│   ├── dispatch.go
│   ├── order.go
│   └── services.go
├── shared/
│   └── types.go
└── utils/
    ├── geo/
    │   └── geo.go
    ├── jwt/
    │   └── jwt.go
    ├── secure/
    │   └── secure.go
    ├── utils.go
    └── validator/
        └── validator.go
Download .txt
SYMBOL INDEX (214 symbols across 49 files)

FILE: database/models/courier.go
  type Courier (line 3) | type Courier struct

FILE: database/models/customer.go
  type Model (line 7) | type Model struct
  type Customer (line 14) | type Customer struct

FILE: database/models/delivery.go
  type State (line 3) | type State
  constant Pending (line 7) | Pending         State = "pending"
  constant PendingPickup (line 8) | PendingPickup         = "pending_pickup"
  constant NearingPickup (line 9) | NearingPickup         = "nearing_pickup"
  constant AtPickup (line 10) | AtPickup              = "at_pickup"
  constant DeliveryOngoing (line 11) | DeliveryOngoing       = "delivery_ongoing"
  constant NearingDropoff (line 12) | NearingDropoff        = "nearing_dropoff"
  constant AtDropOff (line 13) | AtDropOff             = "at_dropoff"
  constant Completed (line 14) | Completed             = "completed"
  constant Cancelled (line 15) | Cancelled             = "cancelled"
  constant AwaitingDispatch (line 18) | AwaitingDispatch = "awaiting_dispatch"
  constant Dispatched (line 19) | Dispatched       = "dispatched"
  constant OnTrip (line 20) | OnTrip           = "on_trip"
  constant Offline (line 21) | Offline          = "offline"
  constant Inactive (line 24) | Inactive = "inactive"
  constant Searching (line 27) | Searching = "searching"
  constant AtRest (line 28) | AtRest    = "atRest"
  type Delivery (line 31) | type Delivery struct

FILE: database/models/order.go
  type Order (line 3) | type Order struct

FILE: database/models/product.go
  type Product (line 3) | type Product struct

FILE: database/models/tripPoint.go
  type TripPoint (line 3) | type TripPoint struct

FILE: database/models/vehicle.go
  type VehicleType (line 3) | type VehicleType
  constant Motor (line 6) | Motor VehicleType = "motor"
  constant Car (line 7) | Car               = "car"
  type Vehicle (line 10) | type Vehicle struct

FILE: database/postgres/postgres.go
  type Config (line 12) | type Config struct
  function SetupDatabase (line 22) | func SetupDatabase(db *gorm.DB, models ...interface{}) error {
  function New (line 27) | func New(config *Config) (*gorm.DB, error) {

FILE: database/redis/redis.go
  type Config (line 10) | type Config struct
  function New (line 17) | func New(config *Config) *redis.Client {

FILE: database/seeds.go
  type SeedFn (line 15) | type SeedFn
  function RunSeeds (line 17) | func RunSeeds(db *gorm.DB, seeds []SeedFn) {
  function SeedProducts (line 28) | func SeedProducts(DB *gorm.DB, path string) {
  function SeedCouriers (line 54) | func SeedCouriers(DB *gorm.DB, path string) {
  function SeedCustomers (line 83) | func SeedCustomers(DB *gorm.DB, path string) {
  function SeedVehicles (line 112) | func SeedVehicles(DB *gorm.DB, path string) {

FILE: demo/customer__closest_couriers.js
  function connect (line 3) | function connect(id) {
  function parseMessage (line 35) | function parseMessage(message) {}
  function main (line 37) | function main() {

FILE: demo/customer__delivery_request.js
  function connect (line 3) | function connect(id) {
  function main (line 42) | function main() {

FILE: demo/electrons.js
  class Courier (line 26) | class Courier {
    method constructor (line 27) | constructor(id, coord) {
    method _initialization (line 37) | _initialization() {
    method _handleNewDelivery (line 56) | _handleNewDelivery(parsed) {
    method _sendLocationUpdate (line 75) | _sendLocationUpdate() {
    method handleMessage (line 96) | handleMessage(message) {
  function main (line 106) | function main() {

FILE: handler/auth.go
  method Refresh (line 10) | func (h *Handler) Refresh(c *gin.Context) {

FILE: handler/base.go
  method handleCustomerRating (line 13) | func (h *Handler) handleCustomerRating(c *gin.Context) {
  method handleCourierRating (line 43) | func (h *Handler) handleCourierRating(c *gin.Context) {
  method GetDeliveryCost (line 73) | func (h *Handler) GetDeliveryCost(c *gin.Context) {

FILE: handler/courier.go
  type closestCourierResponse (line 12) | type closestCourierResponse struct
  method GetClosestCouriers (line 16) | func (h *Handler) GetClosestCouriers(c *gin.Context) {

FILE: handler/customer.go
  type CreateCustomerRequest (line 15) | type CreateCustomerRequest struct
  type LoginRequest (line 19) | type LoginRequest struct
  method ListCustomers (line 24) | func (h *Handler) ListCustomers(c *gin.Context) {
  method ViewCustomer (line 40) | func (h *Handler) ViewCustomer(c *gin.Context) {
  method sendSMS (line 64) | func (h *Handler) sendSMS(customer models.Customer, token string) {
  method SignupCustomer (line 78) | func (h *Handler) SignupCustomer(c *gin.Context) {
  method LoginCustomer (line 133) | func (h *Handler) LoginCustomer(c *gin.Context) {

FILE: handler/delivery.go
  type CourierWithEta (line 12) | type CourierWithEta struct
  method acceptDelivery (line 17) | func (h *Handler) acceptDelivery(message []byte, ws *ws.WSConnection) {
  method processDeliveryRequest (line 35) | func (h *Handler) processDeliveryRequest(message []byte, ws *ws.WSConnec...
  method handleDeliveryCancellation (line 79) | func (h *Handler) handleDeliveryCancellation(message []byte, ws *ws.WSCo...

FILE: handler/handler.go
  type Handler (line 19) | type Handler struct
    method Register (line 55) | func (h *Handler) Register(v1 *gin.RouterGroup) {
  function New (line 32) | func New(DB *gorm.DB, jwt jwt.Service, sec *secure.Service, redisDB *red...

FILE: handler/order.go
  method GetOrder (line 10) | func (h *Handler) GetOrder(c *gin.Context) {

FILE: handler/ws.go
  method handleConnection (line 27) | func (h *Handler) handleConnection(entity string) func(c *gin.Context) {
  method processIncomingMessage (line 58) | func (h *Handler) processIncomingMessage(message []byte, ws *ws.WSConnec...
  method getTypeOfMessage (line 73) | func (h *Handler) getTypeOfMessage(message []byte) []byte {
  method handleLocationUpdate (line 102) | func (h *Handler) handleLocationUpdate(message []byte, ws *ws.WSConnecti...

FILE: lib/billing/billing.go
  constant BASE_PRICE (line 10) | BASE_PRICE = 5
  type Billing (line 13) | type Billing struct
    method GetDeliveryCost (line 20) | func (b *Billing) GetDeliveryCost(distance float64) float64 {
  function New (line 16) | func New() *Billing {

FILE: lib/eta/eta.go
  type Eta (line 13) | type Eta struct
    method GMAPS__distanceMatrixBase (line 38) | func (eta *Eta) GMAPS__distanceMatrixBase(origins []shared.Coord, dest...
    method GMAPS__getDistanceAndDuration1to1 (line 66) | func (eta *Eta) GMAPS__getDistanceAndDuration1to1(origin shared.Coord,...
    method GMAPS__getDistanceAndDurationManyTo1 (line 81) | func (eta *Eta) GMAPS__getDistanceAndDurationManyTo1(origins []shared....
  type DistanceFromOrigin (line 18) | type DistanceFromOrigin
  type DurationFromOrigin (line 19) | type DurationFromOrigin
  function New (line 21) | func New(googleAPIKey string) *Eta {

FILE: lib/sms/sms.go
  type SMS (line 12) | type SMS struct
    method SendTextMessage (line 42) | func (s *SMS) SendTextMessage(msg Message) (*Response, error) {
  type Message (line 17) | type Message struct
  type Response (line 26) | type Response struct
  constant API_ENDPOINT (line 33) | API_ENDPOINT = "https://termii.com/api/sms/send"
  function New (line 35) | func New(apiKey string) *SMS {

FILE: lib/ws/connection.go
  constant writeWait (line 12) | writeWait      = 10 * time.Second
  constant pongWait (line 13) | pongWait       = 60 * time.Second
  constant pingPeriod (line 14) | pingPeriod     = (pongWait * 9) / 10
  constant maxMessageSize (line 15) | maxMessageSize = 1024
  type WSConnection (line 18) | type WSConnection struct
    method Deactivate (line 30) | func (w *WSConnection) Deactivate() {
    method ReadPump (line 35) | func (w *WSConnection) ReadPump() {
    method WritePump (line 61) | func (w *WSConnection) WritePump() {
    method GetIdBasedOnType (line 92) | func (w *WSConnection) GetIdBasedOnType() string {
    method JoinRoom (line 100) | func (w *WSConnection) JoinRoom(name string) {
    method LeaveRoom (line 104) | func (w *WSConnection) LeaveRoom(name string) {
    method SendMessage (line 108) | func (w *WSConnection) SendMessage(message []byte) {
    method AckDeliveryAcceptance (line 116) | func (w *WSConnection) AckDeliveryAcceptance(status bool) {

FILE: lib/ws/hub.go
  type Hub (line 8) | type Hub struct
    method GetSize (line 39) | func (h *Hub) GetSize(entities string) int {
    method GetCourier (line 50) | func (h *Hub) GetCourier(id string) *WSConnection {
    method GetCustomer (line 57) | func (h *Hub) GetCustomer(id uint) *WSConnection {
    method createRoom (line 64) | func (h *Hub) createRoom(name string) {
    method Run (line 72) | func (h *Hub) Run() {
  function NewHub (line 21) | func NewHub() *Hub {

FILE: lib/ws/room.go
  type RoomRequest (line 3) | type RoomRequest struct
  type Room (line 8) | type Room struct
    method run (line 32) | func (room *Room) run() {
    method close (line 59) | func (room *Room) close() {
  function NewRoom (line 17) | func NewRoom(name string) *Room {

FILE: main.go
  function main (line 20) | func main() {

FILE: middleware/cors.go
  function CORS (line 5) | func CORS() gin.HandlerFunc {

FILE: middleware/jwt.go
  type TokenParser (line 10) | type TokenParser interface
  function JWT (line 14) | func JWT(tokenParser TokenParser) gin.HandlerFunc {

FILE: plg/handy.go
  function S (line 13) | func S(db *gorm.DB) {
  function C (line 57) | func C(db *gorm.DB) {

FILE: repository/courier.go
  method FindCourier (line 13) | func (r *Repository) FindCourier(id uint) (*models.Courier, error) {
  method UpdateCourier (line 23) | func (r *Repository) UpdateCourier(id uint, data map[string]interface{})...
  method GetCourierFromRedis (line 33) | func (r *Repository) GetCourierFromRedis(id string) (*shared.User, error) {
  method InsertCourierIntoRedis (line 53) | func (r *Repository) InsertCourierIntoRedis(user *shared.User) error {
  method RemoveCourierFromIndex (line 69) | func (r *Repository) RemoveCourierFromIndex(index h3.H3Index, user *shar...
  method InsertCourierIntoIndex (line 79) | func (r *Repository) InsertCourierIntoIndex(index h3.H3Index, user *shar...
  method GetCouriersInIndex (line 89) | func (r *Repository) GetCouriersInIndex(index h3.H3Index) ([]string, err...
  method GetAllCouriers (line 99) | func (r *Repository) GetAllCouriers(ids []string) ([]*shared.User, error) {

FILE: repository/customer.go
  method FindCustomerByQuery (line 7) | func (r *Repository) FindCustomerByQuery(query string) (*models.Customer...
  method FindCustomerByPhone (line 18) | func (r *Repository) FindCustomerByPhone(phone string) (*models.Customer...
  method CreateCustomerWithPhoneAndCode (line 30) | func (r *Repository) CreateCustomerWithPhoneAndCode(phone string, code i...
  method UpdateCustomer (line 41) | func (r *Repository) UpdateCustomer(id uint, data map[string]interface{}...

FILE: repository/delivery.go
  method CreateDelivery (line 11) | func (r *Repository) CreateDelivery(data shared.DeliveryRequest) (*model...
  method FindDelivery (line 30) | func (r *Repository) FindDelivery(id uint, loadAssociations bool) (*mode...
  method UpdateDelivery (line 45) | func (r *Repository) UpdateDelivery(id uint, data map[string]interface{}...
  method DeliveryCount (line 55) | func (r *Repository) DeliveryCount(condition string) (int64, error) {
  method DeliverySum (line 63) | func (r *Repository) DeliverySum(condition string, field string) (int64,...

FILE: repository/order.go
  type CreateOrderSchema (line 10) | type CreateOrderSchema struct
  method CreateOrder (line 14) | func (r *Repository) CreateOrder() (*models.Order, error) {
  method FindOrder (line 25) | func (r *Repository) FindOrder(id uint) (*models.Order, error) {
  method UpdateOrder (line 42) | func (r *Repository) UpdateOrder(id uint, data map[string]interface{}) (...

FILE: repository/product.go
  method FindProduct (line 9) | func (r *Repository) FindProduct(id uint) (*models.Product, error) {
  method FindAllProducts (line 24) | func (r *Repository) FindAllProducts() ([]models.Product, error) {

FILE: repository/repository.go
  type Repository (line 9) | type Repository struct
  function New (line 14) | func New(db *gorm.DB, redisDB *redis.Client) *Repository {

FILE: repository/tripPoint.go
  method CreateTripPoint (line 8) | func (r *Repository) CreateTripPoint(data shared.UserLocationUpdate) (*m...

FILE: server/server.go
  type Config (line 15) | type Config struct
  type Server (line 20) | type Server struct
  function healthCheck (line 24) | func healthCheck(c *gin.Context) {
  function New (line 30) | func New() Server {
  function Start (line 38) | func Start(e *Server, cfg *Config) {

FILE: services/base.go
  type PricePerProduct (line 12) | type PricePerProduct struct
  type GetDeliveryCostResponse (line 17) | type GetDeliveryCostResponse struct
  method RateDelivery (line 23) | func (s *Services) RateDelivery(data shared.RatingRequest) (bool, error) {
  method GetDeliveryCost (line 108) | func (s *Services) GetDeliveryCost(data shared.GetDeliveryCostRequest) (...

FILE: services/delivery.go
  method CreateDelivery (line 13) | func (s *Services) CreateDelivery(data shared.DeliveryRequest) (*models....
  method AcceptDelivery (line 21) | func (s *Services) AcceptDelivery(data shared.AcceptDelivery, courierWS ...
  method CancelDelivery (line 101) | func (s *Services) CancelDelivery(data shared.CancelDeliveryRequest) bool {

FILE: services/dispatch.go
  method HandleLocationUpdate (line 17) | func (s *Services) HandleLocationUpdate(params shared.UserLocationUpdate...
  method relayCoordsToCustomer (line 41) | func (s *Services) relayCoordsToCustomer(params shared.UserLocationUpdat...
  method indexCourierLocation (line 84) | func (s *Services) indexCourierLocation(param shared.UserLocationUpdate)...
  method GetClosestCouriers (line 126) | func (s *Services) GetClosestCouriers(destination shared.Coord, steps in...
  method DispatchDelivery (line 188) | func (s *Services) DispatchDelivery(data shared.DeliveryRequest, deliver...

FILE: services/services.go
  type Services (line 10) | type Services struct
  function New (line 17) | func New(repo *repository.Repository, eta *eta.Eta, hub *ws.Hub, billing...

FILE: shared/types.go
  type Meta (line 14) | type Meta struct
  type Coord (line 18) | type Coord struct
  type User (line 23) | type User struct
  type UserLocationUpdate (line 29) | type UserLocationUpdate struct
  type DeliveryRequest (line 36) | type DeliveryRequest struct
  type CancelDeliveryRequest (line 45) | type CancelDeliveryRequest struct
  type GetClosestCouriersRequest (line 50) | type GetClosestCouriersRequest struct
  type NewDelivery (line 54) | type NewDelivery struct
  type AcceptDelivery (line 61) | type AcceptDelivery struct
  type CourierWithEta (line 66) | type CourierWithEta struct
  type DeliveryAcceptedPayload (line 72) | type DeliveryAcceptedPayload struct
  type NoCourierAvailable (line 80) | type NoCourierAvailable struct
  type CourierLocation (line 85) | type CourierLocation struct
  type PricePerProduct (line 92) | type PricePerProduct struct
  type GetDeliveryCostRequest (line 97) | type GetDeliveryCostRequest struct
  type BaseRating (line 102) | type BaseRating struct
  type CustomerRatingRequest (line 108) | type CustomerRatingRequest struct
  type CourierRatingRequest (line 113) | type CourierRatingRequest struct
  type RatingRequest (line 118) | type RatingRequest struct

FILE: utils/geo/geo.go
  function CoordToIndex (line 8) | func CoordToIndex(param shared.Coord) h3.H3Index {
  function GetRingsFromOrigin (line 15) | func GetRingsFromOrigin(coord shared.Coord, steps int) []h3.H3Index {
  function ConvertMetresToKM (line 19) | func ConvertMetresToKM(distance float64) float64 {

FILE: utils/jwt/jwt.go
  function New (line 16) | func New(algo, secret string, ttlMinutes, minSecretLength int) (Service,...
  type Service (line 36) | type Service struct
    method ParseToken (line 48) | func (s Service) ParseToken(authHeader string) (*jwt.Token, error) {
    method GenerateToken (line 64) | func (s Service) GenerateToken(customer *models.Customer) (string, err...

FILE: utils/secure/secure.go
  function New (line 14) | func New(minPWStr int, h hash.Hash) *Service {
  type Service (line 19) | type Service struct
    method Password (line 25) | func (s *Service) Password(pass string, inputs ...string) bool {
    method Hash (line 31) | func (*Service) Hash(password string) string {
    method HashMatchesPassword (line 37) | func (*Service) HashMatchesPassword(hash, password string) bool {
    method Token (line 42) | func (s *Service) Token(str string) string {

FILE: utils/utils.go
  function GeneratePhoneNumber (line 16) | func GeneratePhoneNumber(phone string) string {
  function GenerateOTP (line 20) | func GenerateOTP() string {
  function StringifyLngLat (line 33) | func StringifyLngLat(props shared.Coord) string {
  function ConvertToUint64 (line 37) | func ConvertToUint64(num string) uint64 {
  function ConvertToInt (line 42) | func ConvertToInt(num string) int {
  function ConvertToVehicleType (line 47) | func ConvertToVehicleType(id string) models.VehicleType {

FILE: utils/validator/validator.go
  type FieldError (line 13) | type FieldError struct
    method String (line 17) | func (q FieldError) String() string {
  type DefaultValidator (line 36) | type DefaultValidator struct
    method ValidateStruct (line 43) | func (v *DefaultValidator) ValidateStruct(obj interface{}) error {
    method Engine (line 57) | func (v *DefaultValidator) Engine() interface{} {
    method lazyinit (line 62) | func (v *DefaultValidator) lazyinit() {
  function kindOfData (line 71) | func kindOfData(data interface{}) reflect.Kind {
Condensed preview — 65 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (124K chars).
[
  {
    "path": ".dockerignore",
    "chars": 58,
    "preview": "Dockerfile\ndocker-compose.yml\n./postmates\n./postmates-app\n"
  },
  {
    "path": ".gitignore",
    "chars": 23,
    "preview": ".env\nnode_modules/\nmain"
  },
  {
    "path": "API_DOCS.md",
    "chars": 1283,
    "preview": "# Docs\n\n### Courier can initiate connection @\n\n`/courier/realtime/:id`\n\n### Customer can initiate connection @\n\n`/custom"
  },
  {
    "path": "Dockerfile",
    "chars": 490,
    "preview": "FROM golang:1.14-alpine AS main-env\n# install gcc for uber/h3-go. see https://github.com/uber/h3/issues/354\nRUN apk add "
  },
  {
    "path": "Dockerfile.wait",
    "chars": 216,
    "preview": "FROM alpine\n\n\n# Add docker-compose-wait tool -------------------\nENV WAIT_VERSION 2.7.3\nADD https://github.com/ufoscout/"
  },
  {
    "path": "README.md",
    "chars": 2377,
    "preview": "# Postmates\n\nThis is the heart of a delivery service. Features include geo-indexing, order-dispatch, proximity-searching"
  },
  {
    "path": "database/couriers.yml",
    "chars": 40,
    "preview": "- Griffith Awuah\n- Andy Osei\n- Yaw Manu\n"
  },
  {
    "path": "database/customers.yml",
    "chars": 14,
    "preview": "- Alicia Keys\n"
  },
  {
    "path": "database/models/courier.go",
    "chars": 444,
    "preview": "package models\n\ntype Courier struct {\n\tModel\n\tFirstName  string   `json:\"firstName\"`\n\tLastName   string   `json:\"lastNam"
  },
  {
    "path": "database/models/customer.go",
    "chars": 708,
    "preview": "package models\n\nimport (\n\t\"time\"\n)\n\ntype Model struct {\n\tID        uint       `gorm:\"primary_key\" json:\"id\"`\n\tCreatedAt "
  },
  {
    "path": "database/models/delivery.go",
    "chars": 1910,
    "preview": "package models\n\ntype State string\n\nconst (\n\t// delivery state types\n\tPending         State = \"pending\"\n\tPendingPickup   "
  },
  {
    "path": "database/models/order.go",
    "chars": 237,
    "preview": "package models\n\ntype Order struct {\n\tModel\n\t// Deliveries []Delivery `json:\"deliveries\"`\n\tCourierID uint    `json:\"couri"
  },
  {
    "path": "database/models/product.go",
    "chars": 74,
    "preview": "package models\n\ntype Product struct {\n\tModel\n\tName string `json:\"name\"`\n}\n"
  },
  {
    "path": "database/models/tripPoint.go",
    "chars": 249,
    "preview": "package models\n\ntype TripPoint struct {\n\tModel\n\tDeliveryID uint      `json:\"deliveryId\"`\n\tDelivery   *Delivery `json:\"de"
  },
  {
    "path": "database/models/vehicle.go",
    "chars": 429,
    "preview": "package models\n\ntype VehicleType string\n\nconst (\n\tMotor VehicleType = \"motor\"\n\tCar               = \"car\"\n)\n\ntype Vehicle"
  },
  {
    "path": "database/postgres/postgres.go",
    "chars": 870,
    "preview": "package postgres\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"gorm.io/driver/postgres\"\n\n\t\"gorm.io/gorm\"\n)\n\ntype Config struct {\n\tHost     s"
  },
  {
    "path": "database/products.yml",
    "chars": 17,
    "preview": "- Express\n- Pool\n"
  },
  {
    "path": "database/redis/redis.go",
    "chars": 586,
    "preview": "package redis\n\nimport (\n\t\"net/url\"\n\t\"os\"\n\n\t\"github.com/go-redis/redis\"\n)\n\ntype Config struct {\n\tAddr     string\n\tPasswor"
  },
  {
    "path": "database/seeds.go",
    "chars": 3897,
    "preview": "package database\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/gwuah/postmates/database/models\"\n\t\"github.com/gw"
  },
  {
    "path": "database/vehicles.yml",
    "chars": 285,
    "preview": "- data:\n    - vehicleModel: yahama\n      regNumber: GX 4888-10\n      type: motor\n      courierId: 1\n    - vehicleModel: "
  },
  {
    "path": "demo/customer__closest_couriers.js",
    "chars": 816,
    "preview": "const WebSocket = require(\"ws\");\n\nfunction connect(id) {\n  console.log(`Customer ${id} initating a connection ... `);\n  "
  },
  {
    "path": "demo/customer__delivery_request.js",
    "chars": 1014,
    "preview": "const WebSocket = require(\"ws\");\n\nfunction connect(id) {\n  console.log(`Customer ${id} initating a connection ... `);\n  "
  },
  {
    "path": "demo/electrons.js",
    "chars": 2515,
    "preview": "const WebSocket = require(\"ws\");\n\nconst outsideScope = {\n  latitude: 5.698188535023582,\n  longitude: -0.239341780857103,"
  },
  {
    "path": "demo/package.json",
    "chars": 244,
    "preview": "{\n  \"name\": \"demo\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\""
  },
  {
    "path": "docker-compose.yml",
    "chars": 1006,
    "preview": "version: '3.7'\nservices:\n  postgres:\n    image: \"postgres:13.1\"\n    hostname: postgres\n    container_name: postmates-pos"
  },
  {
    "path": "go.mod",
    "chars": 943,
    "preview": "module github.com/gwuah/postmates\n\ngo 1.14\n\n// +heroku goVersion go1.14.2\n\nrequire (\n\tgithub.com/dgrijalva/jwt-go v3.2.0"
  },
  {
    "path": "go.sum",
    "chars": 33038,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go."
  },
  {
    "path": "handler/auth.go",
    "chars": 785,
    "preview": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gwuah/postmates/database/models\"\n)\n\nfunc"
  },
  {
    "path": "handler/base.go",
    "chars": 2032,
    "preview": "package handler\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/go-playground/validator/v10\"\n\t\"gi"
  },
  {
    "path": "handler/courier.go",
    "chars": 910,
    "preview": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/go-playground/validator/v10\"\n\t\"github.co"
  },
  {
    "path": "handler/customer.go",
    "chars": 3713,
    "preview": "package handler\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gwuah/postmates/database/m"
  },
  {
    "path": "handler/delivery.go",
    "chars": 1786,
    "preview": "package handler\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\n\t\"github.com/gwuah/postmates/database/models\"\n\t\"github.com/gwuah/post"
  },
  {
    "path": "handler/handler.go",
    "chars": 2114,
    "preview": "package handler\n\nimport (\n\t\"os\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/go-redis/redis\"\n\t\"github.com/gwuah/postmates/l"
  },
  {
    "path": "handler/order.go",
    "chars": 574,
    "preview": "package handler\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gwuah/postmates/utils\"\n)\n\nfunc (h *Handl"
  },
  {
    "path": "handler/ws.go",
    "chars": 2919,
    "preview": "package handler\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gorilla/websocke"
  },
  {
    "path": "lib/billing/billing.go",
    "chars": 364,
    "preview": "package billing\n\nimport (\n\t\"math\"\n\n\t\"github.com/gwuah/postmates/utils/geo\"\n)\n\nconst (\n\tBASE_PRICE = 5\n)\n\ntype Billing st"
  },
  {
    "path": "lib/eta/eta.go",
    "chars": 1962,
    "preview": "package eta\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/gwuah/postmates/shared\"\n\n\t\"googlemaps.github.io/maps\"\n)\n\nty"
  },
  {
    "path": "lib/sms/sms.go",
    "chars": 1411,
    "preview": "package sms\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n)\n\ntype SMS struct {\n\tkey      str"
  },
  {
    "path": "lib/ws/connection.go",
    "chars": 2695,
    "preview": "package ws\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n)\n\nconst (\n\twriteWait      = 10 * time.Secon"
  },
  {
    "path": "lib/ws/hub.go",
    "chars": 2381,
    "preview": "package ws\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n)\n\ntype Hub struct {\n\tcustomers       map[string]*WSConnection\n\tcouriers        map["
  },
  {
    "path": "lib/ws/room.go",
    "chars": 1166,
    "preview": "package ws\n\ntype RoomRequest struct {\n\tw    *WSConnection\n\tname string\n}\n\ntype Room struct {\n\tname       string\n\tbroadca"
  },
  {
    "path": "main.go",
    "chars": 1788,
    "preview": "package main\n\nimport (\n\t\"crypto/sha1\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/gwuah/postmates/database\"\n\t\"github.com/gwuah/pos"
  },
  {
    "path": "middleware/cors.go",
    "chars": 605,
    "preview": "package middleware\n\nimport \"github.com/gin-gonic/gin\"\n\nfunc CORS() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tc."
  },
  {
    "path": "middleware/jwt.go",
    "chars": 611,
    "preview": "package middleware\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype TokenParser"
  },
  {
    "path": "plg/handy.go",
    "chars": 2051,
    "preview": "package plg\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/gwuah/postmates/database/models\"\n\t\"github.com/gwuah/p"
  },
  {
    "path": "repository/courier.go",
    "chars": 2276,
    "preview": "package repository\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/gwuah/postmates/database/models\"\n\t\"github.com/gwuah/p"
  },
  {
    "path": "repository/customer.go",
    "chars": 1080,
    "preview": "package repository\n\nimport (\n\t\"github.com/gwuah/postmates/database/models\"\n)\n\nfunc (r *Repository) FindCustomerByQuery(q"
  },
  {
    "path": "repository/delivery.go",
    "chars": 1830,
    "preview": "package repository\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gwuah/postmates/database/models\"\n\t\"github.com/gwuah/postmates/shared\"\n"
  },
  {
    "path": "repository/order.go",
    "chars": 966,
    "preview": "package repository\n\nimport (\n\t\"errors\"\n\n\t\"github.com/gwuah/postmates/database/models\"\n\t\"gorm.io/gorm/clause\"\n)\n\ntype Cre"
  },
  {
    "path": "repository/product.go",
    "chars": 567,
    "preview": "package repository\n\nimport (\n\t\"errors\"\n\n\t\"github.com/gwuah/postmates/database/models\"\n)\n\nfunc (r *Repository) FindProduc"
  },
  {
    "path": "repository/repository.go",
    "chars": 277,
    "preview": "// This is basically our data layer.\npackage repository\n\nimport (\n\t\"github.com/go-redis/redis\"\n\t\"gorm.io/gorm\"\n)\n\ntype R"
  },
  {
    "path": "repository/tripPoint.go",
    "chars": 468,
    "preview": "package repository\n\nimport (\n\t\"github.com/gwuah/postmates/database/models\"\n\t\"github.com/gwuah/postmates/shared\"\n)\n\nfunc "
  },
  {
    "path": "server/server.go",
    "chars": 1107,
    "preview": "package server\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gin-gonic/gin/b"
  },
  {
    "path": "services/base.go",
    "chars": 3244,
    "preview": "package services\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/gwuah/postmates/database/models\"\n\t\"github.com/gwuah"
  },
  {
    "path": "services/courier.go",
    "chars": 17,
    "preview": "package services\n"
  },
  {
    "path": "services/delivery.go",
    "chars": 2431,
    "preview": "package services\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\n\t\"github.com/gwuah/postmates/database/models\"\n\t\"github.com/gwuah/pos"
  },
  {
    "path": "services/dispatch.go",
    "chars": 5188,
    "preview": "package services\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"log\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/go-redis/redis\"\n\t\"github.com/"
  },
  {
    "path": "services/order.go",
    "chars": 17,
    "preview": "package services\n"
  },
  {
    "path": "services/services.go",
    "chars": 480,
    "preview": "package services\n\nimport (\n\t\"github.com/gwuah/postmates/lib/billing\"\n\t\"github.com/gwuah/postmates/lib/eta\"\n\t\"github.com/"
  },
  {
    "path": "shared/types.go",
    "chars": 2917,
    "preview": "package shared\n\nimport (\n\t\"errors\"\n\n\t\"github.com/gwuah/postmates/database/models\"\n\t\"github.com/uber/h3-go\"\n)\n\nvar (\n\tMAP"
  },
  {
    "path": "utils/geo/geo.go",
    "chars": 434,
    "preview": "package geo\n\nimport (\n\t\"github.com/gwuah/postmates/shared\"\n\t\"github.com/uber/h3-go\"\n)\n\nfunc CoordToIndex(param shared.Co"
  },
  {
    "path": "utils/jwt/jwt.go",
    "chars": 1818,
    "preview": "package jwt\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gwuah/postmates/d"
  },
  {
    "path": "utils/secure/secure.go",
    "chars": 1233,
    "preview": "package secure\n\nimport (\n\t\"fmt\"\n\t\"hash\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/nbutton23/zxcvbn-go\"\n\t\"golang.org/x/crypto/bcry"
  },
  {
    "path": "utils/utils.go",
    "chars": 1067,
    "preview": "package utils\n\nimport (\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/gwuah/postmates/database/models\""
  },
  {
    "path": "utils/validator/validator.go",
    "chars": 1504,
    "preview": "package validator\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/gin-gonic/gin/binding\"\n\t\"github.com/go-pl"
  }
]

About this extraction

This page contains the full source code of the gwuah/postmates GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 65 files (109.9 KB), approximately 41.4k tokens, and a symbol index with 214 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!