Skip to content

Commit 6b9e1ee

Browse files
authored
Feature/1 (#20)
* users schema update: added email column, added indexes * tests update * user repo GetByEmail test added * user email prop added * new google oauth login will result in new user * reset password handler * username vo update * test fix * switched from bcrypt to argon2id * fixed a bug * added Delete method to cache interface * UserPasswordChanged event added * logging fixed * removed auth context
1 parent a7380ae commit 6b9e1ee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2231
-365
lines changed

src/application/cache.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "time"
55
type Cache[T any] interface {
66
Get(key string) (T, error)
77
Set(key string, value T) error
8+
Delete(key string) error
89
}
910

1011
type CacheWithTTL[T any] interface {

src/application/commands/post_user.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ import "goproxy/domain/valueobjects"
44

55
type PostUser struct {
66
Username string
7+
Email string
78
Password valueobjects.Password
89
}

src/application/crypto_service.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package application
22

33
type CryptoService interface {
4-
GenerateSalt() ([]byte, error)
5-
HashValue(value string, salt []byte) ([]byte, error)
6-
ValidateHash(hash []byte, salt []byte, password string) bool
4+
GenerateRandomString(length int) (string, error)
5+
HashValue(value string) (string, error)
6+
ValidateHash(fullHash, password string) bool
77
}

src/application/repository.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type Repository[T any] interface {
1515
type UserRepository interface {
1616
Repository[aggregates.User]
1717
GetByUsername(username string) (aggregates.User, error)
18+
GetByEmail(email string) (aggregates.User, error)
1819
}
1920

2021
type EventRepository interface {

src/application/user_use_cases.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,17 @@ func (u UserUseCases) GetById(id int) (aggregates.User, error) {
3131
return u.repo.GetById(id)
3232
}
3333

34-
func (u UserUseCases) Create(command commands.PostUser) (int, error) {
35-
salt, err := u.cryptoService.GenerateSalt()
36-
if err != nil {
37-
return 0, err
38-
}
34+
func (u UserUseCases) GetByEmail(email string) (aggregates.User, error) {
35+
return u.repo.GetByEmail(email)
36+
}
3937

40-
hash, err := u.cryptoService.HashValue(command.Password.Value, salt)
38+
func (u UserUseCases) Create(command commands.PostUser) (int, error) {
39+
hash, err := u.cryptoService.HashValue(command.Password.Value)
4140
if err != nil {
4241
return 0, err
4342
}
4443

45-
user, err := aggregates.NewUser(-1, command.Username, hash, salt)
44+
user, err := aggregates.NewUser(-1, command.Username, command.Email, hash)
4645
if err != nil {
4746
return 0, err
4847
}
@@ -66,7 +65,7 @@ func (u UserUseCases) Delete(dto commands.DeleteUser) error {
6665
return err
6766
}
6867

69-
isPasswordValid := u.cryptoService.ValidateHash(user.PasswordHash(), user.PasswordSalt(), dto.Password.Value)
68+
isPasswordValid := u.cryptoService.ValidateHash(user.PasswordHash(), dto.Password.Value)
7069
if !isPasswordValid {
7170
return fmt.Errorf("invalid password")
7271
}

src/dal/db.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"database/sql"
55
"fmt"
66
_ "github.com/lib/pq"
7+
"log"
78
"os"
89
)
910

@@ -22,13 +23,28 @@ func ConnectDB() (*sql.DB, error) {
2223

2324
func buildConnectionString() (string, error) {
2425
dbUser := os.Getenv("DB_USER")
26+
if dbUser == "" {
27+
log.Fatalf("DB_USER environment variable not set")
28+
}
29+
2530
dbPass := os.Getenv("DB_PASS")
31+
if dbPass == "" {
32+
log.Fatalf("DB_PASS environment variable not set")
33+
}
34+
2635
dbHost := os.Getenv("DB_HOST")
36+
if dbHost == "" {
37+
log.Fatalf("DB_HOST environment variable not set")
38+
}
39+
2740
dbPort := os.Getenv("DB_PORT")
28-
dbDatabase := os.Getenv("DB_DATABASE")
41+
if dbPort == "" {
42+
log.Fatalf("DB_PORT environment variable not set")
43+
}
2944

30-
if dbUser == "" || dbPass == "" || dbHost == "" || dbPort == "" || dbDatabase == "" {
31-
return "", fmt.Errorf("invalid db credentials")
45+
dbDatabase := os.Getenv("DB_DATABASE")
46+
if dbDatabase == "" {
47+
log.Fatalf("DB_DATABASE environment variable not set")
3248
}
3349

3450
dataSourceName := fmt.
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
CREATE TABLE public.users (
22
id SERIAL PRIMARY KEY,
33
username VARCHAR(255) NOT NULL UNIQUE,
4-
password_hash BYTEA NOT NULL,
5-
password_salt BYTEA NOT NULL,
4+
email VARCHAR(255) NOT NULL UNIQUE,
5+
password_hash VARCHAR(256) NOT NULL,
66
created_at TIMESTAMP DEFAULT now()
77
);
8+
9+
CREATE INDEX idx_email on users(email);
10+
CREATE INDEX idx_username on users(username);
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package mocks
2+
3+
import (
4+
"errors"
5+
"goproxy/domain/events"
6+
"sync"
7+
)
8+
9+
type MockMessageBusService struct {
10+
topics map[string][]events.OutboxEvent
11+
mutex sync.RWMutex
12+
isClosed bool
13+
consumeCh chan events.OutboxEvent
14+
}
15+
16+
func NewMockMessageBusService() *MockMessageBusService {
17+
return &MockMessageBusService{
18+
topics: make(map[string][]events.OutboxEvent),
19+
consumeCh: make(chan events.OutboxEvent, 100),
20+
}
21+
}
22+
23+
func (m *MockMessageBusService) Subscribe(topics []string) error {
24+
m.mutex.Lock()
25+
defer m.mutex.Unlock()
26+
27+
if m.isClosed {
28+
return errors.New("message bus is closed")
29+
}
30+
31+
for _, topic := range topics {
32+
if _, exists := m.topics[topic]; !exists {
33+
m.topics[topic] = []events.OutboxEvent{}
34+
}
35+
}
36+
37+
return nil
38+
}
39+
40+
func (m *MockMessageBusService) Consume() (*events.OutboxEvent, error) {
41+
m.mutex.RLock()
42+
defer m.mutex.RUnlock()
43+
44+
if m.isClosed {
45+
return nil, errors.New("message bus is closed")
46+
}
47+
48+
event, ok := <-m.consumeCh
49+
if !ok {
50+
return nil, errors.New("no events to consume")
51+
}
52+
53+
return &event, nil
54+
}
55+
56+
func (m *MockMessageBusService) Produce(topic string, event events.OutboxEvent) error {
57+
m.mutex.Lock()
58+
defer m.mutex.Unlock()
59+
60+
if m.isClosed {
61+
return errors.New("message bus is closed")
62+
}
63+
64+
if _, exists := m.topics[topic]; !exists {
65+
return errors.New("topic not found")
66+
}
67+
68+
m.topics[topic] = append(m.topics[topic], event)
69+
70+
select {
71+
case m.consumeCh <- event:
72+
default:
73+
}
74+
75+
return nil
76+
}
77+
78+
func (m *MockMessageBusService) Close() error {
79+
m.mutex.Lock()
80+
defer m.mutex.Unlock()
81+
82+
if m.isClosed {
83+
return errors.New("message bus is already closed")
84+
}
85+
86+
m.isClosed = true
87+
close(m.consumeCh)
88+
return nil
89+
}

0 commit comments

Comments
 (0)