From d7e3497e9073b69a2e65c5596de0253fb3db0d0a Mon Sep 17 00:00:00 2001 From: Max Kuznetsov Date: Fri, 17 May 2024 20:42:44 +0300 Subject: [PATCH] New: code refactor, cache impl, logger fix (#4) --- .golangci.yml | 4 +- cmd/config.go | 21 ++-- config-example.yaml | 9 +- docker-compose.yml | 5 +- go.mod | 6 +- go.sum | 8 +- internal/cache/cache.go | 7 ++ internal/cache/inmemory/inmemory.go | 62 +++++++++++ internal/{room => models}/room.go | 12 +-- internal/{user => models}/user.go | 2 +- internal/rest/config.go | 6 ++ internal/rest/rest.go | 39 +++++-- internal/rest/ws/handler.go | 118 +++++++++++++-------- internal/storage/room/inmemory/inmemory.go | 10 +- internal/storage/room/storage.go | 6 +- internal/storage/user/inmemory/inmemory.go | 12 +-- internal/storage/user/storage.go | 10 +- internal/utils/logger.go | 44 ++++++++ main.go | 38 ++++--- 19 files changed, 308 insertions(+), 111 deletions(-) create mode 100644 internal/cache/cache.go create mode 100644 internal/cache/inmemory/inmemory.go rename internal/{room => models}/room.go (91%) rename internal/{user => models}/user.go (95%) create mode 100644 internal/utils/logger.go diff --git a/.golangci.yml b/.golangci.yml index 20cae46..318f4b0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -61,7 +61,7 @@ linters-settings: sections: - standard - default - - prefix(github.com/Icerzack/excalidraw-ws-go) + - prefix(github.com/Icerzack/excaliroom) godot: scope: declarations @@ -69,7 +69,7 @@ linters-settings: - '^ @' goimports: - local-prefixes: github.com/Icerzack/excalidraw-ws-go + local-prefixes: github.com/Icerzack/excaliroom lll: tab-width: 4 diff --git a/cmd/config.go b/cmd/config.go index 28abac3..9f01543 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -4,14 +4,12 @@ import ( "fmt" "os" - "go.uber.org/zap" "gopkg.in/yaml.v3" ) type Config struct { Apps struct { - LogLevel string `yaml:"log_level"` - Rest struct { + Rest struct { Port int `yaml:"port"` Validation struct { JWTHeaderName string `yaml:"jwt_header_name"` @@ -20,6 +18,10 @@ type Config struct { } `yaml:"validation"` } `yaml:"rest"` } `yaml:"apps"` + Logging struct { + Level string `yaml:"level"` + WriteToFile bool `yaml:"write_to_file"` + } `yaml:"logging"` Storage struct { Users struct { Type string `yaml:"type"` @@ -34,12 +36,19 @@ type Config struct { RedisDB int `yaml:"redis_db"` } `yaml:"rooms"` } `yaml:"storage"` + Cache struct { + Type string `yaml:"type"` + TTL int64 `yaml:"ttl"` + RedisAddress string `yaml:"redis_address"` + RedisPassword string `yaml:"redis_password"` + RedisDB int `yaml:"redis_db"` + } `yaml:"cache"` } -func ParseConfig(path string, logger *zap.Logger) (*Config, error) { +func ParseConfig(path string) (*Config, error) { file, err := os.Open(path) if err != nil { - logger.Error("Failed to open config file", zap.Error(err)) + fmt.Println("error opening config file Path:", path, err) return nil, fmt.Errorf("error opening file %w", err) } defer file.Close() @@ -47,7 +56,7 @@ func ParseConfig(path string, logger *zap.Logger) (*Config, error) { var config Config err = yaml.NewDecoder(file).Decode(&config) if err != nil { - logger.Error("Failed to decode config file", zap.Error(err)) + fmt.Println("error decoding config file", err) return nil, fmt.Errorf("error decoding file %w", err) } diff --git a/config-example.yaml b/config-example.yaml index ce17848..33c303b 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -1,5 +1,4 @@ apps: - log_level: "DEBUG" rest: port: 8080 validation: @@ -7,8 +6,16 @@ apps: jwt_validation_url: "" board_validation_url: "" +logging: + level: "DEBUG" + write_to_file: false + storage: users: type: "in-memory" rooms: type: "in-memory" + +cache: + type: "in-memory" + ttl: 300 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 64f1c35..cc40b02 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,8 +2,9 @@ version: "3.8" services: app: - image: icerzack/excaliroom:latest - platform: linux/amd64 + build: + context: . + dockerfile: build/Dockerfile environment: - CONFIG_PATH=config.yaml ports: diff --git a/go.mod b/go.mod index a07de6d..98ea517 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ -module github.com/Icerzack/excalidraw-ws-go +module github.com/Icerzack/excaliroom go 1.21 require ( - github.com/go-chi/chi v1.5.5 + github.com/go-chi/chi/v5 v5.0.12 github.com/gorilla/websocket v1.5.1 go.uber.org/zap v1.27.0 gopkg.in/yaml.v3 v3.0.1 @@ -11,5 +11,5 @@ require ( require ( go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect ) diff --git a/go.sum b/go.sum index e0cf8cb..87a8a6a 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= -github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= +github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= +github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -14,8 +14,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/cache/cache.go b/internal/cache/cache.go new file mode 100644 index 0000000..ed29fc0 --- /dev/null +++ b/internal/cache/cache.go @@ -0,0 +1,7 @@ +package cache + +type Cache interface { + Set(key string, value interface{}) error + Get(key string) (interface{}, error) + SetWithTTL(key string, value interface{}, ttl int64) error +} diff --git a/internal/cache/inmemory/inmemory.go b/internal/cache/inmemory/inmemory.go new file mode 100644 index 0000000..20ebd10 --- /dev/null +++ b/internal/cache/inmemory/inmemory.go @@ -0,0 +1,62 @@ +package inmemory + +import ( + "sync" + "time" + + "go.uber.org/zap" +) + +type Item struct { + Value interface{} + Expiration int64 +} + +type Cache struct { + mu sync.RWMutex + items map[string]*Item + logger *zap.Logger +} + +func NewCache(logger *zap.Logger) *Cache { + return &Cache{ + items: make(map[string]*Item), + logger: logger, + } +} + +func (c *Cache) Set(key string, value interface{}) error { + c.mu.Lock() + defer c.mu.Unlock() + + c.items[key] = &Item{ + Value: value, + } + c.logger.Debug("User added to cache", zap.String("key", key)) + return nil +} + +func (c *Cache) SetWithTTL(key string, value interface{}, ttl int64) error { + c.mu.Lock() + defer c.mu.Unlock() + + c.items[key] = &Item{ + Value: value, + Expiration: time.Now().UnixNano() + ttl*int64(time.Second), + } + c.logger.Debug("User added to cache", zap.String("key", key)) + return nil +} + +func (c *Cache) Get(key string) (interface{}, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + item, ok := c.items[key] + if !ok || item.Expiration < time.Now().UnixNano() { + c.logger.Debug("User not found in cache", zap.String("key", key)) + return nil, nil + } + + return item.Value, nil +} diff --git a/internal/room/room.go b/internal/models/room.go similarity index 91% rename from internal/room/room.go rename to internal/models/room.go index cc57b63..4c39533 100644 --- a/internal/room/room.go +++ b/internal/models/room.go @@ -1,10 +1,8 @@ -package room +package models import ( "crypto/rand" "sync" - - "github.com/Icerzack/excalidraw-ws-go/internal/user" ) type Room struct { @@ -15,7 +13,7 @@ type Room struct { BoardID string // Users is a map of users in the room - Users []*user.User + Users []*User // LeaderID is the unique identifier of the leader of the room LeaderID string @@ -37,14 +35,14 @@ func NewRoom(boardID string) *Room { return &Room{ ID: generateRandomID(), BoardID: boardID, - Users: make([]*user.User, 0), + Users: make([]*User, 0), LeaderID: "0", mtx: &sync.RWMutex{}, RoomMutex: &sync.Mutex{}, } } -func (r *Room) AddUser(newUser *user.User) { +func (r *Room) AddUser(newUser *User) { // Add user to the room r.mtx.Lock() defer r.mtx.Unlock() @@ -63,7 +61,7 @@ func (r *Room) RemoveUser(userID string) { } } -func (r *Room) GetUsers() []*user.User { +func (r *Room) GetUsers() []*User { // Get users of the room r.mtx.RLock() defer r.mtx.RUnlock() diff --git a/internal/user/user.go b/internal/models/user.go similarity index 95% rename from internal/user/user.go rename to internal/models/user.go index f49e61d..549d74a 100644 --- a/internal/user/user.go +++ b/internal/models/user.go @@ -1,4 +1,4 @@ -package user +package models import "github.com/gorilla/websocket" diff --git a/internal/rest/config.go b/internal/rest/config.go index a217a9c..c901ef7 100644 --- a/internal/rest/config.go +++ b/internal/rest/config.go @@ -23,5 +23,11 @@ type Config struct { // RoomsStorageType is the type of the storage that will be used RoomsStorageType string + // CacheType is the type of the cache that will be used + CacheType string + + // CacheTTL is the time to live of the cache + CacheTTL int64 + Logger *zap.Logger } diff --git a/internal/rest/rest.go b/internal/rest/rest.go index f5fe575..4ecb4a1 100644 --- a/internal/rest/rest.go +++ b/internal/rest/rest.go @@ -6,14 +6,16 @@ import ( "net/http" "strconv" - "github.com/go-chi/chi" + "github.com/go-chi/chi/v5" "go.uber.org/zap" - "github.com/Icerzack/excalidraw-ws-go/internal/rest/ws" - "github.com/Icerzack/excalidraw-ws-go/internal/storage/room" - inmemRoom "github.com/Icerzack/excalidraw-ws-go/internal/storage/room/inmemory" - "github.com/Icerzack/excalidraw-ws-go/internal/storage/user" - inmemUser "github.com/Icerzack/excalidraw-ws-go/internal/storage/user/inmemory" + "github.com/Icerzack/excaliroom/internal/cache" + "github.com/Icerzack/excaliroom/internal/cache/inmemory" + "github.com/Icerzack/excaliroom/internal/rest/ws" + "github.com/Icerzack/excaliroom/internal/storage/room" + inmemRoom "github.com/Icerzack/excaliroom/internal/storage/room/inmemory" + "github.com/Icerzack/excaliroom/internal/storage/user" + inmemUser "github.com/Icerzack/excaliroom/internal/storage/user/inmemory" ) type Rest struct { @@ -41,9 +43,13 @@ func (rest *Rest) Start() { // Define the /ws endpoint usersStorage, roomsStorage := rest.defineStorage() + selectedCache := rest.defineCache() + wsServer := ws.NewWebSocketHandler( usersStorage, roomsStorage, + selectedCache, + rest.config.CacheTTL, rest.config.JwtHeaderName, rest.config.JwtValidationURL, rest.config.BoardValidationURL, @@ -68,9 +74,9 @@ func (rest *Rest) Stop() { } } -func (rest *Rest) defineStorage() (*inmemUser.Storage, *inmemRoom.Storage) { - var usersStorage *inmemUser.Storage - var roomsStorage *inmemRoom.Storage +func (rest *Rest) defineStorage() (user.Storage, room.Storage) { + var usersStorage user.Storage + var roomsStorage room.Storage switch rest.config.UsersStorageType { case user.InMemoryStorageType: @@ -91,3 +97,18 @@ func (rest *Rest) defineStorage() (*inmemUser.Storage, *inmemRoom.Storage) { return usersStorage, roomsStorage } + +func (rest *Rest) defineCache() cache.Cache { + var c cache.Cache + + switch rest.config.CacheType { + case room.InMemoryStorageType: + rest.config.Logger.Info("Using in-memory cache") + c = inmemory.NewCache(rest.config.Logger) + default: + rest.config.Logger.Info("Using in-memory cache") + c = inmemory.NewCache(rest.config.Logger) + } + + return c +} diff --git a/internal/rest/ws/handler.go b/internal/rest/ws/handler.go index 043ba2f..f331e67 100644 --- a/internal/rest/ws/handler.go +++ b/internal/rest/ws/handler.go @@ -12,10 +12,10 @@ import ( "github.com/gorilla/websocket" "go.uber.org/zap" - "github.com/Icerzack/excalidraw-ws-go/internal/room" - rStorage "github.com/Icerzack/excalidraw-ws-go/internal/storage/room" - uStorage "github.com/Icerzack/excalidraw-ws-go/internal/storage/user" - "github.com/Icerzack/excalidraw-ws-go/internal/user" + "github.com/Icerzack/excaliroom/internal/cache" + "github.com/Icerzack/excaliroom/internal/models" + "github.com/Icerzack/excaliroom/internal/storage/room" + "github.com/Icerzack/excaliroom/internal/storage/user" ) var ( @@ -24,12 +24,11 @@ var ( ) const ( - EventConnect = "connect" - EventUserConnected = "userConnected" - EventUserDisconnected = "userDisconnected" - EventSetLeader = "setLeader" - EventUserFailedToConnect = "userFailedToConnect" - EventNewData = "newData" + EventConnect = "connect" + EventUserConnected = "userConnected" + EventUserDisconnected = "userDisconnected" + EventSetLeader = "setLeader" + EventNewData = "newData" ) type WebSocketHandler struct { @@ -46,17 +45,25 @@ type WebSocketHandler struct { boardValidationURL string // userStorage is used to store the clients - userStorage uStorage.Storage + userStorage user.Storage // roomStorage is used to store the rooms - roomStorage rStorage.Storage + roomStorage room.Storage + + // cache is used to store the validation results + cache cache.Cache + + // cacheTTLInSeconds is the time to live of the cache + cacheTTLInSeconds int64 logger *zap.Logger } func NewWebSocketHandler( - clientsStorage uStorage.Storage, - roomStorage rStorage.Storage, + clientsStorage user.Storage, + roomStorage room.Storage, + cache cache.Cache, + cacheTTLInSeconds int64, jwtHeaderName string, jwtValidationURL string, boardValidationURL string, @@ -73,6 +80,8 @@ func NewWebSocketHandler( jwtHeaderName: jwtHeaderName, jwtValidationURL: jwtValidationURL, boardValidationURL: boardValidationURL, + cache: cache, + cacheTTLInSeconds: cacheTTLInSeconds, logger: logger, } } @@ -117,10 +126,9 @@ func (ws *WebSocketHandler) messageHandler(conn *websocket.Conn, msg []byte) { //nolint:cyclop func (ws *WebSocketHandler) setLeader(request MessageSetLeaderRequest) { - // Get the UserID from the JWT token - userID, err := ws.validateJWT(request.Jwt) + userID, err := ws.cacheOrValidate(request.Jwt, request.BoardID) if err != nil { - ws.logger.Debug("Failed to validate JWT", zap.Error(err)) + ws.logger.Error("Failed to validate", zap.Error(err)) return } @@ -130,11 +138,6 @@ func (ws *WebSocketHandler) setLeader(request MessageSetLeaderRequest) { return } - // Check if the user has access to the board - if !ws.validateBoardAccess(request.BoardID, request.Jwt) { - return - } - // Check if user belongs to the room if u == nil || u.RoomID != request.BoardID { return @@ -179,12 +182,10 @@ func (ws *WebSocketHandler) setLeader(request MessageSetLeaderRequest) { } } -//nolint:cyclop func (ws *WebSocketHandler) sendDataToRoom(request MessageNewDataRequest) { - // Get the UserID from the JWT token - userID, err := ws.validateJWT(request.Jwt) + userID, err := ws.cacheOrValidate(request.Jwt, request.BoardID) if err != nil { - ws.logger.Debug("Failed to validate JWT", zap.Error(err)) + ws.logger.Error("Failed to validate", zap.Error(err)) return } @@ -193,11 +194,6 @@ func (ws *WebSocketHandler) sendDataToRoom(request MessageNewDataRequest) { return } - // Check if the user has access to the board - if !ws.validateBoardAccess(request.BoardID, request.Jwt) { - return - } - // Check if user belongs to the room u, _ := ws.userStorage.Get(userID) if u == nil || u.RoomID != request.BoardID { @@ -249,7 +245,7 @@ func (ws *WebSocketHandler) sendDataToRoom(request MessageNewDataRequest) { func (ws *WebSocketHandler) unregisterUser(conn *websocket.Conn) { // Get the user - u, _ := ws.userStorage.GetWhere(func(u *user.User) bool { + u, _ := ws.userStorage.GetWhere(func(u *models.User) bool { return u.Conn == conn }) if u == nil { @@ -298,17 +294,9 @@ func (ws *WebSocketHandler) unregisterUser(conn *websocket.Conn) { } func (ws *WebSocketHandler) registerUser(conn *websocket.Conn, request MessageConnectRequest) { - // Get the UserID from the JWT token - userID, err := ws.validateJWT(request.Jwt) + userID, err := ws.cacheOrValidate(request.Jwt, request.BoardID) if err != nil { - ws.logger.Debug("Failed to validate JWT", zap.String("userID", userID), zap.Error(err)) - return - } - - // Check if the user has access to the board - if !ws.validateBoardAccess(request.BoardID, request.Jwt) { - ws.logger.Debug("User not allowed to access this board", - zap.String("userID", userID), zap.String("boardID", request.BoardID)) + ws.logger.Error("Failed to validate", zap.Error(err)) return } @@ -318,14 +306,14 @@ func (ws *WebSocketHandler) registerUser(conn *websocket.Conn, request MessageCo } // Create a room if it doesn't exist - var currentRoom *room.Room + var currentRoom *models.Room if currentRoom, _ = ws.roomStorage.Get(request.BoardID); currentRoom == nil { - currentRoom = room.NewRoom(request.BoardID) + currentRoom = models.NewRoom(request.BoardID) _ = ws.roomStorage.Set(request.BoardID, currentRoom) } // Store the user - newUser := &user.User{ + newUser := &models.User{ ID: userID, RoomID: request.BoardID, Conn: conn, @@ -486,3 +474,43 @@ func messageDefiner(msg []byte) (interface{}, error) { } return nil, ErrInvalidMessage } + +//nolint:nestif +func (ws *WebSocketHandler) cacheOrValidate(jwt, boardID string) (string, error) { + var userID string + + // Check if the user is in cache + if v, err := ws.cache.Get(jwt); v == nil { + if err != nil { + ws.logger.Error("Failed to get from cache", zap.Error(err)) + return "", fmt.Errorf("failed to get from cache: %w", err) + } + // Get the UserID from the JWT token + userID, err = ws.validateJWT(jwt) + if err != nil { + ws.logger.Error("Failed to validate JWT", zap.Error(err)) + return "", fmt.Errorf("failed to validate JWT: %w", err) + } + + // Check if the user has access to the board + if !ws.validateBoardAccess(boardID, jwt) { + ws.logger.Debug("User doesn't have access to the board", + zap.String("userID", userID), zap.String("boardID", boardID)) + return "", fmt.Errorf("user doesn't have access to the board: %w", err) + } + + // Store the validation result + _ = ws.cache.SetWithTTL(jwt, userID, ws.cacheTTLInSeconds) + } else { + if err != nil { + ws.logger.Error("Failed to get from cache", zap.Error(err)) + return "", fmt.Errorf("failed to get from cache: %w", err) + } + var ok bool + userID, ok = v.(string) + if !ok { + return "", fmt.Errorf("failed to get from cache: %w", err) + } + } + return userID, nil +} diff --git a/internal/storage/room/inmemory/inmemory.go b/internal/storage/room/inmemory/inmemory.go index a032498..e705c6c 100644 --- a/internal/storage/room/inmemory/inmemory.go +++ b/internal/storage/room/inmemory/inmemory.go @@ -6,13 +6,13 @@ import ( "go.uber.org/zap" - "github.com/Icerzack/excalidraw-ws-go/internal/room" + "github.com/Icerzack/excaliroom/internal/models" ) var ErrRoomNotFound = errors.New("room not found") type Storage struct { - data map[string]*room.Room + data map[string]*models.Room logger *zap.Logger mtx *sync.Mutex @@ -20,13 +20,13 @@ type Storage struct { func NewStorage(logger *zap.Logger) *Storage { return &Storage{ - data: make(map[string]*room.Room), + data: make(map[string]*models.Room), logger: logger, mtx: &sync.Mutex{}, } } -func (s *Storage) Set(key string, value *room.Room) error { +func (s *Storage) Set(key string, value *models.Room) error { s.mtx.Lock() defer s.mtx.Unlock() s.data[key] = value @@ -34,7 +34,7 @@ func (s *Storage) Set(key string, value *room.Room) error { return nil } -func (s *Storage) Get(key string) (*room.Room, error) { +func (s *Storage) Get(key string) (*models.Room, error) { s.mtx.Lock() defer s.mtx.Unlock() v, ok := s.data[key] diff --git a/internal/storage/room/storage.go b/internal/storage/room/storage.go index 4441060..32260fb 100644 --- a/internal/storage/room/storage.go +++ b/internal/storage/room/storage.go @@ -1,7 +1,7 @@ package room import ( - "github.com/Icerzack/excalidraw-ws-go/internal/room" + "github.com/Icerzack/excaliroom/internal/models" ) const ( @@ -9,7 +9,7 @@ const ( ) type Storage interface { - Set(key string, value *room.Room) error - Get(key string) (*room.Room, error) + Set(key string, value *models.Room) error + Get(key string) (*models.Room, error) Delete(key string) error } diff --git a/internal/storage/user/inmemory/inmemory.go b/internal/storage/user/inmemory/inmemory.go index 911d29a..1a234b5 100644 --- a/internal/storage/user/inmemory/inmemory.go +++ b/internal/storage/user/inmemory/inmemory.go @@ -6,13 +6,13 @@ import ( "go.uber.org/zap" - "github.com/Icerzack/excalidraw-ws-go/internal/user" + "github.com/Icerzack/excaliroom/internal/models" ) var ErrUserNotFound = errors.New("user not found") type Storage struct { - data map[string]*user.User + data map[string]*models.User logger *zap.Logger mtx *sync.Mutex @@ -20,13 +20,13 @@ type Storage struct { func NewStorage(logger *zap.Logger) *Storage { return &Storage{ - data: make(map[string]*user.User), + data: make(map[string]*models.User), logger: logger, mtx: &sync.Mutex{}, } } -func (s *Storage) Set(key string, value *user.User) error { +func (s *Storage) Set(key string, value *models.User) error { s.mtx.Lock() defer s.mtx.Unlock() s.data[key] = value @@ -34,7 +34,7 @@ func (s *Storage) Set(key string, value *user.User) error { return nil } -func (s *Storage) Get(key string) (*user.User, error) { +func (s *Storage) Get(key string) (*models.User, error) { s.mtx.Lock() defer s.mtx.Unlock() v, ok := s.data[key] @@ -53,7 +53,7 @@ func (s *Storage) Delete(key string) error { return nil } -func (s *Storage) GetWhere(predicate func(*user.User) bool) (*user.User, error) { +func (s *Storage) GetWhere(predicate func(*models.User) bool) (*models.User, error) { s.mtx.Lock() defer s.mtx.Unlock() for _, v := range s.data { diff --git a/internal/storage/user/storage.go b/internal/storage/user/storage.go index ffa75da..3afe9c3 100644 --- a/internal/storage/user/storage.go +++ b/internal/storage/user/storage.go @@ -1,14 +1,16 @@ package user -import "github.com/Icerzack/excalidraw-ws-go/internal/user" +import ( + "github.com/Icerzack/excaliroom/internal/models" +) const ( InMemoryStorageType = "in-memory" ) type Storage interface { - Set(key string, value *user.User) error - Get(key string) (*user.User, error) + Set(key string, value *models.User) error + Get(key string) (*models.User, error) Delete(key string) error - GetWhere(predicate func(*user.User) bool) (*user.User, error) + GetWhere(predicate func(*models.User) bool) (*models.User, error) } diff --git a/internal/utils/logger.go b/internal/utils/logger.go new file mode 100644 index 0000000..00efbbd --- /dev/null +++ b/internal/utils/logger.go @@ -0,0 +1,44 @@ +package utils + +import ( + "fmt" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func NewCustomLogger(level zapcore.Level, outputToFiles bool) (*zap.Logger, error) { + encoderConfig := zap.NewProductionEncoderConfig() + encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + encoderConfig.CallerKey = "" + + outputPaths := []string{"stdout"} + errorOutputPaths := []string{"stderr"} + + if outputToFiles { + outputPaths = append(outputPaths, "./logs.log") + errorOutputPaths = append(errorOutputPaths, "./errors.log") + } + + config := zap.Config{ + Level: zap.NewAtomicLevelAt(level), + Development: false, + Sampling: &zap.SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Encoding: "console", + EncoderConfig: encoderConfig, + OutputPaths: outputPaths, + ErrorOutputPaths: errorOutputPaths, + DisableStacktrace: true, + } + + logger, err := config.Build() + if err != nil { + return nil, fmt.Errorf("unable to create logger %w", err) + } + + return logger, nil +} diff --git a/main.go b/main.go index 4cafeab..9744607 100644 --- a/main.go +++ b/main.go @@ -1,33 +1,43 @@ package main import ( + "fmt" "os" "go.uber.org/zap" + "go.uber.org/zap/zapcore" - "github.com/Icerzack/excalidraw-ws-go/cmd" - "github.com/Icerzack/excalidraw-ws-go/internal/rest" + "github.com/Icerzack/excaliroom/cmd" + "github.com/Icerzack/excaliroom/internal/rest" + "github.com/Icerzack/excaliroom/internal/utils" ) func main() { - logger, _ := zap.NewDevelopment() - //nolint:errcheck - defer logger.Sync() - configPath := os.Getenv("CONFIG_PATH") - appConfig, err := cmd.ParseConfig(configPath, logger) + appConfig, err := cmd.ParseConfig(configPath) if err != nil { return } - switch appConfig.Apps.LogLevel { - case "DEBUG": - logger, _ = zap.NewDevelopment() + var level zapcore.Level + var writeToFiles bool + + switch appConfig.Logging.Level { case "INFO": - logger, _ = zap.NewProduction() - default: - logger = zap.NewNop() + level = zap.InfoLevel + case "DEBUG": + level = zap.DebugLevel + } + + if appConfig.Logging.WriteToFile { + writeToFiles = true + } + + logger, err := utils.NewCustomLogger(level, writeToFiles) + if err != nil { + fmt.Println("unable to create logger", err) + return } restApp := rest.NewRest(&rest.Config{ @@ -37,6 +47,8 @@ func main() { BoardValidationURL: appConfig.Apps.Rest.Validation.BoardValidationURL, UsersStorageType: appConfig.Storage.Users.Type, RoomsStorageType: appConfig.Storage.Rooms.Type, + CacheType: appConfig.Cache.Type, + CacheTTL: appConfig.Cache.TTL, Logger: logger, })