├── .gitignore
├── README.md
├── authentication-service
├── README.md
├── authApp
├── authentication-service.dockerfile
├── cmd
│ └── api
│ │ ├── handlers.go
│ │ ├── helpers.go
│ │ ├── main.go
│ │ └── routes.go
├── data
│ └── models.go
├── go.mod
└── go.sum
├── broker-service
├── README.md
├── broker-service.dockerfile
├── brokerApp
├── cmd
│ └── api
│ │ ├── handlers.go
│ │ ├── helpers.go
│ │ ├── main.go
│ │ └── routes.go
├── event
│ ├── emitter.go
│ └── event.go
├── go.mod
├── go.sum
└── logs
│ ├── logs.pb.go
│ ├── logs.proto
│ └── logs_grpc.pb.go
├── demo.webm
├── front-end
├── .DS_Store
├── cmd
│ ├── .DS_Store
│ └── web
│ │ ├── main.go
│ │ └── templates
│ │ ├── base.layout.gohtml
│ │ ├── footer.partial.gohtml
│ │ ├── header.partial.gohtml
│ │ └── test.page.gohtml
├── front-end.dockerfile
└── go.mod
├── go-micro-arch.png
├── go.work
├── go.work.sum
├── listener-service
├── README.md
├── event
│ ├── consumer.go
│ └── event.go
├── go.mod
├── go.sum
├── listener-service.dockerfile
├── listenerApp
└── main.go
├── logger-service
├── cmd
│ └── api
│ │ ├── grpc.go
│ │ ├── handlers.go
│ │ ├── helpers.go
│ │ ├── main.go
│ │ ├── routes.go
│ │ └── rpc.go
├── data
│ └── models.go
├── go.mod
├── go.sum
├── logger-service.dockerfile
├── loggerServiceApp
└── logs
│ ├── logs.pb.go
│ ├── logs.proto
│ └── logs_grpc.pb.go
├── mail-service
├── cmd
│ └── api
│ │ ├── handlers.go
│ │ ├── helpers.go
│ │ ├── mailer.go
│ │ ├── main.go
│ │ └── routes.go
├── go.mod
├── go.sum
├── mail-service.dockerfile
├── mailApp
└── templates
│ └── mail
│ ├── mail.html.gohtml
│ └── mail.plain.gohtml
└── project
├── Caddyfile
├── Makefile
├── caddy.dockerfile
├── docker-compose.yml
├── ingress.yml
├── k8s
├── authentication.yml
├── broker.yml
├── front-end.yml
├── listener.yml
├── logger.yml
├── mail.yml
├── mailhog.yml
├── mongo.yml
└── rabbit.yml
├── postgres.yml
└── swarm.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | project/db-data
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Go-micro
2 |
3 | This is a project consisting of 6 different microservices written in GoLang. This is an End-to-End Distributed System that uses different protocols like rpc, GRPC , REST API's and Queueing Systems for communication between the services.
4 |
5 | The main goal of this project is to provide a clear and concise understanding of the communication mechanisms utilized by this microservice architecture-based distributed system. By doing so, users can gain a comprehensive understanding of how the system operates and how these communication mechanisms enable seamless integration between different software services, leading to a more efficient and scalable application..
6 |
7 | This project can be deployed either via a docker swarm (also via minikube cluster in future).
8 |
9 | 
10 | ---
11 |
12 | ### Demo Video
13 | [Screencast from 01-05-24 11:14:35 AM IST.webm](https://github.com/ayushthe1/go-micro/assets/114604338/98876a9e-b408-4b5a-b6d4-ea7ee8c5af9d)
14 |
15 |
16 | ---
17 |
18 | ### These are the core services that perform distributed actions
19 | - [authentication-service](https://github.com/ayushthe1/go-micro/tree/master/authentication-service) for Authenticating users. Stores the users data in a Postgres Container.
20 | - [Broker Service](https://github.com/ayushthe1/go-micro/tree/master/broker-service) is the central node point for handling each request from the client and rendering a response to the client. This Service calls the right service(authentication, listener, logger and mailer ) when a request is called from the front-end
21 | - [logger-service](https://github.com/ayushthe1/go-micro/tree/master/logger-service) receives and accepts the data from the authentication, listener and mailer service ,when each service has been called through the broker service . The data from each service is stored in a MongoDb database.It also handles the gRPC and RPC actions when called by the broker service.
22 | - [mail-service](https://github.com/ayushthe1/go-micro/tree/master/mail-service) Handles sending of mails
23 | - [listener-service](https://github.com/ayushthe1/go-micro/tree/master/listener-service) This service consumes messages in RabbitMQ (running in a different container) and initiates a process.
24 | - [front-end](https://github.com/ayushthe1/go-micro/tree/master/project/Caddyfile) just displays outputs for each action performed internally
25 | - [Caddy-service](https://github.com/ayushthe1/go-micro/tree/master/project/Caddyfile) acts as a reverse proxy. It forwards requests coming to http://localhost to the front-end service.
26 |
27 |
28 |
29 |
30 |
31 | ---
32 |
33 | ### To deploy via Docker swarm
34 |
35 | 1. Ensure you have docker installed and running on your system.
36 |
37 | 2. In your terminal ,type `sudo nano /etc/hosts` and enter your password.
38 |
39 | 3. In the file ,add a line `127.0.0.1 backend` . The updated file should look something like this :
40 |
41 |
42 | 
43 |
44 | 4. Clone this repo
45 |
46 | 5. From inside the repo, cd into `/project` directory.
47 |
48 | 6. Type command `make deploy_swarm` in the terminal.
49 |
50 | 7. The project will be deployed on docker swarm.
51 |
52 |
53 |
--------------------------------------------------------------------------------
/authentication-service/README.md:
--------------------------------------------------------------------------------
1 | ### The Authentication microservice authenticates a user
2 |
3 | - Uses Postgres as its database
4 | - Uses [jackc](github.com/jackc/pgx/v4) as its postgres driver
5 | - Uses [chi router](https://github.com/go-chi/chi/v5) for routing
6 | - Uses [chi](github.com/go-chi/cors) as its CORS
7 |
--------------------------------------------------------------------------------
/authentication-service/authApp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayushthe1/go-micro/779757cab45e468c6ef663631916047495783e51/authentication-service/authApp
--------------------------------------------------------------------------------
/authentication-service/authentication-service.dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | RUN mkdir /app
4 |
5 | COPY authApp /app
6 |
7 | CMD ["/app/authApp"]
--------------------------------------------------------------------------------
/authentication-service/cmd/api/handlers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "net/http"
9 | )
10 |
11 | func (app *Config) Authenticate(w http.ResponseWriter, r *http.Request) {
12 | var requestPayload struct {
13 | Email string `json:"email"`
14 | Password string `json:"password"`
15 | }
16 |
17 | err := app.readJSON(w, r, &requestPayload)
18 | if err != nil {
19 | app.errorJSON(w, err, http.StatusBadRequest)
20 | return
21 | }
22 |
23 | // validate the user against the database
24 | user, err := app.Models.User.GetByEmail(requestPayload.Email)
25 | if err != nil {
26 | app.errorJSON(w, errors.New("invalid credentials"), http.StatusBadRequest)
27 | return
28 | }
29 |
30 | valid, err := user.PasswordMatches(requestPayload.Password)
31 | if err != nil || !valid {
32 | app.errorJSON(w, errors.New("invalid credentials"), http.StatusBadRequest)
33 | return
34 | }
35 |
36 | // log the successfull authentication
37 | err = app.logRequest("authenticationnn", fmt.Sprint("%s logged", user.Email))
38 | if err != nil {
39 | app.errorJSON(w, err)
40 | return
41 | }
42 |
43 | payload := jsonResponse{
44 | Error: false,
45 | Message: fmt.Sprintf("Logged in user %s", user.Email),
46 | Data: user,
47 | }
48 |
49 | app.writeJSON(w, http.StatusAccepted, payload)
50 | }
51 |
52 | func (app *Config) logRequest(name, data string) error {
53 | var entry struct {
54 | Name string `json:"name"`
55 | Data string `json:"data"`
56 | }
57 |
58 | entry.Name = name
59 | entry.Data = data
60 |
61 | jsonData, _ := json.Marshal(entry)
62 | logServiceURL := "http://logger-service/log"
63 |
64 | request, err := http.NewRequest("POST", logServiceURL, bytes.NewBuffer(jsonData))
65 | if err != nil {
66 | return err
67 | }
68 |
69 | client := &http.Client{}
70 | _, err = client.Do(request)
71 | if err != nil {
72 | return err
73 | }
74 |
75 | return nil
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/authentication-service/cmd/api/helpers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "io"
7 | "net/http"
8 | )
9 |
10 | type jsonResponse struct {
11 | Error bool `json:"ERRoR"`
12 | Message string `json:"message"`
13 | Data any `json:"data,omitempty"`
14 | }
15 |
16 | // function to read json
17 | func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data any) error {
18 | maxBytes := 1048576 // 1 MB
19 |
20 | // limiting the size of incoming request bodies
21 | r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
22 |
23 | dec := json.NewDecoder(r.Body)
24 | err := dec.Decode(data)
25 | if err != nil {
26 | return err
27 | }
28 |
29 | // decode a JSON value into an empty struct{}.
30 | err = dec.Decode(&struct{}{})
31 | // io.EOF is an error value indicating the end of the input source has been reached.
32 | // If err is not equal to io.EOF, it means that the decoding process did not reach the end of the input source, and there are additional JSON values remaining.
33 | if err != io.EOF {
34 | return errors.New("body must have only a single json value")
35 | }
36 |
37 | return nil
38 | }
39 |
40 | // function to write json
41 | // The ellipsis (...) before http.Header indicates that it can accept a variable number of arguments, allowing you to pass or store multiple http.Header values.
42 | func (app *Config) writeJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
43 | out, err := json.Marshal(data)
44 | if err != nil {
45 | return err
46 | }
47 |
48 | if len(headers) > 0 {
49 | for key, value := range headers[0] {
50 | w.Header()[key] = value
51 | }
52 | }
53 |
54 | w.Header().Set("Content-Type", "application/json")
55 | w.WriteHeader(status)
56 |
57 | _, err = w.Write(out)
58 | if err != nil {
59 | return err
60 | }
61 |
62 | return nil
63 | }
64 |
65 | // function to write error message as json
66 | func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
67 | statusCode := http.StatusBadRequest
68 |
69 | if len(status) > 0 {
70 | statusCode = status[0]
71 | }
72 |
73 | var payload jsonResponse
74 | payload.Error = true
75 | payload.Message = err.Error()
76 |
77 | return app.writeJSON(w, statusCode, payload)
78 | }
79 |
--------------------------------------------------------------------------------
/authentication-service/cmd/api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "authentication/data"
5 |
6 | // Package sql provides a generic interface around SQL (or SQL-like) databases.The sql package must be used in conjunction with a database driver
7 | "database/sql"
8 | "fmt"
9 | "log"
10 | "net/http"
11 | "os"
12 | "time"
13 |
14 | // "pgx" Go driver is a software component that acts as a bridge between your Go application and the PostgreSQL database server. It provides a set of tools and functions that make it easy for your Go code to send requests and receive responses from the database. It simplifies the process of integrating a database into your application, making it easier to store and retrieve data as needed.
15 |
16 | _ "github.com/jackc/pgconn"
17 | _ "github.com/jackc/pgx/v4"
18 | _ "github.com/jackc/pgx/v4/stdlib"
19 | )
20 |
21 | // Broker is also listening on port 8084 and we can make authentication service also listen on that port as docker lets multiple containers listen on same port and treats them as indiviual servers
22 |
23 | type Config struct {
24 | DB *sql.DB
25 | Models data.Models
26 | }
27 |
28 | // authentication service will listen on port 80 inside of docker.
29 | // you can have multiple services listening on the same port inside docker, just as if they were separate machines. So every single web/api service inside of docker can all listen on port 80.
30 | const webPort = "80"
31 |
32 | var counts int64
33 |
34 | func main() {
35 | log.Println("Starting authentication service")
36 |
37 | // connect to DB
38 | conn := connectToDB()
39 | if conn == nil {
40 | log.Panic("Can't connect to Postgres!")
41 | }
42 |
43 | // set up config
44 | app := Config{
45 | DB: conn,
46 | Models: data.New(conn),
47 | }
48 |
49 | srv := &http.Server{
50 | Addr: fmt.Sprintf(":%s", webPort),
51 | Handler: app.routes(),
52 | }
53 |
54 | err := srv.ListenAndServe()
55 | if err != nil {
56 | log.Panic(err)
57 | }
58 | }
59 |
60 | func openDB(dsn string) (*sql.DB, error) {
61 | db, err := sql.Open("pgx", dsn)
62 | if err != nil {
63 | return nil, err
64 | }
65 |
66 | err = db.Ping()
67 | if err != nil {
68 | return nil, err
69 | }
70 |
71 | return db, nil
72 | }
73 |
74 | func connectToDB() *sql.DB {
75 | log.Println("Getting DSN string")
76 | dsn := os.Getenv("DSN")
77 | log.Println("DSN is", dsn)
78 |
79 | for {
80 | connection, err := openDB(dsn)
81 | if err != nil {
82 | log.Println("Postgres not yet ready ...")
83 | counts++
84 | } else {
85 | log.Println("Connected to Postgres")
86 | return connection
87 | }
88 |
89 | if counts > 10 {
90 | log.Println(err)
91 | return nil
92 | }
93 |
94 | log.Println("Backing off for 2 sec ...")
95 | time.Sleep(2 * time.Second)
96 | continue
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/authentication-service/cmd/api/routes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/go-chi/chi/middleware"
7 | "github.com/go-chi/chi/v5"
8 | "github.com/go-chi/cors"
9 | )
10 |
11 | func (app *Config) routes() http.Handler {
12 | mux := chi.NewRouter()
13 |
14 | // specify who is allowed to connect
15 | mux.Use(cors.Handler(cors.Options{
16 | AllowedOrigins: []string{"https://*", "http://*"},
17 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
18 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
19 | ExposedHeaders: []string{"Link"},
20 | AllowCredentials: true,
21 | MaxAge: 300,
22 | }))
23 |
24 | mux.Use(middleware.Heartbeat("/ping"))
25 |
26 | // create a new route
27 | mux.Post("/authenticate", app.Authenticate)
28 |
29 | return mux
30 | }
31 |
--------------------------------------------------------------------------------
/authentication-service/data/models.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "errors"
7 | "log"
8 | "time"
9 |
10 | "golang.org/x/crypto/bcrypt"
11 | )
12 |
13 | const dbTimeout = time.Second * 3
14 |
15 | var db *sql.DB
16 |
17 | // New is the function used to create an instance of the data package. It returns the type
18 | // Model, which embeds all the types we want to be available to our application.
19 | func New(dbPool *sql.DB) Models {
20 | db = dbPool
21 |
22 | return Models{
23 | User: User{},
24 | }
25 | }
26 |
27 | // Models is the type for this package. Note that any model that is included as a member
28 | // in this type is available to us throughout the application, anywhere that the
29 | // app variable is used, provided that the model is also added in the New function.
30 | type Models struct {
31 | User User
32 | }
33 |
34 | // User is the structure which holds one user from the database.
35 | type User struct {
36 | ID int `json:"id"`
37 | Email string `json:"email"`
38 | FirstName string `json:"first_name,omitempty"`
39 | LastName string `json:"last_name,omitempty"`
40 | Password string `json:"-"`
41 | Active int `json:"active"`
42 | CreatedAt time.Time `json:"created_at"`
43 | UpdatedAt time.Time `json:"updated_at"`
44 | }
45 |
46 | // GetAll returns a slice of all users, sorted by last name
47 | func (u *User) GetAll() ([]*User, error) {
48 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
49 | defer cancel()
50 |
51 | query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at
52 | from users order by last_name`
53 |
54 | rows, err := db.QueryContext(ctx, query)
55 | if err != nil {
56 | return nil, err
57 | }
58 | defer rows.Close()
59 |
60 | var users []*User
61 |
62 | for rows.Next() {
63 | var user User
64 | err := rows.Scan(
65 | &user.ID,
66 | &user.Email,
67 | &user.FirstName,
68 | &user.LastName,
69 | &user.Password,
70 | &user.Active,
71 | &user.CreatedAt,
72 | &user.UpdatedAt,
73 | )
74 | if err != nil {
75 | log.Println("Error scanning", err)
76 | return nil, err
77 | }
78 |
79 | users = append(users, &user)
80 | }
81 |
82 | return users, nil
83 | }
84 |
85 | // GetByEmail returns one user by email
86 | func (u *User) GetByEmail(email string) (*User, error) {
87 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
88 | defer cancel()
89 |
90 | query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where email = $1`
91 |
92 | var user User
93 | row := db.QueryRowContext(ctx, query, email)
94 |
95 | err := row.Scan(
96 | &user.ID,
97 | &user.Email,
98 | &user.FirstName,
99 | &user.LastName,
100 | &user.Password,
101 | &user.Active,
102 | &user.CreatedAt,
103 | &user.UpdatedAt,
104 | )
105 |
106 | if err != nil {
107 | return nil, err
108 | }
109 |
110 | return &user, nil
111 | }
112 |
113 | // GetOne returns one user by id
114 | func (u *User) GetOne(id int) (*User, error) {
115 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
116 | defer cancel()
117 |
118 | query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where id = $1`
119 |
120 | var user User
121 | row := db.QueryRowContext(ctx, query, id)
122 |
123 | err := row.Scan(
124 | &user.ID,
125 | &user.Email,
126 | &user.FirstName,
127 | &user.LastName,
128 | &user.Password,
129 | &user.Active,
130 | &user.CreatedAt,
131 | &user.UpdatedAt,
132 | )
133 |
134 | if err != nil {
135 | return nil, err
136 | }
137 |
138 | return &user, nil
139 | }
140 |
141 | // Update updates one user in the database, using the information
142 | // stored in the receiver u
143 | func (u *User) Update() error {
144 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
145 | defer cancel()
146 |
147 | stmt := `update users set
148 | email = $1,
149 | first_name = $2,
150 | last_name = $3,
151 | user_active = $4,
152 | updated_at = $5
153 | where id = $6
154 | `
155 |
156 | _, err := db.ExecContext(ctx, stmt,
157 | u.Email,
158 | u.FirstName,
159 | u.LastName,
160 | u.Active,
161 | time.Now(),
162 | u.ID,
163 | )
164 |
165 | if err != nil {
166 | return err
167 | }
168 |
169 | return nil
170 | }
171 |
172 | // Delete deletes one user from the database, by User.ID
173 | func (u *User) Delete() error {
174 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
175 | defer cancel()
176 |
177 | stmt := `delete from users where id = $1`
178 |
179 | _, err := db.ExecContext(ctx, stmt, u.ID)
180 | if err != nil {
181 | return err
182 | }
183 |
184 | return nil
185 | }
186 |
187 | // DeleteByID deletes one user from the database, by ID
188 | func (u *User) DeleteByID(id int) error {
189 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
190 | defer cancel()
191 |
192 | stmt := `delete from users where id = $1`
193 |
194 | _, err := db.ExecContext(ctx, stmt, id)
195 | if err != nil {
196 | return err
197 | }
198 |
199 | return nil
200 | }
201 |
202 | // Insert inserts a new user into the database, and returns the ID of the newly inserted row
203 | func (u *User) Insert(user User) (int, error) {
204 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
205 | defer cancel()
206 |
207 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
208 | if err != nil {
209 | return 0, err
210 | }
211 |
212 | var newID int
213 | stmt := `insert into users (email, first_name, last_name, password, user_active, created_at, updated_at)
214 | values ($1, $2, $3, $4, $5, $6, $7) returning id`
215 |
216 | err = db.QueryRowContext(ctx, stmt,
217 | user.Email,
218 | user.FirstName,
219 | user.LastName,
220 | hashedPassword,
221 | user.Active,
222 | time.Now(),
223 | time.Now(),
224 | ).Scan(&newID)
225 |
226 | if err != nil {
227 | return 0, err
228 | }
229 |
230 | return newID, nil
231 | }
232 |
233 | // ResetPassword is the method we will use to change a user's password.
234 | func (u *User) ResetPassword(password string) error {
235 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
236 | defer cancel()
237 |
238 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
239 | if err != nil {
240 | return err
241 | }
242 |
243 | stmt := `update users set password = $1 where id = $2`
244 | _, err = db.ExecContext(ctx, stmt, hashedPassword, u.ID)
245 | if err != nil {
246 | return err
247 | }
248 |
249 | return nil
250 | }
251 |
252 | // PasswordMatches uses Go's bcrypt package to compare a user supplied password
253 | // with the hash we have stored for a given user in the database. If the password
254 | // and hash match, we return true; otherwise, we return false.
255 | func (u *User) PasswordMatches(plainText string) (bool, error) {
256 | err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(plainText))
257 | if err != nil {
258 | switch {
259 | case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
260 | // invalid password
261 | return false, nil
262 | default:
263 | return false, err
264 | }
265 | }
266 |
267 | return true, nil
268 | }
269 |
--------------------------------------------------------------------------------
/authentication-service/go.mod:
--------------------------------------------------------------------------------
1 | module authentication
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/go-chi/chi v1.5.4
7 | github.com/go-chi/chi/v5 v5.0.8
8 | github.com/go-chi/cors v1.2.1
9 | github.com/jackc/pgconn v1.14.0
10 | github.com/jackc/pgx/v4 v4.18.1
11 | golang.org/x/crypto v0.6.0
12 | )
13 |
14 | require (
15 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
16 | github.com/jackc/pgio v1.0.0 // indirect
17 | github.com/jackc/pgpassfile v1.0.0 // indirect
18 | github.com/jackc/pgproto3/v2 v2.3.2 // indirect
19 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
20 | github.com/jackc/pgtype v1.14.0 // indirect
21 | golang.org/x/text v0.7.0 // indirect
22 | )
23 |
--------------------------------------------------------------------------------
/authentication-service/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
3 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
4 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
5 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
6 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
7 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
8 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 | github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs=
13 | github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg=
14 | github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
15 | github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
16 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
17 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
18 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
19 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
20 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
21 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
22 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
23 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
24 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
25 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
26 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
27 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
28 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
29 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
30 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
31 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
32 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
33 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
34 | github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q=
35 | github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
36 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
37 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
38 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
39 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
40 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
41 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
42 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
43 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
44 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
45 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
46 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
47 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
48 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
49 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
50 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
51 | github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0=
52 | github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
53 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
54 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
55 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
56 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
57 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
58 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
59 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
60 | github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
61 | github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
62 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
63 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
64 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
65 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
66 | github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0=
67 | github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
68 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
69 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
70 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
71 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
72 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
73 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
74 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
75 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
76 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
77 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
78 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
79 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
80 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
81 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
82 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
83 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
84 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
85 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
86 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
87 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
88 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
89 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
90 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
91 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
92 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
93 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
94 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
95 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
96 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
97 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
98 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
99 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
100 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
101 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
102 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
103 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
104 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
105 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
106 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
107 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
108 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
109 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
110 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
111 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
112 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
113 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
114 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
115 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
116 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
117 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
118 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
119 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
120 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
121 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
122 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
123 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
124 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
125 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
126 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
127 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
128 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
129 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
130 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
131 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
132 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
133 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
134 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
135 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
136 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
137 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
138 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
139 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
140 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
141 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
142 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
143 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
144 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
145 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
146 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
147 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
148 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
149 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
150 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
151 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
152 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
153 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
154 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
155 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
156 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
157 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
158 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
159 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
160 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
161 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
162 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
163 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
164 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
165 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
166 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
167 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
168 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
169 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
170 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
171 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
172 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
173 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
174 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
175 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
176 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
177 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
178 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
179 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
180 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
181 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
182 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
183 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
184 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
185 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
186 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
187 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
188 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
189 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
190 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
191 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
192 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
193 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
194 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
195 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
196 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
197 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
198 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
199 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
200 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
201 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
202 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
203 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
204 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
205 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
206 |
--------------------------------------------------------------------------------
/broker-service/README.md:
--------------------------------------------------------------------------------
1 | ### The Broker Service processes each request sent by the front-end microservice and sends a response back to it.
2 | It receives request from the client and sends to the logger service.
3 |
4 | ---
5 |
6 | #### Based on the function calls that sends to the logger-service , i pick to use any of these function that does the following
7 | - The broker can send requests using Api's , it sends it to the logger service and saves it on the logger service database then displays it in the frontend
8 |
9 | - The broker can send events to the Queue and send it to the logger service the logger service database, then displays it in the frontend
10 |
11 | - The broker can send requets using RPC , it sends it to the logger service and saves it into logger-Service database then displays resut in the frontend
12 |
13 | ---
14 |
15 |
16 | #### Libraries Used
17 | - Uses [chi router](https://github.com/go-chi/chi/v5) for routing
18 | - Uses [chi](github.com/go-chi/cors) as its CORS
19 |
--------------------------------------------------------------------------------
/broker-service/broker-service.dockerfile:
--------------------------------------------------------------------------------
1 | ## Dockerfile tells docker-compose how to build the image. The Dockerfile is used to build images, while docker-compose helps you run them as containers.
2 |
3 |
4 | # # base go image (builder is the name of this image)
5 | # FROM golang:1.18-alpine as builder
6 |
7 | # # run a command on the docker image we're building
8 | # RUN mkdir /app
9 |
10 | # # copy everything from the current folder (.) into the app folder we created above in our docker image
11 | # COPY . /app
12 |
13 | # # Set the working directory
14 | # WORKDIR /app
15 |
16 | # # BUild our go code , CGO_ENABLED is a environment variable
17 | # RUN CGO_ENABLED=0 go build -o brokerApp ./cmd/api
18 |
19 | # # chmod +x on a file (your script) only means, that you'll make it executable
20 | # RUN chmod +x /app/brokerApp
21 |
22 |
23 |
24 | #### We removed the above lines as we're already building the brokerApp binary from the makefile command build_broker. The above code also does the same thing ,so we avoided repeating.
25 |
26 |
27 |
28 | ## Below is a new Docker image ,seperate to the above image
29 | # Build a tiny docker image
30 | FROM alpine:latest
31 |
32 | # Run command on new docker image
33 | RUN mkdir /app
34 |
35 |
36 | # COPY --from=builder /app/brokerApp /app
37 |
38 | # brokerApp binary will be build intially by makefile target build_broker and then this dockerfile will be RUN
39 | COPY brokerApp /app
40 |
41 | # When we run this command ,it should first build all of our code on one docker image and then create a much smaller Docker image and copy over the executable (brokerApp) to this new image
42 | CMD ["/app/brokerApp"]
--------------------------------------------------------------------------------
/broker-service/brokerApp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayushthe1/go-micro/779757cab45e468c6ef663631916047495783e51/broker-service/brokerApp
--------------------------------------------------------------------------------
/broker-service/cmd/api/handlers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "broker/event"
5 | "broker/logs"
6 | "bytes"
7 | "context"
8 | "encoding/json"
9 | "errors"
10 | "log"
11 | "net/http"
12 | "net/rpc"
13 | "time"
14 |
15 | "google.golang.org/grpc"
16 | "google.golang.org/grpc/credentials/insecure"
17 | )
18 |
19 | type RequestPayload struct {
20 | Action string `json:"action"`
21 | Auth AuthPayload `json:"auth,omitempty"`
22 | Log LogPayload `json:"log,omitempty"`
23 | Mail MailPayload `json:"mail,omitempty"`
24 | }
25 |
26 | type MailPayload struct {
27 | From string `json:"from"`
28 | To string `json:"to"`
29 | Subject string `json:"subject"`
30 | Message string `json:"message"`
31 | }
32 |
33 | type AuthPayload struct {
34 | Email string `json:"email"`
35 | Password string `json:"password"`
36 | }
37 |
38 | type LogPayload struct {
39 | Name string `json:"name"`
40 | Data string `json:"data"`
41 | }
42 |
43 | func (app *Config) Broker(w http.ResponseWriter, r *http.Request) {
44 | payload := jsonResponse{
45 | Error: false,
46 | Message: "Hit the broker",
47 | }
48 |
49 | _ = app.writeJSON(w, http.StatusOK, payload)
50 | }
51 |
52 | // HandleSubmission is the main point of entry into the broker. It accepts a JSON
53 | // payload and performs an action based on the value of "action" in that JSON.
54 | func (app *Config) HandleSubmission(w http.ResponseWriter, r *http.Request) {
55 | var requestPayload RequestPayload
56 |
57 | err := app.readJSON(w, r, &requestPayload)
58 | if err != nil {
59 | app.errorJSON(w, err)
60 | return
61 | }
62 |
63 | switch requestPayload.Action {
64 | case "auth":
65 | app.authenticate(w, requestPayload.Auth)
66 | case "log":
67 | // app.logItem(w, requestPayload.Log)
68 | // app.logEventViaRabbit(w, requestPayload.Log)
69 | app.LogItemViaRPC(w, requestPayload.Log)
70 | case "mail":
71 | app.sendMail(w, requestPayload.Mail)
72 | default:
73 | app.errorJSON(w, errors.New("unknown action"))
74 | }
75 | }
76 |
77 | func (app *Config) logItem(w http.ResponseWriter, entry LogPayload) {
78 | jsonData, _ := json.MarshalIndent(entry, "", "\t")
79 |
80 | logServiceURL := "http://logger-service/log"
81 |
82 | request, err := http.NewRequest("POST", logServiceURL, bytes.NewBuffer(jsonData))
83 | if err != nil {
84 | app.errorJSON(w, err)
85 | return
86 | }
87 |
88 | request.Header.Set("Content-Type", "application/json")
89 |
90 | client := &http.Client{}
91 |
92 | response, err := client.Do(request)
93 | if err != nil {
94 | app.errorJSON(w, err)
95 | return
96 | }
97 | defer response.Body.Close()
98 |
99 | if response.StatusCode != http.StatusAccepted {
100 | app.errorJSON(w, err)
101 | return
102 | }
103 |
104 | var payload jsonResponse
105 | payload.Error = false
106 | payload.Message = "logged"
107 |
108 | app.writeJSON(w, http.StatusAccepted, payload)
109 |
110 | }
111 |
112 | // authenticate calls the authentication microservice and sends back the appropriate response
113 | func (app *Config) authenticate(w http.ResponseWriter, a AuthPayload) {
114 | // create some json we'll send to the auth microservice
115 | jsonData, _ := json.MarshalIndent(a, "", "\t")
116 |
117 | // call the service (This is done form inside the docker container)
118 | log.Println("Sending POST request to authentication-service from broker")
119 |
120 | // Since we don't specify a port, it calls on default port 80 (inside docker)
121 | request, err := http.NewRequest("POST", "http://authentication-service/authenticate", bytes.NewBuffer(jsonData))
122 | if err != nil {
123 | app.errorJSON(w, err)
124 | return
125 | }
126 |
127 | client := &http.Client{}
128 | response, err := client.Do(request)
129 | if err != nil {
130 | app.errorJSON(w, err)
131 | return
132 | }
133 | defer response.Body.Close()
134 |
135 | // make sure we get back the correct status code
136 | if response.StatusCode == http.StatusUnauthorized {
137 | app.errorJSON(w, errors.New("invalid credentials"))
138 | return
139 | } else if response.StatusCode != http.StatusAccepted {
140 | app.errorJSON(w, errors.New("error calling auth service"))
141 | return
142 | }
143 |
144 | // create a variable we'll read response.Body into
145 | var jsonFromService jsonResponse
146 |
147 | // decode the json from the auth service
148 | err = json.NewDecoder(response.Body).Decode(&jsonFromService)
149 | if err != nil {
150 | app.errorJSON(w, err)
151 | return
152 | }
153 |
154 | if jsonFromService.Error {
155 | app.errorJSON(w, err, http.StatusUnauthorized)
156 | return
157 | }
158 |
159 | var payload jsonResponse
160 | payload.Error = false
161 | payload.Message = "Authenticateddd!"
162 | payload.Data = jsonFromService.Data
163 |
164 | app.writeJSON(w, http.StatusAccepted, payload)
165 | }
166 |
167 | func (app *Config) sendMail(w http.ResponseWriter, msg MailPayload) {
168 | jsonData, _ := json.Marshal(msg)
169 |
170 | // call the mail service
171 | mailServiceURL := "http://mailer-service/send"
172 |
173 | // post to mail service
174 | // buffer stores data as a sequence of bytes. The bytes.Buffer type in the standard library treats the data as a byte slice ([]byte).when you read data from a buffer, you receive a byte slice ([]byte). Buffer is like a holding area that holds a certain amount of data until it is processed or transferred elsewhere.
175 | request, err := http.NewRequest("POST", mailServiceURL, bytes.NewBuffer(jsonData))
176 | if err != nil {
177 | log.Println(err)
178 | app.errorJSON(w, err)
179 | return
180 | }
181 |
182 | request.Header.Set("Content-Type", "application/json")
183 |
184 | client := &http.Client{}
185 | response, err := client.Do(request)
186 | if err != nil {
187 | log.Println(err)
188 | app.errorJSON(w, err)
189 | return
190 | }
191 | defer response.Body.Close()
192 |
193 | // make sure we get back the right status code
194 | if response.StatusCode != http.StatusAccepted {
195 | app.errorJSON(w, errors.New("error calling mail service"))
196 | return
197 | }
198 |
199 | // send back json
200 | var payload jsonResponse
201 | payload.Error = false
202 | payload.Message = "Message sent to " + msg.To
203 |
204 | app.writeJSON(w, http.StatusAccepted, payload)
205 |
206 | }
207 |
208 | // Different function to handle logging an item and we'll do so by emitting an event to Rabbit MQ
209 | func (app *Config) logEventViaRabbit(w http.ResponseWriter, l LogPayload) {
210 | err := app.pushToQueue(l.Name, l.Data)
211 | if err != nil {
212 | app.errorJSON(w, err)
213 | return
214 | }
215 |
216 | var payload jsonResponse
217 | payload.Error = false
218 | payload.Message = "logged via RabbitMQ"
219 |
220 | app.writeJSON(w, http.StatusAccepted, payload)
221 | }
222 |
223 | // we'll use this function every time we need to push something to the queue
224 | func (app *Config) pushToQueue(name, msg string) error {
225 | emitter, err := event.NewEventEmitter(app.Rabbit)
226 | if err != nil {
227 | return err
228 | }
229 |
230 | payload := LogPayload{
231 | Name: name,
232 | Data: msg,
233 | }
234 |
235 | j, _ := json.Marshal(&payload)
236 | err = emitter.Push(string(j), "log.INFO")
237 | if err != nil {
238 | return err
239 | }
240 |
241 | return nil
242 | }
243 |
244 | type RPCPayload struct {
245 | Name string
246 | Data string
247 | }
248 |
249 | // log event via rpc
250 | func (app *Config) LogItemViaRPC(w http.ResponseWriter, l LogPayload) {
251 |
252 | // get rpc client ;logger-service is our service name in docker compose
253 | client, err := rpc.Dial("tcp", "logger-service:5001")
254 | if err != nil {
255 | app.errorJSON(w, err)
256 | return
257 | }
258 |
259 | // After we have the client, we need to create some kind of payload. We need to create a type that exactly matches the one that the remote RPCServer expects to get
260 |
261 | rpcPayload := RPCPayload{
262 | Name: l.Name,
263 | Data: l.Data,
264 | }
265 |
266 | var result string
267 | // Any method that i want to expose to rpc on the serverend must be exported. So it has to start with a capital letter
268 | err = client.Call("RPCServer.LogInfo", rpcPayload, &result)
269 | if err != nil {
270 | app.errorJSON(w, err)
271 | return
272 | }
273 |
274 | // write some json back to the user
275 | payload := jsonResponse{
276 | Error: false,
277 | Message: result,
278 | }
279 |
280 | app.writeJSON(w, http.StatusAccepted, payload)
281 |
282 | }
283 |
284 | func (app *Config) LogViaGRPC(w http.ResponseWriter, r *http.Request) {
285 | var requestPayload RequestPayload
286 |
287 | err := app.readJSON(w, r, &requestPayload)
288 | log.Println("THIS IS THE REQUESTpayload :", requestPayload)
289 | if err != nil {
290 | app.errorJSON(w, err)
291 | return
292 | }
293 |
294 | conn, err := grpc.Dial("logger-service:50001", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
295 | if err != nil {
296 | app.errorJSON(w, err)
297 | return
298 | }
299 | defer conn.Close() // defer closing it or we'll have connections left open all over the places and it's a resource leak
300 |
301 | // create a client
302 | c := logs.NewLogServiceClient(conn)
303 | ctx, cancel := context.WithTimeout(context.Background(), time.Second)
304 | defer cancel()
305 |
306 | _, err = c.WriteLog(ctx, &logs.LogRequest{
307 | LogEntry: &logs.Log{
308 | Name: requestPayload.Log.Name,
309 | Data: requestPayload.Log.Data,
310 | },
311 | })
312 | if err != nil {
313 | app.errorJSON(w, err)
314 | return
315 | }
316 |
317 | var payload jsonResponse
318 | payload.Error = false
319 | payload.Message = "logged"
320 |
321 | app.writeJSON(w, http.StatusAccepted, payload)
322 | }
323 |
324 | // package main
325 |
326 | // import (
327 | // "bytes"
328 | // "encoding/json"
329 | // "errors"
330 | // "net/http"
331 | // )
332 |
333 | // type RequestPayload struct {
334 | // Action string `json:"action"`
335 | // // we'll create a different type for each of the possible action. (eg: auth ,mail ,log)
336 | // Auth AuthPayload `json:"auth, omitempty"`
337 | // }
338 |
339 | // type AuthPayload struct {
340 | // // fields we need to authenticate
341 | // Email string `json:"email"`
342 | // Password string `json:"password"`
343 | // }
344 |
345 | // // define a handler and add it to the routes file
346 | // // Note that function Broker starts with capital B as it's required to use this function in other files.
347 | // func (app *Config) Broker(w http.ResponseWriter, r *http.Request) {
348 | // payload := jsonResponse{
349 | // Error: false,
350 | // Message: "Hit the broker babyy",
351 | // }
352 |
353 | // // // Write this data (payload) out
354 | // // // json.Marshal is used to convert a Go data structure (such as a struct, map, or slice) into its corresponding JSON representation.It takes a Go value as input and returns a byte slice containing the JSON-encoded data.
355 | // // out, _ := json.Marshal(payload)
356 | // // w.Header().Set("Content-Type", "application/json")
357 | // // w.WriteHeader(http.StatusAccepted)
358 |
359 | // // // w.Write() function is used to write the response body(w). out is a byte slice containing the JSON-encoded data that will be sent as the response body.
360 | // // w.Write(out)
361 |
362 | // // OR
363 |
364 | // _ = app.writeJSON(w, http.StatusOK, payload)
365 |
366 | // }
367 |
368 | // func (app *Config) HandleSubmission(w http.ResponseWriter, r *http.Request) {
369 |
370 | // // read into variable of type request payload
371 | // var requestPayload RequestPayload
372 |
373 | // err := app.readJSON(w, r, &requestPayload)
374 | // if err != nil {
375 | // app.errorJSON(w, err)
376 | // return
377 | // }
378 |
379 | // switch requestPayload.Action {
380 | // case "auth":
381 | // app.authenticate(w, requestPayload.Auth)
382 | // default:
383 | // app.errorJSON(w, errors.New("unknown action"))
384 | // }
385 | // }
386 |
387 | // func (app *Config) authenticate(w http.ResponseWriter, a AuthPayload) {
388 | // // create some json that we'll send to the auth microservice
389 | // jsonData, _ := json.MarshalIndent(a, "", "\t")
390 |
391 | // // call the service
392 | // request, err := http.NewRequest("POST", "http://localhost:8081/authenticate", bytes.NewBuffer(jsonData))
393 |
394 | // if err != nil {
395 | // app.errorJSON(w, err)
396 | // return
397 | // }
398 |
399 | // client := &http.Client{}
400 | // response, err := client.Do(request)
401 | // if err != nil {
402 | // app.errorJSON(w, err)
403 | // return
404 | // }
405 | // defer response.Body.Close()
406 |
407 | // // make sure we get back the correct status code
408 | // if response.StatusCode == http.StatusUnauthorized {
409 | // app.errorJSON(w, errors.New("Invalid credentials (in handlers.go broker)"))
410 | // return
411 | // } else if response.StatusCode != http.StatusAccepted {
412 | // app.errorJSON(w, errors.New("error calling auth service (in handlers.go broker)"))
413 | // return
414 | // }
415 |
416 | // // create a variable we'll read response.Body into (this response is coming from authentication service)
417 | // var jsonFromService jsonResponse
418 |
419 | // // decode the json from the auth service
420 | // err = json.NewDecoder(response.Body).Decode(&jsonFromService)
421 | // if err != nil {
422 | // app.errorJSON(w, err)
423 | // return
424 | // }
425 |
426 | // if jsonFromService.Error {
427 | // app.errorJSON(w, err, http.StatusUnauthorized)
428 | // return
429 | // }
430 |
431 | // // what we want to send back to the user
432 | // var payload jsonResponse
433 | // payload.Error = false
434 | // payload.Message = "Authenticated oooribabaaa "
435 | // payload.Data = jsonFromService.Data
436 |
437 | // app.writeJSON(w, http.StatusAccepted, payload)
438 | // }
439 |
--------------------------------------------------------------------------------
/broker-service/cmd/api/helpers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "io"
7 | "net/http"
8 | )
9 |
10 | type jsonResponse struct {
11 | Error bool `json:"errorrr"`
12 | Message string `json:"message"`
13 | Data any `json:"data,omitempty"`
14 | }
15 |
16 | // function to read json
17 | func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data any) error {
18 | maxBytes := 1048576 // 1 MB
19 |
20 | // limiting the size of incoming request bodies
21 | r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
22 |
23 | dec := json.NewDecoder(r.Body)
24 | err := dec.Decode(data)
25 | if err != nil {
26 | return err
27 | }
28 |
29 | // decode a JSON value into an empty struct{}.
30 | err = dec.Decode(&struct{}{})
31 | // io.EOF is an error value indicating the end of the input source has been reached.
32 | // If err is not equal to io.EOF, it means that the decoding process did not reach the end of the input source, and there are additional JSON values remaining.
33 | if err != io.EOF {
34 | return errors.New("body must have only a single json value")
35 | }
36 |
37 | return nil
38 | }
39 |
40 | // function to write json
41 | // The ellipsis (...) before http.Header indicates that it can accept a variable number of arguments, allowing you to pass or store multiple http.Header values.
42 | func (app *Config) writeJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
43 | out, err := json.Marshal(data)
44 | if err != nil {
45 | return err
46 | }
47 |
48 | if len(headers) > 0 {
49 | for key, value := range headers[0] {
50 | w.Header()[key] = value
51 | }
52 | }
53 |
54 | w.Header().Set("Content-Type", "application/json")
55 | w.WriteHeader(status)
56 |
57 | _, err = w.Write(out)
58 | if err != nil {
59 | return err
60 | }
61 |
62 | return nil
63 | }
64 |
65 | // function to write error message as json
66 | func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
67 | statusCode := http.StatusBadRequest
68 |
69 | if len(status) > 0 {
70 | statusCode = status[0]
71 | }
72 |
73 | var payload jsonResponse
74 | payload.Error = true
75 | payload.Message = err.Error()
76 |
77 | return app.writeJSON(w, statusCode, payload)
78 | }
79 |
--------------------------------------------------------------------------------
/broker-service/cmd/api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "math"
7 | "net/http"
8 | "os"
9 | "time"
10 |
11 | amqp "github.com/rabbitmq/amqp091-go"
12 | )
13 |
14 | const webPort = "8080" // listen on this port inside docker
15 |
16 | // Receiver that we use for the application
17 | type Config struct {
18 | Rabbit *amqp.Connection
19 | }
20 |
21 | func main() {
22 | // try to connect to rabbitmq
23 | rabbitConn, err := connect()
24 | if err != nil {
25 | log.Println(err)
26 | os.Exit(1)
27 | }
28 | defer rabbitConn.Close()
29 |
30 | app := Config{
31 | Rabbit: rabbitConn,
32 | }
33 |
34 | log.Printf("Starting broker service on port %s \n", webPort)
35 |
36 | // define http server
37 | srv := &http.Server{
38 | Addr: fmt.Sprintf(":%s", webPort),
39 | Handler: app.routes(),
40 | }
41 |
42 | // start the server
43 | err = srv.ListenAndServe()
44 | if err != nil {
45 | log.Panic(err)
46 | }
47 |
48 | }
49 |
50 | func connect() (*amqp.Connection, error) {
51 | var counts int64
52 | var backoff = 1 * time.Second
53 | var connection *amqp.Connection
54 |
55 | // don't continue until rabbit is ready
56 | for {
57 | // rabbitmq matches our service in docker compose
58 | c, err := amqp.Dial("amqp://guest:guest@rabbitmq")
59 | if err != nil {
60 | fmt.Println("RabbitMQ not yet ready...")
61 | counts++
62 | } else {
63 | log.Println("Connected to RabbitMQ")
64 | connection = c
65 | break
66 | }
67 |
68 | // If we didn't connect :
69 |
70 | // if we didn't connect after 5 tries
71 | if counts > 5 {
72 | fmt.Println(err)
73 | return nil, err
74 | }
75 |
76 | // if we haven't tried atleast 5 times ,then call backoff
77 | backoff = time.Duration(math.Pow(float64(counts), 2)) * time.Second
78 | log.Println("backing off....")
79 | // suspends the execution of the loop for the calculated duration.
80 | time.Sleep(backoff)
81 | continue
82 | }
83 |
84 | return connection, nil
85 | }
86 |
--------------------------------------------------------------------------------
/broker-service/cmd/api/routes.go:
--------------------------------------------------------------------------------
1 | // servemux (also known as a router) stores a mapping between the predefined URL paths for your application and the corresponding handlers. Usually you have one servemux for your application containing all your routes.
2 |
3 | // Handlers are responsible for carrying out your application logic and writing response headers and bodies.
4 |
5 | // http.ServeMux is An HTTP request multiplexer, often called a router, is responsible for routing incoming HTTP requests to the appropriate handler functions based on the request's URL or other criteria.
6 |
7 | // http.DefaultServeMux is the default instance of http.ServeMux created by Go's net/http package. When you register your handler functions using functions like http.HandleFunc without explicitly specifying a custom http.ServeMux, they are automatically registered with http.DefaultServeMux.
8 |
9 | package main
10 |
11 | import (
12 | "net/http"
13 |
14 | "github.com/go-chi/chi/v5"
15 | "github.com/go-chi/chi/v5/middleware"
16 | "github.com/go-chi/cors"
17 | )
18 |
19 | func (app *Config) routes() http.Handler {
20 | mux := chi.NewRouter()
21 |
22 | // specify who is allowed to connect
23 | mux.Use(cors.Handler(cors.Options{
24 | AllowedOrigins: []string{"https://*", "http://*"},
25 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
26 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
27 | ExposedHeaders: []string{"Link"},
28 | AllowCredentials: true,
29 | MaxAge: 300,
30 | }))
31 |
32 | mux.Use(middleware.Heartbeat("/ping"))
33 |
34 | mux.Post("/", app.Broker)
35 |
36 | mux.Post("/log-grpc", app.LogViaGRPC)
37 |
38 | // single point of entry to broker service
39 | mux.Post("/handle", app.HandleSubmission)
40 |
41 | return mux
42 | }
43 |
--------------------------------------------------------------------------------
/broker-service/event/emitter.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | amqp "github.com/rabbitmq/amqp091-go"
5 | "log"
6 | )
7 |
8 | // push events onto the queue
9 | type Emitter struct {
10 | connection *amqp.Connection
11 | }
12 |
13 | func (e *Emitter) setup() error {
14 |
15 | channel, err := e.connection.Channel()
16 | if err != nil {
17 | return err
18 | }
19 |
20 | defer channel.Close()
21 | return declareExchange(channel)
22 | }
23 |
24 | func (e *Emitter) Push(event string, severity string) error {
25 | channel, err := e.connection.Channel()
26 | if err != nil {
27 | return err
28 | }
29 | defer channel.Close()
30 |
31 | log.Println("Publishing to channel....")
32 | err = channel.Publish(
33 | "logs_topic",
34 | severity,
35 | false,
36 | false,
37 | amqp.Publishing{
38 | ContentType: "text/plain",
39 | Body: []byte(event),
40 | },
41 | )
42 | if err != nil {
43 | return err
44 | }
45 |
46 | return nil
47 | }
48 |
49 | func NewEventEmitter(conn *amqp.Connection) (Emitter, error) {
50 | emitter := Emitter{
51 | connection: conn,
52 | }
53 |
54 | err := emitter.setup()
55 | if err != nil {
56 | return Emitter{}, err
57 | }
58 |
59 | return emitter, nil
60 | }
61 |
--------------------------------------------------------------------------------
/broker-service/event/event.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | amqp "github.com/rabbitmq/amqp091-go"
5 | )
6 |
7 | func declareExchange(ch *amqp.Channel) error {
8 | return ch.ExchangeDeclare(
9 | "logs_topic", // name of the exchange
10 | "topic", // type
11 | true, //durable
12 | false, // autodeleted
13 | false, // is this an exchange that's just used internally
14 | false, // no-wait?
15 | nil, // arguments?
16 | )
17 | }
18 |
19 | func declareRandomQueue(ch *amqp.Channel) (amqp.Queue, error) {
20 | return ch.QueueDeclare( // declaring a queue with these attributes
21 | "", // name?
22 | false, // durable?
23 | false, // delete when unused?
24 | true, // exclusive?
25 | false, // no-wait?
26 | nil, //arguments
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/broker-service/go.mod:
--------------------------------------------------------------------------------
1 | module broker
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/go-chi/chi/v5 v5.0.8 // indirect
7 | github.com/go-chi/cors v1.2.1 // indirect
8 | github.com/golang/protobuf v1.5.3 // indirect
9 | github.com/rabbitmq/amqp091-go v1.8.1 // indirect
10 | golang.org/x/net v0.9.0 // indirect
11 | golang.org/x/sys v0.7.0 // indirect
12 | golang.org/x/text v0.9.0 // indirect
13 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
14 | google.golang.org/grpc v1.56.2 // indirect
15 | google.golang.org/protobuf v1.30.0 // indirect
16 | )
17 |
--------------------------------------------------------------------------------
/broker-service/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
4 | github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
5 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
6 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
7 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
8 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
9 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
10 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
11 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
12 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
13 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15 | github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA=
16 | github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
17 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
18 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
19 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
20 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
21 | go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
22 | golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
23 | golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
24 | golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
25 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
26 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
27 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
28 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
29 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
30 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
31 | google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
32 | google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
33 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
34 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
35 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
36 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
37 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
38 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
39 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
40 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
41 |
--------------------------------------------------------------------------------
/broker-service/logs/logs.pb.go:
--------------------------------------------------------------------------------
1 | // This file is used to determine what kind of source code should be produced.
2 |
3 | // .proto file is used to define the structure and format of the data that will be exchanged between different systems or services.
4 |
5 | //Once the .proto file is defined, it can be compiled into language-specific code (e.g., Go, Java, Python) using a protocol buffer compiler. The generated code provides convenient APIs to work with the defined messages and services in your programming language of choice. The generated code provides functions and methods to easily create, read, and send data according to the structure defined in the .proto file.
6 |
7 | // In summary, a .proto file is used to define the structure of data that will be exchanged between programs. It acts as a contract or agreement between programs, ensuring they understand each other's data format. The file is used to generate code in different languages, making it easier to work with the defined data structures and enabling communication between programs written in different languages.
8 |
9 | // Code generated by protoc-gen-go. DO NOT EDIT.
10 | // versions:
11 | // protoc-gen-go v1.27.1
12 | // protoc v3.12.4
13 | // source: logs.proto
14 |
15 | package logs
16 |
17 | import (
18 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
19 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
20 | reflect "reflect"
21 | sync "sync"
22 | )
23 |
24 | const (
25 | // Verify that this generated code is sufficiently up-to-date.
26 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
27 | // Verify that runtime/protoimpl is sufficiently up-to-date.
28 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
29 | )
30 |
31 | // kinds of information that will be passed around (information we pass everytime we sends something to logger microservice)
32 | type Log struct {
33 | state protoimpl.MessageState
34 | sizeCache protoimpl.SizeCache
35 | unknownFields protoimpl.UnknownFields
36 |
37 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
38 | Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
39 | }
40 |
41 | func (x *Log) Reset() {
42 | *x = Log{}
43 | if protoimpl.UnsafeEnabled {
44 | mi := &file_logs_proto_msgTypes[0]
45 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
46 | ms.StoreMessageInfo(mi)
47 | }
48 | }
49 |
50 | func (x *Log) String() string {
51 | return protoimpl.X.MessageStringOf(x)
52 | }
53 |
54 | func (*Log) ProtoMessage() {}
55 |
56 | func (x *Log) ProtoReflect() protoreflect.Message {
57 | mi := &file_logs_proto_msgTypes[0]
58 | if protoimpl.UnsafeEnabled && x != nil {
59 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
60 | if ms.LoadMessageInfo() == nil {
61 | ms.StoreMessageInfo(mi)
62 | }
63 | return ms
64 | }
65 | return mi.MessageOf(x)
66 | }
67 |
68 | // Deprecated: Use Log.ProtoReflect.Descriptor instead.
69 | func (*Log) Descriptor() ([]byte, []int) {
70 | return file_logs_proto_rawDescGZIP(), []int{0}
71 | }
72 |
73 | func (x *Log) GetName() string {
74 | if x != nil {
75 | return x.Name
76 | }
77 | return ""
78 | }
79 |
80 | func (x *Log) GetData() string {
81 | if x != nil {
82 | return x.Data
83 | }
84 | return ""
85 | }
86 |
87 | // LogRequest is the request to perform a log operation
88 | type LogRequest struct {
89 | state protoimpl.MessageState
90 | sizeCache protoimpl.SizeCache
91 | unknownFields protoimpl.UnknownFields
92 |
93 | LogEntry *Log `protobuf:"bytes,1,opt,name=logEntry,proto3" json:"logEntry,omitempty"`
94 | }
95 |
96 | func (x *LogRequest) Reset() {
97 | *x = LogRequest{}
98 | if protoimpl.UnsafeEnabled {
99 | mi := &file_logs_proto_msgTypes[1]
100 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
101 | ms.StoreMessageInfo(mi)
102 | }
103 | }
104 |
105 | func (x *LogRequest) String() string {
106 | return protoimpl.X.MessageStringOf(x)
107 | }
108 |
109 | func (*LogRequest) ProtoMessage() {}
110 |
111 | func (x *LogRequest) ProtoReflect() protoreflect.Message {
112 | mi := &file_logs_proto_msgTypes[1]
113 | if protoimpl.UnsafeEnabled && x != nil {
114 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
115 | if ms.LoadMessageInfo() == nil {
116 | ms.StoreMessageInfo(mi)
117 | }
118 | return ms
119 | }
120 | return mi.MessageOf(x)
121 | }
122 |
123 | // Deprecated: Use LogRequest.ProtoReflect.Descriptor instead.
124 | func (*LogRequest) Descriptor() ([]byte, []int) {
125 | return file_logs_proto_rawDescGZIP(), []int{1}
126 | }
127 |
128 | func (x *LogRequest) GetLogEntry() *Log {
129 | if x != nil {
130 | return x.LogEntry
131 | }
132 | return nil
133 | }
134 |
135 | // what we send back in response to LogRequest
136 | type LogResponse struct {
137 | state protoimpl.MessageState
138 | sizeCache protoimpl.SizeCache
139 | unknownFields protoimpl.UnknownFields
140 |
141 | Result string `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"`
142 | }
143 |
144 | func (x *LogResponse) Reset() {
145 | *x = LogResponse{}
146 | if protoimpl.UnsafeEnabled {
147 | mi := &file_logs_proto_msgTypes[2]
148 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
149 | ms.StoreMessageInfo(mi)
150 | }
151 | }
152 |
153 | func (x *LogResponse) String() string {
154 | return protoimpl.X.MessageStringOf(x)
155 | }
156 |
157 | func (*LogResponse) ProtoMessage() {}
158 |
159 | func (x *LogResponse) ProtoReflect() protoreflect.Message {
160 | mi := &file_logs_proto_msgTypes[2]
161 | if protoimpl.UnsafeEnabled && x != nil {
162 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
163 | if ms.LoadMessageInfo() == nil {
164 | ms.StoreMessageInfo(mi)
165 | }
166 | return ms
167 | }
168 | return mi.MessageOf(x)
169 | }
170 |
171 | // Deprecated: Use LogResponse.ProtoReflect.Descriptor instead.
172 | func (*LogResponse) Descriptor() ([]byte, []int) {
173 | return file_logs_proto_rawDescGZIP(), []int{2}
174 | }
175 |
176 | func (x *LogResponse) GetResult() string {
177 | if x != nil {
178 | return x.Result
179 | }
180 | return ""
181 | }
182 |
183 | var File_logs_proto protoreflect.FileDescriptor
184 |
185 | var file_logs_proto_rawDesc = []byte{
186 | 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6c, 0x6f,
187 | 0x67, 0x73, 0x22, 0x2d, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
188 | 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a,
189 | 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74,
190 | 0x61, 0x22, 0x33, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
191 | 0x25, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
192 | 0x0b, 0x32, 0x09, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x08, 0x6c, 0x6f,
193 | 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x25, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73,
194 | 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18,
195 | 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32, 0x3d, 0x0a,
196 | 0x0a, 0x4c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x08, 0x57,
197 | 0x72, 0x69, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x12, 0x10, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x4c,
198 | 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x6c, 0x6f, 0x67, 0x73,
199 | 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x07, 0x5a, 0x05,
200 | 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
201 | }
202 |
203 | var (
204 | file_logs_proto_rawDescOnce sync.Once
205 | file_logs_proto_rawDescData = file_logs_proto_rawDesc
206 | )
207 |
208 | func file_logs_proto_rawDescGZIP() []byte {
209 | file_logs_proto_rawDescOnce.Do(func() {
210 | file_logs_proto_rawDescData = protoimpl.X.CompressGZIP(file_logs_proto_rawDescData)
211 | })
212 | return file_logs_proto_rawDescData
213 | }
214 |
215 | var file_logs_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
216 | var file_logs_proto_goTypes = []interface{}{
217 | (*Log)(nil), // 0: logs.Log
218 | (*LogRequest)(nil), // 1: logs.LogRequest
219 | (*LogResponse)(nil), // 2: logs.LogResponse
220 | }
221 | var file_logs_proto_depIdxs = []int32{
222 | 0, // 0: logs.LogRequest.logEntry:type_name -> logs.Log
223 | 1, // 1: logs.LogService.WriteLog:input_type -> logs.LogRequest
224 | 2, // 2: logs.LogService.WriteLog:output_type -> logs.LogResponse
225 | 2, // [2:3] is the sub-list for method output_type
226 | 1, // [1:2] is the sub-list for method input_type
227 | 1, // [1:1] is the sub-list for extension type_name
228 | 1, // [1:1] is the sub-list for extension extendee
229 | 0, // [0:1] is the sub-list for field type_name
230 | }
231 |
232 | func init() { file_logs_proto_init() }
233 | func file_logs_proto_init() {
234 | if File_logs_proto != nil {
235 | return
236 | }
237 | if !protoimpl.UnsafeEnabled {
238 | file_logs_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
239 | switch v := v.(*Log); i {
240 | case 0:
241 | return &v.state
242 | case 1:
243 | return &v.sizeCache
244 | case 2:
245 | return &v.unknownFields
246 | default:
247 | return nil
248 | }
249 | }
250 | file_logs_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
251 | switch v := v.(*LogRequest); i {
252 | case 0:
253 | return &v.state
254 | case 1:
255 | return &v.sizeCache
256 | case 2:
257 | return &v.unknownFields
258 | default:
259 | return nil
260 | }
261 | }
262 | file_logs_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
263 | switch v := v.(*LogResponse); i {
264 | case 0:
265 | return &v.state
266 | case 1:
267 | return &v.sizeCache
268 | case 2:
269 | return &v.unknownFields
270 | default:
271 | return nil
272 | }
273 | }
274 | }
275 | type x struct{}
276 | out := protoimpl.TypeBuilder{
277 | File: protoimpl.DescBuilder{
278 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
279 | RawDescriptor: file_logs_proto_rawDesc,
280 | NumEnums: 0,
281 | NumMessages: 3,
282 | NumExtensions: 0,
283 | NumServices: 1,
284 | },
285 | GoTypes: file_logs_proto_goTypes,
286 | DependencyIndexes: file_logs_proto_depIdxs,
287 | MessageInfos: file_logs_proto_msgTypes,
288 | }.Build()
289 | File_logs_proto = out.File
290 | file_logs_proto_rawDesc = nil
291 | file_logs_proto_goTypes = nil
292 | file_logs_proto_depIdxs = nil
293 | }
294 |
--------------------------------------------------------------------------------
/broker-service/logs/logs.proto:
--------------------------------------------------------------------------------
1 | // This file is used to determine what kind of source code should be produced.
2 |
3 | // .proto file is used to define the structure and format of the data that will be exchanged between different systems or services.
4 |
5 | //Once the .proto file is defined, it can be compiled into language-specific code (e.g., Go, Java, Python) using a protocol buffer compiler. The generated code provides convenient APIs to work with the defined messages and services in your programming language of choice. The generated code provides functions and methods to easily create, read, and send data according to the structure defined in the .proto file.
6 |
7 | // In summary, a .proto file is used to define the structure of data that will be exchanged between programs. It acts as a contract or agreement between programs, ensuring they understand each other's data format. The file is used to generate code in different languages, making it easier to work with the defined data structures and enabling communication between programs written in different languages.
8 |
9 | syntax = "proto3";
10 |
11 | package logs;
12 |
13 | option go_package = "/logs";
14 |
15 | // kinds of information that will be passed around (information we pass everytime we sends something to logger microservice)
16 | message Log{
17 | string name = 1;
18 | string data = 2;
19 | }
20 |
21 | // LogRequest is the request to perform a log operation
22 | message LogRequest {
23 | Log logEntry = 1;
24 | }
25 |
26 | // what we send back in response to LogRequest
27 | message LogResponse {
28 | string result = 1;
29 |
30 | }
31 |
32 | // define the services
33 | service LogService {
34 | // define the name of the function that this gRPC system is going to have
35 | rpc WriteLog(LogRequest) returns (LogResponse);
36 |
37 | }
38 |
39 | // We run this command using the Protocol Buffers compiler (protoc) to generate Go code from the logs.proto file
40 | // protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative logs.proto
--------------------------------------------------------------------------------
/broker-service/logs/logs_grpc.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2 | // versions:
3 | // - protoc-gen-go-grpc v1.2.0
4 | // - protoc v3.12.4
5 | // source: logs.proto
6 |
7 | package logs
8 |
9 | import (
10 | context "context"
11 | grpc "google.golang.org/grpc"
12 | codes "google.golang.org/grpc/codes"
13 | status "google.golang.org/grpc/status"
14 | )
15 |
16 | // This is a compile-time assertion to ensure that this generated file
17 | // is compatible with the grpc package it is being compiled against.
18 | // Requires gRPC-Go v1.32.0 or later.
19 | const _ = grpc.SupportPackageIsVersion7
20 |
21 | // LogServiceClient is the client API for LogService service.
22 | //
23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
24 | type LogServiceClient interface {
25 | // define the name of the function that this gRPC system is going to have
26 | WriteLog(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error)
27 | }
28 |
29 | type logServiceClient struct {
30 | cc grpc.ClientConnInterface
31 | }
32 |
33 | func NewLogServiceClient(cc grpc.ClientConnInterface) LogServiceClient {
34 | return &logServiceClient{cc}
35 | }
36 |
37 | func (c *logServiceClient) WriteLog(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error) {
38 | out := new(LogResponse)
39 | err := c.cc.Invoke(ctx, "/logs.LogService/WriteLog", in, out, opts...)
40 | if err != nil {
41 | return nil, err
42 | }
43 | return out, nil
44 | }
45 |
46 | // LogServiceServer is the server API for LogService service.
47 | // All implementations must embed UnimplementedLogServiceServer
48 | // for forward compatibility
49 | type LogServiceServer interface {
50 | // define the name of the function that this gRPC system is going to have
51 | WriteLog(context.Context, *LogRequest) (*LogResponse, error)
52 | mustEmbedUnimplementedLogServiceServer()
53 | }
54 |
55 | // UnimplementedLogServiceServer must be embedded to have forward compatible implementations.
56 | type UnimplementedLogServiceServer struct {
57 | }
58 |
59 | func (UnimplementedLogServiceServer) WriteLog(context.Context, *LogRequest) (*LogResponse, error) {
60 | return nil, status.Errorf(codes.Unimplemented, "method WriteLog not implemented")
61 | }
62 | func (UnimplementedLogServiceServer) mustEmbedUnimplementedLogServiceServer() {}
63 |
64 | // UnsafeLogServiceServer may be embedded to opt out of forward compatibility for this service.
65 | // Use of this interface is not recommended, as added methods to LogServiceServer will
66 | // result in compilation errors.
67 | type UnsafeLogServiceServer interface {
68 | mustEmbedUnimplementedLogServiceServer()
69 | }
70 |
71 | func RegisterLogServiceServer(s grpc.ServiceRegistrar, srv LogServiceServer) {
72 | s.RegisterService(&LogService_ServiceDesc, srv)
73 | }
74 |
75 | func _LogService_WriteLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
76 | in := new(LogRequest)
77 | if err := dec(in); err != nil {
78 | return nil, err
79 | }
80 | if interceptor == nil {
81 | return srv.(LogServiceServer).WriteLog(ctx, in)
82 | }
83 | info := &grpc.UnaryServerInfo{
84 | Server: srv,
85 | FullMethod: "/logs.LogService/WriteLog",
86 | }
87 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
88 | return srv.(LogServiceServer).WriteLog(ctx, req.(*LogRequest))
89 | }
90 | return interceptor(ctx, in, info, handler)
91 | }
92 |
93 | // LogService_ServiceDesc is the grpc.ServiceDesc for LogService service.
94 | // It's only intended for direct use with grpc.RegisterService,
95 | // and not to be introspected or modified (even as a copy)
96 | var LogService_ServiceDesc = grpc.ServiceDesc{
97 | ServiceName: "logs.LogService",
98 | HandlerType: (*LogServiceServer)(nil),
99 | Methods: []grpc.MethodDesc{
100 | {
101 | MethodName: "WriteLog",
102 | Handler: _LogService_WriteLog_Handler,
103 | },
104 | },
105 | Streams: []grpc.StreamDesc{},
106 | Metadata: "logs.proto",
107 | }
108 |
--------------------------------------------------------------------------------
/demo.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayushthe1/go-micro/779757cab45e468c6ef663631916047495783e51/demo.webm
--------------------------------------------------------------------------------
/front-end/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayushthe1/go-micro/779757cab45e468c6ef663631916047495783e51/front-end/.DS_Store
--------------------------------------------------------------------------------
/front-end/cmd/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayushthe1/go-micro/779757cab45e468c6ef663631916047495783e51/front-end/cmd/.DS_Store
--------------------------------------------------------------------------------
/front-end/cmd/web/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "embed"
5 | "fmt"
6 | "html/template"
7 | "log"
8 | "net/http"
9 | "os"
10 | )
11 |
12 | func main() {
13 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
14 | render(w, "test.page.gohtml")
15 | })
16 |
17 | fmt.Println("Starting front end service on port 8082")
18 | err := http.ListenAndServe(":8082", nil)
19 | if err != nil {
20 | log.Panic(err)
21 | }
22 | }
23 |
24 | //go:embed templates
25 | var templateFS embed.FS
26 |
27 | // http.ResponseWriter which allows us to write the HTTP response,
28 | // and t, a string that represents the template to render.
29 | func render(w http.ResponseWriter, t string) {
30 |
31 | partials := []string{
32 | "templates/base.layout.gohtml",
33 | "templates/header.partial.gohtml",
34 | "templates/footer.partial.gohtml",
35 | }
36 |
37 | var templateSlice []string
38 | templateSlice = append(templateSlice, fmt.Sprintf("templates/%s", t))
39 |
40 | for _, x := range partials {
41 | templateSlice = append(templateSlice, x)
42 | }
43 |
44 | tmpl, err := template.ParseFS(templateFS, templateSlice...)
45 | if err != nil {
46 | http.Error(w, err.Error(), http.StatusInternalServerError)
47 | return // exit the render function
48 | }
49 | fmt.Println(tmpl)
50 |
51 | var data struct {
52 | BrokerURL string
53 | }
54 |
55 | data.BrokerURL = os.Getenv("BROKER_URL")
56 | // data.BrokerURL = "http://localhost:8080" // for connecting to broker through load balancer (cmd minikube tunnel)
57 |
58 | // use the Execute method to render the template
59 | if err := tmpl.Execute(w, data); err != nil {
60 | http.Error(w, err.Error(), http.StatusInternalServerError)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/front-end/cmd/web/templates/base.layout.gohtml:
--------------------------------------------------------------------------------
1 | {{define "base" }}
2 |
3 |
4 |
5 | {{template "header" .}}
6 |
7 |
Nothing sent yet...
25 | Nothing received yet...
31 | {{.message}}
13 | 14 | 15 | 16 | 17 | 18 | {{end}} -------------------------------------------------------------------------------- /mail-service/templates/mail/mail.plain.gohtml: -------------------------------------------------------------------------------- 1 | {{define "body"}} 2 | 3 | {{.message}} 4 | 5 | {{end}} -------------------------------------------------------------------------------- /project/Caddyfile: -------------------------------------------------------------------------------- 1 | { 2 | email you@gmail.com 3 | } 4 | 5 | (static) { 6 | @static { 7 | file 8 | path *.ico *.css *.js *.gif *.jpg *.jpeg *.png *.svg *.woff *.json 9 | } 10 | header @static Cache-Control max-age=5184000 11 | } 12 | 13 | (security) { 14 | header { 15 | # enable HSTS 16 | Strict-Transport-Security max-age=31536000; 17 | # disable clients from sniffing the media type 18 | X-Content-Type-Options nosniff 19 | # keep referrer data off of HTTP connections 20 | Referrer-Policy no-referrer-when-downgrade 21 | } 22 | } 23 | 24 | localhost:80 { 25 | encode zstd gzip 26 | import static 27 | 28 | reverse_proxy http://front-end:8082 29 | } 30 | 31 | # Any request to port 80 for the server named backend ,send that to broker-service 32 | backend:80 { 33 | # uses a reverse proxy to forward those requests to a backend server running inside a Docker container with the address http://broker-service:8080. 34 | reverse_proxy http://broker-service:8080 # this is address of broker-service listening inside docker 35 | } 36 | 37 | # The key difference between a reverse proxy and a forward proxy is that a forward proxy enables computers isolated on a private network to connect to the public internet, while a reverse proxy enables computers on the internet to access a private subnet. 38 | 39 | # A forward proxy accepts connections from computers on a private network and forwards those requests to the public internet. It is the single point of exit for subnet users who want to access resources outside of their private network. 40 | 41 | # The reverse proxy acts as a single point of entry for external systems to access resources on a private subnet. -------------------------------------------------------------------------------- /project/Makefile: -------------------------------------------------------------------------------- 1 | FRONT_END_BINARY=frontApp 2 | BROKER_BINARY=brokerApp 3 | AUTH_BINARY=authApp 4 | LOGGER_BINARY=loggerServiceApp 5 | MAILER_BINARY=mailApp 6 | LISTENER_BINARY=listenerApp 7 | FRONT_BINARY=frontEndApp 8 | 9 | 10 | deploy_swarm: 11 | docker swarm init 12 | @echo "Deploying project on docker swarm...." 13 | docker stack deploy -c swarm.yml myMicroApp 14 | 15 | undeploy_swarm: 16 | @echo "Leaving the docker swarm...." 17 | docker swarm leave --force 18 | 19 | ## up: starts all containers in the background without forcing build 20 | up: 21 | @echo "Starting Docker images..." 22 | docker-compose up -d 23 | @echo "Docker images started!" 24 | 25 | ## up_build: stops docker-compose (if running), builds all projects and starts docker compose 26 | up_build: build_broker build_auth build_logger build_mail build_listener ## The make targets up_build depends on will be executed first 27 | @echo "Stopping docker images (if running...)" 28 | docker-compose down 29 | @echo "Building (when required) and starting docker images..." 30 | ## This command starts the Docker containers defined in the docker-compose.yml file. The --build flag ensures that the containers are built if any changes are detected. The -d flag runs the containers in the background (detached mode). 31 | docker-compose up --build -d 32 | @echo "Docker images built and started!" 33 | 34 | ## down: stop docker compose 35 | down: 36 | @echo "Stopping docker compose..." 37 | docker-compose down 38 | @echo "Done!" 39 | 40 | ## build_broker: builds the broker binary as a linux executable 41 | build_broker: 42 | @echo "Building broker binary..." 43 | cd ../broker-service && env GOOS=linux CGO_ENABLED=0 go build -o ${BROKER_BINARY} ./cmd/api 44 | @echo "Done!" 45 | 46 | ## build_front_linux: builds the front end binary as a linux executable 47 | build_front_linux: 48 | @echo "Building front end linux binary..." 49 | cd ../front-end && env GOOS=linux CGO_ENABLED=0 go build -o ${FRONT_BINARY} ./cmd/web 50 | @echo "Done!" 51 | 52 | ## build_logger: builds the logger binary as a linux executable 53 | build_logger: 54 | @echo "Building logger binary..." 55 | cd ../logger-service && env GOOS=linux CGO_ENABLED=0 go build -o ${LOGGER_BINARY} ./cmd/api 56 | @echo "Done!" 57 | 58 | ## build_listener: builds the listener binary as a linux executable 59 | build_listener: 60 | @echo "Building listener binary..." 61 | cd ../listener-service && env GOOS=linux CGO_ENABLED=0 go build -o ${LISTENER_BINARY} . 62 | @echo "Done!" 63 | 64 | ## build_auth: builds the auth binary as a linux executable 65 | build_auth: 66 | @echo "Building auth binary..." 67 | cd ../authentication-service && env GOOS=linux CGO_ENABLED=0 go build -o ${AUTH_BINARY} ./cmd/api 68 | @echo "Done!" 69 | 70 | ## build_front: builds the frone end binary 71 | build_front: 72 | @echo "Building front end binary..." 73 | cd ../front-end && env CGO_ENABLED=0 go build -o ${FRONT_END_BINARY} ./cmd/web 74 | @echo "Done!" 75 | 76 | ## build_mailer: builds the mailer-service binary 77 | build_mail: 78 | @echo "Building mail binary..." 79 | cd ../mail-service && env CGO_ENABLED=0 go build -o ${MAILER_BINARY} ./cmd/api 80 | @echo "Done!" 81 | 82 | ## start: starts the front end 83 | start: build_front 84 | @echo "Starting front end" 85 | cd ../front-end && ./${FRONT_END_BINARY} & 86 | 87 | ## stop: stop the front end 88 | stop: 89 | @echo "Stopping front end..." 90 | @-pkill -SIGTERM -f "./${FRONT_END_BINARY}" 91 | @echo "Stopped front end!" -------------------------------------------------------------------------------- /project/caddy.dockerfile: -------------------------------------------------------------------------------- 1 | FROM caddy:2.4.6-alpine 2 | 3 | COPY Caddyfile /etc/caddy/Caddyfile 4 | -------------------------------------------------------------------------------- /project/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | broker-service: 6 | # build section specifies how to build the container image for the service. 7 | build: 8 | # specifies the build context directory,where the build process should look for files to include in the container image. 9 | context: ./../broker-service 10 | # specifies the path to the Dockerfile that will be used to build the image 11 | dockerfile: ./../broker-service/broker-service.dockerfile 12 | restart: always 13 | ports: 14 | # map port 8080 on my localhost to port 8084 on the docker image 15 | # we should be able to hit the broker service on port 8080 from my front-end (or we can hit the broker-service on localhost:8080 on postman to check its response as all traffic on localhost:8080 is forwarded to port 8084 of container broker-service) 16 | # 8084 bcoz in broker-service/main.go ,we have configured broker service to listen on port 8084.So it will listen on port 8084 inside the container. 17 | - "8080:80" 18 | # The deploy section contains configuration options for deploying the service to a swarm cluster (Docker's native orchestration and clustering solution). 19 | deploy: 20 | mode: replicated 21 | # we can only have 1 replica in this case bcoz we can't listen to 2 docker images on port 8080 on localhost 22 | replicas: 1 23 | 24 | 25 | logger-service: 26 | build: 27 | context: ./../logger-service 28 | dockerfile: ./../logger-service/logger-service.dockerfile 29 | restart: always 30 | deploy: 31 | mode: replicated 32 | replicas: 1 33 | 34 | 35 | # Add the authentication service 36 | authentication-service: 37 | build: 38 | # The context field determines the directory path where the build context resides. The build context is the set of files and directories that are sent to the Docker daemon for building the Docker image of the service. It includes the Dockerfile and any files referenced by the Dockerfile during the build process. 39 | context: ./../authentication-service 40 | dockerfile: ./../authentication-service/authentication-service.dockerfile 41 | restart: always 42 | ports: 43 | # listen on port 8081 outside of docker, and on port 80 inside of docker. 44 | # Inside docker ,we can have multiple services listen on same port 45 | - "8081:80" 46 | deploy: 47 | mode: replicated 48 | replicas: 1 49 | environment: 50 | DSN: "host=postgres port=5432 user=postgres password=password dbname=users sslmode=disable timezone=UTC connect_timeout=5" 51 | 52 | # Add the mailer service 53 | mailer-service: 54 | build: 55 | context: ./../mail-service 56 | dockerfile: ./../mail-service/mail-service.dockerfile 57 | restart: always 58 | deploy: 59 | mode: replicated 60 | replicas: 1 61 | environment: 62 | MAIL_DOMAIN: localhost 63 | MAIL_HOST: mailhog 64 | MAIL_PORT: 1025 65 | MAIL_USERNAME: "" 66 | MAIL_PASSWORD: "" 67 | MAIL_ENCRYPTION: none 68 | FROM_NAME: "John Smith" 69 | FROM_ADDRESS: john.smith@example.com 70 | 71 | 72 | 73 | # Add a Postegres service 74 | postgres: 75 | image: 'postgres:14.2' 76 | ports: 77 | - "5432:5432" # map port 5444 on my local machine to 5432 on my docker container 78 | restart: always 79 | deploy: 80 | mode: replicated 81 | replicas: 1 82 | environment: 83 | POSTGRES_USER: postgres 84 | POSTGRES_PASSWORD: password 85 | POSTGRES_DB: users 86 | volumes: # volumes so that the data persists 87 | - ./db-data/postgres/:/var/lib/postgresql/data/ 88 | 89 | mongo: 90 | image: 'mongo:4.2.16-bionic' 91 | ports: 92 | - "27017:27017" 93 | environment: 94 | MONGO_INITDB_DATABASE: logs 95 | MONGO_INITDB_ROOT_USERNAME: admin 96 | MONGO_INITDB_ROOT_PASSWORD: password 97 | volumes: 98 | - ./db-data/mongo/:/data/db 99 | 100 | 101 | mailhog: 102 | image: 'mailhog/mailhog:latest' 103 | ports: 104 | - "1025:1025" 105 | - "8025:8025" 106 | 107 | # get the rabbitmq image from dockerhub 108 | rabbitmq: 109 | image: 'rabbitmq:3.9-alpine' 110 | ports: 111 | - "5672:5672" 112 | deploy: 113 | mode: replicated 114 | replicas: 1 115 | volumes: 116 | - ./db-data/rabbitmq/:/var/lib/rabbitmq/ 117 | 118 | 119 | listener-service: 120 | build: 121 | context: ./../listener-service 122 | dockerfile: ./../listener-service/listener-service.dockerfile 123 | deploy: 124 | mode: replicated 125 | replicas: 1 126 | 127 | -------------------------------------------------------------------------------- /project/ingress.yml: -------------------------------------------------------------------------------- 1 | # an Ingress is an API resource that serves as an entry point to our cluster from the external world. It allows you to expose HTTP and HTTPS routes to services running inside our Kubernetes cluster. In simple terms, Ingress enables external access to our cluster's services and provides a way to route incoming traffic to different services based on rules defined in the Ingress configuration. 2 | 3 | # After creating this file ,we have to edit the /etc/host file and add '127.0.0.1 front-end.info broker-service.info' . 4 | 5 | # By mapping front-end.info and broker-service.info to 127.0.0.1, any request made to these domain names from our local machine will be redirected back to the machine itself. In other words, any attempt to access front-end.info or broker-service.info will be directed to the local machine's network stack. /etc/hosts file acts as a local DNS resolver, and by adding these entries, you are effectively bypassing any public DNS resolution for these domain names 6 | 7 | apiVersion: networking.k8s.io/v1 8 | kind: Ingress 9 | metadata: 10 | name: my-ingress 11 | annotations: 12 | nginx.ingress.kubernetes.io/rewrite-target: /$1 13 | spec: 14 | rules: 15 | - host: front-end.info 16 | http: 17 | paths: 18 | - path: / 19 | pathType: Prefix 20 | backend: 21 | service: 22 | name: front-end # name of the pod to route to 23 | port: 24 | number: 8082 25 | - host: broker-service.info 26 | http: 27 | paths: 28 | - path: /(.*) 29 | pathType: Prefix 30 | backend: 31 | service: 32 | name: broker-service 33 | port: 34 | number: 8080 35 | -------------------------------------------------------------------------------- /project/k8s/authentication.yml: -------------------------------------------------------------------------------- 1 | # This YAML file can be used with the Kubernetes kubectl command-line tool to create the Deployment and Service in a Kubernetes cluster. Once applied, Kubernetes will ensure that the specified number of replicas (in this case, one) of the containerized application (specified by the Docker image) are running, and a stable endpoint is provided through the Service to access these replicas. The Service acts as a load balancer, distributing traffic among the pods managed by the Deployment, making the application highly available and scalable. 2 | # The host.minikube.internal hostname is used to access services running inside the Minikube cluster from your host machine. 3 | 4 | apiVersion: apps/v1 5 | kind: Deployment 6 | metadata: 7 | name: authentication-service 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: authentication-service 13 | template: 14 | metadata: 15 | labels: 16 | app: authentication-service 17 | spec: 18 | containers: 19 | - name: authentication-service 20 | image: "ayushthe1/authentication-service:2.0.0" 21 | resources: 22 | requests: 23 | memory: "64Mi" 24 | cpu: "250m" 25 | limits: 26 | memory: "128Mi" 27 | cpu: "500m" 28 | env: 29 | - name: DSN 30 | value: "host=host.minikube.internal port=5432 user=postgres password=password dbname=users sslmode=disable timezone=UTC connect_timeout=5" 31 | ports: 32 | - containerPort: 80 #This indicates that the container listens on port 80 33 | 34 | --- 35 | 36 | # A Service in Kubernetes provides a stable endpoint to access the pods managed by the Deployment. 37 | 38 | apiVersion: v1 39 | kind: Service 40 | metadata: 41 | name: authentication-service 42 | # The spec section defines the desired state for the Service 43 | spec: 44 | # The selector allows the Service to determine which pods to target. 45 | selector: 46 | # The Service will target pods with the label "app: authentication-service" (matching the Deployment's selector) 47 | app: authentication-service 48 | ports: 49 | - protocol: TCP 50 | name: main-port 51 | port: 80 # port number on which the Service should listen internally. It represents the port on which the Service is exposed internally within the cluster. 52 | targetPort: 80 # port number on which the backend Pod is listening. It represents the port on which the actual application or service is running inside the Pod. When a request is sent to the Service, it will forward the traffic to the backend Pods on this targetPort. 53 | 54 | 55 | # The `minikube tunnel` command creates a network tunnel to expose services of type LoadBalancer to your local machine. -------------------------------------------------------------------------------- /project/k8s/broker.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: broker-service 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: broker-service 10 | template: 11 | metadata: 12 | labels: 13 | app: broker-service 14 | spec: 15 | containers: 16 | - name: broker-service 17 | image: 'ayushthe1/broker-service:1.0.1' 18 | resources: 19 | limits: 20 | memory: "128Mi" 21 | cpu: "500m" 22 | ports: 23 | - containerPort: 8080 # This indicates that the container listens on port 8080. 24 | 25 | --- 26 | 27 | apiVersion: v1 28 | kind: Service 29 | metadata: 30 | name: broker-service 31 | spec: 32 | selector: 33 | app: broker-service 34 | ports: 35 | - protocol: TCP 36 | name: main-port 37 | port: 8080 # The port number exposed by the Service 38 | targetPort: 8080 # The port number to which traffic should be forwarded to the pods. 39 | 40 | -------------------------------------------------------------------------------- /project/k8s/front-end.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: front-end 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: front-end 10 | template: 11 | metadata: 12 | labels: 13 | app: front-end 14 | spec: 15 | containers: 16 | - name: front-end 17 | image: "ayushthe1/front-end:1.1.2" 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "250m" 22 | limits: 23 | memory: "128Mi" 24 | cpu: "500m" 25 | env: 26 | - name: BROKER_URL 27 | value: "http://broker-service.info" 28 | ports: 29 | - containerPort: 8082 30 | 31 | --- 32 | 33 | apiVersion: v1 34 | kind: Service 35 | metadata: 36 | name: front-end 37 | spec: 38 | selector: 39 | app: front-end 40 | ports: 41 | - protocol: TCP 42 | name: main-port 43 | port: 8082 44 | targetPort: 8082 -------------------------------------------------------------------------------- /project/k8s/listener.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: listener-service 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: listener-service 10 | template: 11 | metadata: 12 | labels: 13 | app: listener-service 14 | spec: 15 | containers: 16 | - name: listener-service 17 | image: "ayushthe1/listener-service:1.0.0" 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "250m" 22 | limits: 23 | memory: "128Mi" 24 | cpu: "500m" 25 | ports: 26 | - containerPort: 80 27 | 28 | --- 29 | 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | name: listener-service 34 | spec: 35 | selector: 36 | app: listener-service 37 | ports: 38 | - protocol: TCP 39 | name: web-port 40 | port: 80 41 | targetPort: 80 -------------------------------------------------------------------------------- /project/k8s/logger.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: logger-service 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: logger-service 10 | template: 11 | metadata: 12 | labels: 13 | app: logger-service 14 | spec: 15 | containers: 16 | - name: logger-service 17 | image: "ayushthe1/logger-service:1.0.0" 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "250m" 22 | limits: 23 | memory: "128Mi" 24 | cpu: "500m" 25 | ports: 26 | - containerPort: 80 # port we're posting to using JSON 27 | - containerPort: 5001 # port for RPC 28 | - containerPort: 50001 # port for GRPC 29 | 30 | --- 31 | 32 | apiVersion: v1 33 | kind: Service 34 | metadata: 35 | name: logger-service 36 | spec: 37 | selector: 38 | app: logger-service 39 | ports: 40 | - protocol: TCP 41 | name: web-port 42 | port: 80 43 | targetPort: 80 44 | - protocol: TCP 45 | name: rpc-port 46 | port: 5001 47 | targetPort: 5001 48 | - protocol: TCP 49 | name: grpc-port 50 | port: 50001 51 | targetPort: 50001 -------------------------------------------------------------------------------- /project/k8s/mail.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mailer-service # we use this as mailServiceURL in broker-service/handlers.go 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: mailer-service 9 | template: 10 | metadata: 11 | labels: 12 | app: mailer-service 13 | spec: 14 | containers: 15 | - name: mailer-service 16 | image: 'ayushthe1/mail-service:1.0.0' 17 | resources: 18 | limits: 19 | memory: "128Mi" 20 | cpu: "500m" 21 | env: 22 | - name: MAIL_DOMAIN 23 | value: "" 24 | - name: MAIL_HOST 25 | value: "localhost" 26 | - name: MAIL_PORT 27 | value: "1025" 28 | - name: MAIL_ENCRYPTION 29 | value: "none" 30 | - name: MAIL_USERNAME 31 | value: "" 32 | - name: MAIL_PASSWORD 33 | value: "" 34 | - name: FROM_NAME 35 | value: "John Smith" 36 | - name: FROM_ADDRESS 37 | value: "admin@example.com" 38 | ports: 39 | - containerPort: 80 40 | 41 | --- 42 | 43 | apiVersion: v1 44 | kind: Service 45 | metadata: 46 | name: mailer-service 47 | spec: 48 | selector: 49 | app: mailer-service 50 | ports: 51 | - protocol: TCP 52 | name: main-port 53 | port: 80 54 | targetPort: 80 55 | 56 | -------------------------------------------------------------------------------- /project/k8s/mailhog.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mailhog 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: mailhog 9 | template: 10 | metadata: 11 | labels: 12 | app: mailhog 13 | spec: 14 | containers: 15 | - name: mailhog 16 | image: 'mailhog/mailhog:latest' 17 | resources: 18 | limits: 19 | memory: "128Mi" 20 | cpu: "500m" 21 | ports: 22 | - containerPort: 1025 23 | - containerPort: 8025 24 | 25 | --- 26 | 27 | apiVersion: v1 28 | kind: Service 29 | metadata: 30 | name: mailhog 31 | spec: 32 | selector: 33 | app: mailhog 34 | ports: 35 | - protocol: TCP 36 | name: smtp-port 37 | port: 1025 38 | targetPort: 1025 39 | - protocol: TCP 40 | name: web-port # port for the web interface 41 | port: 8025 42 | targetPort: 8025 43 | 44 | -------------------------------------------------------------------------------- /project/k8s/mongo.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mongo 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: mongo 10 | template: 11 | metadata: 12 | labels: 13 | app: mongo 14 | spec: 15 | containers: 16 | - name: mongo 17 | image: "mongo:4.2.17-bionic" 18 | env: 19 | - name: MONGO_INITDB_DATABASE 20 | value: 'logs' 21 | - name: MONGO_INITDB_ROOT_USERNAME 22 | value: "admin" 23 | - name: MONGO_INITDB_ROOT_PASSWORD 24 | value: "password" 25 | ports: 26 | - containerPort: 27017 27 | 28 | --- 29 | # Define the service that's associated with this deployment 30 | 31 | apiVersion: v1 32 | kind: Service 33 | metadata: 34 | name: mongo 35 | spec: 36 | selector: 37 | app: mongo # This line specifies the label name: mongo, which matches the label used in the Deployment's selector. 38 | ports: 39 | - protocol: TCP 40 | name: main-port 41 | port: 27017 42 | targetPort: 27017 -------------------------------------------------------------------------------- /project/k8s/rabbit.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: rabbitmq 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: rabbitmq 10 | template: 11 | metadata: 12 | labels: 13 | app: rabbitmq 14 | spec: 15 | containers: 16 | - name: rabbitmq 17 | image: "rabbitmq:3.9-alpine" 18 | ports: 19 | - containerPort: 5672 20 | 21 | --- 22 | apiVersion: v1 23 | kind: Service 24 | metadata: 25 | # It's important to use rabbitmq here bcoz thats how we're referring to it in the actual code for our microservice. So we have to use the same name here that we used in the URLs we specified when we connect to RabbitMQ in our go code. 26 | name: rabbitmq 27 | spec: 28 | selector: 29 | app: rabbitmq 30 | ports: 31 | - protocol: TCP 32 | name: main-port 33 | port: 5672 34 | targetPort: 5672 35 | 36 | 37 | -------------------------------------------------------------------------------- /project/postgres.yml: -------------------------------------------------------------------------------- 1 | # This is a docker compose file .We will setup postgres outside of our cluster and not with other microservice.. 2 | 3 | # This Docker Compose file can be used with the docker-compose command-line tool to manage the PostgreSQL database service and its dependencies. By running docker-compose up, it will create and start the Postgres container with the specified configurations. The data will be persisted on the host machine due to the volume mount, ensuring that the data is available across container restarts and even if the entire application stack is removed and redeployed. 4 | 5 | version: '3' 6 | 7 | services: 8 | # Add a Postegres service 9 | postgres: 10 | image: 'postgres:14.2' 11 | ports: 12 | - "5432:5432" # map port 5432 on my local machine to 5432 on my docker container 13 | restart: always 14 | deploy: 15 | mode: replicated 16 | replicas: 1 17 | environment: 18 | POSTGRES_USER: postgres 19 | POSTGRES_PASSWORD: password 20 | POSTGRES_DB: users 21 | volumes: # volumes so that the data persists 22 | - ./db-data/postgres/:/var/lib/postgresql/data/ 23 | 24 | 25 | -------------------------------------------------------------------------------- /project/swarm.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | # We should name the services the same as we did in our docker-compose file, so that we don't have to change the urls we call for each of those services 5 | 6 | caddy: 7 | image: ayushthe1/micro-caddy:1.0.0 8 | deploy: 9 | mode: replicated 10 | replicas: 1 11 | ports: 12 | - '80:80' 13 | - '443:443' 14 | volumes: 15 | - caddy_data:/data 16 | - caddy_config:/config 17 | 18 | front-end: 19 | image: ayushthe1/front-end:1.1.2 20 | deploy: 21 | mode: replicated 22 | replicas: 1 23 | environment: 24 | # This evironment 25 | BROKER_URL: "http://backend" 26 | 27 | broker-service: 28 | image: ayushthe1/broker-service:1.0.1 29 | # ports: 30 | # # map port 8080 on my machine to port 80 in docker 31 | # - "8080:80" 32 | deploy: 33 | mode: replicated 34 | replicas: 1 35 | 36 | listener-service: 37 | image: ayushthe1/listener-service:1.0.0 38 | deploy: 39 | mode: replicated 40 | replicas: 1 41 | 42 | authentication-service: 43 | image: ayushthe1/authentication-service:1.0.0 44 | deploy: 45 | mode: replicated 46 | replicas: 1 47 | # add environment variables 48 | environment: 49 | DSN: "host=postgres port=5432 user=postgres password=password dbname=users sslmode=disable timezone=UTC connect_timeout=5" 50 | 51 | logger-service: 52 | image: ayushthe1/logger-service:1.0.0 53 | deploy: 54 | mode: replicated 55 | replicas: 1 56 | 57 | mailer-service: 58 | image: ayushthe1/mail-service:1.0.0 59 | deploy: 60 | mode: replicated 61 | replicas: 1 62 | environment: 63 | MAIL_DOMAIN: localhost 64 | MAIL_HOST: mailhog 65 | MAIL_PORT: 1025 66 | MAIL_USERNAME: "" 67 | MAIL_PASSWORD: "" 68 | MAIL_ENCRYPTION: none 69 | FROM_NAME: "John Smith" 70 | FROM_ADDRESS: john.smith@example.com 71 | 72 | 73 | rabbitmq: 74 | image: 'rabbitmq:3.9-alpine' 75 | deploy: 76 | # global means keep one instance of this service running on every node of the swarm 77 | mode: global 78 | 79 | 80 | 81 | mailhog: 82 | image: 'mailhog/mailhog:latest' 83 | ports: 84 | - '8025:8025' 85 | deploy: 86 | mode: global # we only want 1 instance of mailhog to be running 87 | 88 | mongo: 89 | image: 'mongo:4.2.17-bionic' 90 | ports: 91 | - '27017:27017' 92 | deploy: 93 | mode: global 94 | environment: 95 | MONGO_INITDB_DATABASE: logs 96 | MONGO_INITDB_ROOT_USERNAME: admin 97 | MONGO_INITDB_ROOT_PASSWORD: password 98 | volumes: 99 | # When the image comes up ,map the local directory in the db-data to the appropriate directory in the docker image 100 | - ./db-data/mongo/:/data/db 101 | 102 | 103 | postgres: 104 | image: 'postgres:14.2' 105 | ports: 106 | - "5432:5432" # map port 5444 on my local machine to 5432 on my docker container 107 | restart: always 108 | deploy: 109 | mode: replicated 110 | replicas: 1 111 | environment: 112 | POSTGRES_USER: postgres 113 | POSTGRES_PASSWORD: password 114 | POSTGRES_DB: users 115 | volumes: # volumes so that the data persists 116 | - ./db-data/postgres/:/var/lib/postgresql/data/ 117 | 118 | # This is telling the swarm where to find the data 119 | volumes: 120 | caddy_data: 121 | external: true 122 | caddy_config: 123 | --------------------------------------------------------------------------------