├── .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 | ![Architecture](./assets/architecture.png) 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 |
6 |
7 |

Test microservices

8 |
9 | Test Broker 10 | Test Auth 11 | Test Log 12 | Test Mail 13 | Test gRPC log 14 | 15 |
16 | Output shows here... 17 |
18 |
19 |
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 | --------------------------------------------------------------------------------