diff --git a/main.go b/main.go index 8ecc3aa..f508396 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,12 @@ package main import ( - "backend/src" + "backend/src/handlers" + "backend/src/middleware" + "backend/src/models" + "backend/src/repo" + "backend/src/services" + "backend/src/utils" "crypto/rand" "crypto/rsa" @@ -30,27 +35,31 @@ func main() { panic(err) } - jwtUtil := src.NewJwtUtil(key) - passwordUtil := src.NewPasswordUtil() - db := src.NewDB(sqlDb) - userService := src.NewUserService(src.UserServiceDeps{ - Jwt: jwtUtil, - Password: passwordUtil, - Db: db, - Cache: src.NewCacheInmem[string, src.UserDTO](60 * 60), - }) + jwtUtil := utils.NewJwtUtil(key) + passwordUtil := utils.NewPasswordUtil() + userRepo := repo.NewUserRepo(sqlDb) + userCache := repo.NewCacheInmem[string, models.UserDTO](60 * 60) + + userService := services.NewUserService( + services.UserServiceDeps{ + Jwt: jwtUtil, + Password: passwordUtil, + UserRepo: userRepo, + UserCache: userCache, + }, + ) r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) userGroup := r.Group("/user") - userGroup.POST("/create", src.NewUserCreateHandler(userService)) - userGroup.POST("/login", src.NewUserLoginHandler(userService)) + userGroup.POST("/create", handlers.NewUserCreateHandler(userService)) + userGroup.POST("/login", handlers.NewUserLoginHandler(userService)) dummyGroup := r.Group("/dummy") - dummyGroup.Use(src.NewAuthMiddleware(userService)) - dummyGroup.GET("/", src.NewDummyHandler()) + dummyGroup.Use(middleware.NewAuthMiddleware(userService)) + dummyGroup.GET("/", handlers.NewDummyHandler()) r.Run(":8080") } diff --git a/src/db.go b/src/db.go deleted file mode 100644 index 79af35a..0000000 --- a/src/db.go +++ /dev/null @@ -1,70 +0,0 @@ -package src - -import ( - "context" - "database/sql" - "errors" -) - -type DB interface { - CreateUser(ctx context.Context, dto UserDTO) (*UserDTO, error) - GetUserById(ctx context.Context, id string) (*UserDTO, error) - GetUserByLogin(ctx context.Context, login string) (*UserDTO, error) -} - -func NewDB(db *sql.DB) DB { - return &dbImpl{db} -} - -type dbImpl struct { - db *sql.DB -} - -func (d *dbImpl) CreateUser(ctx context.Context, dto UserDTO) (*UserDTO, error) { - query := `insert into users (login, secret, name) values ($1, $2, $3) returning id;` - row := d.db.QueryRowContext(ctx, query, dto.Login, dto.Secret, dto.Name) - - id := "" - if err := row.Scan(&id); err != nil { - return nil, err - } - - return &UserDTO{ - Id: id, - Login: dto.Login, - Secret: dto.Secret, - Name: dto.Name, - }, nil -} - -func (d *dbImpl) GetUserById(ctx context.Context, id string) (*UserDTO, error) { - query := `select id, login, secret, name from users where id = $1;` - row := d.db.QueryRowContext(ctx, query, id) - - dto := &UserDTO{} - err := row.Scan(&dto.Id, &dto.Login, &dto.Secret, &dto.Name) - if err == nil { - return dto, nil - } - if errors.Is(err, sql.ErrNoRows) { - return nil, nil - } - - return nil, err -} - -func (d *dbImpl) GetUserByLogin(ctx context.Context, login string) (*UserDTO, error) { - query := `select id, login, secret, name from users where login = $1;` - row := d.db.QueryRowContext(ctx, query, login) - - dto := &UserDTO{} - err := row.Scan(&dto.Id, &dto.Login, &dto.Secret, &dto.Name) - if err == nil { - return dto, nil - } - if errors.Is(err, sql.ErrNoRows) { - return nil, nil - } - - return nil, err -} diff --git a/src/dummy_handler.go b/src/handlers/dummy_handler.go similarity index 88% rename from src/dummy_handler.go rename to src/handlers/dummy_handler.go index 9ad39f0..7dc5af3 100644 --- a/src/dummy_handler.go +++ b/src/handlers/dummy_handler.go @@ -1,4 +1,4 @@ -package src +package handlers import "github.com/gin-gonic/gin" diff --git a/src/user_create_handler.go b/src/handlers/user_create_handler.go similarity index 77% rename from src/user_create_handler.go rename to src/handlers/user_create_handler.go index 305cf39..3ab2269 100644 --- a/src/user_create_handler.go +++ b/src/handlers/user_create_handler.go @@ -1,6 +1,7 @@ -package src +package handlers import ( + "backend/src/services" "encoding/json" "github.com/gin-gonic/gin" @@ -18,7 +19,7 @@ type createUserOutput struct { Name string `json:"name"` } -func NewUserCreateHandler(userService UserService) gin.HandlerFunc { +func NewUserCreateHandler(userService services.UserService) gin.HandlerFunc { return func(ctx *gin.Context) { params := createUserInput{} if err := ctx.ShouldBindJSON(¶ms); err != nil { @@ -26,12 +27,12 @@ func NewUserCreateHandler(userService UserService) gin.HandlerFunc { return } - dto, err := userService.CreateUser(ctx, UserCreateParams{ + dto, err := userService.CreateUser(ctx, services.UserCreateParams{ Login: params.Login, Password: params.Password, Name: params.Name, }) - if err == ErrUserExists || err == ErrUserBadPassword { + if err == services.ErrUserExists || err == services.ErrUserBadPassword { ctx.Data(400, "plain/text", []byte(err.Error())) return } diff --git a/src/user_login_handler.go b/src/handlers/user_login_handler.go similarity index 78% rename from src/user_login_handler.go rename to src/handlers/user_login_handler.go index 819d990..2a273e4 100644 --- a/src/user_login_handler.go +++ b/src/handlers/user_login_handler.go @@ -1,6 +1,7 @@ -package src +package handlers import ( + "backend/src/services" "encoding/json" "github.com/gin-gonic/gin" @@ -15,7 +16,7 @@ type loginUserOutput struct { Token string `json:"token"` } -func NewUserLoginHandler(userService UserService) gin.HandlerFunc { +func NewUserLoginHandler(userService services.UserService) gin.HandlerFunc { return func(ctx *gin.Context) { params := loginUserInput{} if err := ctx.ShouldBindJSON(¶ms); err != nil { @@ -24,7 +25,7 @@ func NewUserLoginHandler(userService UserService) gin.HandlerFunc { } token, err := userService.AuthenticateUser(ctx, params.Login, params.Password) - if err == ErrUserNotExists || err == ErrUserWrongPassword { + if err == services.ErrUserNotExists || err == services.ErrUserWrongPassword { ctx.AbortWithError(400, err) return } diff --git a/src/auth_middleware.go b/src/middleware/auth.go similarity index 64% rename from src/auth_middleware.go rename to src/middleware/auth.go index a08f530..9392816 100644 --- a/src/auth_middleware.go +++ b/src/middleware/auth.go @@ -1,12 +1,13 @@ -package src +package middleware import ( + "backend/src/services" "fmt" "github.com/gin-gonic/gin" ) -func NewAuthMiddleware(userService UserService) gin.HandlerFunc { +func NewAuthMiddleware(userService services.UserService) gin.HandlerFunc { return func(ctx *gin.Context) { token := ctx.GetHeader("X-Auth") if token == "" { @@ -15,7 +16,7 @@ func NewAuthMiddleware(userService UserService) gin.HandlerFunc { } user, err := userService.ValidateToken(ctx, token) - if err == ErrUserWrongToken || err == ErrUserNotExists { + if err == services.ErrUserWrongToken || err == services.ErrUserNotExists { ctx.AbortWithError(403, err) return } diff --git a/src/model.go b/src/model.go deleted file mode 100644 index b5c1e9c..0000000 --- a/src/model.go +++ /dev/null @@ -1,15 +0,0 @@ -package src - -type UserDTO struct { - Id string - Login string - Secret string - Name string -} - -type UserDAO struct { - Id string `json:"id"` - Login string `json:"login"` - Secret string `json:"secret"` - Name string `json:"name"` -} diff --git a/src/models/user.go b/src/models/user.go new file mode 100644 index 0000000..1deb356 --- /dev/null +++ b/src/models/user.go @@ -0,0 +1,8 @@ +package models + +type UserDTO struct { + Id string + Login string + Secret string + Name string +} diff --git a/src/cache_inmem.go b/src/repo/cache_inmem.go similarity index 98% rename from src/cache_inmem.go rename to src/repo/cache_inmem.go index 57ff597..7b27175 100644 --- a/src/cache_inmem.go +++ b/src/repo/cache_inmem.go @@ -1,4 +1,4 @@ -package src +package repo import ( "sync" diff --git a/src/repo/user_repo.go b/src/repo/user_repo.go new file mode 100644 index 0000000..a7d4e61 --- /dev/null +++ b/src/repo/user_repo.go @@ -0,0 +1,78 @@ +package repo + +import ( + "backend/src/models" + "context" + "database/sql" + "errors" +) + +// type userDAO struct { +// Id string `json:"id"` +// Login string `json:"login"` +// Secret string `json:"secret"` +// Name string `json:"name"` +// } + +type UserRepo interface { + CreateUser(ctx context.Context, dto models.UserDTO) (*models.UserDTO, error) + GetUserById(ctx context.Context, id string) (*models.UserDTO, error) + GetUserByLogin(ctx context.Context, login string) (*models.UserDTO, error) +} + +func NewUserRepo(db *sql.DB) UserRepo { + return &userRepo{db} +} + +type userRepo struct { + db *sql.DB +} + +func (u *userRepo) CreateUser(ctx context.Context, dto models.UserDTO) (*models.UserDTO, error) { + query := `insert into users (login, secret, name) values ($1, $2, $3) returning id;` + row := u.db.QueryRowContext(ctx, query, dto.Login, dto.Secret, dto.Name) + + id := "" + if err := row.Scan(&id); err != nil { + return nil, err + } + + return &models.UserDTO{ + Id: id, + Login: dto.Login, + Secret: dto.Secret, + Name: dto.Name, + }, nil +} + +func (u *userRepo) GetUserById(ctx context.Context, id string) (*models.UserDTO, error) { + query := `select id, login, secret, name from users where id = $1;` + row := u.db.QueryRowContext(ctx, query, id) + + dto := &models.UserDTO{} + err := row.Scan(&dto.Id, &dto.Login, &dto.Secret, &dto.Name) + if err == nil { + return dto, nil + } + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + + return nil, err +} + +func (u *userRepo) GetUserByLogin(ctx context.Context, login string) (*models.UserDTO, error) { + query := `select id, login, secret, name from users where login = $1;` + row := u.db.QueryRowContext(ctx, query, login) + + dto := &models.UserDTO{} + err := row.Scan(&dto.Id, &dto.Login, &dto.Secret, &dto.Name) + if err == nil { + return dto, nil + } + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + + return nil, err +} diff --git a/src/service.go b/src/services/user_service.go similarity index 67% rename from src/service.go rename to src/services/user_service.go index 51ce773..8c8a6ce 100644 --- a/src/service.go +++ b/src/services/user_service.go @@ -1,6 +1,9 @@ -package src +package services import ( + "backend/src/models" + "backend/src/repo" + "backend/src/utils" "context" "fmt" ) @@ -15,9 +18,9 @@ var ( ) type UserService interface { - CreateUser(ctx context.Context, params UserCreateParams) (*UserDTO, error) + CreateUser(ctx context.Context, params UserCreateParams) (*models.UserDTO, error) AuthenticateUser(ctx context.Context, login, password string) (string, error) - ValidateToken(ctx context.Context, tokenStr string) (*UserDTO, error) + ValidateToken(ctx context.Context, tokenStr string) (*models.UserDTO, error) } func NewUserService(deps UserServiceDeps) UserService { @@ -25,10 +28,10 @@ func NewUserService(deps UserServiceDeps) UserService { } type UserServiceDeps struct { - Db DB - Jwt JwtUtil - Password PasswordUtil - Cache Cache[string, UserDTO] + Jwt utils.JwtUtil + Password utils.PasswordUtil + UserRepo repo.UserRepo + UserCache repo.Cache[string, models.UserDTO] } type userService struct { @@ -41,8 +44,8 @@ type UserCreateParams struct { Name string } -func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (*UserDTO, error) { - exisitngUser, err := u.deps.Db.GetUserByLogin(ctx, params.Login) +func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (*models.UserDTO, error) { + exisitngUser, err := u.deps.UserRepo.GetUserByLogin(ctx, params.Login) if err != nil { return nil, err } @@ -59,24 +62,24 @@ func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) ( return nil, err } - user := UserDTO{ + user := models.UserDTO{ Login: params.Login, Secret: string(secret), Name: params.Name, } - result, err := u.deps.Db.CreateUser(ctx, user) + result, err := u.deps.UserRepo.CreateUser(ctx, user) if err != nil { return nil, err } - u.deps.Cache.Set(result.Id, *result, -1) + u.deps.UserCache.Set(result.Id, *result, -1) return result, nil } func (u *userService) AuthenticateUser(ctx context.Context, login, password string) (string, error) { - user, err := u.deps.Db.GetUserByLogin(ctx, login) + user, err := u.deps.UserRepo.GetUserByLogin(ctx, login) if err != nil { return "", err } @@ -88,22 +91,23 @@ func (u *userService) AuthenticateUser(ctx context.Context, login, password stri return "", ErrUserWrongPassword } - jwt, err := u.deps.Jwt.Create(*user) + payload := utils.JwtPayload{UserId: user.Id} + jwt, err := u.deps.Jwt.Create(payload) if err != nil { return "", err } - u.deps.Cache.Set(user.Id, *user, -1) + u.deps.UserCache.Set(user.Id, *user, -1) return jwt, nil } -func (u *userService) getUserById(ctx context.Context, userId string) (*UserDTO, error) { - if user, ok := u.deps.Cache.Get(userId); ok { +func (u *userService) getUserById(ctx context.Context, userId string) (*models.UserDTO, error) { + if user, ok := u.deps.UserCache.Get(userId); ok { return &user, nil } - user, err := u.deps.Db.GetUserById(ctx, userId) + user, err := u.deps.UserRepo.GetUserById(ctx, userId) if err != nil { return nil, err } @@ -111,12 +115,12 @@ func (u *userService) getUserById(ctx context.Context, userId string) (*UserDTO, return nil, ErrUserNotExists } - u.deps.Cache.Set(user.Id, *user, -1) + u.deps.UserCache.Set(user.Id, *user, -1) return user, nil } -func (u *userService) ValidateToken(ctx context.Context, tokenStr string) (*UserDTO, error) { +func (u *userService) ValidateToken(ctx context.Context, tokenStr string) (*models.UserDTO, error) { payload, err := u.deps.Jwt.Parse(tokenStr) if err != nil { return nil, ErrUserWrongToken diff --git a/src/jwt.go b/src/utils/jwt.go similarity index 61% rename from src/jwt.go rename to src/utils/jwt.go index 54aff46..1cafcbb 100644 --- a/src/jwt.go +++ b/src/utils/jwt.go @@ -1,4 +1,4 @@ -package src +package utils import ( "crypto/rsa" @@ -8,12 +8,16 @@ import ( ) type JwtPayload struct { - jwt.RegisteredClaims UserId string `json:"userId"` } +type jwtClaims struct { + jwt.RegisteredClaims + JwtPayload +} + type JwtUtil interface { - Create(user UserDTO) (string, error) + Create(payload JwtPayload) (string, error) Parse(tokenStr string) (JwtPayload, error) } @@ -27,9 +31,9 @@ type jwtUtil struct { privateKey *rsa.PrivateKey } -func (j *jwtUtil) Create(user UserDTO) (string, error) { - payload := &JwtPayload{UserId: user.Id} - token := jwt.NewWithClaims(jwt.SigningMethodRS256, payload) +func (j *jwtUtil) Create(payload JwtPayload) (string, error) { + claims := &jwtClaims{JwtPayload: payload} + token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) tokenStr, err := token.SignedString(j.privateKey) if err != nil { return "", err @@ -38,15 +42,15 @@ func (j *jwtUtil) Create(user UserDTO) (string, error) { } func (j *jwtUtil) Parse(tokenStr string) (JwtPayload, error) { - token, err := jwt.ParseWithClaims(tokenStr, &JwtPayload{}, func(t *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenStr, &jwtClaims{}, func(t *jwt.Token) (interface{}, error) { return &j.privateKey.PublicKey, nil }) if err != nil { return JwtPayload{}, err } - if payload, ok := token.Claims.(*JwtPayload); ok { - return *payload, nil + if claims, ok := token.Claims.(*jwtClaims); ok { + return claims.JwtPayload, nil } return JwtPayload{}, fmt.Errorf("cant get payload") diff --git a/src/password_util.go b/src/utils/password.go similarity index 98% rename from src/password_util.go rename to src/utils/password.go index 76ccf7f..930a38c 100644 --- a/src/password_util.go +++ b/src/utils/password.go @@ -1,4 +1,4 @@ -package src +package utils import ( "fmt"