diff --git a/cmd/grpc_server/main.go b/cmd/grpc_server/main.go index 811d868..eadf145 100644 --- a/cmd/grpc_server/main.go +++ b/cmd/grpc_server/main.go @@ -5,16 +5,16 @@ import ( "flag" "log" "net" - "time" - sq "github.com/Masterminds/squirrel" "github.com/jackc/pgx/v4/pgxpool" "google.golang.org/grpc" "google.golang.org/grpc/reflection" - "google.golang.org/protobuf/types/known/emptypb" + usersAPI "github.com/Chuiko-GIT/auth/internal/api/users" "github.com/Chuiko-GIT/auth/internal/config" "github.com/Chuiko-GIT/auth/internal/config/env" + "github.com/Chuiko-GIT/auth/internal/repository/users" + srv "github.com/Chuiko-GIT/auth/internal/service/users" "github.com/Chuiko-GIT/auth/pkg/user_api" ) @@ -26,97 +26,11 @@ func init() { flag.StringVar(&configPath, "config-path", ".env", "path to config file") } -type server struct { - user_api.UnimplementedUserAPIServer - pool *pgxpool.Pool -} - -func (s server) Create(ctx context.Context, req *user_api.CreateRequest) (*user_api.CreateResponse, error) { - builderInsert := sq.Insert("users"). - PlaceholderFormat(sq.Dollar). - Columns("name", "email", "password", "password_confirm", "role"). - Values(req.User.Name, req.User.Email, req.User.Password, req.User.PasswordConfirm, req.User.Role.String()). - Suffix("RETURNING id") - - query, args, err := builderInsert.ToSql() - if err != nil { - log.Fatalf("failed to build query: %v", err) - } - - var userID int - if err = s.pool.QueryRow(ctx, query, args...).Scan(&userID); err != nil { - log.Fatalf("failed to select users: %v", err) - } - - log.Printf("inserted user with id: %d", userID) - - return &user_api.CreateResponse{ - Id: int64(userID), - }, nil -} - -func (s server) Delete(ctx context.Context, req *user_api.DeleteRequest) (*emptypb.Empty, error) { - return &emptypb.Empty{}, nil -} - -func (s server) Update(ctx context.Context, req *user_api.UpdateRequest) (*emptypb.Empty, error) { - builderUpdate := sq.Update("users"). - PlaceholderFormat(sq.Dollar). - Set("name", "Alex"). - Set("email", "test@gmail.com"). - Set("password", "test-test"). - Set("password_confirm", "true"). - Set("role", "admin"). - Set("updated_at", time.Now()). - Where(sq.Eq{"id": req.GetId()}) - - query, args, err := builderUpdate.ToSql() - if err != nil { - log.Fatalf("failed to build query: %v", err) - } - - res, err := s.pool.Exec(ctx, query, args...) - if err != nil { - log.Fatalf("failed to update user: %v", err) - } - - log.Printf("updated %d rows", res.RowsAffected()) - - return &emptypb.Empty{}, nil -} - -func (s server) Get(ctx context.Context, req *user_api.GetRequest) (*user_api.GetResponse, error) { - builderSelectOne := sq.Select("id", "name", "email", "password", "password_confirm", "role", "created_at", "updated_at"). - From("users"). - PlaceholderFormat(sq.Dollar). - Where(sq.Eq{"id": req.GetId()}). - Limit(1) - - query, args, err := builderSelectOne.ToSql() - if err != nil { - log.Fatalf("failed to build query: %v", err) - } - - var resp user_api.User - err = s.pool. - QueryRow(ctx, query, args...). - Scan(resp.Id, resp.User.Name, resp.User.Email, resp.User.Password, resp.User.PasswordConfirm, resp.User.Role, resp.CreatedAt, resp.UpdatedAt) - if err != nil { - log.Fatalf("failed to select user: %v", err) - } - - log.Printf("id: %d, name: %s, email: %s,password: %s,passwordConfirm: %s,role: %s, created_at: %v, updated_at: %v\n", - resp.Id, resp.User.Name, resp.User.Email, resp.User.Password, resp.User.PasswordConfirm, resp.User.Role, resp.CreatedAt, resp.UpdatedAt) - - return &user_api.GetResponse{User: &resp}, nil - -} - func main() { flag.Parse() ctx := context.Background() - if err := config.Load(configPath); err != nil { + if err := config.Load("local.env"); err != nil { log.Fatalf("failed to load config: %v", err) } @@ -129,9 +43,11 @@ func main() { if err != nil { log.Fatalf("failed to connect to database: %v", err) } - defer pool.Close() + repoUsers := users.NewRepository(pool) + serviceUser := srv.NewService(repoUsers) + grpcConfig, err := env.NewGRPCConfig() if err != nil { log.Fatalf("failed to get grpc config: %v", err) @@ -144,7 +60,7 @@ func main() { s := grpc.NewServer() reflection.Register(s) - user_api.RegisterUserAPIServer(s, server{pool: pool}) + user_api.RegisterUserAPIServer(s, usersAPI.NewImplementation(serviceUser)) log.Printf("server listening at %v", lis.Addr()) diff --git a/go.mod b/go.mod index 3d44edf..d4010dd 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ toolchain go1.22.5 require ( github.com/Masterminds/squirrel v1.5.4 - github.com/brianvoe/gofakeit v3.18.0+incompatible github.com/fatih/color v1.17.0 github.com/jackc/pgx/v4 v4.18.3 github.com/joho/godotenv v1.5.1 diff --git a/go.sum b/go.sum index 33433f9..f58a6de 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/brianvoe/gofakeit v3.18.0+incompatible h1:wDOmHc9DLG4nRjUVVaxA+CEglKOW72Y5+4WNxUIkjM8= -github.com/brianvoe/gofakeit v3.18.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= diff --git a/internal/api/users/create.go b/internal/api/users/create.go new file mode 100644 index 0000000..2c9ae66 --- /dev/null +++ b/internal/api/users/create.go @@ -0,0 +1,16 @@ +package users + +import ( + "context" + + "github.com/Chuiko-GIT/auth/internal/converter" + "github.com/Chuiko-GIT/auth/pkg/user_api" +) + +func (i *Implementation) Create(ctx context.Context, req *user_api.CreateRequest) (*user_api.CreateResponse, error) { + id, err := i.userService.Create(ctx, converter.ToUserInfoFromDesc(req.GetUser())) + if err != nil { + return &user_api.CreateResponse{}, err + } + return &user_api.CreateResponse{Id: id}, nil +} diff --git a/internal/api/users/delete.go b/internal/api/users/delete.go new file mode 100644 index 0000000..d035896 --- /dev/null +++ b/internal/api/users/delete.go @@ -0,0 +1,13 @@ +package users + +import ( + "context" + + "google.golang.org/protobuf/types/known/emptypb" + + "github.com/Chuiko-GIT/auth/pkg/user_api" +) + +func (i *Implementation) Delete(ctx context.Context, req *user_api.DeleteRequest) (*emptypb.Empty, error) { + return &emptypb.Empty{}, i.userService.Delete(ctx, req.Id) +} diff --git a/internal/api/users/get.go b/internal/api/users/get.go new file mode 100644 index 0000000..9c0a570 --- /dev/null +++ b/internal/api/users/get.go @@ -0,0 +1,17 @@ +package users + +import ( + "context" + + "github.com/Chuiko-GIT/auth/internal/converter" + "github.com/Chuiko-GIT/auth/pkg/user_api" +) + +func (i *Implementation) Get(ctx context.Context, req *user_api.GetRequest) (*user_api.GetResponse, error) { + resp, err := i.userService.Get(ctx, req.Id) + if err != nil { + return &user_api.GetResponse{}, err + } + + return &user_api.GetResponse{User: converter.ToUserFromService(resp)}, nil +} diff --git a/internal/api/users/service.go b/internal/api/users/service.go new file mode 100644 index 0000000..3874d8b --- /dev/null +++ b/internal/api/users/service.go @@ -0,0 +1,20 @@ +package users + +import ( + "github.com/Chuiko-GIT/auth/internal/service" + "github.com/Chuiko-GIT/auth/pkg/user_api" +) + +// В курсе решили назвать название этого файла тоже service.go +// В курсе так же не придумали название данному слою, поэтому Implementation + +type Implementation struct { + user_api.UnimplementedUserAPIServer + userService service.Users +} + +func NewImplementation(userService service.Users) *Implementation { + return &Implementation{ + userService: userService, + } +} diff --git a/internal/api/users/update.go b/internal/api/users/update.go new file mode 100644 index 0000000..1b7eced --- /dev/null +++ b/internal/api/users/update.go @@ -0,0 +1,14 @@ +package users + +import ( + "context" + + "google.golang.org/protobuf/types/known/emptypb" + + "github.com/Chuiko-GIT/auth/internal/converter" + "github.com/Chuiko-GIT/auth/pkg/user_api" +) + +func (i *Implementation) Update(ctx context.Context, req *user_api.UpdateRequest) (*emptypb.Empty, error) { + return &emptypb.Empty{}, i.userService.Update(ctx, converter.ToUserUpdateFromDesc(req)) +} diff --git a/internal/converter/users.go b/internal/converter/users.go new file mode 100644 index 0000000..5f40c59 --- /dev/null +++ b/internal/converter/users.go @@ -0,0 +1,50 @@ +package converter + +import ( + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/Chuiko-GIT/auth/internal/model" + "github.com/Chuiko-GIT/auth/pkg/user_api" +) + +func ToUserFromService(user model.User) *user_api.User { + var updatedAt *timestamppb.Timestamp + if user.UpdatedAt.Valid { + updatedAt = timestamppb.New(user.UpdatedAt.Time) + } + + return &user_api.User{ + Id: user.ID, + User: ToUserInfoFromService(user.UserInfo), + CreatedAt: timestamppb.New(user.CreatedAt), + UpdatedAt: updatedAt, + } +} + +func ToUserInfoFromService(userInfo model.UserInfo) *user_api.UserInfo { + return &user_api.UserInfo{ + Name: userInfo.Name, + Email: userInfo.Email, + Password: userInfo.Password, + PasswordConfirm: userInfo.PasswordConfirm, + Role: user_api.Role(user_api.Role_value[userInfo.Role]), + } +} + +func ToUserInfoFromDesc(userInfo *user_api.UserInfo) model.UserInfo { + return model.UserInfo{ + Name: userInfo.Name, + Email: userInfo.Name, + Password: userInfo.Password, + PasswordConfirm: userInfo.PasswordConfirm, + Role: userInfo.Role.String(), + } +} + +func ToUserUpdateFromDesc(userUpdate *user_api.UpdateRequest) model.UpdateUser { + return model.UpdateUser{ + ID: userUpdate.Id, + Name: userUpdate.User.Name.Value, + Email: userUpdate.User.Email.Value, + } +} diff --git a/internal/model/users.go b/internal/model/users.go new file mode 100644 index 0000000..71213ce --- /dev/null +++ b/internal/model/users.go @@ -0,0 +1,29 @@ +package model + +import ( + "database/sql" + "time" +) + +type ( + UserInfo struct { + Name string + Email string + Password string + PasswordConfirm string + Role string + } + + User struct { + ID int64 + UserInfo UserInfo + CreatedAt time.Time + UpdatedAt sql.NullTime + } + + UpdateUser struct { + ID int64 + Name string + Email string + } +) diff --git a/internal/repository/repository.go b/internal/repository/repository.go new file mode 100644 index 0000000..2bb4a47 --- /dev/null +++ b/internal/repository/repository.go @@ -0,0 +1,17 @@ +package repository + +import ( + "context" + + "github.com/Chuiko-GIT/auth/internal/model" +) + +type ( + Users interface { + Create(ctx context.Context, user model.UserInfo) (int64, error) + Get(ctx context.Context, id int64) (model.User, error) + GetAll(ctx context.Context) ([]model.User, error) + Update(ctx context.Context, req model.UpdateUser) error + Delete(ctx context.Context, id int64) error + } +) diff --git a/internal/repository/users/converter/users.go b/internal/repository/users/converter/users.go new file mode 100644 index 0000000..971dcb3 --- /dev/null +++ b/internal/repository/users/converter/users.go @@ -0,0 +1,25 @@ +package converter + +import ( + "github.com/Chuiko-GIT/auth/internal/model" + dbModel "github.com/Chuiko-GIT/auth/internal/repository/users/model" +) + +func ToUserFromRepo(user dbModel.UserRepo) model.User { + return model.User{ + ID: user.ID, + UserInfo: ToUserInfoFromRepo(user.UserInfo), + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + } +} + +func ToUserInfoFromRepo(userInfo dbModel.UserInfoRepo) model.UserInfo { + return model.UserInfo{ + Name: userInfo.Name, + Email: userInfo.Email, + Password: userInfo.Password, + PasswordConfirm: userInfo.PasswordConfirm, + Role: userInfo.Role, + } +} diff --git a/internal/repository/users/model/users.go b/internal/repository/users/model/users.go new file mode 100644 index 0000000..7b16c3c --- /dev/null +++ b/internal/repository/users/model/users.go @@ -0,0 +1,23 @@ +package model + +import ( + "database/sql" + "time" +) + +type ( + UserInfoRepo struct { + Name string `db:"name"` + Email string `db:"name"` + Password string `db:"password"` + PasswordConfirm string `db:"password_confirm"` + Role string `db:"role"` + } + + UserRepo struct { + ID int64 `db:"id"` + UserInfo UserInfoRepo `db:""` + CreatedAt time.Time `db:"created_at"` + UpdatedAt sql.NullTime `db:"updated_at"` + } +) diff --git a/internal/repository/users/users.go b/internal/repository/users/users.go new file mode 100644 index 0000000..8c95cfc --- /dev/null +++ b/internal/repository/users/users.go @@ -0,0 +1,133 @@ +package users + +import ( + "context" + "errors" + "fmt" + "log" + "time" + + sq "github.com/Masterminds/squirrel" + "github.com/jackc/pgx/v4/pgxpool" + + "github.com/Chuiko-GIT/auth/internal/model" + "github.com/Chuiko-GIT/auth/internal/repository" + "github.com/Chuiko-GIT/auth/internal/repository/users/converter" + repoModel "github.com/Chuiko-GIT/auth/internal/repository/users/model" +) + +const ( + tableUsers = "users" + + userColumnID = "id" + userColumnName = "name" + userColumnEmail = "email" + userColumnPassword = "password" + userColumnPasswordConfirm = "password_confirm" + userColumnRole = "role" + userColumnCreatedAt = "created_at" + userColumnUpdatedAt = "updated_at" +) + +var _ repository.Users = &Repo{} + +type Repo struct { + db *pgxpool.Pool +} + +func NewRepository(db *pgxpool.Pool) *Repo { + return &Repo{db: db} +} + +func (r Repo) Create(ctx context.Context, user model.UserInfo) (int64, error) { + builderInsert := sq.Insert(tableUsers). + PlaceholderFormat(sq.Dollar). + Columns(userColumnName, userColumnEmail, userColumnPassword, userColumnPasswordConfirm, userColumnRole). + Values(user.Name, user.Email, user.Password, user.PasswordConfirm, user.Role). + Suffix("RETURNING id") + + query, args, err := builderInsert.ToSql() + if err != nil { + return 0, errors.New(fmt.Sprintf("failed to build query: %v", err)) + } + + var userID int64 + if err = r.db.QueryRow(ctx, query, args...).Scan(&userID); err != nil { + return 0, errors.New(fmt.Sprintf("failed to select users: %v", err)) + } + + log.Printf("inserted user with id: %d", userID) + + return userID, nil +} + +func (r Repo) Get(ctx context.Context, id int64) (model.User, error) { + builderSelectOne := sq.Select(userColumnID, userColumnName, userColumnEmail, userColumnPassword, userColumnPasswordConfirm, userColumnRole, userColumnCreatedAt, userColumnUpdatedAt). + From(tableUsers). + PlaceholderFormat(sq.Dollar). + Where(sq.Eq{userColumnID: id}). + Limit(1) + + query, args, err := builderSelectOne.ToSql() + if err != nil { + return model.User{}, errors.New(fmt.Sprintf("failed to build query: %v", err)) + } + + var resp repoModel.UserRepo + err = r.db. + QueryRow(ctx, query, args...). + Scan(&resp.ID, &resp.UserInfo.Name, &resp.UserInfo.Email, &resp.UserInfo.Password, &resp.UserInfo.PasswordConfirm, &resp.UserInfo.Role, &resp.CreatedAt, &resp.UpdatedAt) + if err != nil { + return model.User{}, errors.New(fmt.Sprintf("failed to select user: %v", err)) + } + + log.Printf("id: %d, name: %s, email: %s,password: %s,passwordConfirm: %s,role: %s, created_at: %v, updated_at: %v\n", + &resp.ID, &resp.UserInfo.Name, &resp.UserInfo.Email, &resp.UserInfo.Password, &resp.UserInfo.PasswordConfirm, &resp.UserInfo.Role, &resp.CreatedAt, &resp.UpdatedAt) + + return converter.ToUserFromRepo(resp), nil +} + +func (r Repo) GetAll(ctx context.Context) ([]model.User, error) { + // TODO implement me + panic("implement me") +} + +func (r Repo) Update(ctx context.Context, request model.UpdateUser) error { + builderUpdate := sq.Update(tableUsers). + PlaceholderFormat(sq.Dollar). + Set(userColumnName, request.Name). + Set(userColumnEmail, request.Email). + Set(userColumnUpdatedAt, time.Now()). + Where(sq.Eq{userColumnID: request.ID}) + + query, args, err := builderUpdate.ToSql() + if err != nil { + return errors.New(fmt.Sprintf("failed to build query: %v", err)) + } + + res, err := r.db.Exec(ctx, query, args...) + if err != nil { + return errors.New(fmt.Sprintf("failed to update user: %v", err)) + } + + log.Printf("updated %d rows", res.RowsAffected()) + + return nil +} + +func (r Repo) Delete(ctx context.Context, id int64) error { + builder := sq.Delete("users"). + PlaceholderFormat(sq.Dollar). + Where("id = $1", id) + + query, args, err := builder.ToSql() + if err != nil { + return errors.New("failed to build query") + } + + if _, err = r.db.Exec(ctx, query, args...); err != nil { + return errors.New("failed to delete user") + } + + return nil +} diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 0000000..846a28b --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1,17 @@ +package service + +import ( + "context" + + "github.com/Chuiko-GIT/auth/internal/model" +) + +type ( + Users interface { + Create(ctx context.Context, user model.UserInfo) (int64, error) + Get(ctx context.Context, id int64) (model.User, error) + GetAll(ctx context.Context) ([]model.User, error) + Update(ctx context.Context, request model.UpdateUser) error + Delete(ctx context.Context, id int64) error + } +) diff --git a/internal/service/users/create.go b/internal/service/users/create.go new file mode 100644 index 0000000..f87473a --- /dev/null +++ b/internal/service/users/create.go @@ -0,0 +1,16 @@ +package users + +import ( + "context" + "errors" + + "github.com/Chuiko-GIT/auth/internal/model" +) + +func (s Serv) Create(ctx context.Context, user model.UserInfo) (int64, error) { + id, err := s.usersRepo.Create(ctx, user) + if err != nil { + return 0, errors.New("failed create user") + } + return id, nil +} diff --git a/internal/service/users/delete.go b/internal/service/users/delete.go new file mode 100644 index 0000000..5f2a43e --- /dev/null +++ b/internal/service/users/delete.go @@ -0,0 +1,13 @@ +package users + +import ( + "context" + "errors" +) + +func (s Serv) Delete(ctx context.Context, id int64) error { + if err := s.usersRepo.Delete(ctx, id); err != nil { + return errors.New("failed delete user") + } + return nil +} diff --git a/internal/service/users/get.go b/internal/service/users/get.go new file mode 100644 index 0000000..48ca996 --- /dev/null +++ b/internal/service/users/get.go @@ -0,0 +1,15 @@ +package users + +import ( + "context" + + "github.com/Chuiko-GIT/auth/internal/model" +) + +func (s Serv) Get(ctx context.Context, id int64) (model.User, error) { + return s.usersRepo.Get(ctx, id) +} + +func (s Serv) GetAll(ctx context.Context) ([]model.User, error) { + return s.usersRepo.GetAll(ctx) +} diff --git a/internal/service/users/service.go b/internal/service/users/service.go new file mode 100644 index 0000000..f08b247 --- /dev/null +++ b/internal/service/users/service.go @@ -0,0 +1,18 @@ +package users + +import ( + "github.com/Chuiko-GIT/auth/internal/repository" + "github.com/Chuiko-GIT/auth/internal/service" +) + +var _ service.Users = &Serv{} + +type Serv struct { + usersRepo repository.Users +} + +func NewService(userRepo repository.Users) *Serv { + return &Serv{ + usersRepo: userRepo, + } +} diff --git a/internal/service/users/update.go b/internal/service/users/update.go new file mode 100644 index 0000000..a0e3b75 --- /dev/null +++ b/internal/service/users/update.go @@ -0,0 +1,15 @@ +package users + +import ( + "context" + "errors" + + "github.com/Chuiko-GIT/auth/internal/model" +) + +func (s Serv) Update(ctx context.Context, request model.UpdateUser) error { + if err := s.usersRepo.Update(ctx, request); err != nil { + return errors.New("failed update user") + } + return nil +}