Compare commits
	
		
			8 Commits
		
	
	
		
			2a64b5761e
			...
			b8417c47b9
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b8417c47b9 | |||
| 9edb249430 | |||
| eaabd28fc2 | |||
| 116e59897f | |||
| 27cab1505c | |||
| 3dba9b23dc | |||
| 8e76d2a6cd | |||
| c0a58ff6b6 | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -6,5 +6,5 @@ cred.txt
 | 
				
			|||||||
dist/
 | 
					dist/
 | 
				
			||||||
.air.toml
 | 
					.air.toml
 | 
				
			||||||
tmp/
 | 
					tmp/
 | 
				
			||||||
broker-service/brokerApp
 | 
					*/*App
 | 
				
			||||||
front-end/frontApp
 | 
					project/db-data/
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								authentication-service/authentication-service.dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								authentication-service/authentication-service.dockerfile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					FROM alpine:latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN mkdir /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY authApp /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CMD ["/app/authApp"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										40
									
								
								authentication-service/cmd/api/handlers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								authentication-service/cmd/api/handlers.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *Config) Authenticate(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						var requestPayload struct {
 | 
				
			||||||
 | 
							Email    string `json:"email"`
 | 
				
			||||||
 | 
							Password string `json:"password"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := app.readJSON(w, r, &requestPayload)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							app.errorJSON(w, err, http.StatusBadRequest)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						user, err := app.Models.User.GetByEmail(requestPayload.Email)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// user not found
 | 
				
			||||||
 | 
							app.errorJSON(w, errors.New("invalid credentials"), http.StatusBadRequest)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						valid, err := user.PasswordMatches(requestPayload.Password)
 | 
				
			||||||
 | 
						if err != nil || !valid {
 | 
				
			||||||
 | 
							app.errorJSON(w, errors.New("invalid credentials"), http.StatusBadRequest)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						payload := jsonResponse{
 | 
				
			||||||
 | 
							Error:   false,
 | 
				
			||||||
 | 
							Message: fmt.Sprintf("%s %s is authorized to log in.", user.FirstName, user.LastName),
 | 
				
			||||||
 | 
							Data:    user,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.writeJSON(w, http.StatusAccepted, payload)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										75
									
								
								authentication-service/cmd/api/helpers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								authentication-service/cmd/api/helpers.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type jsonResponse struct {
 | 
				
			||||||
 | 
						Error   bool   `json:"error"`
 | 
				
			||||||
 | 
						Message string `json:"message"`
 | 
				
			||||||
 | 
						Data    any    `json:"data,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data any) error {
 | 
				
			||||||
 | 
						maxBytes := 1048576 // one megabyte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dec := json.NewDecoder(r.Body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := dec.Decode(data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = dec.Decode(&struct{}{})
 | 
				
			||||||
 | 
						if err != io.EOF {
 | 
				
			||||||
 | 
							return errors.New("body must have only a single JSON value")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *Config) writeJSON(
 | 
				
			||||||
 | 
						w http.ResponseWriter,
 | 
				
			||||||
 | 
						status int,
 | 
				
			||||||
 | 
						data any,
 | 
				
			||||||
 | 
						headers ...http.Header,
 | 
				
			||||||
 | 
					) error {
 | 
				
			||||||
 | 
						out, err := json.Marshal(data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(headers) > 0 {
 | 
				
			||||||
 | 
							for key, value := range headers[0] {
 | 
				
			||||||
 | 
								w.Header()[key] = value
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json")
 | 
				
			||||||
 | 
						w.WriteHeader(status)
 | 
				
			||||||
 | 
						_, err = w.Write(out)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
 | 
				
			||||||
 | 
						statusCode := http.StatusBadRequest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(status) > 0 {
 | 
				
			||||||
 | 
							statusCode = status[0]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var payload jsonResponse
 | 
				
			||||||
 | 
						payload.Error = true
 | 
				
			||||||
 | 
						payload.Message = err.Error()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return app.writeJSON(w, statusCode, payload)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										88
									
								
								authentication-service/cmd/api/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								authentication-service/cmd/api/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"authentication/data"
 | 
				
			||||||
 | 
						"database/sql"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_ "github.com/jackc/pgconn"
 | 
				
			||||||
 | 
						_ "github.com/jackc/pgx/v4"
 | 
				
			||||||
 | 
						_ "github.com/jackc/pgx/v4/stdlib"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const webPort = "4000"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var counts int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Config struct {
 | 
				
			||||||
 | 
						DB     *sql.DB
 | 
				
			||||||
 | 
						Models data.Models
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						// connect to DB
 | 
				
			||||||
 | 
						conn := connectToDB()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if conn == nil {
 | 
				
			||||||
 | 
							log.Panic("Can't connect to Postgres!")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// set up config
 | 
				
			||||||
 | 
						app := Config{
 | 
				
			||||||
 | 
							DB:     conn,
 | 
				
			||||||
 | 
							Models: data.New(conn),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Printf("Starting authentication service on port %s\n", webPort)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// define http server
 | 
				
			||||||
 | 
						srv := &http.Server{
 | 
				
			||||||
 | 
							Addr:    fmt.Sprintf(":%s", webPort),
 | 
				
			||||||
 | 
							Handler: app.routes(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := srv.ListenAndServe()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func openDB(dsn string) (*sql.DB, error) {
 | 
				
			||||||
 | 
						db, err := sql.Open("pgx", dsn)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = db.Ping()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return db, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func connectToDB() *sql.DB {
 | 
				
			||||||
 | 
						dsn := os.Getenv("DSN")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							connection, err := openDB(dsn)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println("Postgres not yet ready ...: ", err)
 | 
				
			||||||
 | 
								counts++
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								log.Println("Connected to Postgres!")
 | 
				
			||||||
 | 
								return connection
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if counts > 10 {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							log.Println("Backing off for two seconds...")
 | 
				
			||||||
 | 
							time.Sleep(2 * time.Second)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										29
									
								
								authentication-service/cmd/api/routes.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								authentication-service/cmd/api/routes.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-chi/chi/v5"
 | 
				
			||||||
 | 
						"github.com/go-chi/chi/v5/middleware"
 | 
				
			||||||
 | 
						"github.com/go-chi/cors"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *Config) routes() http.Handler {
 | 
				
			||||||
 | 
						mux := chi.NewRouter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// specify who is allowed to connect
 | 
				
			||||||
 | 
						mux.Use(cors.Handler(cors.Options{
 | 
				
			||||||
 | 
							AllowedOrigins:   []string{"https://*", "http://*"},
 | 
				
			||||||
 | 
							AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
 | 
				
			||||||
 | 
							AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
 | 
				
			||||||
 | 
							ExposedHeaders:   []string{"Link"},
 | 
				
			||||||
 | 
							AllowCredentials: true,
 | 
				
			||||||
 | 
							MaxAge:           300,
 | 
				
			||||||
 | 
						}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mux.Use(middleware.Heartbeat("/ping"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mux.Post("/authenticate", app.Authenticate)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return mux
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										264
									
								
								authentication-service/data/models.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								authentication-service/data/models.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,264 @@
 | 
				
			|||||||
 | 
					package data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"database/sql"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"golang.org/x/crypto/bcrypt"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dbTimeout = time.Second * 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var db *sql.DB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// New is the function used to create an instance of the data package. It returns the type
 | 
				
			||||||
 | 
					// Model, which embeds all the types we want to be available to our application.
 | 
				
			||||||
 | 
					func New(dbPool *sql.DB) Models {
 | 
				
			||||||
 | 
						db = dbPool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Models{
 | 
				
			||||||
 | 
							User: User{},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Models is the type for this package. Note that any model that is included as a member
 | 
				
			||||||
 | 
					// in this type is available to us throughout the application, anywhere that the
 | 
				
			||||||
 | 
					// app variable is used, provided that the model is also added in the New function.
 | 
				
			||||||
 | 
					type Models struct {
 | 
				
			||||||
 | 
						User User
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// User is the structure which holds one user from the database.
 | 
				
			||||||
 | 
					type User struct {
 | 
				
			||||||
 | 
						ID        int       `json:"id"`
 | 
				
			||||||
 | 
						Email     string    `json:"email"`
 | 
				
			||||||
 | 
						FirstName string    `json:"first_name,omitempty"`
 | 
				
			||||||
 | 
						LastName  string    `json:"last_name,omitempty"`
 | 
				
			||||||
 | 
						Password  string    `json:"-"`
 | 
				
			||||||
 | 
						Active    int       `json:"active"`
 | 
				
			||||||
 | 
						CreatedAt time.Time `json:"created_at"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time `json:"updated_at"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAll returns a slice of all users, sorted by last name
 | 
				
			||||||
 | 
					func (u *User) GetAll() ([]*User, error) {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at
 | 
				
			||||||
 | 
						from users order by last_name`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rows, err := db.QueryContext(ctx, query)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer rows.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var users []*User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for rows.Next() {
 | 
				
			||||||
 | 
							var user User
 | 
				
			||||||
 | 
							err := rows.Scan(
 | 
				
			||||||
 | 
								&user.ID,
 | 
				
			||||||
 | 
								&user.Email,
 | 
				
			||||||
 | 
								&user.FirstName,
 | 
				
			||||||
 | 
								&user.LastName,
 | 
				
			||||||
 | 
								&user.Password,
 | 
				
			||||||
 | 
								&user.Active,
 | 
				
			||||||
 | 
								&user.CreatedAt,
 | 
				
			||||||
 | 
								&user.UpdatedAt,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println("Error scanning", err)
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							users = append(users, &user)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return users, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetByEmail returns one user by email
 | 
				
			||||||
 | 
					func (u *User) GetByEmail(email string) (*User, error) {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where email = $1`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var user User
 | 
				
			||||||
 | 
						row := db.QueryRowContext(ctx, query, email)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := row.Scan(
 | 
				
			||||||
 | 
							&user.ID,
 | 
				
			||||||
 | 
							&user.Email,
 | 
				
			||||||
 | 
							&user.FirstName,
 | 
				
			||||||
 | 
							&user.LastName,
 | 
				
			||||||
 | 
							&user.Password,
 | 
				
			||||||
 | 
							&user.Active,
 | 
				
			||||||
 | 
							&user.CreatedAt,
 | 
				
			||||||
 | 
							&user.UpdatedAt,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &user, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetOne returns one user by id
 | 
				
			||||||
 | 
					func (u *User) GetOne(id int) (*User, error) {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where id = $1`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var user User
 | 
				
			||||||
 | 
						row := db.QueryRowContext(ctx, query, id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := row.Scan(
 | 
				
			||||||
 | 
							&user.ID,
 | 
				
			||||||
 | 
							&user.Email,
 | 
				
			||||||
 | 
							&user.FirstName,
 | 
				
			||||||
 | 
							&user.LastName,
 | 
				
			||||||
 | 
							&user.Password,
 | 
				
			||||||
 | 
							&user.Active,
 | 
				
			||||||
 | 
							&user.CreatedAt,
 | 
				
			||||||
 | 
							&user.UpdatedAt,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &user, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Update updates one user in the database, using the information
 | 
				
			||||||
 | 
					// stored in the receiver u
 | 
				
			||||||
 | 
					func (u *User) Update() error {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stmt := `update users set
 | 
				
			||||||
 | 
							email = $1,
 | 
				
			||||||
 | 
							first_name = $2,
 | 
				
			||||||
 | 
							last_name = $3,
 | 
				
			||||||
 | 
							user_active = $4,
 | 
				
			||||||
 | 
							updated_at = $5
 | 
				
			||||||
 | 
							where id = $6
 | 
				
			||||||
 | 
						`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err := db.ExecContext(ctx, stmt,
 | 
				
			||||||
 | 
							u.Email,
 | 
				
			||||||
 | 
							u.FirstName,
 | 
				
			||||||
 | 
							u.LastName,
 | 
				
			||||||
 | 
							u.Active,
 | 
				
			||||||
 | 
							time.Now(),
 | 
				
			||||||
 | 
							u.ID,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Delete deletes one user from the database, by User.ID
 | 
				
			||||||
 | 
					func (u *User) Delete() error {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stmt := `delete from users where id = $1`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err := db.ExecContext(ctx, stmt, u.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteByID deletes one user from the database, by ID
 | 
				
			||||||
 | 
					func (u *User) DeleteByID(id int) error {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stmt := `delete from users where id = $1`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err := db.ExecContext(ctx, stmt, id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Insert inserts a new user into the database, and returns the ID of the newly inserted row
 | 
				
			||||||
 | 
					func (u *User) Insert(user User) (int, error) {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var newID int
 | 
				
			||||||
 | 
						stmt := `insert into users (email, first_name, last_name, password, user_active, created_at, updated_at)
 | 
				
			||||||
 | 
							values ($1, $2, $3, $4, $5, $6, $7) returning id`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = db.QueryRowContext(ctx, stmt,
 | 
				
			||||||
 | 
							user.Email,
 | 
				
			||||||
 | 
							user.FirstName,
 | 
				
			||||||
 | 
							user.LastName,
 | 
				
			||||||
 | 
							hashedPassword,
 | 
				
			||||||
 | 
							user.Active,
 | 
				
			||||||
 | 
							time.Now(),
 | 
				
			||||||
 | 
							time.Now(),
 | 
				
			||||||
 | 
						).Scan(&newID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return newID, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResetPassword is the method we will use to change a user's password.
 | 
				
			||||||
 | 
					func (u *User) ResetPassword(password string) error {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stmt := `update users set password = $1 where id = $2`
 | 
				
			||||||
 | 
						_, err = db.ExecContext(ctx, stmt, hashedPassword, u.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PasswordMatches uses Go's bcrypt package to compare a user supplied password
 | 
				
			||||||
 | 
					// with the hash we have stored for a given user in the database. If the password
 | 
				
			||||||
 | 
					// and hash match, we return true; otherwise, we return false.
 | 
				
			||||||
 | 
					func (u *User) PasswordMatches(plainText string) (bool, error) {
 | 
				
			||||||
 | 
						err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(plainText))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							switch {
 | 
				
			||||||
 | 
							case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
 | 
				
			||||||
 | 
								// invalid password
 | 
				
			||||||
 | 
								return false, nil
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								authentication-service/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								authentication-service/go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					module authentication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.22.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/go-chi/chi/v5 v5.1.0
 | 
				
			||||||
 | 
						github.com/go-chi/cors v1.2.1
 | 
				
			||||||
 | 
						github.com/jackc/pgconn v1.14.3
 | 
				
			||||||
 | 
						github.com/jackc/pgx/v4 v4.18.3
 | 
				
			||||||
 | 
						golang.org/x/crypto v0.26.0
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/jackc/chunkreader/v2 v2.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/jackc/pgio v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/jackc/pgpassfile v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/jackc/pgproto3/v2 v2.3.3 // indirect
 | 
				
			||||||
 | 
						github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
 | 
				
			||||||
 | 
						github.com/jackc/pgtype v1.14.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/text v0.17.0 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										184
									
								
								authentication-service/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								authentication-service/go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,184 @@
 | 
				
			|||||||
 | 
					github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
				
			||||||
 | 
					github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
 | 
				
			||||||
 | 
					github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 | 
				
			||||||
 | 
					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/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
 | 
				
			||||||
 | 
					github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 | 
				
			||||||
 | 
					github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
 | 
				
			||||||
 | 
					github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
 | 
				
			||||||
 | 
					github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 | 
				
			||||||
 | 
					github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 | 
				
			||||||
 | 
					github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 | 
				
			||||||
 | 
					github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
 | 
				
			||||||
 | 
					github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 | 
				
			||||||
 | 
					github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 | 
				
			||||||
 | 
					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.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
 | 
				
			||||||
 | 
					github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
 | 
				
			||||||
 | 
					github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
 | 
				
			||||||
 | 
					github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
 | 
				
			||||||
 | 
					github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
 | 
				
			||||||
 | 
					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/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
 | 
				
			||||||
 | 
					github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
 | 
				
			||||||
 | 
					github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
 | 
				
			||||||
 | 
					github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
 | 
				
			||||||
 | 
					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/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.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 | 
				
			||||||
 | 
					github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 | 
				
			||||||
 | 
					github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
 | 
				
			||||||
 | 
					github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 | 
				
			||||||
 | 
					github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
 | 
				
			||||||
 | 
					github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
 | 
				
			||||||
 | 
					github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
 | 
				
			||||||
 | 
					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.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
 | 
				
			||||||
 | 
					github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
 | 
				
			||||||
 | 
					github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 | 
				
			||||||
 | 
					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.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
 | 
				
			||||||
 | 
					github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
 | 
				
			||||||
 | 
					github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
 | 
				
			||||||
 | 
					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.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 | 
				
			||||||
 | 
					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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
				
			||||||
 | 
					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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
				
			||||||
 | 
					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.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
 | 
				
			||||||
 | 
					github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 | 
				
			||||||
 | 
					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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 | 
				
			||||||
 | 
					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/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/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
 | 
				
			||||||
 | 
					github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
 | 
				
			||||||
 | 
					github.com/shopspring/decimal v1.2.0/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/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
				
			||||||
 | 
					github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 | 
				
			||||||
 | 
					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.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 | 
				
			||||||
 | 
					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.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 | 
				
			||||||
 | 
					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=
 | 
				
			||||||
 | 
					go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 | 
				
			||||||
 | 
					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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
 | 
				
			||||||
 | 
					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/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 | 
				
			||||||
 | 
					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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
 | 
					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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
 | 
				
			||||||
 | 
					golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
 | 
					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-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/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
				
			||||||
 | 
					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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
 | 
					golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
				
			||||||
 | 
					gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
					honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 | 
				
			||||||
							
								
								
									
										60
									
								
								authentication-service/users.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								authentication-service/users.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: user_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE SEQUENCE public.user_id_seq
 | 
				
			||||||
 | 
					    START WITH 1
 | 
				
			||||||
 | 
					    INCREMENT BY 1
 | 
				
			||||||
 | 
					    NO MINVALUE
 | 
				
			||||||
 | 
					    NO MAXVALUE
 | 
				
			||||||
 | 
					    CACHE 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE public.user_id_seq OWNER TO postgres;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SET default_tablespace = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SET default_table_access_method = heap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: users; Type: TABLE; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE public.users (
 | 
				
			||||||
 | 
					    id integer DEFAULT nextval('public.user_id_seq'::regclass) NOT NULL,
 | 
				
			||||||
 | 
					    email character varying(255),
 | 
				
			||||||
 | 
					    first_name character varying(255),
 | 
				
			||||||
 | 
					    last_name character varying(255),
 | 
				
			||||||
 | 
					    password character varying(60),
 | 
				
			||||||
 | 
					    user_active integer DEFAULT 0,
 | 
				
			||||||
 | 
					    created_at timestamp without time zone,
 | 
				
			||||||
 | 
					    updated_at timestamp without time zone
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE public.users OWNER TO postgres;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SELECT pg_catalog.setval('public.user_id_seq', 1, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.users
 | 
				
			||||||
 | 
					    ADD CONSTRAINT users_pkey PRIMARY KEY (id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					INSERT INTO "public"."users"("email","first_name","last_name","password","user_active","created_at","updated_at")
 | 
				
			||||||
 | 
					VALUES
 | 
				
			||||||
 | 
					(E'admin@example.com',E'Admin',E'User',E'$2a$12$1zGLuYDDNvATh4RA4avbKuheAMpb1svexSzrQm7up.bnpwQHs0jNe',1,E'2022-03-14 00:00:00',E'2022-03-14 00:00:00');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1,9 +1,23 @@
 | 
				
			|||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type RequestPayload struct {
 | 
				
			||||||
 | 
						Action string      `string:"action"`
 | 
				
			||||||
 | 
						Auth   AuthPayload `                json:"auth,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AuthPayload struct {
 | 
				
			||||||
 | 
						Email    string `json:"email"`
 | 
				
			||||||
 | 
						Password string `json:"password"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (app *Config) Broker(w http.ResponseWriter, r *http.Request) {
 | 
					func (app *Config) Broker(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	payload := jsonResponse{
 | 
						payload := jsonResponse{
 | 
				
			||||||
		Error:   false,
 | 
							Error:   false,
 | 
				
			||||||
@ -12,3 +26,77 @@ func (app *Config) Broker(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	app.writeJSON(w, http.StatusOK, payload)
 | 
						app.writeJSON(w, http.StatusOK, payload)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *Config) HandleSubmission(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						var requestPayload RequestPayload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := app.readJSON(w, r, &requestPayload)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							app.errorJSON(w, err, http.StatusBadRequest)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch requestPayload.Action {
 | 
				
			||||||
 | 
						case "auth":
 | 
				
			||||||
 | 
							app.authenticate(w, requestPayload.Auth)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							app.errorJSON(w, errors.New("unknown action"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *Config) authenticate(w http.ResponseWriter, a AuthPayload) {
 | 
				
			||||||
 | 
						// create some json we'll send to the auth microservice
 | 
				
			||||||
 | 
						authPayload, err := json.MarshalIndent(a, "", "\t")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							app.errorJSON(w, err, http.StatusBadRequest)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// call the service
 | 
				
			||||||
 | 
						req, err := http.NewRequest(
 | 
				
			||||||
 | 
							"POST",
 | 
				
			||||||
 | 
							"http://authentication-service:4000/authenticate",
 | 
				
			||||||
 | 
							bytes.NewBuffer(authPayload),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							app.errorJSON(w, err, http.StatusBadRequest)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req.Header.Add("Content-Type", "application/json")
 | 
				
			||||||
 | 
						client := &http.Client{}
 | 
				
			||||||
 | 
						resp, err := client.Do(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							app.errorJSON(w, err, http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Println(resp.Body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// make sure we get back the correct status code
 | 
				
			||||||
 | 
						if resp.StatusCode != http.StatusAccepted {
 | 
				
			||||||
 | 
							app.errorJSON(w, errors.New("invalid credentials"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// create a variable we'll read resp.Body into
 | 
				
			||||||
 | 
						var respPayload jsonResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = json.NewDecoder(resp.Body).Decode(&respPayload)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							app.errorJSON(w, err, http.StatusBadRequest)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if respPayload.Error {
 | 
				
			||||||
 | 
							app.errorJSON(w, errors.New(respPayload.Message), http.StatusUnauthorized)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var payload jsonResponse
 | 
				
			||||||
 | 
						payload.Error = false
 | 
				
			||||||
 | 
						payload.Message = "Authenticated!"
 | 
				
			||||||
 | 
						payload.Data = respPayload.Data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.writeJSON(w, http.StatusOK, payload)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -24,6 +24,7 @@ func (app *Config) routes() http.Handler {
 | 
				
			|||||||
	mux.Use(middleware.Heartbeat("/ping"))
 | 
						mux.Use(middleware.Heartbeat("/ping"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mux.Post("/", app.Broker)
 | 
						mux.Post("/", app.Broker)
 | 
				
			||||||
 | 
						mux.Post("/handle", app.HandleSubmission)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return mux
 | 
						return mux
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@
 | 
				
			|||||||
                <hr>
 | 
					                <hr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <a id="brokerBtn" class="btn btn-outline-secondary" href="javascript:void(0);">Test Broker</a>
 | 
					                <a id="brokerBtn" class="btn btn-outline-secondary" href="javascript:void(0);">Test Broker</a>
 | 
				
			||||||
 | 
					                <a id="authBrokerBtn" class="btn btn-outline-secondary" href="javascript:void(0);">Test Auth</a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div id="output" class="mt-5" style="outline: 1px solid silver; padding: 2em;">
 | 
					                <div id="output" class="mt-5" style="outline: 1px solid silver; padding: 2em;">
 | 
				
			||||||
                    <span class="text-muted">Output shows here...</span>
 | 
					                    <span class="text-muted">Output shows here...</span>
 | 
				
			||||||
@ -34,6 +35,7 @@
 | 
				
			|||||||
{{define "js"}}
 | 
					{{define "js"}}
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
        let brokerBtn = document.getElementById("brokerBtn");
 | 
					        let brokerBtn = document.getElementById("brokerBtn");
 | 
				
			||||||
 | 
					        let authBrokerBtn = document.getElementById("authBrokerBtn");
 | 
				
			||||||
        let output = document.getElementById("output");
 | 
					        let output = document.getElementById("output");
 | 
				
			||||||
        let sent = document.getElementById("payload");
 | 
					        let sent = document.getElementById("payload");
 | 
				
			||||||
        let received = document.getElementById("received");
 | 
					        let received = document.getElementById("received");
 | 
				
			||||||
@ -57,5 +59,37 @@
 | 
				
			|||||||
                    output.innerHTML += "<br><br>Error: " + error;
 | 
					                    output.innerHTML += "<br><br>Error: " + error;
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        authBrokerBtn.addEventListener("click", () => {
 | 
				
			||||||
 | 
					            const payload = {
 | 
				
			||||||
 | 
					                action: "auth",
 | 
				
			||||||
 | 
					                auth: {
 | 
				
			||||||
 | 
					                    email: "admin@example.com",
 | 
				
			||||||
 | 
					                    password: "verysecret",
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            const headers = new Headers();
 | 
				
			||||||
 | 
					            headers.append("Content-Type", "application/json");
 | 
				
			||||||
 | 
					            const body = {
 | 
				
			||||||
 | 
					                method: 'POST',
 | 
				
			||||||
 | 
					                body: JSON.stringify(payload),
 | 
				
			||||||
 | 
					                headers: headers,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            fetch("http:\/\/localhost:8080/handle", body)
 | 
				
			||||||
 | 
					            .then((response) => response.json())
 | 
				
			||||||
 | 
					                .then((data) => {
 | 
				
			||||||
 | 
					                    sent.innerHTML = JSON.stringify(payload, undefined, 4);
 | 
				
			||||||
 | 
					                    received.innerHTML = JSON.stringify(data, undefined, 4);
 | 
				
			||||||
 | 
					                    if (data.error) {
 | 
				
			||||||
 | 
					                        console.log(data.message);
 | 
				
			||||||
 | 
					                        output.innerHTML += `<br><strong>Error:</strong>: ${data.message}`;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        output.innerHTML += `<br><strong>Response from broker service</strong>: ${data.message}`;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .catch((error) => {
 | 
				
			||||||
 | 
					                    output.innerHTML += "<br><br>Error: " + error;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
{{end}}
 | 
					{{end}}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,24 +1,25 @@
 | 
				
			|||||||
FRONT_END_BINARY=frontApp
 | 
					FRONT_END_BINARY=frontApp
 | 
				
			||||||
BROKER_BINARY=brokerApp
 | 
					BROKER_BINARY=brokerApp
 | 
				
			||||||
 | 
					AUTH_BINARY=authApp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## up: starts all containers in the background without forcing build
 | 
					## up: starts all containers in the background without forcing build
 | 
				
			||||||
up:
 | 
					up:
 | 
				
			||||||
	@echo "Starting Docker images..."
 | 
						@echo "Starting Docker images..."
 | 
				
			||||||
	sudo docker compose up -d
 | 
						docker compose up -d
 | 
				
			||||||
	@echo "Docker images started!"
 | 
						@echo "Docker images started!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## up_build: stops docker-compose (if running), builds all projects and starts docker compose
 | 
					## up_build: stops docker-compose (if running), builds all projects and starts docker compose
 | 
				
			||||||
up_build: build_broker
 | 
					up_build: build_broker build_auth
 | 
				
			||||||
	@echo "Stopping docker images (if running...)"
 | 
						@echo "Stopping docker images (if running...)"
 | 
				
			||||||
	sudo docker compose down
 | 
						docker compose down
 | 
				
			||||||
	@echo "Building (when required) and starting docker images..."
 | 
						@echo "Building (when required) and starting docker images..."
 | 
				
			||||||
	sudo docker compose up --build -d
 | 
						docker compose up --build -d
 | 
				
			||||||
	@echo "Docker images built and started!"
 | 
						@echo "Docker images built and started!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## down: stop docker compose
 | 
					## down: stop docker compose
 | 
				
			||||||
down:
 | 
					down:
 | 
				
			||||||
	@echo "Stopping docker compose..."
 | 
						@echo "Stopping docker compose..."
 | 
				
			||||||
	sudo docker compose down
 | 
						docker compose down
 | 
				
			||||||
	@echo "Done!"
 | 
						@echo "Done!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## build_broker: builds the broker binary as a linux executable
 | 
					## build_broker: builds the broker binary as a linux executable
 | 
				
			||||||
@ -27,6 +28,12 @@ build_broker:
 | 
				
			|||||||
	cd ../broker-service && env GOOS=linux CGO_ENABLED=0 go build -o ${BROKER_BINARY} ./cmd/api
 | 
						cd ../broker-service && env GOOS=linux CGO_ENABLED=0 go build -o ${BROKER_BINARY} ./cmd/api
 | 
				
			||||||
	@echo "Done!"
 | 
						@echo "Done!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## build_auth: builds the auth binary as a linux executable
 | 
				
			||||||
 | 
					build_auth:
 | 
				
			||||||
 | 
						@echo "Building auth binary..."
 | 
				
			||||||
 | 
						cd ../authentication-service && env GOOS=linux CGO_ENABLED=0 go build -o ${AUTH_BINARY} ./cmd/api
 | 
				
			||||||
 | 
						@echo "Done!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## build_front: builds the frone end binary
 | 
					## build_front: builds the frone end binary
 | 
				
			||||||
build_front:
 | 
					build_front:
 | 
				
			||||||
	@echo "Building front end binary..."
 | 
						@echo "Building front end binary..."
 | 
				
			||||||
 | 
				
			|||||||
@ -9,3 +9,36 @@ services:
 | 
				
			|||||||
    deploy:
 | 
					    deploy:
 | 
				
			||||||
      mode: replicated
 | 
					      mode: replicated
 | 
				
			||||||
      replicas: 1
 | 
					      replicas: 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  authentication-service:
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: ./../authentication-service/
 | 
				
			||||||
 | 
					      dockerfile: ./../authentication-service/authentication-service.dockerfile
 | 
				
			||||||
 | 
					    ports:
 | 
				
			||||||
 | 
					      - "8081:4000"
 | 
				
			||||||
 | 
					    deploy:
 | 
				
			||||||
 | 
					      mode: replicated
 | 
				
			||||||
 | 
					      replicas: 1
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      DSN: "host=postgres port=5432 user=postgres password=password dbname=users timezone=UTC connect_timeout=5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  postgres:
 | 
				
			||||||
 | 
					    image: postgres
 | 
				
			||||||
 | 
					    ports:
 | 
				
			||||||
 | 
					      - "5432:5432"
 | 
				
			||||||
 | 
					    restart: always
 | 
				
			||||||
 | 
					    deploy:
 | 
				
			||||||
 | 
					      mode: replicated
 | 
				
			||||||
 | 
					      replicas: 1
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      POSTGRES_USER: postgres
 | 
				
			||||||
 | 
					      POSTGRES_PASSWORD: password
 | 
				
			||||||
 | 
					      POSTGRES_DB: users
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./db-data/postgres:/var/lib/postgresql/data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  adminer:
 | 
				
			||||||
 | 
					    image: adminer
 | 
				
			||||||
 | 
					    restart: always
 | 
				
			||||||
 | 
					    ports:
 | 
				
			||||||
 | 
					      - 8090:8080
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user