├── .gitignore
├── .idea
├── .gitignore
├── microservices-with-go.iml
├── modules.xml
└── vcs.xml
├── README.md
├── assets
└── architecture.png
├── authentication-service
├── authApp
├── authentication-service.dockerfile
├── cmd
│ └── api
│ │ ├── handlers.go
│ │ ├── handlers_test.go
│ │ ├── helpers.go
│ │ ├── main.go
│ │ ├── routes.go
│ │ ├── routes_test.go
│ │ └── setup_test.go
├── data
│ ├── models.go
│ ├── repository.go
│ └── test-models.go
├── go.mod
└── go.sum
├── broker-service
├── broker-service.dockerfile
├── brokerApp
├── cmd
│ └── api
│ │ ├── handlers.go
│ │ ├── helpers.go
│ │ ├── main.go
│ │ └── routes.go
├── event
│ ├── consumer.go
│ ├── emitter.go
│ └── event.go
├── go.mod
└── logs
│ ├── logs.pb.go
│ ├── logs.proto
│ └── logs_grpc.pb.go
├── front-end
├── cmd
│ └── web
│ │ ├── main.go
│ │ └── templates
│ │ ├── base.layout.gohtml
│ │ ├── header.partial.gohtml
│ │ └── test.page.gohtml
├── front-end.dockerfile
├── frontEndApp
└── go.mod
├── go.work
├── go.work.sum
├── k8s
├── authentication.yml
├── broker.yml
├── front-end.yml
├── listener.yml
├── logger.yml
├── mail.yml
├── mailhog.yml
├── mongo.yml
└── rabbit.yml
├── listener-service
├── 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
├── mailerApp
└── templates
│ ├── mail.html.gohtml
│ └── mail.plain.gohtml
├── migrations
└── users_migration.up.sql
└── project
├── Caddyfile
├── Makefile
├── caddy.dockerfile
├── docker-compose.yml
├── ingress.yml
├── postgres.yml
└── swarm.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | project/db-data/postgres/*
3 | front-end/frontApp
4 | authentication-service/authApp
5 | project/db-data/*
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/microservices-with-go.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Microservices with Go
2 |
3 | The project utilizes Go, Kubernetes, RabbitMQ, MongoDB, Postgres, and a microservices architecture. It focuses on efficient data handling and processing, with RabbitMQ for message queuing, MongoDB and Postgres for database management, orchestrated via Kubernetes. The implementation in Go supports fast and effective system development, ensuring scalability and robustness.
4 |
5 | 
6 |
7 |
--------------------------------------------------------------------------------
/assets/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Furkan-Gulsen/microservices-with-go/0f61805b11cc6137315bbfbbfaf1d95cd9b5a831/assets/architecture.png
--------------------------------------------------------------------------------
/authentication-service/authApp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Furkan-Gulsen/microservices-with-go/0f61805b11cc6137315bbfbbfaf1d95cd9b5a831/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 | "authentication/data"
5 | "bytes"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "net/http"
10 | )
11 |
12 | func (app *Config) Authenticate(w http.ResponseWriter, r *http.Request) {
13 | var requestPayload struct {
14 | Email string `json:"email"`
15 | Password string `json:"password"`
16 | }
17 |
18 | err := app.readJSON(w, r, &requestPayload)
19 | if err != nil {
20 | app.errorJSON(w, err, http.StatusBadRequest)
21 | return
22 | }
23 |
24 | user, err := app.Repo.GetByEmail(requestPayload.Email)
25 | if err != nil {
26 | app.errorJSON(w, errors.New("invalid credentials"), http.StatusBadRequest)
27 | return
28 | }
29 |
30 | valid, err := app.Repo.PasswordMatches(requestPayload.Password, *user)
31 | if err != nil || !valid {
32 | app.errorJSON(w, errors.New("invalid credentials"), http.StatusBadRequest)
33 | return
34 | }
35 |
36 | err = app.logRequest("authentication", fmt.Sprintf("%s logged in", user.Email))
37 | if err != nil {
38 | app.errorJSON(w, err)
39 | return
40 | }
41 |
42 | payload := jsonResponse{
43 | Error: false,
44 | Message: fmt.Sprintf("Logged in user %s", user.Email),
45 | Data: user,
46 | }
47 |
48 | app.writeJSON(w, http.StatusAccepted, payload)
49 | }
50 |
51 | func (app *Config) CreateUserHandler(w http.ResponseWriter, r *http.Request) {
52 | var user struct {
53 | Email string `json:"email"`
54 | FirstName string `json:"first_name,omitempty"`
55 | LastName string `json:"last_name,omitempty"`
56 | Password string `json:"password"`
57 | Active int `json:"active"`
58 | }
59 |
60 | err := app.readJSON(w, r, &user)
61 | if err != nil {
62 | app.errorJSON(w, err, http.StatusBadRequest)
63 | return
64 | }
65 |
66 | newID, err := app.Repo.Insert(data.User{
67 | Email: user.Email,
68 | FirstName: user.FirstName,
69 | LastName: user.LastName,
70 | Password: user.Password,
71 | Active: user.Active,
72 | })
73 | if err != nil {
74 | app.errorJSON(w, err, http.StatusInternalServerError)
75 | return
76 | }
77 |
78 | payload := jsonResponse{
79 | Error: false,
80 | Message: fmt.Sprintf("New user created with ID %d", newID),
81 | }
82 | app.writeJSON(w, http.StatusCreated, payload)
83 | }
84 |
85 | func (app *Config) logRequest(name, data string) error {
86 | var entry struct {
87 | Name string `json:"name"`
88 | Data string `json:"data"`
89 | }
90 |
91 | entry.Name = name
92 | entry.Data = data
93 |
94 | jsonData, _ := json.MarshalIndent(entry, "", "\t")
95 | logServiceURL := "http://logger-service/log"
96 |
97 | request, err := http.NewRequest("POST", logServiceURL, bytes.NewBuffer(jsonData))
98 | if err != nil {
99 | return err
100 | }
101 |
102 | client := &http.Client{}
103 | _, err = client.Do(request)
104 | if err != nil {
105 | return err
106 | }
107 |
108 | return nil
109 | }
110 |
--------------------------------------------------------------------------------
/authentication-service/cmd/api/handlers_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 | )
10 |
11 | func Test_Authenticate(t *testing.T) {
12 |
13 | postBody := map[string]interface{}{
14 | "email": "me@here.com",
15 | "password": "verysecret",
16 | }
17 |
18 | body, _ := json.Marshal(postBody)
19 |
20 | req, _ := http.NewRequest("POST", "/authenticate", bytes.NewReader(body))
21 | rr := httptest.NewRecorder()
22 |
23 | handler := http.HandlerFunc(testApp.Authenticate)
24 |
25 | handler.ServeHTTP(rr, req)
26 |
27 | if rr.Code != http.StatusAccepted {
28 | t.Errorf("expected http.StatusAccepted but got %d", rr.Code)
29 | }
30 | }
--------------------------------------------------------------------------------
/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 interface{} `json:"data,omitempty"`
14 | }
15 |
16 | func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data interface{}) error {
17 | maxBytes := 1048576 // one megabyte
18 |
19 | r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
20 |
21 | dec := json.NewDecoder(r.Body)
22 | err := dec.Decode(data)
23 | if err != nil {
24 | return err
25 | }
26 |
27 | err = dec.Decode(&struct{}{})
28 | if err != io.EOF {
29 | return errors.New("body must only have a single JSON value")
30 | }
31 |
32 | return nil
33 | }
34 |
35 | func (app *Config) writeJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
36 | out, err := json.Marshal(data)
37 | if err != nil {
38 | return err
39 | }
40 |
41 | if len(headers) > 0 {
42 | for key, value := range headers[0] {
43 | w.Header()[key] = value
44 | }
45 | }
46 |
47 | w.Header().Set("Content-Type", "application/json")
48 | w.WriteHeader(status)
49 | _, err = w.Write(out)
50 | if err != nil {
51 | return err
52 | }
53 |
54 | return nil
55 | }
56 |
57 | func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
58 | statusCode := http.StatusBadRequest
59 |
60 | if len(status) > 0 {
61 | statusCode = status[0]
62 | }
63 |
64 | var payload jsonResponse
65 | payload.Error = true
66 | payload.Message = err.Error()
67 |
68 | return app.writeJSON(w, statusCode, payload)
69 | }
70 |
--------------------------------------------------------------------------------
/authentication-service/cmd/api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "authentication/data"
5 | "database/sql"
6 | "fmt"
7 | "log"
8 | "net/http"
9 | "os"
10 | "time"
11 |
12 | _ "github.com/jackc/pgconn"
13 | _ "github.com/jackc/pgx/v4"
14 | _ "github.com/jackc/pgx/v4/stdlib"
15 | )
16 |
17 | const webPort = "80"
18 |
19 | var counts int64
20 |
21 | type Config struct {
22 | Repo data.Repository
23 | }
24 |
25 | func main() {
26 | log.Println("Starting authentication service")
27 |
28 | conn := connectToDB()
29 | if conn == nil {
30 | log.Panic("Can't connect to Postgres!")
31 | }
32 |
33 | app := Config{}
34 |
35 | srv := &http.Server{
36 | Addr: fmt.Sprintf(":%s", webPort),
37 | Handler: app.routes(),
38 | }
39 |
40 | err := srv.ListenAndServe()
41 | if err != nil {
42 | log.Panic(err)
43 | }
44 | }
45 |
46 | func openDB(dsn string) (*sql.DB, error) {
47 | db, err := sql.Open("pgx", dsn)
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | err = db.Ping()
53 | if err != nil {
54 | return nil, err
55 | }
56 |
57 | return db, nil
58 | }
59 |
60 | func connectToDB() *sql.DB {
61 | dsn := os.Getenv("DSN")
62 |
63 | for {
64 | connection, err := openDB(dsn)
65 | if err != nil {
66 | log.Println("Postgres not yet ready ...")
67 | counts++
68 | } else {
69 | log.Println("Connected to Postgres!")
70 | return connection
71 | }
72 |
73 | if counts > 10 {
74 | log.Println(err)
75 | return nil
76 | }
77 |
78 | log.Println("Backing off for two seconds....")
79 | time.Sleep(2 * time.Second)
80 | continue
81 | }
82 | }
83 |
84 | func (app *Config) setupRepo(conn *sql.DB) {
85 | db := data.NewPostgresRepository(conn)
86 | app.Repo = db
87 | }
88 |
--------------------------------------------------------------------------------
/authentication-service/cmd/api/routes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/go-chi/chi/v5"
7 | "github.com/go-chi/chi/v5/middleware"
8 | "github.com/go-chi/cors"
9 | )
10 |
11 | func (app *Config) routes() http.Handler {
12 | mux := chi.NewRouter()
13 |
14 | mux.Use(cors.Handler(cors.Options{
15 | AllowedOrigins: []string{"https://*", "http://*"},
16 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
17 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
18 | ExposedHeaders: []string{"Link"},
19 | AllowCredentials: true,
20 | MaxAge: 300,
21 | }))
22 |
23 | mux.Use(middleware.Heartbeat("/ping"))
24 |
25 | mux.Post("/authenticate", app.Authenticate)
26 | mux.Post("/register", app.CreateUserHandler)
27 |
28 | return mux
29 | }
30 |
--------------------------------------------------------------------------------
/authentication-service/cmd/api/routes_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | "testing"
6 |
7 | "github.com/go-chi/chi/v5"
8 | )
9 |
10 | func Test_routes_exist(t *testing.T) {
11 | testApp := Config{}
12 |
13 | testRoutes := testApp.routes()
14 | chiRoutes := testRoutes.(chi.Router)
15 |
16 | routes := []string{"/authenticate"}
17 |
18 | for _, route := range routes {
19 | routeExists(t, chiRoutes, route)
20 | }
21 | }
22 |
23 | func routeExists(t *testing.T, routes chi.Router, route string) {
24 | found := false
25 |
26 | _ = chi.Walk(routes, func(method string, foundRoute string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
27 | if route == foundRoute {
28 | found = true
29 | }
30 | return nil
31 | })
32 |
33 | if !found {
34 | t.Errorf("did not find %s in registered routes", route)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/authentication-service/cmd/api/setup_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "authentication/data"
5 | "os"
6 | "testing"
7 | )
8 |
9 | var testApp Config
10 |
11 | func TestMain(m *testing.M) {
12 | repo := data.NewPostgresTestRepository(nil)
13 | testApp.Repo = repo
14 | os.Exit(m.Run())
15 | }
16 |
--------------------------------------------------------------------------------
/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 | type PostgresRepository struct {
18 | Conn *sql.DB
19 | }
20 |
21 | func NewPostgresRepository(pool *sql.DB) *PostgresRepository {
22 | db = pool
23 | return &PostgresRepository{
24 | Conn: pool,
25 | }
26 | }
27 |
28 | type User struct {
29 | ID int `json:"id"`
30 | Email string `json:"email"`
31 | FirstName string `json:"first_name,omitempty"`
32 | LastName string `json:"last_name,omitempty"`
33 | Password string `json:"-"`
34 | Active int `json:"active"`
35 | CreatedAt time.Time `json:"created_at"`
36 | UpdatedAt time.Time `json:"updated_at"`
37 | }
38 |
39 | func (u *PostgresRepository) GetAll() ([]*User, error) {
40 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
41 | defer cancel()
42 |
43 | query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at
44 | from users order by last_name`
45 |
46 | rows, err := db.QueryContext(ctx, query)
47 | if err != nil {
48 | return nil, err
49 | }
50 | defer rows.Close()
51 |
52 | var users []*User
53 |
54 | for rows.Next() {
55 | var user User
56 | err := rows.Scan(
57 | &user.ID,
58 | &user.Email,
59 | &user.FirstName,
60 | &user.LastName,
61 | &user.Password,
62 | &user.Active,
63 | &user.CreatedAt,
64 | &user.UpdatedAt,
65 | )
66 | if err != nil {
67 | log.Println("Error scanning", err)
68 | return nil, err
69 | }
70 |
71 | users = append(users, &user)
72 | }
73 |
74 | return users, nil
75 | }
76 |
77 | func (u *PostgresRepository) GetByEmail(email string) (*User, error) {
78 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
79 | defer cancel()
80 |
81 | query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where email = $1`
82 |
83 | var user User
84 | row := db.QueryRowContext(ctx, query, email)
85 |
86 | err := row.Scan(
87 | &user.ID,
88 | &user.Email,
89 | &user.FirstName,
90 | &user.LastName,
91 | &user.Password,
92 | &user.Active,
93 | &user.CreatedAt,
94 | &user.UpdatedAt,
95 | )
96 |
97 | if err != nil {
98 | return nil, err
99 | }
100 |
101 | return &user, nil
102 | }
103 |
104 | func (u *PostgresRepository) GetOne(id int) (*User, error) {
105 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
106 | defer cancel()
107 |
108 | query := `select id, email, first_name, last_name, password, user_active, created_at, updated_at from users where id = $1`
109 |
110 | var user User
111 | row := db.QueryRowContext(ctx, query, id)
112 |
113 | err := row.Scan(
114 | &user.ID,
115 | &user.Email,
116 | &user.FirstName,
117 | &user.LastName,
118 | &user.Password,
119 | &user.Active,
120 | &user.CreatedAt,
121 | &user.UpdatedAt,
122 | )
123 |
124 | if err != nil {
125 | return nil, err
126 | }
127 |
128 | return &user, nil
129 | }
130 |
131 | func (u *PostgresRepository) Update(user User) error {
132 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
133 | defer cancel()
134 |
135 | stmt := `update users set
136 | email = $1,
137 | first_name = $2,
138 | last_name = $3,
139 | user_active = $4,
140 | updated_at = $5
141 | where id = $6
142 | `
143 |
144 | _, err := db.ExecContext(ctx, stmt,
145 | user.Email,
146 | user.FirstName,
147 | user.LastName,
148 | user.Active,
149 | time.Now(),
150 | user.ID,
151 | )
152 |
153 | if err != nil {
154 | return err
155 | }
156 |
157 | return nil
158 | }
159 |
160 | // DeleteByID deletes one user from the database, by ID
161 | func (u *PostgresRepository) DeleteByID(id int) error {
162 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
163 | defer cancel()
164 |
165 | stmt := `delete from users where id = $1`
166 |
167 | _, err := db.ExecContext(ctx, stmt, id)
168 | if err != nil {
169 | return err
170 | }
171 |
172 | return nil
173 | }
174 |
175 | func (u *PostgresRepository) Insert(user User) (int, error) {
176 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
177 | defer cancel()
178 |
179 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
180 | if err != nil {
181 | return 0, err
182 | }
183 |
184 | var newID int
185 | stmt := `insert into users (email, first_name, last_name, password, user_active, created_at, updated_at)
186 | values ($1, $2, $3, $4, $5, $6, $7) returning id`
187 |
188 | err = db.QueryRowContext(ctx, stmt,
189 | user.Email,
190 | user.FirstName,
191 | user.LastName,
192 | hashedPassword,
193 | user.Active,
194 | time.Now(),
195 | time.Now(),
196 | ).Scan(&newID)
197 |
198 | if err != nil {
199 | return 0, err
200 | }
201 |
202 | return newID, nil
203 | }
204 |
205 | func (u *PostgresRepository) ResetPassword(password string, user User) error {
206 | ctx, cancel := context.WithTimeout(context.Background(), dbTimeout)
207 | defer cancel()
208 |
209 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
210 | if err != nil {
211 | return err
212 | }
213 |
214 | stmt := `update users set password = $1 where id = $2`
215 | _, err = db.ExecContext(ctx, stmt, hashedPassword, user.ID)
216 | if err != nil {
217 | return err
218 | }
219 |
220 | return nil
221 | }
222 |
223 | func (u *PostgresRepository) PasswordMatches(plainText string, user User) (bool, error) {
224 | err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(plainText))
225 | if err != nil {
226 | switch {
227 | case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
228 | return false, nil
229 | default:
230 | return false, err
231 | }
232 | }
233 |
234 | return true, nil
235 | }
236 |
--------------------------------------------------------------------------------
/authentication-service/data/repository.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | type Repository interface {
4 | GetAll() ([]*User, error)
5 | GetByEmail(email string) (*User, error)
6 | GetOne(id int) (*User, error)
7 | Update(user User) error
8 | DeleteByID(id int) error
9 | Insert(user User) (int, error)
10 | ResetPassword(password string, user User) error
11 | PasswordMatches(plainText string, user User) (bool, error)
12 | }
--------------------------------------------------------------------------------
/authentication-service/data/test-models.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "database/sql"
5 | "time"
6 | )
7 |
8 | type PostgresTestRepository struct {
9 | Conn *sql.DB
10 | }
11 |
12 | func NewPostgresTestRepository(db *sql.DB) *PostgresTestRepository {
13 | return &PostgresTestRepository{
14 | Conn: db,
15 | }
16 | }
17 |
18 | func (u *PostgresTestRepository) GetAll() ([]*User, error) {
19 | users := []*User{}
20 |
21 | return users, nil
22 | }
23 |
24 | func (u *PostgresTestRepository) GetByEmail(email string) (*User, error) {
25 | user := User{
26 | ID: 1,
27 | FirstName: "First",
28 | LastName: "Last",
29 | Email: "me@here.com",
30 | Password: "",
31 | Active: 1,
32 | CreatedAt: time.Now(),
33 | UpdatedAt: time.Now(),
34 | }
35 |
36 | return &user, nil
37 | }
38 |
39 | func (u *PostgresTestRepository) GetOne(id int) (*User, error) {
40 | user := User{
41 | ID: 1,
42 | FirstName: "First",
43 | LastName: "Last",
44 | Email: "me@here.com",
45 | Password: "",
46 | Active: 1,
47 | CreatedAt: time.Now(),
48 | UpdatedAt: time.Now(),
49 | }
50 |
51 | return &user, nil
52 | }
53 |
54 | func (u *PostgresTestRepository) Update(user User) error {
55 | return nil
56 | }
57 |
58 | func (u *PostgresTestRepository) DeleteByID(id int) error {
59 | return nil
60 | }
61 |
62 | func (u *PostgresTestRepository) Insert(user User) (int, error) {
63 | return 2, nil
64 | }
65 |
66 | func (u *PostgresTestRepository) ResetPassword(password string, user User) error {
67 | return nil
68 | }
69 |
70 | func (u *PostgresTestRepository) PasswordMatches(plainText string, user User) (bool, error) {
71 | return true, nil
72 | }
73 |
--------------------------------------------------------------------------------
/authentication-service/go.mod:
--------------------------------------------------------------------------------
1 | module authentication
2 |
3 | go 1.21.3
4 |
5 | require (
6 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
7 | github.com/jackc/pgconn v1.14.0 // indirect
8 | github.com/jackc/pgio v1.0.0 // indirect
9 | github.com/jackc/pgpassfile v1.0.0 // indirect
10 | github.com/jackc/pgproto3/v2 v2.3.2 // indirect
11 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
12 | github.com/jackc/pgtype v1.14.0 // indirect
13 | github.com/jackc/pgx/v4 v4.18.1 // indirect
14 | golang.org/x/crypto v0.15.0 // indirect
15 | golang.org/x/text v0.14.0 // indirect
16 | )
17 |
--------------------------------------------------------------------------------
/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/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
3 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
4 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
5 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
6 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
10 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
11 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
12 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
13 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
14 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
15 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
16 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
17 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
18 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
19 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
20 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
21 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
22 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
23 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
24 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
25 | github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q=
26 | github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
27 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
28 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
29 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
30 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
31 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
32 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
33 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
34 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
35 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
36 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
37 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
38 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
39 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
40 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
41 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
42 | github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0=
43 | github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
44 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
45 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
46 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
47 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
48 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
49 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
50 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
51 | github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
52 | github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
53 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
54 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
55 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
56 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
57 | github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0=
58 | github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
59 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
60 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
61 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
62 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
63 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
64 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
65 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
66 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
67 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
68 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
69 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
70 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
71 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
72 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
73 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
74 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
75 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
76 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
77 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
78 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
79 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
80 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
81 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
82 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
83 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
84 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
85 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
86 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
87 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
88 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
89 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
90 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
91 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
92 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
93 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
94 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
95 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
96 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
97 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
98 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
99 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
100 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
101 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
102 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
103 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
104 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
105 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
106 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
107 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
108 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
109 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
110 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
111 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
112 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
113 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
114 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
115 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
116 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
117 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
118 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
119 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
120 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
121 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
122 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
123 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
124 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
125 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
126 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
127 | golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
128 | golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
129 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
130 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
131 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
132 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
133 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
134 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
135 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
136 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
137 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
138 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
139 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
140 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
141 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
142 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
143 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
144 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
145 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
146 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
147 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
148 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
149 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
150 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
151 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
152 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
153 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
154 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
155 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
156 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
157 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
158 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
159 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
160 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
161 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
162 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
163 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
164 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
165 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
166 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
167 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
168 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
169 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
170 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
171 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
172 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
173 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
174 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
175 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
176 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
177 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
178 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
179 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
180 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
181 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
182 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
183 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
184 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
185 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
186 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
187 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
188 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
189 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
190 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
191 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
192 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
193 |
--------------------------------------------------------------------------------
/broker-service/broker-service.dockerfile:
--------------------------------------------------------------------------------
1 | # FROM golang:1.21-alpine as builder
2 |
3 | # RUN mkdir /app
4 |
5 | # COPY . /app
6 |
7 | # WORKDIR /app
8 |
9 | # RUN CGO_ENABLED=0 go build -o brokerApp ./cmd/api
10 |
11 | # RUN chmod +x /app/brokerApp
12 |
13 | # FROM alpine:latest
14 |
15 | # RUN mkdir /app
16 |
17 | # COPY --from=builder /app/brokerApp /app
18 |
19 | # CMD [ "/app/brokerApp" ]
20 |
21 | FROM alpine:latest
22 |
23 | RUN mkdir /app
24 |
25 | COPY brokerApp /app
26 |
27 | CMD [ "/app/brokerApp"]
--------------------------------------------------------------------------------
/broker-service/brokerApp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Furkan-Gulsen/microservices-with-go/0f61805b11cc6137315bbfbbfaf1d95cd9b5a831/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 | "net/http"
11 | "net/rpc"
12 | "time"
13 |
14 | "google.golang.org/grpc"
15 | "google.golang.org/grpc/credentials/insecure"
16 | )
17 |
18 | type RequestPayload struct {
19 | Action string `json:"action"`
20 | Auth AuthPayload `json:"auth,omitempty"`
21 | Log LogPayload `json:"log,omitempty"`
22 | Mail MailPayload `json:"mail,omitempty"`
23 | }
24 |
25 | type MailPayload struct {
26 | From string `json:"from"`
27 | To string `json:"to"`
28 | Subject string `json:"subject"`
29 | Message string `json:"message"`
30 | }
31 |
32 | type AuthPayload struct {
33 | Email string `json:"email"`
34 | Password string `json:"password"`
35 | }
36 |
37 | type LogPayload struct {
38 | Name string `json:"name"`
39 | Data string `json:"data"`
40 | }
41 |
42 | func (app *Config) Broker(w http.ResponseWriter, r *http.Request) {
43 | payload := jsonResponse{
44 | Error: false,
45 | Message: "Hit the broker",
46 | }
47 |
48 | _ = app.writeJSON(w, http.StatusOK, payload)
49 | }
50 |
51 | func (app *Config) HandleSubmission(w http.ResponseWriter, r *http.Request) {
52 | var requestPayload RequestPayload
53 |
54 | err := app.readJSON(w, r, &requestPayload)
55 | if err != nil {
56 | app.errorJSON(w, err)
57 | return
58 | }
59 |
60 | switch requestPayload.Action {
61 | case "auth":
62 | app.authenticate(w, requestPayload.Auth)
63 | case "log":
64 | app.logItemViaRPC(w, requestPayload.Log)
65 | case "mail":
66 | app.sendMail(w, requestPayload.Mail)
67 | default:
68 | app.errorJSON(w, errors.New("unknown action"))
69 | }
70 | }
71 |
72 | func (app *Config) logItem(w http.ResponseWriter, entry LogPayload) {
73 | jsonData, _ := json.MarshalIndent(entry, "", "\t")
74 |
75 | logServiceURL := "http://logger-service/log"
76 |
77 | request, err := http.NewRequest("POST", logServiceURL, bytes.NewBuffer(jsonData))
78 | if err != nil {
79 | app.errorJSON(w, err)
80 | return
81 | }
82 |
83 | request.Header.Set("Content-Type", "application/json")
84 |
85 | client := &http.Client{}
86 |
87 | response, err := client.Do(request)
88 | if err != nil {
89 | app.errorJSON(w, err)
90 | return
91 | }
92 | defer response.Body.Close()
93 |
94 | if response.StatusCode != http.StatusAccepted {
95 | app.errorJSON(w, err)
96 | return
97 | }
98 |
99 | var payload jsonResponse
100 | payload.Error = false
101 | payload.Message = "logged"
102 |
103 | app.writeJSON(w, http.StatusAccepted, payload)
104 |
105 | }
106 |
107 | func (app *Config) authenticate(w http.ResponseWriter, a AuthPayload) {
108 | jsonData, _ := json.MarshalIndent(a, "", "\t")
109 |
110 | request, err := http.NewRequest("POST", "http://authentication-service/authenticate", bytes.NewBuffer(jsonData))
111 | if err != nil {
112 | app.errorJSON(w, err)
113 | return
114 | }
115 |
116 | client := &http.Client{}
117 | response, err := client.Do(request)
118 | if err != nil {
119 | app.errorJSON(w, err)
120 | return
121 | }
122 | defer response.Body.Close()
123 |
124 | if response.StatusCode == http.StatusUnauthorized {
125 | app.errorJSON(w, errors.New("invalid credentials"))
126 | return
127 | } else if response.StatusCode != http.StatusAccepted {
128 | app.errorJSON(w, errors.New("error calling auth service"))
129 | return
130 | }
131 |
132 | var jsonFromService jsonResponse
133 |
134 | err = json.NewDecoder(response.Body).Decode(&jsonFromService)
135 | if err != nil {
136 | app.errorJSON(w, err)
137 | return
138 | }
139 |
140 | if jsonFromService.Error {
141 | app.errorJSON(w, err, http.StatusUnauthorized)
142 | return
143 | }
144 |
145 | var payload jsonResponse
146 | payload.Error = false
147 | payload.Message = "Authenticated!"
148 | payload.Data = jsonFromService.Data
149 |
150 | app.writeJSON(w, http.StatusAccepted, payload)
151 | }
152 |
153 | func (app *Config) sendMail(w http.ResponseWriter, msg MailPayload) {
154 | jsonData, _ := json.MarshalIndent(msg, "", "\t")
155 |
156 | mailServiceURL := "http://mailer-service/send"
157 |
158 | request, err := http.NewRequest("POST", mailServiceURL, bytes.NewBuffer(jsonData))
159 | if err != nil {
160 | app.errorJSON(w, err)
161 | return
162 | }
163 |
164 | request.Header.Set("Content-Type", "application/json")
165 |
166 | client := &http.Client{}
167 | response, err := client.Do(request)
168 | if err != nil {
169 | app.errorJSON(w, err)
170 | return
171 | }
172 | defer response.Body.Close()
173 |
174 | if response.StatusCode != http.StatusAccepted {
175 | app.errorJSON(w, errors.New("error calling mail service"))
176 | return
177 | }
178 |
179 | var payload jsonResponse
180 | payload.Error = false
181 | payload.Message = "Message sent to " + msg.To
182 |
183 | app.writeJSON(w, http.StatusAccepted, payload)
184 |
185 | }
186 |
187 | func (app *Config) logEventViaRabbit(w http.ResponseWriter, l LogPayload) {
188 | err := app.pushToQueue(l.Name, l.Data)
189 | if err != nil {
190 | app.errorJSON(w, err)
191 | return
192 | }
193 |
194 | var payload jsonResponse
195 | payload.Error = false
196 | payload.Message = "logged via RabbitMQ"
197 |
198 | app.writeJSON(w, http.StatusAccepted, payload)
199 | }
200 |
201 | func (app *Config) pushToQueue(name, msg string) error {
202 | emitter, err := event.NewEventEmitter(app.Rabbit)
203 | if err != nil {
204 | return err
205 | }
206 |
207 | payload := LogPayload{
208 | Name: name,
209 | Data: msg,
210 | }
211 |
212 | j, err := json.MarshalIndent(&payload, "", "\t")
213 | if err != nil {
214 | return err
215 | }
216 |
217 | err = emitter.Push(string(j), "log.INFO")
218 | if err != nil {
219 | return err
220 | }
221 | return nil
222 | }
223 |
224 | type RPCPayload struct {
225 | Name string
226 | Data string
227 | }
228 |
229 | func (app *Config) logItemViaRPC(w http.ResponseWriter, l LogPayload) {
230 | client, err := rpc.Dial("tcp", "logger-service:5001")
231 | if err != nil {
232 | app.errorJSON(w, err)
233 | return
234 | }
235 |
236 | rpcPayload := RPCPayload{
237 | Name: l.Name,
238 | Data: l.Data,
239 | }
240 |
241 | var result string
242 | err = client.Call("RPCServer.LogInfo", rpcPayload, &result)
243 | if err != nil {
244 | app.errorJSON(w, err)
245 | return
246 | }
247 |
248 | payload := jsonResponse{
249 | Error: false,
250 | Message: result,
251 | }
252 |
253 | app.writeJSON(w, http.StatusAccepted, payload)
254 | }
255 |
256 | func (app *Config) LogViaGRPC(w http.ResponseWriter, r *http.Request) {
257 | var requestPayload RequestPayload
258 |
259 | err := app.readJSON(w, r, &requestPayload)
260 | if err != nil {
261 | app.errorJSON(w, err)
262 | return
263 | }
264 |
265 | conn, err := grpc.Dial("logger-service:50001", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
266 | if err != nil {
267 | app.errorJSON(w, err)
268 | return
269 | }
270 | defer conn.Close()
271 |
272 | c := logs.NewLogServiceClient(conn)
273 | ctx, cancel := context.WithTimeout(context.Background(), time.Second)
274 | defer cancel()
275 |
276 | _, err = c.WriteLog(ctx, &logs.LogRequest{
277 | LogEntry: &logs.Log{
278 | Name: requestPayload.Log.Name,
279 | Data: requestPayload.Log.Data,
280 | },
281 | })
282 | if err != nil {
283 | app.errorJSON(w, err)
284 | return
285 | }
286 |
287 | var payload jsonResponse
288 | payload.Error = false
289 | payload.Message = "logged"
290 |
291 | app.writeJSON(w, http.StatusAccepted, payload)
292 | }
293 |
--------------------------------------------------------------------------------
/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:"error"`
12 | Message string `json:"message"`
13 | Data interface{} `json:"data,omitempty"`
14 | }
15 |
16 | func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data interface{}) error {
17 | maxBytes := 1048576 // one megabyte
18 |
19 | r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
20 |
21 | dec := json.NewDecoder(r.Body)
22 | err := dec.Decode(data)
23 | if err != nil {
24 | return err
25 | }
26 |
27 | err = dec.Decode(&struct{}{})
28 | if err != io.EOF {
29 | return errors.New("body must only have a single JSON value")
30 | }
31 |
32 | return nil
33 | }
34 |
35 | func (app *Config) writeJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
36 | out, err := json.Marshal(data)
37 | if err != nil {
38 | return err
39 | }
40 |
41 | if len(headers) > 0 {
42 | for key, value := range headers[0] {
43 | w.Header()[key] = value
44 | }
45 | }
46 |
47 | w.Header().Set("Content-Type", "application/json")
48 | w.Header().Set("Access-Control-Allow-Origin", "*")
49 | w.WriteHeader(status)
50 | _, err = w.Write(out)
51 | if err != nil {
52 | return err
53 | }
54 |
55 | return nil
56 | }
57 |
58 | func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
59 | statusCode := http.StatusBadRequest
60 |
61 | if len(status) > 0 {
62 | statusCode = status[0]
63 | }
64 |
65 | var payload jsonResponse
66 | payload.Error = true
67 | payload.Message = err.Error()
68 |
69 | return app.writeJSON(w, statusCode, payload)
70 | }
71 |
--------------------------------------------------------------------------------
/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"
15 |
16 | type Config struct {
17 | Rabbit *amqp.Connection
18 | }
19 |
20 | func main() {
21 | rabbitConn, err := connect()
22 | if err != nil {
23 | log.Println(err)
24 | os.Exit(1)
25 | }
26 | defer rabbitConn.Close()
27 |
28 | app := Config{
29 | Rabbit: rabbitConn,
30 | }
31 |
32 | log.Printf("Starting broker service on port %s\n", webPort)
33 |
34 | srv := &http.Server{
35 | Addr: fmt.Sprintf(":%s", webPort),
36 | Handler: app.routes(),
37 | }
38 |
39 | err = srv.ListenAndServe()
40 | if err != nil {
41 | log.Panic(err)
42 | }
43 | }
44 |
45 | func connect() (*amqp.Connection, error) {
46 | var counts int64
47 | var backOff = 1 * time.Second
48 | var connection *amqp.Connection
49 |
50 | for {
51 | c, err := amqp.Dial("amqp://guest:guest@rabbitmq")
52 | if err != nil {
53 | fmt.Println("RabbitMQ not yet ready...")
54 | counts++
55 | } else {
56 | log.Println("Connected to RabbitMQ!")
57 | connection = c
58 | break
59 | }
60 |
61 | if counts > 5 {
62 | fmt.Println(err)
63 | return nil, err
64 | }
65 |
66 | backOff = time.Duration(math.Pow(float64(counts), 2)) * time.Second
67 | log.Println("backing off...")
68 | time.Sleep(backOff)
69 | continue
70 | }
71 |
72 | return connection, nil
73 | }
74 |
--------------------------------------------------------------------------------
/broker-service/cmd/api/routes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/go-chi/chi/v5"
7 | "github.com/go-chi/chi/v5/middleware"
8 | "github.com/go-chi/cors"
9 | )
10 |
11 | func (app *Config) routes() http.Handler {
12 | mux := chi.NewRouter()
13 |
14 | mux.Use(cors.Handler(cors.Options{
15 | AllowedOrigins: []string{"https://*", "http://*"},
16 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
17 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
18 | ExposedHeaders: []string{"Link"},
19 | AllowCredentials: true,
20 | MaxAge: 300,
21 | }))
22 |
23 | mux.Use(middleware.Heartbeat("/ping"))
24 |
25 | mux.Post("/", app.Broker)
26 |
27 | mux.Post("/handle", app.HandleSubmission)
28 |
29 | mux.Post("/log-grpc", app.LogViaGRPC)
30 |
31 | return mux
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/broker-service/event/consumer.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "net/http"
9 |
10 | amqp "github.com/rabbitmq/amqp091-go"
11 | )
12 |
13 | type Consumer struct {
14 | conn *amqp.Connection
15 | queueName string
16 | }
17 |
18 | func NewConsumer(conn *amqp.Connection) (Consumer, error) {
19 | consumer := Consumer{
20 | conn: conn,
21 | }
22 |
23 | err := consumer.setup()
24 | if err != nil {
25 | return Consumer{}, err
26 | }
27 |
28 | return consumer, nil
29 | }
30 |
31 | func (consumer *Consumer) setup() error {
32 | channel, err := consumer.conn.Channel()
33 | if err != nil {
34 | return err
35 | }
36 |
37 | return declareExchange(channel)
38 | }
39 |
40 | type Payload struct {
41 | Name string `json:"name"`
42 | Data string `json:"data"`
43 | }
44 |
45 | func (consumer *Consumer) Listen(topics []string) error {
46 | ch, err := consumer.conn.Channel()
47 | if err != nil {
48 | return err
49 | }
50 | defer ch.Close()
51 |
52 | q, err := declareRandomQueue(ch)
53 | if err != nil {
54 | return err
55 | }
56 |
57 | for _, s := range topics {
58 | ch.QueueBind(
59 | q.Name,
60 | s,
61 | "logs_topic",
62 | false,
63 | nil,
64 | )
65 |
66 | if err != nil {
67 | return err
68 | }
69 | }
70 |
71 | messages, err := ch.Consume(q.Name, "", true, false, false, false, nil)
72 | if err != nil {
73 | return err
74 | }
75 |
76 | forever := make(chan bool)
77 | go func() {
78 | for d := range messages {
79 | var payload Payload
80 | _ = json.Unmarshal(d.Body, &payload)
81 |
82 | go handlePayload(payload)
83 | }
84 | }()
85 |
86 | fmt.Printf("Waiting for message [Exchange, Queue] [logs_topic, %s]\n", q.Name)
87 | <-forever
88 |
89 | return nil
90 | }
91 |
92 | func handlePayload(payload Payload) {
93 | switch payload.Name {
94 | case "log", "event":
95 | err := logEvent(payload)
96 | if err != nil {
97 | log.Println(err)
98 | }
99 |
100 | case "auth":
101 |
102 | default:
103 | err := logEvent(payload)
104 | if err != nil {
105 | log.Println(err)
106 | }
107 | }
108 | }
109 |
110 | func logEvent(entry Payload) error {
111 | jsonData, _ := json.MarshalIndent(entry, "", "\t")
112 |
113 | logServiceURL := "http://logger-service/log"
114 |
115 | request, err := http.NewRequest("POST", logServiceURL, bytes.NewBuffer(jsonData))
116 | if err != nil {
117 | return err
118 | }
119 |
120 | request.Header.Set("Content-Type", "application/json")
121 |
122 | client := &http.Client{}
123 |
124 | response, err := client.Do(request)
125 | if err != nil {
126 | return err
127 | }
128 | defer response.Body.Close()
129 |
130 | if response.StatusCode != http.StatusAccepted {
131 | return err
132 | }
133 |
134 | return nil
135 | }
136 |
--------------------------------------------------------------------------------
/broker-service/event/emitter.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "log"
5 |
6 | amqp "github.com/rabbitmq/amqp091-go"
7 | )
8 |
9 | type Emitter struct {
10 | connection *amqp.Connection
11 | }
12 |
13 | func (e *Emitter) setup() error {
14 | channel, err := e.connection.Channel()
15 | if err != nil {
16 | return err
17 | }
18 |
19 | defer channel.Close()
20 | return declareExchange(channel)
21 | }
22 |
23 | func (e *Emitter) Push(event string, severity string) error {
24 | channel, err := e.connection.Channel()
25 | if err != nil {
26 | return err
27 | }
28 | defer channel.Close()
29 |
30 | log.Println("Pushing to channel")
31 |
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 | }
--------------------------------------------------------------------------------
/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",
10 | "topic",
11 | true,
12 | false,
13 | false,
14 | false,
15 | nil,
16 | )
17 | }
18 |
19 | func declareRandomQueue(ch *amqp.Channel) (amqp.Queue, error) {
20 | return ch.QueueDeclare(
21 | "",
22 | false,
23 | false,
24 | true,
25 | false,
26 | nil,
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/broker-service/go.mod:
--------------------------------------------------------------------------------
1 | module broker
2 |
3 | go 1.21.3
4 |
5 | require (
6 | github.com/go-chi/chi/v5 v5.0.10
7 | github.com/go-chi/cors v1.2.1
8 | )
9 |
--------------------------------------------------------------------------------
/broker-service/logs/logs.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.27.1
4 | // protoc v3.20.0
5 | // source: logs.proto
6 |
7 | package logs
8 |
9 | import (
10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 | reflect "reflect"
13 | sync "sync"
14 | )
15 |
16 | const (
17 | // Verify that this generated code is sufficiently up-to-date.
18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
19 | // Verify that runtime/protoimpl is sufficiently up-to-date.
20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
21 | )
22 |
23 | type Log struct {
24 | state protoimpl.MessageState
25 | sizeCache protoimpl.SizeCache
26 | unknownFields protoimpl.UnknownFields
27 |
28 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
29 | Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
30 | }
31 |
32 | func (x *Log) Reset() {
33 | *x = Log{}
34 | if protoimpl.UnsafeEnabled {
35 | mi := &file_logs_proto_msgTypes[0]
36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
37 | ms.StoreMessageInfo(mi)
38 | }
39 | }
40 |
41 | func (x *Log) String() string {
42 | return protoimpl.X.MessageStringOf(x)
43 | }
44 |
45 | func (*Log) ProtoMessage() {}
46 |
47 | func (x *Log) ProtoReflect() protoreflect.Message {
48 | mi := &file_logs_proto_msgTypes[0]
49 | if protoimpl.UnsafeEnabled && x != nil {
50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
51 | if ms.LoadMessageInfo() == nil {
52 | ms.StoreMessageInfo(mi)
53 | }
54 | return ms
55 | }
56 | return mi.MessageOf(x)
57 | }
58 |
59 | // Deprecated: Use Log.ProtoReflect.Descriptor instead.
60 | func (*Log) Descriptor() ([]byte, []int) {
61 | return file_logs_proto_rawDescGZIP(), []int{0}
62 | }
63 |
64 | func (x *Log) GetName() string {
65 | if x != nil {
66 | return x.Name
67 | }
68 | return ""
69 | }
70 |
71 | func (x *Log) GetData() string {
72 | if x != nil {
73 | return x.Data
74 | }
75 | return ""
76 | }
77 |
78 | type LogRequest struct {
79 | state protoimpl.MessageState
80 | sizeCache protoimpl.SizeCache
81 | unknownFields protoimpl.UnknownFields
82 |
83 | LogEntry *Log `protobuf:"bytes,1,opt,name=logEntry,proto3" json:"logEntry,omitempty"`
84 | }
85 |
86 | func (x *LogRequest) Reset() {
87 | *x = LogRequest{}
88 | if protoimpl.UnsafeEnabled {
89 | mi := &file_logs_proto_msgTypes[1]
90 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
91 | ms.StoreMessageInfo(mi)
92 | }
93 | }
94 |
95 | func (x *LogRequest) String() string {
96 | return protoimpl.X.MessageStringOf(x)
97 | }
98 |
99 | func (*LogRequest) ProtoMessage() {}
100 |
101 | func (x *LogRequest) ProtoReflect() protoreflect.Message {
102 | mi := &file_logs_proto_msgTypes[1]
103 | if protoimpl.UnsafeEnabled && x != nil {
104 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
105 | if ms.LoadMessageInfo() == nil {
106 | ms.StoreMessageInfo(mi)
107 | }
108 | return ms
109 | }
110 | return mi.MessageOf(x)
111 | }
112 |
113 | // Deprecated: Use LogRequest.ProtoReflect.Descriptor instead.
114 | func (*LogRequest) Descriptor() ([]byte, []int) {
115 | return file_logs_proto_rawDescGZIP(), []int{1}
116 | }
117 |
118 | func (x *LogRequest) GetLogEntry() *Log {
119 | if x != nil {
120 | return x.LogEntry
121 | }
122 | return nil
123 | }
124 |
125 | type LogResponse struct {
126 | state protoimpl.MessageState
127 | sizeCache protoimpl.SizeCache
128 | unknownFields protoimpl.UnknownFields
129 |
130 | Result string `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"`
131 | }
132 |
133 | func (x *LogResponse) Reset() {
134 | *x = LogResponse{}
135 | if protoimpl.UnsafeEnabled {
136 | mi := &file_logs_proto_msgTypes[2]
137 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
138 | ms.StoreMessageInfo(mi)
139 | }
140 | }
141 |
142 | func (x *LogResponse) String() string {
143 | return protoimpl.X.MessageStringOf(x)
144 | }
145 |
146 | func (*LogResponse) ProtoMessage() {}
147 |
148 | func (x *LogResponse) ProtoReflect() protoreflect.Message {
149 | mi := &file_logs_proto_msgTypes[2]
150 | if protoimpl.UnsafeEnabled && x != nil {
151 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
152 | if ms.LoadMessageInfo() == nil {
153 | ms.StoreMessageInfo(mi)
154 | }
155 | return ms
156 | }
157 | return mi.MessageOf(x)
158 | }
159 |
160 | // Deprecated: Use LogResponse.ProtoReflect.Descriptor instead.
161 | func (*LogResponse) Descriptor() ([]byte, []int) {
162 | return file_logs_proto_rawDescGZIP(), []int{2}
163 | }
164 |
165 | func (x *LogResponse) GetResult() string {
166 | if x != nil {
167 | return x.Result
168 | }
169 | return ""
170 | }
171 |
172 | var File_logs_proto protoreflect.FileDescriptor
173 |
174 | var file_logs_proto_rawDesc = []byte{
175 | 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6c, 0x6f,
176 | 0x67, 0x73, 0x22, 0x2d, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
177 | 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a,
178 | 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74,
179 | 0x61, 0x22, 0x33, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
180 | 0x25, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
181 | 0x0b, 0x32, 0x09, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x08, 0x6c, 0x6f,
182 | 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x25, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73,
183 | 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18,
184 | 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32, 0x3d, 0x0a,
185 | 0x0a, 0x4c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x08, 0x57,
186 | 0x72, 0x69, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x12, 0x10, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x4c,
187 | 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x6c, 0x6f, 0x67, 0x73,
188 | 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x07, 0x5a, 0x05,
189 | 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
190 | }
191 |
192 | var (
193 | file_logs_proto_rawDescOnce sync.Once
194 | file_logs_proto_rawDescData = file_logs_proto_rawDesc
195 | )
196 |
197 | func file_logs_proto_rawDescGZIP() []byte {
198 | file_logs_proto_rawDescOnce.Do(func() {
199 | file_logs_proto_rawDescData = protoimpl.X.CompressGZIP(file_logs_proto_rawDescData)
200 | })
201 | return file_logs_proto_rawDescData
202 | }
203 |
204 | var file_logs_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
205 | var file_logs_proto_goTypes = []interface{}{
206 | (*Log)(nil), // 0: logs.Log
207 | (*LogRequest)(nil), // 1: logs.LogRequest
208 | (*LogResponse)(nil), // 2: logs.LogResponse
209 | }
210 | var file_logs_proto_depIdxs = []int32{
211 | 0, // 0: logs.LogRequest.logEntry:type_name -> logs.Log
212 | 1, // 1: logs.LogService.WriteLog:input_type -> logs.LogRequest
213 | 2, // 2: logs.LogService.WriteLog:output_type -> logs.LogResponse
214 | 2, // [2:3] is the sub-list for method output_type
215 | 1, // [1:2] is the sub-list for method input_type
216 | 1, // [1:1] is the sub-list for extension type_name
217 | 1, // [1:1] is the sub-list for extension extendee
218 | 0, // [0:1] is the sub-list for field type_name
219 | }
220 |
221 | func init() { file_logs_proto_init() }
222 | func file_logs_proto_init() {
223 | if File_logs_proto != nil {
224 | return
225 | }
226 | if !protoimpl.UnsafeEnabled {
227 | file_logs_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
228 | switch v := v.(*Log); i {
229 | case 0:
230 | return &v.state
231 | case 1:
232 | return &v.sizeCache
233 | case 2:
234 | return &v.unknownFields
235 | default:
236 | return nil
237 | }
238 | }
239 | file_logs_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
240 | switch v := v.(*LogRequest); i {
241 | case 0:
242 | return &v.state
243 | case 1:
244 | return &v.sizeCache
245 | case 2:
246 | return &v.unknownFields
247 | default:
248 | return nil
249 | }
250 | }
251 | file_logs_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
252 | switch v := v.(*LogResponse); i {
253 | case 0:
254 | return &v.state
255 | case 1:
256 | return &v.sizeCache
257 | case 2:
258 | return &v.unknownFields
259 | default:
260 | return nil
261 | }
262 | }
263 | }
264 | type x struct{}
265 | out := protoimpl.TypeBuilder{
266 | File: protoimpl.DescBuilder{
267 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
268 | RawDescriptor: file_logs_proto_rawDesc,
269 | NumEnums: 0,
270 | NumMessages: 3,
271 | NumExtensions: 0,
272 | NumServices: 1,
273 | },
274 | GoTypes: file_logs_proto_goTypes,
275 | DependencyIndexes: file_logs_proto_depIdxs,
276 | MessageInfos: file_logs_proto_msgTypes,
277 | }.Build()
278 | File_logs_proto = out.File
279 | file_logs_proto_rawDesc = nil
280 | file_logs_proto_goTypes = nil
281 | file_logs_proto_depIdxs = nil
282 | }
283 |
--------------------------------------------------------------------------------
/broker-service/logs/logs.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package logs;
4 |
5 | option go_package = "/logs";
6 |
7 | message Log {
8 | string name = 1;
9 | string data = 2;
10 | }
11 |
12 | message LogRequest {
13 | Log logEntry = 1;
14 | }
15 |
16 | message LogResponse {
17 | string result = 1;
18 | }
19 |
20 | service LogService {
21 | rpc WriteLog(LogRequest) returns (LogResponse);
22 | }
--------------------------------------------------------------------------------
/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.20.0
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 | WriteLog(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error)
26 | }
27 |
28 | type logServiceClient struct {
29 | cc grpc.ClientConnInterface
30 | }
31 |
32 | func NewLogServiceClient(cc grpc.ClientConnInterface) LogServiceClient {
33 | return &logServiceClient{cc}
34 | }
35 |
36 | func (c *logServiceClient) WriteLog(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error) {
37 | out := new(LogResponse)
38 | err := c.cc.Invoke(ctx, "/logs.LogService/WriteLog", in, out, opts...)
39 | if err != nil {
40 | return nil, err
41 | }
42 | return out, nil
43 | }
44 |
45 | // LogServiceServer is the server API for LogService service.
46 | // All implementations must embed UnimplementedLogServiceServer
47 | // for forward compatibility
48 | type LogServiceServer interface {
49 | WriteLog(context.Context, *LogRequest) (*LogResponse, error)
50 | mustEmbedUnimplementedLogServiceServer()
51 | }
52 |
53 | // UnimplementedLogServiceServer must be embedded to have forward compatible implementations.
54 | type UnimplementedLogServiceServer struct {
55 | }
56 |
57 | func (UnimplementedLogServiceServer) WriteLog(context.Context, *LogRequest) (*LogResponse, error) {
58 | return nil, status.Errorf(codes.Unimplemented, "method WriteLog not implemented")
59 | }
60 | func (UnimplementedLogServiceServer) mustEmbedUnimplementedLogServiceServer() {}
61 |
62 | // UnsafeLogServiceServer may be embedded to opt out of forward compatibility for this service.
63 | // Use of this interface is not recommended, as added methods to LogServiceServer will
64 | // result in compilation errors.
65 | type UnsafeLogServiceServer interface {
66 | mustEmbedUnimplementedLogServiceServer()
67 | }
68 |
69 | func RegisterLogServiceServer(s grpc.ServiceRegistrar, srv LogServiceServer) {
70 | s.RegisterService(&LogService_ServiceDesc, srv)
71 | }
72 |
73 | func _LogService_WriteLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
74 | in := new(LogRequest)
75 | if err := dec(in); err != nil {
76 | return nil, err
77 | }
78 | if interceptor == nil {
79 | return srv.(LogServiceServer).WriteLog(ctx, in)
80 | }
81 | info := &grpc.UnaryServerInfo{
82 | Server: srv,
83 | FullMethod: "/logs.LogService/WriteLog",
84 | }
85 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
86 | return srv.(LogServiceServer).WriteLog(ctx, req.(*LogRequest))
87 | }
88 | return interceptor(ctx, in, info, handler)
89 | }
90 |
91 | // LogService_ServiceDesc is the grpc.ServiceDesc for LogService service.
92 | // It's only intended for direct use with grpc.RegisterService,
93 | // and not to be introspected or modified (even as a copy)
94 | var LogService_ServiceDesc = grpc.ServiceDesc{
95 | ServiceName: "logs.LogService",
96 | HandlerType: (*LogServiceServer)(nil),
97 | Methods: []grpc.MethodDesc{
98 | {
99 | MethodName: "WriteLog",
100 | Handler: _LogService_WriteLog_Handler,
101 | },
102 | },
103 | Streams: []grpc.StreamDesc{},
104 | Metadata: "logs.proto",
105 | }
106 |
--------------------------------------------------------------------------------
/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 8081")
18 | err := http.ListenAndServe(":8081", nil)
19 | if err != nil {
20 | log.Panic(err)
21 | }
22 | }
23 |
24 | //go:embed templates
25 | var templateFS embed.FS
26 |
27 | func render(w http.ResponseWriter, t string) {
28 |
29 | partials := []string{
30 | "templates/base.layout.gohtml",
31 | "templates/header.partial.gohtml",
32 | }
33 |
34 | var templateSlice []string
35 | templateSlice = append(templateSlice, fmt.Sprintf("templates/%s", t))
36 |
37 | for _, x := range partials {
38 | templateSlice = append(templateSlice, x)
39 | }
40 |
41 | tmpl, err := template.ParseFS(templateFS, templateSlice...)
42 | if err != nil {
43 | http.Error(w, err.Error(), http.StatusInternalServerError)
44 | return
45 | }
46 |
47 | var data struct {
48 | BrokerURL string
49 | }
50 |
51 | data.BrokerURL = os.Getenv("BROKER_URL")
52 |
53 | if err := tmpl.Execute(w, data); err != nil {
54 | http.Error(w, err.Error(), http.StatusInternalServerError)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/front-end/cmd/web/templates/base.layout.gohtml:
--------------------------------------------------------------------------------
1 | {{define "base" }}
2 |
3 |
4 |
5 | {{template "header" .}}
6 |
7 |
8 |
9 | {{block "content" .}}
10 |
11 | {{end}}
12 |
13 | {{block "js" .}}
14 |
15 | {{end}}
16 |
17 |
18 |
19 |
20 | {{end}}
--------------------------------------------------------------------------------
/front-end/cmd/web/templates/header.partial.gohtml:
--------------------------------------------------------------------------------
1 | {{define "header"}}
2 |
3 |
4 |
5 |
7 |
8 | Microservices in Go
9 |
10 |
11 |
12 |
13 | {{end}}
--------------------------------------------------------------------------------
/front-end/cmd/web/templates/test.page.gohtml:
--------------------------------------------------------------------------------
1 | {{template "base" .}}
2 |
3 | {{define "content" }}
4 |
5 |
20 |
21 |
22 |
Sent
23 |
24 |
Nothing sent yet...
25 |
26 |
27 |
28 |
Received
29 |
30 |
Nothing received yet...
31 |
32 |
33 |
34 |
35 | {{end}}
36 |
37 | {{define "js"}}
38 |
211 | {{end}}
212 |
--------------------------------------------------------------------------------
/front-end/front-end.dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | RUN mkdir /app
4 |
5 | COPY frontEndApp /app
6 |
7 | CMD [ "/app/frontEndApp"]
--------------------------------------------------------------------------------
/front-end/frontEndApp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Furkan-Gulsen/microservices-with-go/0f61805b11cc6137315bbfbbfaf1d95cd9b5a831/front-end/frontEndApp
--------------------------------------------------------------------------------
/front-end/go.mod:
--------------------------------------------------------------------------------
1 | module front-end
2 |
3 | go 1.21.3
4 |
--------------------------------------------------------------------------------
/go.work:
--------------------------------------------------------------------------------
1 | go 1.21.3
2 |
3 | use (
4 | ./broker-service
5 | ./front-end
6 | ./authentication-service
7 | ./logger-service
8 | ./mail-service
9 | ./listener-service
10 | )
11 |
--------------------------------------------------------------------------------
/go.work.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
2 | cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
3 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
4 | github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
5 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
6 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
7 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
8 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
9 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
10 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
11 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
12 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
13 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
14 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
15 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
16 | go.mongodb.org/mongo-driver v1.13.0 h1:67DgFFjYOCMWdtTEmKFpV3ffWlFnh+CYZ8ZS/tXWUfY=
17 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
18 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
19 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
20 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
21 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
22 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
23 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
24 | golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
25 | golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
26 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
27 | golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
28 | golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
29 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
30 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
31 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
32 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
33 |
--------------------------------------------------------------------------------
/k8s/authentication.yml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: authentication-service
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: authentication-service
10 | template:
11 | metadata:
12 | labels:
13 | app: authentication-service
14 | spec:
15 | containers:
16 | - name: authentication-service
17 | image: 'codeblogger/authentication-service:1.0.1'
18 | resources:
19 | requests:
20 | memory: '64Mi'
21 | cpu: '250m'
22 | limits:
23 | memory: '128Mi'
24 | cpu: '500m'
25 | env:
26 | - name: DSN
27 | value: 'host=host.minikube.internal port=5432 user=postgres password=password dbname=users sslmode=disable timezone=UTC connect_timeout=5'
28 | ports:
29 | - containerPort: 80
30 |
31 | ---
32 | apiVersion: v1
33 | kind: Service
34 | metadata:
35 | name: authentication-service
36 | spec:
37 | selector:
38 | app: authentication-service
39 | ports:
40 | - protocol: TCP
41 | name: main-port
42 | port: 80
43 | targetPort: 80
44 |
--------------------------------------------------------------------------------
/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: 'codeblogger/broker-service:1.0.2'
18 | resources:
19 | requests:
20 | memory: '64Mi'
21 | cpu: '250m'
22 | limits:
23 | memory: '128Mi'
24 | cpu: '500m'
25 | ports:
26 | - containerPort: 8080
27 |
28 | ---
29 | apiVersion: v1
30 | kind: Service
31 | metadata:
32 | name: broker-service
33 | spec:
34 | selector:
35 | app: broker-service
36 | ports:
37 | - protocol: TCP
38 | name: main-port
39 | port: 8080
40 | targetPort: 8080
41 |
--------------------------------------------------------------------------------
/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: 'codeblogger/front-end:1.0.3'
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: 8081
30 |
31 | ---
32 | apiVersion: v1
33 | kind: Service
34 | metadata:
35 | name: front-end
36 | spec:
37 | selector:
38 | app: front-end
39 | ports:
40 | - protocol: TCP
41 | name: main-port
42 | port: 8081
43 | targetPort: 8081
44 |
--------------------------------------------------------------------------------
/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: 'codeblogger/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 | apiVersion: v1
30 | kind: Service
31 | metadata:
32 | name: listener-service
33 | spec:
34 | selector:
35 | app: listener-service
36 | ports:
37 | - protocol: TCP
38 | name: web-port
39 | port: 80
40 | targetPort: 80
41 |
--------------------------------------------------------------------------------
/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: 'codeblogger/logger-service:1.0.1'
18 | resources:
19 | requests:
20 | memory: '64Mi'
21 | cpu: '250m'
22 | limits:
23 | memory: '128Mi'
24 | cpu: '500m'
25 | ports:
26 | - containerPort: 80
27 | - containerPort: 5001
28 | - containerPort: 50001
29 |
30 | ---
31 | apiVersion: v1
32 | kind: Service
33 | metadata:
34 | name: logger-service
35 | spec:
36 | selector:
37 | app: logger-service
38 | ports:
39 | - protocol: TCP
40 | name: web-port
41 | port: 80
42 | targetPort: 80
43 | - protocol: TCP
44 | name: rpc-port
45 | port: 5001
46 | targetPort: 5001
47 | - protocol: TCP
48 | name: grpc-port
49 | port: 50001
50 | targetPort: 50001
51 |
--------------------------------------------------------------------------------
/k8s/mail.yml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: mailer-service
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: mailer-service
10 | template:
11 | metadata:
12 | labels:
13 | app: mailer-service
14 | spec:
15 | containers:
16 | - name: mailer-service
17 | image: 'codeblogger/mail-service:1.0.1'
18 | resources:
19 | requests:
20 | memory: '64Mi'
21 | cpu: '250m'
22 | limits:
23 | memory: '128Mi'
24 | cpu: '500m'
25 | env:
26 | - name: MAIL_DOMAIN
27 | value: ''
28 | - name: MAIL_HOST
29 | value: 'localhost'
30 | - name: MAIL_PORT
31 | value: '1025'
32 | - name: MAIL_ENCRYPTION
33 | value: 'none'
34 | - name: MAIL_USERNAME
35 | value: ''
36 | - name: MAIL_PASSWORD
37 | value: ''
38 | - name: FROM_NAME
39 | value: 'John Smith'
40 | - name: FROM_ADDRESS
41 | value: 'admin@example.com'
42 | ports:
43 | - containerPort: 80
44 |
45 | ---
46 | apiVersion: v1
47 | kind: Service
48 | metadata:
49 | name: mailer-service
50 | spec:
51 | selector:
52 | app: mailer-service
53 | ports:
54 | - protocol: TCP
55 | name: main-port
56 | port: 80
57 | targetPort: 80
58 |
--------------------------------------------------------------------------------
/k8s/mailhog.yml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: mailhog
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: mailhog
10 | template:
11 | metadata:
12 | labels:
13 | app: mailhog
14 | spec:
15 | containers:
16 | - name: mailhog
17 | image: "mailhog/mailhog:latest"
18 | resources:
19 | requests:
20 | memory: "64Mi"
21 | cpu: "250m"
22 | limits:
23 | memory: "128Mi"
24 | cpu: "500m"
25 | ports:
26 | - containerPort: 1025
27 | - containerPort: 8025
28 |
29 | ---
30 |
31 | apiVersion: v1
32 | kind: Service
33 | metadata:
34 | name: mailhog
35 | spec:
36 | selector:
37 | app: mailhog
38 | ports:
39 | - protocol: TCP
40 | name: smtp-port
41 | port: 1025
42 | targetPort: 1025
43 | - protocol: TCP
44 | name: web-port
45 | port: 8025
46 | targetPort: 8025
--------------------------------------------------------------------------------
/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 | resources:
19 | requests:
20 | memory: "64Mi"
21 | cpu: "250m"
22 | limits:
23 | memory: "128Mi"
24 | cpu: "500m"
25 | env:
26 | - name: MONGO_INITDB_DATABASE
27 | value: "logs"
28 | - name: MONGO_INITDB_ROOT_USERNAME
29 | value: "admin"
30 | - name: MONGO_INITDB_ROOT_PASSWORD
31 | value: "password"
32 | ports:
33 | - containerPort: 27017
34 |
35 | ---
36 |
37 | apiVersion: v1
38 | kind: Service
39 | metadata:
40 | name: mongo
41 | spec:
42 | selector:
43 | app: mongo
44 | ports:
45 | - protocol: TCP
46 | name: main-port
47 | port: 27017
48 | targetPort: 27017
--------------------------------------------------------------------------------
/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 | resources:
19 | requests:
20 | memory: "128Mi"
21 | cpu: "250m"
22 | limits:
23 | memory: "512Mi"
24 | cpu: "500m"
25 | ports:
26 | - containerPort: 5672
27 |
28 | ---
29 |
30 | apiVersion: v1
31 | kind: Service
32 | metadata:
33 | name: rabbitmq
34 | spec:
35 | selector:
36 | app: rabbitmq
37 | ports:
38 | - protocol: TCP
39 | name: main-port
40 | port: 5672
41 | targetPort: 5672
--------------------------------------------------------------------------------
/listener-service/event/consumer.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "net/http"
9 |
10 | amqp "github.com/rabbitmq/amqp091-go"
11 | )
12 |
13 | type Consumer struct {
14 | conn *amqp.Connection
15 | queueName string
16 | }
17 |
18 | func NewConsumer(conn *amqp.Connection) (Consumer, error) {
19 | consumer := Consumer{
20 | conn: conn,
21 | }
22 |
23 | err := consumer.setup()
24 | if err != nil {
25 | return Consumer{}, err
26 | }
27 |
28 | return consumer, nil
29 | }
30 |
31 | func (consumer *Consumer) setup() error {
32 | channel, err := consumer.conn.Channel()
33 | if err != nil {
34 | return err
35 | }
36 |
37 | return declareExchange(channel)
38 | }
39 |
40 | type Payload struct {
41 | Name string `json:"name"`
42 | Data string `json:"data"`
43 | }
44 |
45 | func (consumer *Consumer) Listen(topics []string) error {
46 | ch, err := consumer.conn.Channel()
47 | if err != nil {
48 | return err
49 | }
50 | defer ch.Close()
51 |
52 | q, err := declareRandomQueue(ch)
53 | if err != nil {
54 | return err
55 | }
56 |
57 | for _, s := range topics {
58 | ch.QueueBind(
59 | q.Name,
60 | s,
61 | "logs_topic",
62 | false,
63 | nil,
64 | )
65 |
66 | if err != nil {
67 | return err
68 | }
69 | }
70 |
71 | messages, err := ch.Consume(q.Name, "", true, false, false, false, nil)
72 | if err != nil {
73 | return err
74 | }
75 |
76 | forever := make(chan bool)
77 | go func() {
78 | for d := range messages {
79 | var payload Payload
80 | _ = json.Unmarshal(d.Body, &payload)
81 |
82 | go handlePayload(payload)
83 | }
84 | }()
85 |
86 | fmt.Printf("Waiting for message [Exchange, Queue] [logs_topic, %s]\n", q.Name)
87 | <-forever
88 |
89 | return nil
90 | }
91 |
92 | func handlePayload(payload Payload) {
93 | switch payload.Name {
94 | case "log", "event":
95 | err := logEvent(payload)
96 | if err != nil {
97 | log.Println(err)
98 | }
99 |
100 | case "auth":
101 |
102 | default:
103 | err := logEvent(payload)
104 | if err != nil {
105 | log.Println(err)
106 | }
107 | }
108 | }
109 |
110 | func logEvent(entry Payload) error {
111 | jsonData, _ := json.MarshalIndent(entry, "", "\t")
112 |
113 | logServiceURL := "http://logger-service/log"
114 |
115 | request, err := http.NewRequest("POST", logServiceURL, bytes.NewBuffer(jsonData))
116 | if err != nil {
117 | return err
118 | }
119 |
120 | request.Header.Set("Content-Type", "application/json")
121 |
122 | client := &http.Client{}
123 |
124 | response, err := client.Do(request)
125 | if err != nil {
126 | return err
127 | }
128 | defer response.Body.Close()
129 |
130 | if response.StatusCode != http.StatusAccepted {
131 | return err
132 | }
133 |
134 | return nil
135 | }
136 |
--------------------------------------------------------------------------------
/listener-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",
10 | "topic",
11 | true,
12 | false,
13 | false,
14 | false,
15 | nil,
16 | )
17 | }
18 |
19 | func declareRandomQueue(ch *amqp.Channel) (amqp.Queue, error) {
20 | return ch.QueueDeclare(
21 | "",
22 | false,
23 | false,
24 | true,
25 | false,
26 | nil,
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/listener-service/go.mod:
--------------------------------------------------------------------------------
1 | module listener
2 |
3 | go 1.21.3
4 |
5 | require github.com/rabbitmq/amqp091-go v1.9.0 // indirect
6 |
--------------------------------------------------------------------------------
/listener-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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
4 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
5 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7 | github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo=
8 | github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
10 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
11 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
12 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
13 | go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
15 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
16 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
17 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
18 |
--------------------------------------------------------------------------------
/listener-service/listener-service.dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | RUN mkdir /app
4 |
5 | COPY listenerApp /app
6 |
7 | CMD [ "/app/listenerApp"]
--------------------------------------------------------------------------------
/listener-service/listenerApp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Furkan-Gulsen/microservices-with-go/0f61805b11cc6137315bbfbbfaf1d95cd9b5a831/listener-service/listenerApp
--------------------------------------------------------------------------------
/listener-service/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "listener/event"
6 | "log"
7 | "math"
8 | "os"
9 | "time"
10 |
11 | amqp "github.com/rabbitmq/amqp091-go"
12 | )
13 |
14 | func main() {
15 | rabbitConn, err := connect()
16 | if err != nil {
17 | log.Println(err)
18 | os.Exit(1)
19 | }
20 | defer rabbitConn.Close()
21 |
22 | log.Println("Listening for and consuming RabbitMQ messages...")
23 |
24 | consumer, err := event.NewConsumer(rabbitConn)
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | err = consumer.Listen([]string{"log.INFO", "log.WARNING", "log.ERROR"})
30 | if err != nil {
31 | log.Println(err)
32 | }
33 | }
34 |
35 | func connect() (*amqp.Connection, error) {
36 | var counts int64
37 | var backOff = 1 * time.Second
38 | var connection *amqp.Connection
39 |
40 | for {
41 | c, err := amqp.Dial("amqp://guest:guest@rabbitmq")
42 | if err != nil {
43 | fmt.Println("RabbitMQ not yet ready...")
44 | counts++
45 | } else {
46 | log.Println("Connected to RabbitMQ!")
47 | connection = c
48 | break
49 | }
50 |
51 | if counts > 5 {
52 | fmt.Println(err)
53 | return nil, err
54 | }
55 |
56 | backOff = time.Duration(math.Pow(float64(counts), 2)) * time.Second
57 | log.Println("backing off...")
58 | time.Sleep(backOff)
59 | continue
60 | }
61 |
62 | return connection, nil
63 | }
64 |
--------------------------------------------------------------------------------
/logger-service/cmd/api/grpc.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "log-service/data"
8 | "log-service/logs"
9 | "net"
10 |
11 | "google.golang.org/grpc"
12 | )
13 |
14 | type LogServer struct {
15 | logs.UnimplementedLogServiceServer
16 | Models data.Models
17 | }
18 |
19 | func (l *LogServer) WriteLog(ctx context.Context, req *logs.LogRequest) (*logs.LogResponse, error) {
20 | input := req.GetLogEntry()
21 |
22 | logEntry := data.LogEntry{
23 | Name: input.Name,
24 | Data: input.Data,
25 | }
26 |
27 | err := l.Models.LogEntry.Insert(logEntry)
28 | if err != nil {
29 | res := &logs.LogResponse{Result: "failed"}
30 | return res, err
31 | }
32 |
33 | res := &logs.LogResponse{Result: "logged!"}
34 | return res, nil
35 | }
36 |
37 | func (app *Config) gRPCListen() {
38 | lis, err := net.Listen("tcp", fmt.Sprintf(":%s", gRpcPort))
39 | if err != nil {
40 | log.Fatalf("Failed to listen for gRPC: %v", err)
41 | }
42 |
43 | s := grpc.NewServer()
44 |
45 | logs.RegisterLogServiceServer(s, &LogServer{Models: app.Models})
46 |
47 | log.Printf("gRPC Server started on port %s", gRpcPort)
48 |
49 | if err := s.Serve(lis); err != nil {
50 | log.Fatalf("Failed to listen for gRPC: %v", err)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/logger-service/cmd/api/handlers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log-service/data"
5 | "net/http"
6 | )
7 |
8 | type JSONPayload struct {
9 | Name string `json:"name"`
10 | Data string `json:"data"`
11 | }
12 |
13 | func (app *Config) WriteLog(w http.ResponseWriter, r *http.Request) {
14 | var requestPayload JSONPayload
15 | _ = app.readJSON(w, r, &requestPayload)
16 |
17 | event := data.LogEntry{
18 | Name: requestPayload.Name,
19 | Data: requestPayload.Data,
20 | }
21 |
22 | err := app.Models.LogEntry.Insert(event)
23 | if err != nil {
24 | app.errorJSON(w, err)
25 | return
26 | }
27 |
28 | resp := jsonResponse{
29 | Error: false,
30 | Message: "logged",
31 | }
32 |
33 | app.writeJSON(w, http.StatusAccepted, resp)
34 | }
35 |
--------------------------------------------------------------------------------
/logger-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 | func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data any) error {
17 | maxBytes := 1048576
18 |
19 | r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
20 |
21 | dec := json.NewDecoder(r.Body)
22 | err := dec.Decode(data)
23 | if err != nil {
24 | return err
25 | }
26 |
27 | err = dec.Decode(&struct{}{})
28 | if err != io.EOF {
29 | return errors.New("body must have only a single JSON value")
30 | }
31 |
32 | return nil
33 | }
34 |
35 | func (app *Config) writeJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
36 | out, err := json.Marshal(data)
37 | if err != nil {
38 | return err
39 | }
40 |
41 | if len(headers) > 0 {
42 | for key, value := range headers[0] {
43 | w.Header()[key] = value
44 | }
45 | }
46 |
47 | w.Header().Set("Content-Type", "application/json")
48 | w.Header().Set("Access-Control-Allow-Origin", "*")
49 | w.WriteHeader(status)
50 | _, err = w.Write(out)
51 | if err != nil {
52 | return err
53 | }
54 |
55 | return nil
56 | }
57 |
58 | func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
59 | statusCode := http.StatusBadRequest
60 |
61 | if len(status) > 0 {
62 | statusCode = status[0]
63 | }
64 |
65 | var payload jsonResponse
66 | payload.Error = true
67 | payload.Message = err.Error()
68 |
69 | return app.writeJSON(w, statusCode, payload)
70 | }
71 |
--------------------------------------------------------------------------------
/logger-service/cmd/api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "log-service/data"
8 | "net"
9 | "net/http"
10 | "net/rpc"
11 | "time"
12 |
13 | "go.mongodb.org/mongo-driver/mongo"
14 | "go.mongodb.org/mongo-driver/mongo/options"
15 | )
16 |
17 | const (
18 | webPort = "80"
19 | rpcPort = "5001"
20 | mongoURL = "mongodb://mongo:27017"
21 | gRpcPort = "50001"
22 | )
23 |
24 | var client *mongo.Client
25 |
26 | type Config struct {
27 | Models data.Models
28 | }
29 |
30 | func main() {
31 | mongoClient, err := connectToMongo()
32 | if err != nil {
33 | log.Panic(err)
34 | }
35 | client = mongoClient
36 |
37 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
38 | defer cancel()
39 |
40 | defer func() {
41 | if err = client.Disconnect(ctx); err != nil {
42 | panic(err)
43 | }
44 | }()
45 |
46 | app := Config{
47 | Models: data.New(client),
48 | }
49 |
50 | err = rpc.Register(new(RPCServer))
51 | go app.rpcListen()
52 |
53 | go app.gRPCListen()
54 |
55 | // start web server
56 | log.Println("Starting service on port", webPort)
57 | srv := &http.Server{
58 | Addr: fmt.Sprintf(":%s", webPort),
59 | Handler: app.routes(),
60 | }
61 |
62 | err = srv.ListenAndServe()
63 | if err != nil {
64 | log.Panic()
65 | }
66 |
67 | }
68 |
69 | func (app *Config) rpcListen() error {
70 | log.Println("Starting RPC server on port ", rpcPort)
71 | listen, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", rpcPort))
72 | if err != nil {
73 | return err
74 | }
75 | defer listen.Close()
76 |
77 | for {
78 | rpcConn, err := listen.Accept()
79 | if err != nil {
80 | continue
81 | }
82 | go rpc.ServeConn(rpcConn)
83 | }
84 |
85 | }
86 |
87 | func connectToMongo() (*mongo.Client, error) {
88 | clientOptions := options.Client().ApplyURI(mongoURL)
89 | clientOptions.SetAuth(options.Credential{
90 | Username: "admin",
91 | Password: "password",
92 | })
93 |
94 | c, err := mongo.Connect(context.TODO(), clientOptions)
95 | if err != nil {
96 | log.Println("Error connecting:", err)
97 | return nil, err
98 | }
99 |
100 | log.Println("Connected to mongo!")
101 |
102 | return c, nil
103 | }
104 |
--------------------------------------------------------------------------------
/logger-service/cmd/api/routes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/go-chi/chi/v5"
7 | "github.com/go-chi/chi/v5/middleware"
8 | "github.com/go-chi/cors"
9 | )
10 |
11 | func (app *Config) routes() http.Handler {
12 | mux := chi.NewRouter()
13 |
14 | mux.Use(cors.Handler(cors.Options{
15 | AllowedOrigins: []string{"https://*", "http://*"},
16 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
17 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
18 | ExposedHeaders: []string{"Link"},
19 | AllowCredentials: true,
20 | MaxAge: 300,
21 | }))
22 |
23 | mux.Use(middleware.Heartbeat("/ping"))
24 |
25 | mux.Post("/log", app.WriteLog)
26 |
27 | return mux
28 | }
29 |
--------------------------------------------------------------------------------
/logger-service/cmd/api/rpc.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log"
6 | "log-service/data"
7 | "time"
8 | )
9 |
10 | type RPCServer struct{}
11 |
12 | type RPCPayload struct {
13 | Name string
14 | Data string
15 | }
16 |
17 | func (r *RPCServer) LogInfo(payload RPCPayload, resp *string) error {
18 | collection := client.Database("logs").Collection("logs")
19 | _, err := collection.InsertOne(context.TODO(), data.LogEntry{
20 | Name: payload.Name,
21 | Data: payload.Data,
22 | CreatedAt: time.Now(),
23 | })
24 | if err != nil {
25 | log.Println("error writing to mongo", err)
26 | return err
27 | }
28 |
29 | *resp = "Processed payload via RPC:" + payload.Name
30 | return nil
31 | }
32 |
--------------------------------------------------------------------------------
/logger-service/data/models.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "context"
5 | "log"
6 | "time"
7 |
8 | "go.mongodb.org/mongo-driver/bson"
9 | "go.mongodb.org/mongo-driver/bson/primitive"
10 | "go.mongodb.org/mongo-driver/mongo"
11 | "go.mongodb.org/mongo-driver/mongo/options"
12 | )
13 |
14 | var client *mongo.Client
15 |
16 | func New(mongo *mongo.Client) Models {
17 | client = mongo
18 |
19 | return Models{
20 | LogEntry: LogEntry{},
21 | }
22 | }
23 |
24 | type Models struct {
25 | LogEntry LogEntry
26 | }
27 |
28 | type LogEntry struct {
29 | ID string `bson:"_id,omitempty" json:"id,omitempty"`
30 | Name string `bson:"name" json:"name"`
31 | Data string `bson:"data" json:"data"`
32 | CreatedAt time.Time `bson:"created_at" json:"created_at"`
33 | UpdatedAt time.Time `bson:"updated_at" json:"updated_at"`
34 | }
35 |
36 | func (l *LogEntry) Insert(entry LogEntry) error {
37 | collection := client.Database("logs").Collection("logs")
38 |
39 | _, err := collection.InsertOne(context.TODO(), LogEntry{
40 | Name: entry.Name,
41 | Data: entry.Data,
42 | CreatedAt: time.Now(),
43 | UpdatedAt: time.Now(),
44 | })
45 | if err != nil {
46 | log.Println("Error inserting into logs:", err)
47 | return err
48 | }
49 |
50 | return nil
51 | }
52 |
53 | func (l *LogEntry) All() ([]*LogEntry, error) {
54 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
55 | defer cancel()
56 |
57 | collection := client.Database("logs").Collection("logs")
58 |
59 | opts := options.Find()
60 | opts.SetSort(bson.D{{"created_at", -1}})
61 |
62 | cursor, err := collection.Find(context.TODO(), bson.D{}, opts)
63 | if err != nil {
64 | log.Println("Finding all docs error:", err)
65 | return nil, err
66 | }
67 | defer cursor.Close(ctx)
68 |
69 | var logs []*LogEntry
70 |
71 | for cursor.Next(ctx) {
72 | var item LogEntry
73 |
74 | err := cursor.Decode(&item)
75 | if err != nil {
76 | log.Print("Error decoding log into slice:", err)
77 | return nil, err
78 | } else {
79 | logs = append(logs, &item)
80 | }
81 | }
82 |
83 | return logs, nil
84 | }
85 |
86 | func (l *LogEntry) GetOne(id string) (*LogEntry, error) {
87 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
88 | defer cancel()
89 |
90 | collection := client.Database("logs").Collection("logs")
91 |
92 | docID, err := primitive.ObjectIDFromHex(id)
93 | if err != nil {
94 | return nil, err
95 | }
96 |
97 | var entry LogEntry
98 | err = collection.FindOne(ctx, bson.M{"_id": docID}).Decode(&entry)
99 | if err != nil {
100 | return nil, err
101 | }
102 |
103 | return &entry, nil
104 | }
105 |
106 | func (l *LogEntry) DropCollection() error {
107 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
108 | defer cancel()
109 |
110 | collection := client.Database("logs").Collection("logs")
111 |
112 | if err := collection.Drop(ctx); err != nil {
113 | return err
114 | }
115 |
116 | return nil
117 | }
118 |
119 | func (l *LogEntry) Update() (*mongo.UpdateResult, error) {
120 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
121 | defer cancel()
122 |
123 | collection := client.Database("logs").Collection("logs")
124 |
125 | docID, err := primitive.ObjectIDFromHex(l.ID)
126 | if err != nil {
127 | return nil, err
128 | }
129 |
130 | result, err := collection.UpdateOne(
131 | ctx,
132 | bson.M{"_id": docID},
133 | bson.D{
134 | {"$set", bson.D{
135 | {"name", l.Name},
136 | {"data", l.Data},
137 | {"updated_at", time.Now()},
138 | }},
139 | },
140 | )
141 |
142 | if err != nil {
143 | return nil, err
144 | }
145 |
146 | return result, nil
147 | }
148 |
--------------------------------------------------------------------------------
/logger-service/go.mod:
--------------------------------------------------------------------------------
1 | module log-service
2 |
3 | go 1.21.3
4 |
5 | require (
6 | github.com/golang/protobuf v1.5.3 // indirect
7 | github.com/golang/snappy v0.0.1 // indirect
8 | github.com/klauspost/compress v1.13.6 // indirect
9 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
10 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect
11 | github.com/xdg-go/scram v1.1.2 // indirect
12 | github.com/xdg-go/stringprep v1.0.4 // indirect
13 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
14 | go.mongodb.org/mongo-driver v1.13.0 // indirect
15 | golang.org/x/crypto v0.12.0 // indirect
16 | golang.org/x/net v0.14.0 // indirect
17 | golang.org/x/sync v0.3.0 // indirect
18 | golang.org/x/sys v0.11.0 // indirect
19 | golang.org/x/text v0.12.0 // indirect
20 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
21 | google.golang.org/grpc v1.59.0 // indirect
22 | google.golang.org/protobuf v1.31.0 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/logger-service/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
3 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
4 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
5 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
6 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
7 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
8 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
9 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
10 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
11 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
12 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
13 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
14 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
15 | go.mongodb.org/mongo-driver v1.13.0/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
16 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
17 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
18 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
19 | golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
20 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
21 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
22 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
23 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
24 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
25 | golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
26 | golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
27 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
28 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
29 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
30 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
31 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
32 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
33 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
34 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
35 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
36 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
37 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
38 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
39 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
40 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
41 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
42 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
43 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
44 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
45 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
46 | golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
47 | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
48 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
49 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
50 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
51 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
52 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
53 | google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
54 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
55 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
56 | google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
57 | google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
58 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
59 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
60 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
61 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
62 |
--------------------------------------------------------------------------------
/logger-service/logger-service.dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | RUN mkdir /app
4 |
5 | COPY loggerServiceApp /app
6 |
7 | CMD [ "/app/loggerServiceApp"]
--------------------------------------------------------------------------------
/logger-service/loggerServiceApp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Furkan-Gulsen/microservices-with-go/0f61805b11cc6137315bbfbbfaf1d95cd9b5a831/logger-service/loggerServiceApp
--------------------------------------------------------------------------------
/logger-service/logs/logs.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.27.1
4 | // protoc v3.20.0
5 | // source: logs.proto
6 |
7 | package logs
8 |
9 | import (
10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 | reflect "reflect"
13 | sync "sync"
14 | )
15 |
16 | const (
17 | // Verify that this generated code is sufficiently up-to-date.
18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
19 | // Verify that runtime/protoimpl is sufficiently up-to-date.
20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
21 | )
22 |
23 | type Log struct {
24 | state protoimpl.MessageState
25 | sizeCache protoimpl.SizeCache
26 | unknownFields protoimpl.UnknownFields
27 |
28 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
29 | Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
30 | }
31 |
32 | func (x *Log) Reset() {
33 | *x = Log{}
34 | if protoimpl.UnsafeEnabled {
35 | mi := &file_logs_proto_msgTypes[0]
36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
37 | ms.StoreMessageInfo(mi)
38 | }
39 | }
40 |
41 | func (x *Log) String() string {
42 | return protoimpl.X.MessageStringOf(x)
43 | }
44 |
45 | func (*Log) ProtoMessage() {}
46 |
47 | func (x *Log) ProtoReflect() protoreflect.Message {
48 | mi := &file_logs_proto_msgTypes[0]
49 | if protoimpl.UnsafeEnabled && x != nil {
50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
51 | if ms.LoadMessageInfo() == nil {
52 | ms.StoreMessageInfo(mi)
53 | }
54 | return ms
55 | }
56 | return mi.MessageOf(x)
57 | }
58 |
59 | // Deprecated: Use Log.ProtoReflect.Descriptor instead.
60 | func (*Log) Descriptor() ([]byte, []int) {
61 | return file_logs_proto_rawDescGZIP(), []int{0}
62 | }
63 |
64 | func (x *Log) GetName() string {
65 | if x != nil {
66 | return x.Name
67 | }
68 | return ""
69 | }
70 |
71 | func (x *Log) GetData() string {
72 | if x != nil {
73 | return x.Data
74 | }
75 | return ""
76 | }
77 |
78 | type LogRequest struct {
79 | state protoimpl.MessageState
80 | sizeCache protoimpl.SizeCache
81 | unknownFields protoimpl.UnknownFields
82 |
83 | LogEntry *Log `protobuf:"bytes,1,opt,name=logEntry,proto3" json:"logEntry,omitempty"`
84 | }
85 |
86 | func (x *LogRequest) Reset() {
87 | *x = LogRequest{}
88 | if protoimpl.UnsafeEnabled {
89 | mi := &file_logs_proto_msgTypes[1]
90 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
91 | ms.StoreMessageInfo(mi)
92 | }
93 | }
94 |
95 | func (x *LogRequest) String() string {
96 | return protoimpl.X.MessageStringOf(x)
97 | }
98 |
99 | func (*LogRequest) ProtoMessage() {}
100 |
101 | func (x *LogRequest) ProtoReflect() protoreflect.Message {
102 | mi := &file_logs_proto_msgTypes[1]
103 | if protoimpl.UnsafeEnabled && x != nil {
104 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
105 | if ms.LoadMessageInfo() == nil {
106 | ms.StoreMessageInfo(mi)
107 | }
108 | return ms
109 | }
110 | return mi.MessageOf(x)
111 | }
112 |
113 | // Deprecated: Use LogRequest.ProtoReflect.Descriptor instead.
114 | func (*LogRequest) Descriptor() ([]byte, []int) {
115 | return file_logs_proto_rawDescGZIP(), []int{1}
116 | }
117 |
118 | func (x *LogRequest) GetLogEntry() *Log {
119 | if x != nil {
120 | return x.LogEntry
121 | }
122 | return nil
123 | }
124 |
125 | type LogResponse struct {
126 | state protoimpl.MessageState
127 | sizeCache protoimpl.SizeCache
128 | unknownFields protoimpl.UnknownFields
129 |
130 | Result string `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"`
131 | }
132 |
133 | func (x *LogResponse) Reset() {
134 | *x = LogResponse{}
135 | if protoimpl.UnsafeEnabled {
136 | mi := &file_logs_proto_msgTypes[2]
137 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
138 | ms.StoreMessageInfo(mi)
139 | }
140 | }
141 |
142 | func (x *LogResponse) String() string {
143 | return protoimpl.X.MessageStringOf(x)
144 | }
145 |
146 | func (*LogResponse) ProtoMessage() {}
147 |
148 | func (x *LogResponse) ProtoReflect() protoreflect.Message {
149 | mi := &file_logs_proto_msgTypes[2]
150 | if protoimpl.UnsafeEnabled && x != nil {
151 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
152 | if ms.LoadMessageInfo() == nil {
153 | ms.StoreMessageInfo(mi)
154 | }
155 | return ms
156 | }
157 | return mi.MessageOf(x)
158 | }
159 |
160 | // Deprecated: Use LogResponse.ProtoReflect.Descriptor instead.
161 | func (*LogResponse) Descriptor() ([]byte, []int) {
162 | return file_logs_proto_rawDescGZIP(), []int{2}
163 | }
164 |
165 | func (x *LogResponse) GetResult() string {
166 | if x != nil {
167 | return x.Result
168 | }
169 | return ""
170 | }
171 |
172 | var File_logs_proto protoreflect.FileDescriptor
173 |
174 | var file_logs_proto_rawDesc = []byte{
175 | 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6c, 0x6f,
176 | 0x67, 0x73, 0x22, 0x2d, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
177 | 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a,
178 | 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74,
179 | 0x61, 0x22, 0x33, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
180 | 0x25, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
181 | 0x0b, 0x32, 0x09, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x08, 0x6c, 0x6f,
182 | 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x25, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73,
183 | 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18,
184 | 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32, 0x3d, 0x0a,
185 | 0x0a, 0x4c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x08, 0x57,
186 | 0x72, 0x69, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x12, 0x10, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x4c,
187 | 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x6c, 0x6f, 0x67, 0x73,
188 | 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x07, 0x5a, 0x05,
189 | 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
190 | }
191 |
192 | var (
193 | file_logs_proto_rawDescOnce sync.Once
194 | file_logs_proto_rawDescData = file_logs_proto_rawDesc
195 | )
196 |
197 | func file_logs_proto_rawDescGZIP() []byte {
198 | file_logs_proto_rawDescOnce.Do(func() {
199 | file_logs_proto_rawDescData = protoimpl.X.CompressGZIP(file_logs_proto_rawDescData)
200 | })
201 | return file_logs_proto_rawDescData
202 | }
203 |
204 | var file_logs_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
205 | var file_logs_proto_goTypes = []interface{}{
206 | (*Log)(nil), // 0: logs.Log
207 | (*LogRequest)(nil), // 1: logs.LogRequest
208 | (*LogResponse)(nil), // 2: logs.LogResponse
209 | }
210 | var file_logs_proto_depIdxs = []int32{
211 | 0, // 0: logs.LogRequest.logEntry:type_name -> logs.Log
212 | 1, // 1: logs.LogService.WriteLog:input_type -> logs.LogRequest
213 | 2, // 2: logs.LogService.WriteLog:output_type -> logs.LogResponse
214 | 2, // [2:3] is the sub-list for method output_type
215 | 1, // [1:2] is the sub-list for method input_type
216 | 1, // [1:1] is the sub-list for extension type_name
217 | 1, // [1:1] is the sub-list for extension extendee
218 | 0, // [0:1] is the sub-list for field type_name
219 | }
220 |
221 | func init() { file_logs_proto_init() }
222 | func file_logs_proto_init() {
223 | if File_logs_proto != nil {
224 | return
225 | }
226 | if !protoimpl.UnsafeEnabled {
227 | file_logs_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
228 | switch v := v.(*Log); i {
229 | case 0:
230 | return &v.state
231 | case 1:
232 | return &v.sizeCache
233 | case 2:
234 | return &v.unknownFields
235 | default:
236 | return nil
237 | }
238 | }
239 | file_logs_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
240 | switch v := v.(*LogRequest); i {
241 | case 0:
242 | return &v.state
243 | case 1:
244 | return &v.sizeCache
245 | case 2:
246 | return &v.unknownFields
247 | default:
248 | return nil
249 | }
250 | }
251 | file_logs_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
252 | switch v := v.(*LogResponse); i {
253 | case 0:
254 | return &v.state
255 | case 1:
256 | return &v.sizeCache
257 | case 2:
258 | return &v.unknownFields
259 | default:
260 | return nil
261 | }
262 | }
263 | }
264 | type x struct{}
265 | out := protoimpl.TypeBuilder{
266 | File: protoimpl.DescBuilder{
267 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
268 | RawDescriptor: file_logs_proto_rawDesc,
269 | NumEnums: 0,
270 | NumMessages: 3,
271 | NumExtensions: 0,
272 | NumServices: 1,
273 | },
274 | GoTypes: file_logs_proto_goTypes,
275 | DependencyIndexes: file_logs_proto_depIdxs,
276 | MessageInfos: file_logs_proto_msgTypes,
277 | }.Build()
278 | File_logs_proto = out.File
279 | file_logs_proto_rawDesc = nil
280 | file_logs_proto_goTypes = nil
281 | file_logs_proto_depIdxs = nil
282 | }
283 |
--------------------------------------------------------------------------------
/logger-service/logs/logs.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package logs;
4 |
5 | option go_package = "/logs";
6 |
7 | message Log {
8 | string name = 1;
9 | string data = 2;
10 | }
11 |
12 | message LogRequest {
13 | Log logEntry = 1;
14 | }
15 |
16 | message LogResponse {
17 | string result = 1;
18 | }
19 |
20 | service LogService {
21 | rpc WriteLog(LogRequest) returns (LogResponse);
22 | }
--------------------------------------------------------------------------------
/logger-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.20.0
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 | WriteLog(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error)
26 | }
27 |
28 | type logServiceClient struct {
29 | cc grpc.ClientConnInterface
30 | }
31 |
32 | func NewLogServiceClient(cc grpc.ClientConnInterface) LogServiceClient {
33 | return &logServiceClient{cc}
34 | }
35 |
36 | func (c *logServiceClient) WriteLog(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error) {
37 | out := new(LogResponse)
38 | err := c.cc.Invoke(ctx, "/logs.LogService/WriteLog", in, out, opts...)
39 | if err != nil {
40 | return nil, err
41 | }
42 | return out, nil
43 | }
44 |
45 | // LogServiceServer is the server API for LogService service.
46 | // All implementations must embed UnimplementedLogServiceServer
47 | // for forward compatibility
48 | type LogServiceServer interface {
49 | WriteLog(context.Context, *LogRequest) (*LogResponse, error)
50 | mustEmbedUnimplementedLogServiceServer()
51 | }
52 |
53 | // UnimplementedLogServiceServer must be embedded to have forward compatible implementations.
54 | type UnimplementedLogServiceServer struct {
55 | }
56 |
57 | func (UnimplementedLogServiceServer) WriteLog(context.Context, *LogRequest) (*LogResponse, error) {
58 | return nil, status.Errorf(codes.Unimplemented, "method WriteLog not implemented")
59 | }
60 | func (UnimplementedLogServiceServer) mustEmbedUnimplementedLogServiceServer() {}
61 |
62 | // UnsafeLogServiceServer may be embedded to opt out of forward compatibility for this service.
63 | // Use of this interface is not recommended, as added methods to LogServiceServer will
64 | // result in compilation errors.
65 | type UnsafeLogServiceServer interface {
66 | mustEmbedUnimplementedLogServiceServer()
67 | }
68 |
69 | func RegisterLogServiceServer(s grpc.ServiceRegistrar, srv LogServiceServer) {
70 | s.RegisterService(&LogService_ServiceDesc, srv)
71 | }
72 |
73 | func _LogService_WriteLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
74 | in := new(LogRequest)
75 | if err := dec(in); err != nil {
76 | return nil, err
77 | }
78 | if interceptor == nil {
79 | return srv.(LogServiceServer).WriteLog(ctx, in)
80 | }
81 | info := &grpc.UnaryServerInfo{
82 | Server: srv,
83 | FullMethod: "/logs.LogService/WriteLog",
84 | }
85 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
86 | return srv.(LogServiceServer).WriteLog(ctx, req.(*LogRequest))
87 | }
88 | return interceptor(ctx, in, info, handler)
89 | }
90 |
91 | // LogService_ServiceDesc is the grpc.ServiceDesc for LogService service.
92 | // It's only intended for direct use with grpc.RegisterService,
93 | // and not to be introspected or modified (even as a copy)
94 | var LogService_ServiceDesc = grpc.ServiceDesc{
95 | ServiceName: "logs.LogService",
96 | HandlerType: (*LogServiceServer)(nil),
97 | Methods: []grpc.MethodDesc{
98 | {
99 | MethodName: "WriteLog",
100 | Handler: _LogService_WriteLog_Handler,
101 | },
102 | },
103 | Streams: []grpc.StreamDesc{},
104 | Metadata: "logs.proto",
105 | }
106 |
--------------------------------------------------------------------------------
/mail-service/cmd/api/handlers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "net/http"
4 |
5 | func (app *Config) SendMail(w http.ResponseWriter, r *http.Request) {
6 | type mailMessage struct {
7 | From string `json:"from"`
8 | To string `json:"to"`
9 | Subject string `json:"subject"`
10 | Message string `json:"message"`
11 | }
12 |
13 | var requestPayload mailMessage
14 |
15 | err := app.readJSON(w, r, &requestPayload)
16 | if err != nil {
17 | app.errorJSON(w, err)
18 | return
19 | }
20 |
21 | msg := Message {
22 | From: requestPayload.From,
23 | To: requestPayload.To,
24 | Subject: requestPayload.Subject,
25 | Data: requestPayload.Message,
26 | }
27 |
28 | err = app.Mailer.SendSMTPMessage(msg)
29 | if err != nil {
30 | app.errorJSON(w, err)
31 | return
32 | }
33 |
34 | payload := jsonResponse {
35 | Error: false,
36 | Message: "sent to " + requestPayload.To,
37 | }
38 |
39 | app.writeJSON(w, http.StatusAccepted, payload)
40 | }
41 |
--------------------------------------------------------------------------------
/mail-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 | func (app *Config) readJSON(w http.ResponseWriter, r *http.Request, data any) error {
17 | maxBytes := 1048576
18 |
19 | r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
20 |
21 | dec := json.NewDecoder(r.Body)
22 | err := dec.Decode(data)
23 | if err != nil {
24 | return err
25 | }
26 |
27 | err = dec.Decode(&struct{}{})
28 | if err != io.EOF {
29 | return errors.New("body must have only a single JSON value")
30 | }
31 |
32 | return nil
33 | }
34 |
35 | func (app *Config) writeJSON(w http.ResponseWriter, status int, data any, headers ...http.Header) error {
36 | out, err := json.Marshal(data)
37 | if err != nil {
38 | return err
39 | }
40 |
41 | if len(headers) > 0 {
42 | for key, value := range headers[0] {
43 | w.Header()[key] = value
44 | }
45 | }
46 |
47 | w.Header().Set("Content-Type", "application/json")
48 | w.Header().Set("Access-Control-Allow-Origin", "*")
49 | w.WriteHeader(status)
50 | _, err = w.Write(out)
51 | if err != nil {
52 | return err
53 | }
54 |
55 | return nil
56 | }
57 |
58 | func (app *Config) errorJSON(w http.ResponseWriter, err error, status ...int) error {
59 | statusCode := http.StatusBadRequest
60 |
61 | if len(status) > 0 {
62 | statusCode = status[0]
63 | }
64 |
65 | var payload jsonResponse
66 | payload.Error = true
67 | payload.Message = err.Error()
68 |
69 | return app.writeJSON(w, statusCode, payload)
70 | }
71 |
--------------------------------------------------------------------------------
/mail-service/cmd/api/mailer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "html/template"
6 | "time"
7 |
8 | "github.com/vanng822/go-premailer/premailer"
9 | mail "github.com/xhit/go-simple-mail/v2"
10 | )
11 |
12 | type Mail struct {
13 | Domain string
14 | Host string
15 | Port int
16 | Username string
17 | Password string
18 | Encryption string
19 | FromAddress string
20 | FromName string
21 | }
22 |
23 | type Message struct {
24 | From string
25 | FromName string
26 | To string
27 | Subject string
28 | Attachments []string
29 | Data any
30 | DataMap map[string]any
31 | }
32 |
33 | func (m *Mail) SendSMTPMessage(msg Message) error {
34 | if msg.From == "" {
35 | msg.From = m.FromAddress
36 | }
37 |
38 | if msg.FromName == "" {
39 | msg.FromName = m.FromName
40 | }
41 |
42 | data := map[string]any {
43 | "message": msg.Data,
44 | }
45 |
46 | msg.DataMap = data
47 |
48 | formattedMessage, err := m.buildHTMLMessage(msg)
49 | if err != nil {
50 | return err
51 | }
52 |
53 | plainMessage, err := m.buildPlainTextMessage(msg)
54 | if err != nil {
55 | return err
56 | }
57 |
58 | server := mail.NewSMTPClient()
59 | server.Host = m.Host
60 | server.Port = m.Port
61 | server.Username = m.Username
62 | server.Password = m.Password
63 | server.Encryption = m.getEncryption(m.Encryption)
64 | server.KeepAlive = false
65 | server.ConnectTimeout = 10 * time.Second
66 | server.SendTimeout = 10 * time.Second
67 |
68 | smtpClient, err := server.Connect()
69 | if err != nil {
70 | return err
71 | }
72 |
73 | email := mail.NewMSG()
74 | email.SetFrom(msg.From).
75 | AddTo(msg.To).
76 | SetSubject(msg.Subject)
77 |
78 | email.SetBody(mail.TextPlain, plainMessage)
79 | email.AddAlternative(mail.TextHTML, formattedMessage)
80 |
81 | if len(msg.Attachments) > 0 {
82 | for _, x := range msg.Attachments {
83 | email.AddAttachment(x)
84 | }
85 | }
86 |
87 | err = email.Send(smtpClient)
88 | if err != nil {
89 | return err
90 | }
91 |
92 | return nil
93 | }
94 |
95 | func (m *Mail) buildHTMLMessage(msg Message) (string, error) {
96 | templateToRender := "./templates/mail.html.gohtml"
97 |
98 | t, err := template.New("email-html").ParseFiles(templateToRender)
99 | if err != nil {
100 | return "", err
101 | }
102 |
103 | var tpl bytes.Buffer
104 | if err = t.ExecuteTemplate(&tpl, "body", msg.DataMap); err != nil {
105 | return "", err
106 | }
107 |
108 | formattedMessage := tpl.String()
109 | formattedMessage, err = m.inlineCSS(formattedMessage)
110 | if err != nil {
111 | return "", err
112 | }
113 |
114 | return formattedMessage, nil
115 | }
116 |
117 | func (m *Mail) buildPlainTextMessage(msg Message) (string, error) {
118 | templateToRender := "./templates/mail.plain.gohtml"
119 |
120 | t, err := template.New("email-plain").ParseFiles(templateToRender)
121 | if err != nil {
122 | return "", err
123 | }
124 |
125 | var tpl bytes.Buffer
126 | if err = t.ExecuteTemplate(&tpl, "body", msg.DataMap); err != nil {
127 | return "", err
128 | }
129 |
130 | plainMessage := tpl.String()
131 |
132 | return plainMessage, nil
133 | }
134 |
135 | func (m *Mail) inlineCSS(s string) (string, error) {
136 | options := premailer.Options{
137 | RemoveClasses: false,
138 | CssToAttributes: false,
139 | KeepBangImportant: true,
140 | }
141 |
142 | prem, err := premailer.NewPremailerFromString(s, &options)
143 | if err != nil {
144 | return "", err
145 | }
146 |
147 | html, err := prem.Transform()
148 | if err != nil {
149 | return "", err
150 | }
151 |
152 | return html, nil
153 | }
154 |
155 | func (m *Mail) getEncryption(s string) mail.Encryption {
156 | switch s {
157 | case "tls":
158 | return mail.EncryptionSTARTTLS
159 | case "ssl":
160 | return mail.EncryptionSSLTLS
161 | case "none", "":
162 | return mail.EncryptionNone
163 | default:
164 | return mail.EncryptionSTARTTLS
165 | }
166 | }
--------------------------------------------------------------------------------
/mail-service/cmd/api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "os"
8 | "strconv"
9 | )
10 |
11 | type Config struct {
12 | Mailer Mail
13 | }
14 |
15 | const webPort = "80"
16 |
17 | func main() {
18 | app := Config{
19 | Mailer: createMail(),
20 | }
21 |
22 | log.Println("Starting mail service on port", webPort)
23 |
24 | srv := &http.Server{
25 | Addr: fmt.Sprintf(":%s", webPort),
26 | Handler: app.routes(),
27 | }
28 |
29 | err := srv.ListenAndServe()
30 | if err != nil {
31 | log.Panic(err)
32 | }
33 | }
34 |
35 | func createMail() Mail {
36 | port, _ := strconv.Atoi(os.Getenv("MAIL_PORT"))
37 | m := Mail{
38 | Domain: os.Getenv("MAIL_DOMAIN"),
39 | Host: os.Getenv("MAIL_HOST"),
40 | Port: port,
41 | Username: os.Getenv("MAIL_USERNAME"),
42 | Password: os.Getenv("MAIL_PASSWORD"),
43 | Encryption: os.Getenv("MAIL_ENCRYPTION"),
44 | FromName: os.Getenv("FROM_NAME"),
45 | FromAddress: os.Getenv("FROM_ADDRESS"),
46 | }
47 |
48 | return m
49 | }
50 |
--------------------------------------------------------------------------------
/mail-service/cmd/api/routes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/go-chi/chi/v5"
7 | "github.com/go-chi/chi/v5/middleware"
8 | "github.com/go-chi/cors"
9 | )
10 |
11 | func (app *Config) routes() http.Handler {
12 | mux := chi.NewRouter()
13 |
14 | mux.Use(cors.Handler(cors.Options{
15 | AllowedOrigins: []string{"https://*", "http://*"},
16 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
17 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
18 | ExposedHeaders: []string{"Link"},
19 | AllowCredentials: true,
20 | MaxAge: 300,
21 | }))
22 |
23 | mux.Use(middleware.Heartbeat("/ping"))
24 |
25 | mux.Post("/send", app.SendMail)
26 |
27 | return mux
28 | }
29 |
--------------------------------------------------------------------------------
/mail-service/go.mod:
--------------------------------------------------------------------------------
1 | module mailer-service
2 |
3 | go 1.21.3
4 |
5 | require (
6 | github.com/go-chi/chi/v5 v5.0.10
7 | github.com/go-chi/cors v1.2.1
8 | )
9 |
10 | require (
11 | github.com/PuerkitoBio/goquery v1.5.1 // indirect
12 | github.com/andybalholm/cascadia v1.1.0 // indirect
13 | github.com/gorilla/css v1.0.0 // indirect
14 | github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
15 | github.com/vanng822/css v1.0.1 // indirect
16 | github.com/vanng822/go-premailer v1.20.2 // indirect
17 | github.com/xhit/go-simple-mail/v2 v2.16.0 // indirect
18 | golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
19 | )
20 |
--------------------------------------------------------------------------------
/mail-service/go.sum:
--------------------------------------------------------------------------------
1 | github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
2 | github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
3 | github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
4 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
7 | github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
8 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
9 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
10 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
13 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
14 | github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
15 | github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
16 | github.com/unrolled/render v1.0.3/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM=
17 | github.com/vanng822/css v1.0.1 h1:10yiXc4e8NI8ldU6mSrWmSWMuyWgPr9DZ63RSlsgDw8=
18 | github.com/vanng822/css v1.0.1/go.mod h1:tcnB1voG49QhCrwq1W0w5hhGasvOg+VQp9i9H1rCM1w=
19 | github.com/vanng822/go-premailer v1.20.2 h1:vKs4VdtfXDqL7IXC2pkiBObc1bXM9bYH3Wa+wYw2DnI=
20 | github.com/vanng822/go-premailer v1.20.2/go.mod h1:RAxbRFp6M/B171gsKu8dsyq+Y5NGsUUvYfg+WQWusbE=
21 | github.com/vanng822/r2router v0.0.0-20150523112421-1023140a4f30/go.mod h1:1BVq8p2jVr55Ost2PkZWDrG86PiJ/0lxqcXoAcGxvWU=
22 | github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
23 | github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
24 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
25 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
26 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
27 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
28 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
29 | golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
30 | golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
31 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
32 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
33 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
34 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
35 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
36 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
37 |
--------------------------------------------------------------------------------
/mail-service/mail-service.dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | RUN mkdir /app
4 |
5 | COPY mailerApp /app
6 | COPY templates /templates
7 |
8 | CMD [ "/app/mailerApp"]
--------------------------------------------------------------------------------
/mail-service/mailerApp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Furkan-Gulsen/microservices-with-go/0f61805b11cc6137315bbfbbfaf1d95cd9b5a831/mail-service/mailerApp
--------------------------------------------------------------------------------
/mail-service/templates/mail.html.gohtml:
--------------------------------------------------------------------------------
1 | {{define "body"}}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{.message}}
12 |
13 |
14 | {{end}}
--------------------------------------------------------------------------------
/mail-service/templates/mail.plain.gohtml:
--------------------------------------------------------------------------------
1 | {{define "body"}}
2 |
3 | {{.message}}
4 |
5 | {{end}}
--------------------------------------------------------------------------------
/migrations/users_migration.up.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE users (
2 | id SERIAL PRIMARY KEY,
3 | email VARCHAR(255) UNIQUE NOT NULL,
4 | first_name VARCHAR(255),
5 | last_name VARCHAR(255),
6 | password VARCHAR(255) NOT NULL,
7 | user_active INTEGER NOT NULL,
8 | created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
9 | updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
10 | );
11 |
--------------------------------------------------------------------------------
/project/Caddyfile:
--------------------------------------------------------------------------------
1 | {
2 | email m.furkangulsen@hotmail.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:8081
29 | }
30 |
31 | backend:80 {
32 | reverse_proxy http://broker-service:8080
33 | }
--------------------------------------------------------------------------------
/project/Makefile:
--------------------------------------------------------------------------------
1 | FRONT_END_BINARY=frontApp
2 | BROKER_BINARY=brokerApp
3 | AUTH_BINARY=authApp
4 | LOGGER_BINARY=loggerServiceApp
5 | MAIL_BINARY=mailerApp
6 | LISTENER_BINARY=listenerApp
7 | FRONT_BINARY=frontEndApp
8 |
9 | ## up: starts all containers in the background without forcing build
10 | up:
11 | @echo "Starting Docker images..."
12 | docker-compose up -d
13 | @echo "Docker images started!"
14 |
15 | ## up_build: stops docker-compose (if running), builds all projects and starts docker compose
16 | up_build: build_broker build_auth build_logger build_mail build_listener
17 | @echo "Stopping docker images (if running...)"
18 | docker-compose down
19 | @echo "Building (when required) and starting docker images..."
20 | docker-compose up --build -d
21 | @echo "Docker images built and started!"
22 |
23 | ## down: stop docker compose
24 | down:
25 | @echo "Stopping docker compose..."
26 | docker-compose down
27 | @echo "Done!"
28 |
29 | ## build_front_linux: builds the front end binary as a linux executable
30 | build_front_linux:
31 | @echo "Building front end linux binary..."
32 | cd ../front-end && env GOOS=linux CGO_ENABLED=0 go build -o ${FRONT_BINARY} ./cmd/web
33 | @echo "Done!"
34 |
35 | ## build_broker: builds the broker binary as a linux executable
36 | build_broker:
37 | @echo "Building broker binary..."
38 | cd ../broker-service && env GOOS=linux CGO_ENABLED=0 go build -o ${BROKER_BINARY} ./cmd/api
39 | @echo "Done!"
40 |
41 | ## build_listener: builds the listener binary as a linux executable
42 | build_listener:
43 | @echo "Building listener binary..."
44 | cd ../listener-service && env GOOS=linux CGO_ENABLED=0 go build -o ${LISTENER_BINARY} .
45 | @echo "Done!"
46 |
47 | ## build_logger: builds the logger binary as a linux executable
48 | build_logger:
49 | @echo "Building logger binary..."
50 | cd ../logger-service && env GOOS=linux CGO_ENABLED=0 go build -o ${LOGGER_BINARY} ./cmd/api
51 | @echo "Done!"
52 |
53 | ## build_auth: builds the auth binary as a linux executable
54 | build_auth:
55 | @echo "Building auth binary..."
56 | cd ../authentication-service && env GOOS=linux CGO_ENABLED=0 go build -o ${AUTH_BINARY} ./cmd/api
57 | @echo "Done!"
58 |
59 | ## build_mail: builds the mail binary as a linux executable
60 | build_mail:
61 | @echo "Building mail binary..."
62 | cd ../mail-service && env GOOS=linux CGO_ENABLED=0 go build -o ${MAIL_BINARY} ./cmd/api
63 | @echo "Done!"
64 |
65 | ## build_front: builds the frone end binary
66 | build_front:
67 | @echo "Building front end binary..."
68 | cd ../front-end && env CGO_ENABLED=0 go build -o ${FRONT_END_BINARY} ./cmd/web
69 | @echo "Done!"
70 |
71 | ## start: starts the front end
72 | start: build_front
73 | @echo "Starting front end"
74 | cd ../front-end && ./${FRONT_END_BINARY} &
75 |
76 | ## stop: stop the front end
77 | stop:
78 | @echo "Stopping front end..."
79 | @-pkill -SIGTERM -f "./${FRONT_END_BINARY}"
80 | @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 | broker-service:
5 | build:
6 | context: ./../broker-service
7 | dockerfile: ./../broker-service/broker-service.dockerfile
8 | restart: always
9 | ports:
10 | - '8080:80'
11 | deploy:
12 | mode: replicated
13 | replicas: 1
14 |
15 | logger-service:
16 | build:
17 | context: ./../logger-service
18 | dockerfile: ./../logger-service/logger-service.dockerfile
19 | restart: always
20 | deploy:
21 | mode: replicated
22 | replicas: 1
23 |
24 | mailer-service:
25 | build:
26 | context: ./../mail-service
27 | dockerfile: ./../mail-service/mail-service.dockerfile
28 | restart: always
29 | deploy:
30 | mode: replicated
31 | replicas: 1
32 | environment:
33 | MAIL_DOMAIN: localhost
34 | MAIL_HOST: mailhog
35 | MAIL_PORT: 1025
36 | MAIL_ENCRYPTION: none
37 | MAIL_USERNAME: ''
38 | MAIL_PASSWORD: ''
39 | FROM_NAME: 'John Smith'
40 | FROM_ADDRESS: john.smith@example.com
41 |
42 | authentication-service:
43 | build:
44 | context: ./../authentication-service/
45 | dockerfile: ./../authentication-service/authentication-service.dockerfile
46 | restart: always
47 | ports:
48 | - '8081:80'
49 | deploy:
50 | mode: replicated
51 | replicas: 1
52 | environment:
53 | DSN: 'host=postgres port=5432 user=postgres password=password dbname=users sslmode=disable timezone=UTC connect_timeout=5'
54 |
55 | listener-service:
56 | build:
57 | context: ./../listener-service
58 | dockerfile: ./../listener-service/listener-service.dockerfile
59 | deploy:
60 | mode: replicated
61 | replicas: 1
62 |
63 | postgres:
64 | image: 'postgres:14.2'
65 | ports:
66 | - '5432:5432'
67 | restart: always
68 | deploy:
69 | mode: replicated
70 | replicas: 1
71 | environment:
72 | POSTGRES_USER: postgres
73 | POSTGRES_PASSWORD: password
74 | POSTGRES_DB: users
75 | volumes:
76 | - ./db-data/postgres/:/var/lib/postgresql/data/
77 |
78 | mongo:
79 | image: 'mongo:4.2.17-bionic'
80 | ports:
81 | - '27018:27017'
82 | deploy:
83 | mode: replicated
84 | replicas: 1
85 | environment:
86 | MONGO_INITDB_DATABASE: logs
87 | MONGO_INITDB_ROOT_USERNAME: admin
88 | MONGO_INITDB_ROOT_PASSWORD: password
89 | volumes:
90 | - ./db-data/mongo/:/data/db
91 |
92 | mailhog:
93 | image: 'mailhog/mailhog:latest'
94 | platform: linux/amd64
95 | ports:
96 | - '1025:1025'
97 | - '8025:8025'
98 |
99 | rabbitmq:
100 | image: 'rabbitmq:3.9-alpine'
101 | ports:
102 | - '5672:5672'
103 | deploy:
104 | mode: replicated
105 | replicas: 1
106 | volumes:
107 | - ./db-data/rabbitmq/:/var/lib/rabbitmq/
108 |
--------------------------------------------------------------------------------
/project/ingress.yml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: my-ingress
5 | annotations:
6 | nginx.ingress.kubernetes.io/rewrite-target: /$1
7 | spec:
8 | rules:
9 | - host: front-end.info
10 | http:
11 | paths:
12 | - path: /
13 | pathType: Prefix
14 | backend:
15 | service:
16 | name: front-end
17 | port:
18 | number: 8081
19 | - host: broker-service.info
20 | http:
21 | paths:
22 | - path: /
23 | pathType: Prefix
24 | backend:
25 | service:
26 | name: broker-service
27 | port:
28 | number: 8080
29 |
30 |
--------------------------------------------------------------------------------
/project/postgres.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 |
5 | postgres:
6 | image: 'postgres:14.2'
7 | ports:
8 | - "5432:5432"
9 | restart: always
10 | deploy:
11 | mode: replicated
12 | replicas: 1
13 | environment:
14 | POSTGRES_USER: postgres
15 | POSTGRES_PASSWORD: password
16 | POSTGRES_DB: users
17 | volumes:
18 | - ./db-data/postgres/:/var/lib/postgresql/data/
19 |
--------------------------------------------------------------------------------
/project/swarm.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | caddy:
5 | image: codeblogger/micro-caddy:1.0.0
6 | deploy:
7 | mode: replicated
8 | replicas: 1
9 | ports:
10 | - '80:80'
11 | - '443:443'
12 | volumes:
13 | - caddy-data:/data
14 | - caddy-config:/config
15 |
16 | front-end:
17 | image: codeblogger/front-end:1.0.3
18 | deploy:
19 | mode: replicated
20 | replicas: 1
21 | environment:
22 | BROKER_URL: 'http://backend'
23 |
24 | broker-service:
25 | image: codeblogger/broker-service:1.0.2
26 | # ports:
27 | # - '8080:80'
28 | deploy:
29 | mode: replicated
30 | replicas: 1
31 |
32 | listener-service:
33 | image: codeblogger/listener-service:1.0.0
34 | deploy:
35 | mode: replicated
36 | replicas: 1
37 |
38 | authentication-service:
39 | image: codeblogger/authentication-service:1.0.1
40 | deploy:
41 | mode: replicated
42 | replicas: 1
43 | environment:
44 | DSN: 'host=postgres port=5432 user=postgres password=password dbname=users sslmode=disable timezone=UTC connect_timeout=5'
45 |
46 | logger-service:
47 | image: codeblogger/logger-service:1.0.1
48 | deploy:
49 | mode: replicated
50 | replicas: 1
51 |
52 | mailer-service:
53 | image: codeblogger/mail-service:1.0.1
54 | deploy:
55 | mode: replicated
56 | replicas: 1
57 | environment:
58 | MAIL_DOMAIN: localhost
59 | MAIL_HOST: mailhog
60 | MAIL_PORT: 1025
61 | MAIL_ENCRYPTION: none
62 | MAIL_USERNAME: ''
63 | MAIL_PASSWORD: ''
64 | FROM_NAME: 'John Smith'
65 | FROM_ADDRESS: john.smith@example.com
66 |
67 | rabbitmq:
68 | image: 'rabbitmq:3.9-alpine'
69 | deploy:
70 | mode: global
71 |
72 | mailhog:
73 | image: 'mailhog/mailhog:latest'
74 | # platform: linux/amd64
75 | ports:
76 | - '8025:8025'
77 | deploy:
78 | mode: global
79 |
80 | mongo:
81 | image: 'mongo:4.2.17-bionic'
82 | ports:
83 | - '27017:27017'
84 | deploy:
85 | mode: global
86 | environment:
87 | MONGO_INITDB_DATABASE: logs
88 | MONGO_INITDB_ROOT_USERNAME: admin
89 | MONGO_INITDB_ROOT_PASSWORD: password
90 | volumes:
91 | - ./db-data/mongo/:/data/db
92 |
93 | postgres:
94 | image: 'postgres:14.2'
95 | ports:
96 | - '5432:5432'
97 | deploy:
98 | mode: replicated
99 | replicas: 1
100 | environment:
101 | POSTGRES_USER: postgres
102 | POSTGRES_PASSWORD: password
103 | POSTGRES_DB: users
104 | volumes:
105 | - ./db-data/postgres/:/var/lib/postgresql/data/
106 |
107 | volumes:
108 | caddy-data:
109 | external: true
110 | caddy-config:
111 |
--------------------------------------------------------------------------------