From 86d3c272295bf8958867171414763e4489a47d10 Mon Sep 17 00:00:00 2001 From: Str1m Date: Wed, 9 Apr 2025 15:00:27 +0300 Subject: [PATCH 1/4] rework repo structure --- internal/repository/users/converter/user.go | 1 + internal/repository/users/model/user.go | 15 + pkg/auth_v1/auth.pb.go | 541 -------------------- pkg/auth_v1/auth_grpc.pb.go | 236 --------- 4 files changed, 16 insertions(+), 777 deletions(-) create mode 100644 internal/repository/users/converter/user.go create mode 100644 internal/repository/users/model/user.go delete mode 100644 pkg/auth_v1/auth.pb.go delete mode 100644 pkg/auth_v1/auth_grpc.pb.go diff --git a/internal/repository/users/converter/user.go b/internal/repository/users/converter/user.go new file mode 100644 index 0000000..89f617e --- /dev/null +++ b/internal/repository/users/converter/user.go @@ -0,0 +1 @@ +package converter diff --git a/internal/repository/users/model/user.go b/internal/repository/users/model/user.go new file mode 100644 index 0000000..f1edbd9 --- /dev/null +++ b/internal/repository/users/model/user.go @@ -0,0 +1,15 @@ +package model + +import ( + desc "github.com/Str1m/auth/pkg/auth_v1" + "time" +) + +type User struct { + ID int64 `db:"id"` + Name string `db:"name"` + Email string `db:"email"` + Role desc.Role `db:"role"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} diff --git a/pkg/auth_v1/auth.pb.go b/pkg/auth_v1/auth.pb.go deleted file mode 100644 index 34a830e..0000000 --- a/pkg/auth_v1/auth.pb.go +++ /dev/null @@ -1,541 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.6 -// protoc v5.29.3 -// source: auth.proto - -package auth_v1 - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - emptypb "google.golang.org/protobuf/types/known/emptypb" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" - wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Role int32 - -const ( - Role_ROLE_UNSPECIFIED Role = 0 - Role_USER Role = 1 - Role_ADMIN Role = 2 -) - -// Enum value maps for Role. -var ( - Role_name = map[int32]string{ - 0: "ROLE_UNSPECIFIED", - 1: "USER", - 2: "ADMIN", - } - Role_value = map[string]int32{ - "ROLE_UNSPECIFIED": 0, - "USER": 1, - "ADMIN": 2, - } -) - -func (x Role) Enum() *Role { - p := new(Role) - *p = x - return p -} - -func (x Role) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (Role) Descriptor() protoreflect.EnumDescriptor { - return file_auth_proto_enumTypes[0].Descriptor() -} - -func (Role) Type() protoreflect.EnumType { - return &file_auth_proto_enumTypes[0] -} - -func (x Role) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use Role.Descriptor instead. -func (Role) EnumDescriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{0} -} - -type CreateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` - Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` - PasswordConfirm string `protobuf:"bytes,4,opt,name=password_confirm,json=passwordConfirm,proto3" json:"password_confirm,omitempty"` - Role Role `protobuf:"varint,5,opt,name=role,proto3,enum=auth_v1.Role" json:"role,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *CreateRequest) Reset() { - *x = CreateRequest{} - mi := &file_auth_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *CreateRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreateRequest) ProtoMessage() {} - -func (x *CreateRequest) ProtoReflect() protoreflect.Message { - mi := &file_auth_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreateRequest.ProtoReflect.Descriptor instead. -func (*CreateRequest) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{0} -} - -func (x *CreateRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *CreateRequest) GetEmail() string { - if x != nil { - return x.Email - } - return "" -} - -func (x *CreateRequest) GetPassword() string { - if x != nil { - return x.Password - } - return "" -} - -func (x *CreateRequest) GetPasswordConfirm() string { - if x != nil { - return x.PasswordConfirm - } - return "" -} - -func (x *CreateRequest) GetRole() Role { - if x != nil { - return x.Role - } - return Role_ROLE_UNSPECIFIED -} - -type CreateResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *CreateResponse) Reset() { - *x = CreateResponse{} - mi := &file_auth_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *CreateResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreateResponse) ProtoMessage() {} - -func (x *CreateResponse) ProtoReflect() protoreflect.Message { - mi := &file_auth_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreateResponse.ProtoReflect.Descriptor instead. -func (*CreateResponse) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{1} -} - -func (x *CreateResponse) GetId() int64 { - if x != nil { - return x.Id - } - return 0 -} - -type GetRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetRequest) Reset() { - *x = GetRequest{} - mi := &file_auth_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetRequest) ProtoMessage() {} - -func (x *GetRequest) ProtoReflect() protoreflect.Message { - mi := &file_auth_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetRequest.ProtoReflect.Descriptor instead. -func (*GetRequest) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{2} -} - -func (x *GetRequest) GetId() int64 { - if x != nil { - return x.Id - } - return 0 -} - -type GetResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` - Role Role `protobuf:"varint,4,opt,name=role,proto3,enum=auth_v1.Role" json:"role,omitempty"` - CreatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetResponse) Reset() { - *x = GetResponse{} - mi := &file_auth_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetResponse) ProtoMessage() {} - -func (x *GetResponse) ProtoReflect() protoreflect.Message { - mi := &file_auth_proto_msgTypes[3] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetResponse.ProtoReflect.Descriptor instead. -func (*GetResponse) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{3} -} - -func (x *GetResponse) GetId() int64 { - if x != nil { - return x.Id - } - return 0 -} - -func (x *GetResponse) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *GetResponse) GetEmail() string { - if x != nil { - return x.Email - } - return "" -} - -func (x *GetResponse) GetRole() Role { - if x != nil { - return x.Role - } - return Role_ROLE_UNSPECIFIED -} - -func (x *GetResponse) GetCreatedAt() *timestamppb.Timestamp { - if x != nil { - return x.CreatedAt - } - return nil -} - -func (x *GetResponse) GetUpdatedAt() *timestamppb.Timestamp { - if x != nil { - return x.UpdatedAt - } - return nil -} - -type UpdateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Name *wrapperspb.StringValue `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - Email *wrapperspb.StringValue `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpdateRequest) Reset() { - *x = UpdateRequest{} - mi := &file_auth_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpdateRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpdateRequest) ProtoMessage() {} - -func (x *UpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_auth_proto_msgTypes[4] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpdateRequest.ProtoReflect.Descriptor instead. -func (*UpdateRequest) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{4} -} - -func (x *UpdateRequest) GetId() int64 { - if x != nil { - return x.Id - } - return 0 -} - -func (x *UpdateRequest) GetName() *wrapperspb.StringValue { - if x != nil { - return x.Name - } - return nil -} - -func (x *UpdateRequest) GetEmail() *wrapperspb.StringValue { - if x != nil { - return x.Email - } - return nil -} - -type DeleteRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeleteRequest) Reset() { - *x = DeleteRequest{} - mi := &file_auth_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeleteRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteRequest) ProtoMessage() {} - -func (x *DeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_auth_proto_msgTypes[5] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. -func (*DeleteRequest) Descriptor() ([]byte, []int) { - return file_auth_proto_rawDescGZIP(), []int{5} -} - -func (x *DeleteRequest) GetId() int64 { - if x != nil { - return x.Id - } - return 0 -} - -var File_auth_proto protoreflect.FileDescriptor - -const file_auth_proto_rawDesc = "" + - "\n" + - "\n" + - "auth.proto\x12\aauth_v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xa3\x01\n" + - "\rCreateRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + - "\x05email\x18\x02 \x01(\tR\x05email\x12\x1a\n" + - "\bpassword\x18\x03 \x01(\tR\bpassword\x12)\n" + - "\x10password_confirm\x18\x04 \x01(\tR\x0fpasswordConfirm\x12!\n" + - "\x04role\x18\x05 \x01(\x0e2\r.auth_v1.RoleR\x04role\" \n" + - "\x0eCreateResponse\x12\x0e\n" + - "\x02id\x18\x01 \x01(\x03R\x02id\"\x1c\n" + - "\n" + - "GetRequest\x12\x0e\n" + - "\x02id\x18\x01 \x01(\x03R\x02id\"\xe0\x01\n" + - "\vGetResponse\x12\x0e\n" + - "\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" + - "\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" + - "\x05email\x18\x03 \x01(\tR\x05email\x12!\n" + - "\x04role\x18\x04 \x01(\x0e2\r.auth_v1.RoleR\x04role\x129\n" + - "\n" + - "created_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + - "\n" + - "updated_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"\x85\x01\n" + - "\rUpdateRequest\x12\x0e\n" + - "\x02id\x18\x01 \x01(\x03R\x02id\x120\n" + - "\x04name\x18\x02 \x01(\v2\x1c.google.protobuf.StringValueR\x04name\x122\n" + - "\x05email\x18\x03 \x01(\v2\x1c.google.protobuf.StringValueR\x05email\"\x1f\n" + - "\rDeleteRequest\x12\x0e\n" + - "\x02id\x18\x01 \x01(\x03R\x02id*1\n" + - "\x04Role\x12\x14\n" + - "\x10ROLE_UNSPECIFIED\x10\x00\x12\b\n" + - "\x04USER\x10\x01\x12\t\n" + - "\x05ADMIN\x10\x022\xe9\x01\n" + - "\x06AuthV1\x129\n" + - "\x06Create\x12\x16.auth_v1.CreateRequest\x1a\x17.auth_v1.CreateResponse\x120\n" + - "\x03Get\x12\x13.auth_v1.GetRequest\x1a\x14.auth_v1.GetResponse\x128\n" + - "\x06Update\x12\x16.auth_v1.UpdateRequest\x1a\x16.google.protobuf.Empty\x128\n" + - "\x06Delete\x12\x16.auth_v1.DeleteRequest\x1a\x16.google.protobuf.EmptyB+Z)github.com/Str1m/auth/pkg/auth_v1;auth_v1b\x06proto3" - -var ( - file_auth_proto_rawDescOnce sync.Once - file_auth_proto_rawDescData []byte -) - -func file_auth_proto_rawDescGZIP() []byte { - file_auth_proto_rawDescOnce.Do(func() { - file_auth_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_auth_proto_rawDesc), len(file_auth_proto_rawDesc))) - }) - return file_auth_proto_rawDescData -} - -var file_auth_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 6) -var file_auth_proto_goTypes = []any{ - (Role)(0), // 0: auth_v1.Role - (*CreateRequest)(nil), // 1: auth_v1.CreateRequest - (*CreateResponse)(nil), // 2: auth_v1.CreateResponse - (*GetRequest)(nil), // 3: auth_v1.GetRequest - (*GetResponse)(nil), // 4: auth_v1.GetResponse - (*UpdateRequest)(nil), // 5: auth_v1.UpdateRequest - (*DeleteRequest)(nil), // 6: auth_v1.DeleteRequest - (*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp - (*wrapperspb.StringValue)(nil), // 8: google.protobuf.StringValue - (*emptypb.Empty)(nil), // 9: google.protobuf.Empty -} -var file_auth_proto_depIdxs = []int32{ - 0, // 0: auth_v1.CreateRequest.role:type_name -> auth_v1.Role - 0, // 1: auth_v1.GetResponse.role:type_name -> auth_v1.Role - 7, // 2: auth_v1.GetResponse.created_at:type_name -> google.protobuf.Timestamp - 7, // 3: auth_v1.GetResponse.updated_at:type_name -> google.protobuf.Timestamp - 8, // 4: auth_v1.UpdateRequest.name:type_name -> google.protobuf.StringValue - 8, // 5: auth_v1.UpdateRequest.email:type_name -> google.protobuf.StringValue - 1, // 6: auth_v1.AuthV1.Create:input_type -> auth_v1.CreateRequest - 3, // 7: auth_v1.AuthV1.Get:input_type -> auth_v1.GetRequest - 5, // 8: auth_v1.AuthV1.Update:input_type -> auth_v1.UpdateRequest - 6, // 9: auth_v1.AuthV1.Delete:input_type -> auth_v1.DeleteRequest - 2, // 10: auth_v1.AuthV1.Create:output_type -> auth_v1.CreateResponse - 4, // 11: auth_v1.AuthV1.Get:output_type -> auth_v1.GetResponse - 9, // 12: auth_v1.AuthV1.Update:output_type -> google.protobuf.Empty - 9, // 13: auth_v1.AuthV1.Delete:output_type -> google.protobuf.Empty - 10, // [10:14] is the sub-list for method output_type - 6, // [6:10] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name -} - -func init() { file_auth_proto_init() } -func file_auth_proto_init() { - if File_auth_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_auth_proto_rawDesc), len(file_auth_proto_rawDesc)), - NumEnums: 1, - NumMessages: 6, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_auth_proto_goTypes, - DependencyIndexes: file_auth_proto_depIdxs, - EnumInfos: file_auth_proto_enumTypes, - MessageInfos: file_auth_proto_msgTypes, - }.Build() - File_auth_proto = out.File - file_auth_proto_goTypes = nil - file_auth_proto_depIdxs = nil -} diff --git a/pkg/auth_v1/auth_grpc.pb.go b/pkg/auth_v1/auth_grpc.pb.go deleted file mode 100644 index bbf93c4..0000000 --- a/pkg/auth_v1/auth_grpc.pb.go +++ /dev/null @@ -1,236 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.3 -// source: auth.proto - -package auth_v1 - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - emptypb "google.golang.org/protobuf/types/known/emptypb" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.64.0 or later. -const _ = grpc.SupportPackageIsVersion9 - -const ( - AuthV1_Create_FullMethodName = "/auth_v1.AuthV1/Create" - AuthV1_Get_FullMethodName = "/auth_v1.AuthV1/Get" - AuthV1_Update_FullMethodName = "/auth_v1.AuthV1/Update" - AuthV1_Delete_FullMethodName = "/auth_v1.AuthV1/Delete" -) - -// AuthV1Client is the client API for AuthV1 service. -// -// 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. -type AuthV1Client interface { - Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) - Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) - Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) -} - -type authV1Client struct { - cc grpc.ClientConnInterface -} - -func NewAuthV1Client(cc grpc.ClientConnInterface) AuthV1Client { - return &authV1Client{cc} -} - -func (c *authV1Client) Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(CreateResponse) - err := c.cc.Invoke(ctx, AuthV1_Create_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *authV1Client) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetResponse) - err := c.cc.Invoke(ctx, AuthV1_Get_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *authV1Client) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, AuthV1_Update_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *authV1Client) Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, AuthV1_Delete_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// AuthV1Server is the server API for AuthV1 service. -// All implementations must embed UnimplementedAuthV1Server -// for forward compatibility. -type AuthV1Server interface { - Create(context.Context, *CreateRequest) (*CreateResponse, error) - Get(context.Context, *GetRequest) (*GetResponse, error) - Update(context.Context, *UpdateRequest) (*emptypb.Empty, error) - Delete(context.Context, *DeleteRequest) (*emptypb.Empty, error) - mustEmbedUnimplementedAuthV1Server() -} - -// UnimplementedAuthV1Server must be embedded to have -// forward compatible implementations. -// -// NOTE: this should be embedded by value instead of pointer to avoid a nil -// pointer dereference when methods are called. -type UnimplementedAuthV1Server struct{} - -func (UnimplementedAuthV1Server) Create(context.Context, *CreateRequest) (*CreateResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Create not implemented") -} -func (UnimplementedAuthV1Server) Get(context.Context, *GetRequest) (*GetResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") -} -func (UnimplementedAuthV1Server) Update(context.Context, *UpdateRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") -} -func (UnimplementedAuthV1Server) Delete(context.Context, *DeleteRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") -} -func (UnimplementedAuthV1Server) mustEmbedUnimplementedAuthV1Server() {} -func (UnimplementedAuthV1Server) testEmbeddedByValue() {} - -// UnsafeAuthV1Server may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to AuthV1Server will -// result in compilation errors. -type UnsafeAuthV1Server interface { - mustEmbedUnimplementedAuthV1Server() -} - -func RegisterAuthV1Server(s grpc.ServiceRegistrar, srv AuthV1Server) { - // If the following call pancis, it indicates UnimplementedAuthV1Server was - // embedded by pointer and is nil. This will cause panics if an - // unimplemented method is ever invoked, so we test this at initialization - // time to prevent it from happening at runtime later due to I/O. - if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { - t.testEmbeddedByValue() - } - s.RegisterService(&AuthV1_ServiceDesc, srv) -} - -func _AuthV1_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AuthV1Server).Create(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: AuthV1_Create_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthV1Server).Create(ctx, req.(*CreateRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _AuthV1_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AuthV1Server).Get(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: AuthV1_Get_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthV1Server).Get(ctx, req.(*GetRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _AuthV1_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpdateRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AuthV1Server).Update(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: AuthV1_Update_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthV1Server).Update(ctx, req.(*UpdateRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _AuthV1_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AuthV1Server).Delete(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: AuthV1_Delete_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthV1Server).Delete(ctx, req.(*DeleteRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// AuthV1_ServiceDesc is the grpc.ServiceDesc for AuthV1 service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var AuthV1_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "auth_v1.AuthV1", - HandlerType: (*AuthV1Server)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Create", - Handler: _AuthV1_Create_Handler, - }, - { - MethodName: "Get", - Handler: _AuthV1_Get_Handler, - }, - { - MethodName: "Update", - Handler: _AuthV1_Update_Handler, - }, - { - MethodName: "Delete", - Handler: _AuthV1_Delete_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "auth.proto", -} From ff3cf8ed47c07f02e17175ee293669a919f858fe Mon Sep 17 00:00:00 2001 From: Str1m Date: Wed, 9 Apr 2025 15:07:00 +0300 Subject: [PATCH 2/4] fix linter problems --- Makefile | 4 +- api/auth_v1/auth.proto | 31 +- cmd/auth/main.go | 22 +- go.mod | 3 + go.sum | 7 + internal/grpc/auth/server.go | 37 +- internal/repository/storage.go | 7 + internal/repository/users/converter/user.go | 17 + internal/repository/users/model/user.go | 3 +- .../repository/users/postgres/repository.go | 143 ++++ internal/storage/postgres/postgres.go | 137 ---- internal/storage/storage.go | 1 - migrations/20250408120927_create_tables.sql | 2 +- pkg/auth_v1/auth.pb.go | 634 ++++++++++++++++++ pkg/auth_v1/auth_grpc.pb.go | 237 +++++++ 15 files changed, 1101 insertions(+), 184 deletions(-) create mode 100644 internal/repository/storage.go create mode 100644 internal/repository/users/postgres/repository.go delete mode 100644 internal/storage/postgres/postgres.go delete mode 100644 internal/storage/storage.go create mode 100644 pkg/auth_v1/auth.pb.go create mode 100644 pkg/auth_v1/auth_grpc.pb.go diff --git a/Makefile b/Makefile index e71641d..262cc59 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ lint: install-deps: GOBIN=$(LOCAL_BIN) go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.6 GOBIN=$(LOCAL_BIN) go install -mod=mod google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.71.1 + GOBIN=$(LOCAL_BIN) go install github.com/pressly/goose/v3/cmd/goose@latest generate: make generate-auth-api @@ -28,9 +29,6 @@ generate-auth-api: LOCAL_MIGRATION_DIR=$(MIGRATION_DIR) LOCAL_MIGRATION_DSN="host=localhost port=$(POSTGRES_PORT) dbname=$(POSTGRES_DB) user=$(POSTGRES_USER) password=$(POSTGRES_PASSWORD) sslmode=disable" -install-goose: - GOBIN=$(LOCAL_BIN) go install github.com/pressly/goose/v3/cmd/goose@latest - local-migration-status: $(LOCAL_BIN)/goose -dir ${LOCAL_MIGRATION_DIR} postgres ${LOCAL_MIGRATION_DSN} status -v diff --git a/api/auth_v1/auth.proto b/api/auth_v1/auth.proto index 2b5d41a..611f062 100644 --- a/api/auth_v1/auth.proto +++ b/api/auth_v1/auth.proto @@ -15,13 +15,16 @@ service AuthV1{ rpc Delete(DeleteRequest) returns (google.protobuf.Empty); } -enum Role { - ROLE_UNSPECIFIED = 0; - USER = 1; - ADMIN = 2; +message User { + int64 id = 1; + string name = 2; + string email = 3; + Role role = 4; + google.protobuf.Timestamp created_at = 5; + google.protobuf.Timestamp updated_at = 6; } -message CreateRequest { +message UserInfo { string name = 1; string email = 2; string password = 3; @@ -29,6 +32,15 @@ message CreateRequest { Role role = 5; } +enum Role { + USER = 0; + ADMIN = 1; +} + +message CreateRequest { + UserInfo userInfo = 1; +} + message CreateResponse { int64 id = 1; } @@ -38,17 +50,12 @@ message GetRequest{ } message GetResponse { - int64 id = 1; - string name = 2; - string email = 3; - Role role = 4; - google.protobuf.Timestamp created_at = 5; - google.protobuf.Timestamp updated_at = 6; + User user = 1; } message UpdateRequest { int64 id = 1; - google.protobuf.StringValue name = 2; + google.protobuf.StringValue name = 2; google.protobuf.StringValue email = 3; } diff --git a/cmd/auth/main.go b/cmd/auth/main.go index 4cf89de..594d9e3 100644 --- a/cmd/auth/main.go +++ b/cmd/auth/main.go @@ -1,21 +1,23 @@ package main import ( + "context" "flag" "log/slog" "net" "os" + "github.com/Str1m/auth/internal/grpc/auth" + "github.com/Str1m/auth/internal/repository/users/postgres" + "github.com/jackc/pgx/v5/pgxpool" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + "github.com/Str1m/auth/internal/config" "github.com/Str1m/auth/internal/config/env" - "github.com/Str1m/auth/internal/grpc/auth" "github.com/Str1m/auth/internal/lib/logger/handlers/slogpretty" "github.com/Str1m/auth/internal/lib/logger/sl" - "github.com/Str1m/auth/internal/storage/postgres" desc "github.com/Str1m/auth/pkg/auth_v1" - - "google.golang.org/grpc" - "google.golang.org/grpc/reflection" ) const ( @@ -43,11 +45,13 @@ func main() { log.Error("failed to get postgres config", sl.Err(err)) } - _, err = postgres.New(pgConfig.DSN()) + pool, err := pgxpool.New(context.Background(), pgConfig.DSN()) if err != nil { - log.Error("failed to connect to db", sl.Err(err)) + panic("") } + repo := postgres.NewRepository(pool) + l, err := net.Listen("tcp", grpcConfig.Addr()) if err != nil { log.Error("failed to listen", sl.Err(err)) @@ -56,7 +60,9 @@ func main() { s := grpc.NewServer() reflection.Register(s) - desc.RegisterAuthV1Server(s, &auth.Server{}) + grpcServer := auth.New(repo) + + desc.RegisterAuthV1Server(s, grpcServer) log.Info("server listening", slog.String("Addr", grpcConfig.Addr())) diff --git a/go.mod b/go.mod index 2404506..8f981f8 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/Str1m/auth go 1.24.1 require ( + github.com/Masterminds/squirrel v1.5.4 github.com/fatih/color v1.18.0 github.com/jackc/pgx/v5 v5.7.4 github.com/joho/godotenv v1.5.1 @@ -14,6 +15,8 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect golang.org/x/crypto v0.31.0 // indirect diff --git a/go.sum b/go.sum index 9884fe8..fae4e9c 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -15,6 +17,10 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -23,6 +29,7 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= diff --git a/internal/grpc/auth/server.go b/internal/grpc/auth/server.go index 6bbfd94..3539b5f 100644 --- a/internal/grpc/auth/server.go +++ b/internal/grpc/auth/server.go @@ -3,7 +3,6 @@ package auth import ( "context" - "github.com/Str1m/auth/internal/models" desc "github.com/Str1m/auth/pkg/auth_v1" "google.golang.org/grpc/codes" @@ -13,42 +12,38 @@ import ( type Server struct { desc.UnimplementedAuthV1Server - auth Auth + repo Repo } -func New(auth Auth) *Server { - return &Server{auth: auth} +func New(repo Repo) *Server { + return &Server{ + repo: repo, + } } -type Auth interface { - Create(ctx context.Context, name, email, password, passwordConfirm string, role desc.Role) (int64, error) - Get(ctx context.Context, id int64) (models.UserInfo, error) +type Repo interface { + Create(ctx context.Context, info *desc.UserInfo) (int64, error) + Get(ctx context.Context, id int64) (*desc.User, error) Update(ctx context.Context, id int64, name, email *string) error Delete(ctx context.Context, id int64) error } func (s *Server) Create(ctx context.Context, req *desc.CreateRequest) (*desc.CreateResponse, error) { - id, err := s.auth.Create(ctx, req.GetName(), req.GetEmail(), req.GetPassword(), - req.GetPasswordConfirm(), req.GetRole()) + id, err := s.repo.Create(ctx, req.GetUserInfo()) if err != nil { return nil, status.Error(codes.Internal, "failed to create user") } - return &desc.CreateResponse{Id: id}, nil + return &desc.CreateResponse{ + Id: id, + }, nil } func (s *Server) Get(ctx context.Context, req *desc.GetRequest) (*desc.GetResponse, error) { - userInfo, err := s.auth.Get(ctx, req.GetId()) + user, err := s.repo.Get(ctx, req.GetId()) if err != nil { return nil, status.Error(codes.Internal, "failed to get user") } - return &desc.GetResponse{ - Id: userInfo.ID, - Name: userInfo.Name, - Email: userInfo.Email, - Role: userInfo.Role, - CreatedAt: userInfo.CreatedAt, - UpdatedAt: userInfo.UpdatedAt, - }, nil + return &desc.GetResponse{User: user}, nil } func (s *Server) Update(ctx context.Context, req *desc.UpdateRequest) (*emptypb.Empty, error) { @@ -59,14 +54,14 @@ func (s *Server) Update(ctx context.Context, req *desc.UpdateRequest) (*emptypb. if req.GetEmail() != nil { email = &req.Email.Value } - if err := s.auth.Update(ctx, req.GetId(), name, email); err != nil { + if err := s.repo.Update(ctx, req.GetId(), name, email); err != nil { return nil, status.Error(codes.Internal, "failed to update user") } return &emptypb.Empty{}, nil } func (s *Server) Delete(ctx context.Context, req *desc.DeleteRequest) (*emptypb.Empty, error) { - if err := s.auth.Delete(ctx, req.GetId()); err != nil { + if err := s.repo.Delete(ctx, req.GetId()); err != nil { return nil, status.Error(codes.Internal, "failed to delete user") } return &emptypb.Empty{}, nil diff --git a/internal/repository/storage.go b/internal/repository/storage.go new file mode 100644 index 0000000..b3fe2c6 --- /dev/null +++ b/internal/repository/storage.go @@ -0,0 +1,7 @@ +package repository + +import "errors" + +var ( + ErrUserNotFound = errors.New("user not found") +) diff --git a/internal/repository/users/converter/user.go b/internal/repository/users/converter/user.go index 89f617e..26722ed 100644 --- a/internal/repository/users/converter/user.go +++ b/internal/repository/users/converter/user.go @@ -1 +1,18 @@ package converter + +import ( + "github.com/Str1m/auth/internal/repository/users/model" + desc "github.com/Str1m/auth/pkg/auth_v1" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func ToUserFromRepo(user *model.User) *desc.User { + return &desc.User{ + Id: user.ID, + Name: user.Name, + Email: user.Email, + Role: user.Role, + CreatedAt: timestamppb.New(user.CreatedAt), + UpdatedAt: timestamppb.New(user.UpdatedAt), + } +} diff --git a/internal/repository/users/model/user.go b/internal/repository/users/model/user.go index f1edbd9..9a3c879 100644 --- a/internal/repository/users/model/user.go +++ b/internal/repository/users/model/user.go @@ -1,8 +1,9 @@ package model import ( - desc "github.com/Str1m/auth/pkg/auth_v1" "time" + + desc "github.com/Str1m/auth/pkg/auth_v1" ) type User struct { diff --git a/internal/repository/users/postgres/repository.go b/internal/repository/users/postgres/repository.go new file mode 100644 index 0000000..a76fe49 --- /dev/null +++ b/internal/repository/users/postgres/repository.go @@ -0,0 +1,143 @@ +package postgres + +import ( + "context" + "fmt" + + "github.com/Str1m/auth/internal/repository" + "github.com/Str1m/auth/internal/repository/users/converter" + "github.com/Str1m/auth/internal/repository/users/model" + desc "github.com/Str1m/auth/pkg/auth_v1" + + sq "github.com/Masterminds/squirrel" + "github.com/jackc/pgx/v5/pgxpool" +) + +const ( + tableName = "users" + + idColumn = "id" + nameColumn = "name" + emailColumn = "email" + passwordHashColumn = "password_hash" + roleColumn = "role" + createdAtColumn = "created_at" + updatedAtColumn = "updated_at" +) + +type Repo struct { + db *pgxpool.Pool +} + +func NewRepository(db *pgxpool.Pool) *Repo { + return &Repo{db: db} +} + +func (r *Repo) Close() { + r.db.Close() +} + +func (r *Repo) Create(ctx context.Context, info *desc.UserInfo) (int64, error) { + const op = "repository.users.Create" + + builder := sq.Insert(tableName). + PlaceholderFormat(sq.Dollar). + Columns(nameColumn, emailColumn, passwordHashColumn, roleColumn). + Values(info.GetName(), info.GetEmail(), info.GetPassword(), info.GetRole()).Suffix("RETURNING id") + + query, args, err := builder.ToSql() + if err != nil { + return 0, fmt.Errorf("%s: %w", op, err) + } + + var id int64 + err = r.db.QueryRow(ctx, query, args...).Scan(&id) + if err != nil { + return 0, fmt.Errorf("%s: %w", op, err) + } + + return id, nil +} + +func (r *Repo) Get(ctx context.Context, id int64) (*desc.User, error) { + const op = "repository.users.Get" + + builder := sq.Select(idColumn, nameColumn, emailColumn, roleColumn, createdAtColumn, updatedAtColumn). + PlaceholderFormat(sq.Dollar). + From(tableName). + Where(sq.Eq{idColumn: id}) + + var user model.User + query, args, err := builder.ToSql() + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + err = r.db.QueryRow(ctx, query, args...).Scan( + &user.ID, + &user.Name, + &user.Email, + &user.Role, + &user.CreatedAt, + &user.UpdatedAt) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return converter.ToUserFromRepo(&user), nil +} + +func (r *Repo) Update(ctx context.Context, id int64, name, email *string) error { + const op = "repository.users.Update" + + builder := sq.Update(tableName). + PlaceholderFormat(sq.Dollar). + Where(sq.Eq{idColumn: id}) + + if name != nil { + builder = builder.Set(nameColumn, *name) + } + + if email != nil { + builder = builder.Set(emailColumn, *email) + } + + query, args, err := builder.ToSql() + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + result, err := r.db.Exec(ctx, query, args...) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + if result.RowsAffected() == 0 { + return repository.ErrUserNotFound + } + + return nil +} + +func (r *Repo) Delete(ctx context.Context, id int64) error { + const op = "repository.users.Delete" + + builder := sq.Delete(tableName). + PlaceholderFormat(sq.Dollar). + Where(sq.Eq{idColumn: id}) + + query, args, err := builder.ToSql() + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + result, err := r.db.Exec(ctx, query, args...) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + if result.RowsAffected() == 0 { + return repository.ErrUserNotFound + } + + return nil +} diff --git a/internal/storage/postgres/postgres.go b/internal/storage/postgres/postgres.go deleted file mode 100644 index 2bd88cb..0000000 --- a/internal/storage/postgres/postgres.go +++ /dev/null @@ -1,137 +0,0 @@ -package postgres - -import ( - "context" - "database/sql" - "fmt" - "time" - - "github.com/Str1m/auth/internal/models" - desc "github.com/Str1m/auth/pkg/auth_v1" - _ "github.com/jackc/pgx/v5/stdlib" - "google.golang.org/protobuf/types/known/timestamppb" -) - -type Storage struct { - db *sql.DB -} - -func New(dsn string) (*Storage, error) { - db, err := sql.Open("pgx", dsn) - if err != nil { - return nil, err - } - - if err = db.Ping(); err != nil { - return nil, err - } - - return &Storage{db: db}, nil -} - -func (s *Storage) Close() { - _ = s.db.Close() -} - -func (s *Storage) SaveUser(ctx context.Context, name string, email string, - passHash []byte, role desc.Role) (int64, error) { - const op = "storage.postgres.SaveUser" - - query := ` - INSERT INTO users (name, email, password_hash, role) - VALUES ($1, $2, $3, $4) - RETURNING id - ` - - var id int64 - err := s.db.QueryRowContext(ctx, query, name, email, passHash, int32(role)).Scan(&id) - if err != nil { - return 0, fmt.Errorf("%s: %w", op, err) - } - - return id, nil -} - -func (s *Storage) UpdateUser(ctx context.Context, id int64, name, email *string) error { - const op = "storage.postgres.UpdateUser" - - if name == nil && email == nil { - return nil - } - - query := `UPDATE users SET ` - args := []interface{}{} - argIndex := 1 - - if name != nil { - query += fmt.Sprintf("name = $%d, ", argIndex) - args = append(args, *name) - argIndex++ - } - if email != nil { - query += fmt.Sprintf("email = $%d, ", argIndex) - args = append(args, *email) - argIndex++ - } - - query = query[:len(query)-2] + fmt.Sprintf(" WHERE id = $%d", argIndex) - args = append(args, id) - - _, err := s.db.ExecContext(ctx, query, args...) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - - return nil -} - -func (s *Storage) User(ctx context.Context, email string) (models.UserInfo, error) { - const op = "storage.postgres.User" - - query := ` - SELECT id, name, email, role, created_at, updated_at - FROM users - WHERE email = $1 - ` - - var u models.UserInfo - var role int32 - var createdAt, updatedAt time.Time - - err := s.db.QueryRowContext(ctx, query, email).Scan( - &u.ID, &u.Name, &u.Email, &role, &createdAt, &updatedAt, - ) - if err != nil { - if err == sql.ErrNoRows { - return u, fmt.Errorf("%s: user not found: %w", op, err) - } - return u, fmt.Errorf("%s: %w", op, err) - } - - u.Role = desc.Role(role) - u.CreatedAt = timestamppb.New(createdAt) - u.UpdatedAt = timestamppb.New(updatedAt) - - return u, nil -} - -func (s *Storage) DeleteUser(ctx context.Context, id int64) error { - const op = "storage.postgres.DeleteUser" - - query := `DELETE FROM users WHERE id = $1` - - result, err := s.db.ExecContext(ctx, query, id) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - - rowsAffected, err := result.RowsAffected() - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - if rowsAffected == 0 { - return fmt.Errorf("%s: no user with id %d", op, id) - } - - return nil -} diff --git a/internal/storage/storage.go b/internal/storage/storage.go deleted file mode 100644 index 82be054..0000000 --- a/internal/storage/storage.go +++ /dev/null @@ -1 +0,0 @@ -package storage diff --git a/migrations/20250408120927_create_tables.sql b/migrations/20250408120927_create_tables.sql index fc61a7d..01fd555 100644 --- a/migrations/20250408120927_create_tables.sql +++ b/migrations/20250408120927_create_tables.sql @@ -5,7 +5,7 @@ CREATE TABLE users ( name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, password_hash BYTEA NOT NULL, - role SMALLINT NOT NULL DEFAULT 0, -- соответствует enum Role + role SMALLINT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); diff --git a/pkg/auth_v1/auth.pb.go b/pkg/auth_v1/auth.pb.go new file mode 100644 index 0000000..4556505 --- /dev/null +++ b/pkg/auth_v1/auth.pb.go @@ -0,0 +1,634 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: auth.proto + +package auth_v1 + +import ( + reflect "reflect" + sync "sync" + unsafe "unsafe" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Role int32 + +const ( + Role_USER Role = 0 + Role_ADMIN Role = 1 +) + +// Enum value maps for Role. +var ( + Role_name = map[int32]string{ + 0: "USER", + 1: "ADMIN", + } + Role_value = map[string]int32{ + "USER": 0, + "ADMIN": 1, + } +) + +func (x Role) Enum() *Role { + p := new(Role) + *p = x + return p +} + +func (x Role) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Role) Descriptor() protoreflect.EnumDescriptor { + return file_auth_proto_enumTypes[0].Descriptor() +} + +func (Role) Type() protoreflect.EnumType { + return &file_auth_proto_enumTypes[0] +} + +func (x Role) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Role.Descriptor instead. +func (Role) EnumDescriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{0} +} + +type User struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` + Role Role `protobuf:"varint,4,opt,name=role,proto3,enum=auth_v1.Role" json:"role,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *User) Reset() { + *x = User{} + mi := &file_auth_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*User) ProtoMessage() {} + +func (x *User) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use User.ProtoReflect.Descriptor instead. +func (*User) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{0} +} + +func (x *User) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *User) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *User) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *User) GetRole() Role { + if x != nil { + return x.Role + } + return Role_USER +} + +func (x *User) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *User) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +type UserInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + PasswordConfirm string `protobuf:"bytes,4,opt,name=password_confirm,json=passwordConfirm,proto3" json:"password_confirm,omitempty"` + Role Role `protobuf:"varint,5,opt,name=role,proto3,enum=auth_v1.Role" json:"role,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UserInfo) Reset() { + *x = UserInfo{} + mi := &file_auth_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UserInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserInfo) ProtoMessage() {} + +func (x *UserInfo) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UserInfo.ProtoReflect.Descriptor instead. +func (*UserInfo) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{1} +} + +func (x *UserInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UserInfo) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *UserInfo) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *UserInfo) GetPasswordConfirm() string { + if x != nil { + return x.PasswordConfirm + } + return "" +} + +func (x *UserInfo) GetRole() Role { + if x != nil { + return x.Role + } + return Role_USER +} + +type CreateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserInfo *UserInfo `protobuf:"bytes,1,opt,name=userInfo,proto3" json:"userInfo,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateRequest) Reset() { + *x = CreateRequest{} + mi := &file_auth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRequest) ProtoMessage() {} + +func (x *CreateRequest) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRequest.ProtoReflect.Descriptor instead. +func (*CreateRequest) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateRequest) GetUserInfo() *UserInfo { + if x != nil { + return x.UserInfo + } + return nil +} + +type CreateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateResponse) Reset() { + *x = CreateResponse{} + mi := &file_auth_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateResponse) ProtoMessage() {} + +func (x *CreateResponse) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateResponse.ProtoReflect.Descriptor instead. +func (*CreateResponse) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{3} +} + +func (x *CreateResponse) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +type GetRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRequest) Reset() { + *x = GetRequest{} + mi := &file_auth_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRequest) ProtoMessage() {} + +func (x *GetRequest) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRequest.ProtoReflect.Descriptor instead. +func (*GetRequest) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{4} +} + +func (x *GetRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +type GetResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetResponse) Reset() { + *x = GetResponse{} + mi := &file_auth_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResponse) ProtoMessage() {} + +func (x *GetResponse) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetResponse.ProtoReflect.Descriptor instead. +func (*GetResponse) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{5} +} + +func (x *GetResponse) GetUser() *User { + if x != nil { + return x.User + } + return nil +} + +type UpdateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name *wrapperspb.StringValue `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Email *wrapperspb.StringValue `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateRequest) Reset() { + *x = UpdateRequest{} + mi := &file_auth_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRequest) ProtoMessage() {} + +func (x *UpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRequest.ProtoReflect.Descriptor instead. +func (*UpdateRequest) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{6} +} + +func (x *UpdateRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *UpdateRequest) GetName() *wrapperspb.StringValue { + if x != nil { + return x.Name + } + return nil +} + +func (x *UpdateRequest) GetEmail() *wrapperspb.StringValue { + if x != nil { + return x.Email + } + return nil +} + +type DeleteRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteRequest) Reset() { + *x = DeleteRequest{} + mi := &file_auth_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRequest) ProtoMessage() {} + +func (x *DeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. +func (*DeleteRequest) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{7} +} + +func (x *DeleteRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +var File_auth_proto protoreflect.FileDescriptor + +const file_auth_proto_rawDesc = "" + + "\n" + + "\n" + + "auth.proto\x12\aauth_v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xd9\x01\n" + + "\x04User\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" + + "\x05email\x18\x03 \x01(\tR\x05email\x12!\n" + + "\x04role\x18\x04 \x01(\x0e2\r.auth_v1.RoleR\x04role\x129\n" + + "\n" + + "created_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "updated_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"\x9e\x01\n" + + "\bUserInfo\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + + "\x05email\x18\x02 \x01(\tR\x05email\x12\x1a\n" + + "\bpassword\x18\x03 \x01(\tR\bpassword\x12)\n" + + "\x10password_confirm\x18\x04 \x01(\tR\x0fpasswordConfirm\x12!\n" + + "\x04role\x18\x05 \x01(\x0e2\r.auth_v1.RoleR\x04role\">\n" + + "\rCreateRequest\x12-\n" + + "\buserInfo\x18\x01 \x01(\v2\x11.auth_v1.UserInfoR\buserInfo\" \n" + + "\x0eCreateResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\"\x1c\n" + + "\n" + + "GetRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\"0\n" + + "\vGetResponse\x12!\n" + + "\x04user\x18\x01 \x01(\v2\r.auth_v1.UserR\x04user\"\x85\x01\n" + + "\rUpdateRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x120\n" + + "\x04name\x18\x02 \x01(\v2\x1c.google.protobuf.StringValueR\x04name\x122\n" + + "\x05email\x18\x03 \x01(\v2\x1c.google.protobuf.StringValueR\x05email\"\x1f\n" + + "\rDeleteRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id*\x1b\n" + + "\x04Role\x12\b\n" + + "\x04USER\x10\x00\x12\t\n" + + "\x05ADMIN\x10\x012\xe9\x01\n" + + "\x06AuthV1\x129\n" + + "\x06Create\x12\x16.auth_v1.CreateRequest\x1a\x17.auth_v1.CreateResponse\x120\n" + + "\x03Get\x12\x13.auth_v1.GetRequest\x1a\x14.auth_v1.GetResponse\x128\n" + + "\x06Update\x12\x16.auth_v1.UpdateRequest\x1a\x16.google.protobuf.Empty\x128\n" + + "\x06Delete\x12\x16.auth_v1.DeleteRequest\x1a\x16.google.protobuf.EmptyB+Z)github.com/Str1m/auth/pkg/auth_v1;auth_v1b\x06proto3" + +var ( + file_auth_proto_rawDescOnce sync.Once + file_auth_proto_rawDescData []byte +) + +func file_auth_proto_rawDescGZIP() []byte { + file_auth_proto_rawDescOnce.Do(func() { + file_auth_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_auth_proto_rawDesc), len(file_auth_proto_rawDesc))) + }) + return file_auth_proto_rawDescData +} + +var file_auth_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_auth_proto_goTypes = []any{ + (Role)(0), // 0: auth_v1.Role + (*User)(nil), // 1: auth_v1.User + (*UserInfo)(nil), // 2: auth_v1.UserInfo + (*CreateRequest)(nil), // 3: auth_v1.CreateRequest + (*CreateResponse)(nil), // 4: auth_v1.CreateResponse + (*GetRequest)(nil), // 5: auth_v1.GetRequest + (*GetResponse)(nil), // 6: auth_v1.GetResponse + (*UpdateRequest)(nil), // 7: auth_v1.UpdateRequest + (*DeleteRequest)(nil), // 8: auth_v1.DeleteRequest + (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp + (*wrapperspb.StringValue)(nil), // 10: google.protobuf.StringValue + (*emptypb.Empty)(nil), // 11: google.protobuf.Empty +} +var file_auth_proto_depIdxs = []int32{ + 0, // 0: auth_v1.User.role:type_name -> auth_v1.Role + 9, // 1: auth_v1.User.created_at:type_name -> google.protobuf.Timestamp + 9, // 2: auth_v1.User.updated_at:type_name -> google.protobuf.Timestamp + 0, // 3: auth_v1.UserInfo.role:type_name -> auth_v1.Role + 2, // 4: auth_v1.CreateRequest.userInfo:type_name -> auth_v1.UserInfo + 1, // 5: auth_v1.GetResponse.user:type_name -> auth_v1.User + 10, // 6: auth_v1.UpdateRequest.name:type_name -> google.protobuf.StringValue + 10, // 7: auth_v1.UpdateRequest.email:type_name -> google.protobuf.StringValue + 3, // 8: auth_v1.AuthV1.Create:input_type -> auth_v1.CreateRequest + 5, // 9: auth_v1.AuthV1.Get:input_type -> auth_v1.GetRequest + 7, // 10: auth_v1.AuthV1.Update:input_type -> auth_v1.UpdateRequest + 8, // 11: auth_v1.AuthV1.Delete:input_type -> auth_v1.DeleteRequest + 4, // 12: auth_v1.AuthV1.Create:output_type -> auth_v1.CreateResponse + 6, // 13: auth_v1.AuthV1.Get:output_type -> auth_v1.GetResponse + 11, // 14: auth_v1.AuthV1.Update:output_type -> google.protobuf.Empty + 11, // 15: auth_v1.AuthV1.Delete:output_type -> google.protobuf.Empty + 12, // [12:16] is the sub-list for method output_type + 8, // [8:12] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name +} + +func init() { file_auth_proto_init() } +func file_auth_proto_init() { + if File_auth_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_auth_proto_rawDesc), len(file_auth_proto_rawDesc)), + NumEnums: 1, + NumMessages: 8, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_auth_proto_goTypes, + DependencyIndexes: file_auth_proto_depIdxs, + EnumInfos: file_auth_proto_enumTypes, + MessageInfos: file_auth_proto_msgTypes, + }.Build() + File_auth_proto = out.File + file_auth_proto_goTypes = nil + file_auth_proto_depIdxs = nil +} diff --git a/pkg/auth_v1/auth_grpc.pb.go b/pkg/auth_v1/auth_grpc.pb.go new file mode 100644 index 0000000..fad2f13 --- /dev/null +++ b/pkg/auth_v1/auth_grpc.pb.go @@ -0,0 +1,237 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.3 +// source: auth.proto + +package auth_v1 + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + AuthV1_Create_FullMethodName = "/auth_v1.AuthV1/Create" + AuthV1_Get_FullMethodName = "/auth_v1.AuthV1/Get" + AuthV1_Update_FullMethodName = "/auth_v1.AuthV1/Update" + AuthV1_Delete_FullMethodName = "/auth_v1.AuthV1/Delete" +) + +// AuthV1Client is the client API for AuthV1 service. +// +// 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. +type AuthV1Client interface { + Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) + Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) + Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type authV1Client struct { + cc grpc.ClientConnInterface +} + +func NewAuthV1Client(cc grpc.ClientConnInterface) AuthV1Client { + return &authV1Client{cc} +} + +func (c *authV1Client) Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateResponse) + err := c.cc.Invoke(ctx, AuthV1_Create_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authV1Client) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetResponse) + err := c.cc.Invoke(ctx, AuthV1_Get_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authV1Client) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, AuthV1_Update_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authV1Client) Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, AuthV1_Delete_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AuthV1Server is the server API for AuthV1 service. +// All implementations must embed UnimplementedAuthV1Server +// for forward compatibility. +type AuthV1Server interface { + Create(context.Context, *CreateRequest) (*CreateResponse, error) + Get(context.Context, *GetRequest) (*GetResponse, error) + Update(context.Context, *UpdateRequest) (*emptypb.Empty, error) + Delete(context.Context, *DeleteRequest) (*emptypb.Empty, error) + mustEmbedUnimplementedAuthV1Server() +} + +// UnimplementedAuthV1Server must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAuthV1Server struct{} + +func (UnimplementedAuthV1Server) Create(context.Context, *CreateRequest) (*CreateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Create not implemented") +} +func (UnimplementedAuthV1Server) Get(context.Context, *GetRequest) (*GetResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") +} +func (UnimplementedAuthV1Server) Update(context.Context, *UpdateRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") +} +func (UnimplementedAuthV1Server) Delete(context.Context, *DeleteRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") +} +func (UnimplementedAuthV1Server) mustEmbedUnimplementedAuthV1Server() {} +func (UnimplementedAuthV1Server) testEmbeddedByValue() {} + +// UnsafeAuthV1Server may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AuthV1Server will +// result in compilation errors. +type UnsafeAuthV1Server interface { + mustEmbedUnimplementedAuthV1Server() +} + +func RegisterAuthV1Server(s grpc.ServiceRegistrar, srv AuthV1Server) { + // If the following call pancis, it indicates UnimplementedAuthV1Server was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AuthV1_ServiceDesc, srv) +} + +func _AuthV1_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthV1Server).Create(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthV1_Create_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthV1Server).Create(ctx, req.(*CreateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthV1_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthV1Server).Get(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthV1_Get_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthV1Server).Get(ctx, req.(*GetRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthV1_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthV1Server).Update(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthV1_Update_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthV1Server).Update(ctx, req.(*UpdateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthV1_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthV1Server).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthV1_Delete_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthV1Server).Delete(ctx, req.(*DeleteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AuthV1_ServiceDesc is the grpc.ServiceDesc for AuthV1 service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AuthV1_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "auth_v1.AuthV1", + HandlerType: (*AuthV1Server)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Create", + Handler: _AuthV1_Create_Handler, + }, + { + MethodName: "Get", + Handler: _AuthV1_Get_Handler, + }, + { + MethodName: "Update", + Handler: _AuthV1_Update_Handler, + }, + { + MethodName: "Delete", + Handler: _AuthV1_Delete_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "auth.proto", +} From ad79c20e9d114cba8214b8f25893114eab468a87 Mon Sep 17 00:00:00 2001 From: Str1m Date: Fri, 11 Apr 2025 10:02:13 +0300 Subject: [PATCH 3/4] add service-provider and better main --- internal/api/user/create.go | 1 + internal/api/user/delete.go | 1 + internal/api/user/get.go | 1 + internal/api/user/service.go | 1 + internal/api/user/update.go | 1 + internal/app/app.go | 1 + internal/app/service_provider.go | 1 + internal/closer/closer.go | 1 + internal/converter/user.go | 1 + internal/grpc/auth/server.go | 68 ------------------------------ internal/{models => model}/user.go | 0 internal/service/service.go | 1 + internal/service/user/create.go | 1 + internal/service/user/delete.go | 1 + internal/service/user/get.go | 1 + internal/service/user/service.go | 1 + internal/service/user/update.go | 1 + internal/services/auth/auth.go | 42 ------------------ 18 files changed, 15 insertions(+), 110 deletions(-) create mode 100644 internal/api/user/create.go create mode 100644 internal/api/user/delete.go create mode 100644 internal/api/user/get.go create mode 100644 internal/api/user/service.go create mode 100644 internal/api/user/update.go create mode 100644 internal/app/app.go create mode 100644 internal/app/service_provider.go create mode 100644 internal/closer/closer.go create mode 100644 internal/converter/user.go delete mode 100644 internal/grpc/auth/server.go rename internal/{models => model}/user.go (100%) create mode 100644 internal/service/service.go create mode 100644 internal/service/user/create.go create mode 100644 internal/service/user/delete.go create mode 100644 internal/service/user/get.go create mode 100644 internal/service/user/service.go create mode 100644 internal/service/user/update.go delete mode 100644 internal/services/auth/auth.go diff --git a/internal/api/user/create.go b/internal/api/user/create.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/api/user/create.go @@ -0,0 +1 @@ +package user diff --git a/internal/api/user/delete.go b/internal/api/user/delete.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/api/user/delete.go @@ -0,0 +1 @@ +package user diff --git a/internal/api/user/get.go b/internal/api/user/get.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/api/user/get.go @@ -0,0 +1 @@ +package user diff --git a/internal/api/user/service.go b/internal/api/user/service.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/api/user/service.go @@ -0,0 +1 @@ +package user diff --git a/internal/api/user/update.go b/internal/api/user/update.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/api/user/update.go @@ -0,0 +1 @@ +package user diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..4879f7a --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1 @@ +package app diff --git a/internal/app/service_provider.go b/internal/app/service_provider.go new file mode 100644 index 0000000..4879f7a --- /dev/null +++ b/internal/app/service_provider.go @@ -0,0 +1 @@ +package app diff --git a/internal/closer/closer.go b/internal/closer/closer.go new file mode 100644 index 0000000..4fbb746 --- /dev/null +++ b/internal/closer/closer.go @@ -0,0 +1 @@ +package closer diff --git a/internal/converter/user.go b/internal/converter/user.go new file mode 100644 index 0000000..89f617e --- /dev/null +++ b/internal/converter/user.go @@ -0,0 +1 @@ +package converter diff --git a/internal/grpc/auth/server.go b/internal/grpc/auth/server.go deleted file mode 100644 index 3539b5f..0000000 --- a/internal/grpc/auth/server.go +++ /dev/null @@ -1,68 +0,0 @@ -package auth - -import ( - "context" - - desc "github.com/Str1m/auth/pkg/auth_v1" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/emptypb" -) - -type Server struct { - desc.UnimplementedAuthV1Server - repo Repo -} - -func New(repo Repo) *Server { - return &Server{ - repo: repo, - } -} - -type Repo interface { - Create(ctx context.Context, info *desc.UserInfo) (int64, error) - Get(ctx context.Context, id int64) (*desc.User, error) - Update(ctx context.Context, id int64, name, email *string) error - Delete(ctx context.Context, id int64) error -} - -func (s *Server) Create(ctx context.Context, req *desc.CreateRequest) (*desc.CreateResponse, error) { - id, err := s.repo.Create(ctx, req.GetUserInfo()) - if err != nil { - return nil, status.Error(codes.Internal, "failed to create user") - } - return &desc.CreateResponse{ - Id: id, - }, nil -} - -func (s *Server) Get(ctx context.Context, req *desc.GetRequest) (*desc.GetResponse, error) { - user, err := s.repo.Get(ctx, req.GetId()) - if err != nil { - return nil, status.Error(codes.Internal, "failed to get user") - } - return &desc.GetResponse{User: user}, nil -} - -func (s *Server) Update(ctx context.Context, req *desc.UpdateRequest) (*emptypb.Empty, error) { - var name, email *string - if req.GetName() != nil { - name = &req.Name.Value - } - if req.GetEmail() != nil { - email = &req.Email.Value - } - if err := s.repo.Update(ctx, req.GetId(), name, email); err != nil { - return nil, status.Error(codes.Internal, "failed to update user") - } - return &emptypb.Empty{}, nil -} - -func (s *Server) Delete(ctx context.Context, req *desc.DeleteRequest) (*emptypb.Empty, error) { - if err := s.repo.Delete(ctx, req.GetId()); err != nil { - return nil, status.Error(codes.Internal, "failed to delete user") - } - return &emptypb.Empty{}, nil -} diff --git a/internal/models/user.go b/internal/model/user.go similarity index 100% rename from internal/models/user.go rename to internal/model/user.go diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 0000000..6d43c33 --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1 @@ +package service diff --git a/internal/service/user/create.go b/internal/service/user/create.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/service/user/create.go @@ -0,0 +1 @@ +package user diff --git a/internal/service/user/delete.go b/internal/service/user/delete.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/service/user/delete.go @@ -0,0 +1 @@ +package user diff --git a/internal/service/user/get.go b/internal/service/user/get.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/service/user/get.go @@ -0,0 +1 @@ +package user diff --git a/internal/service/user/service.go b/internal/service/user/service.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/service/user/service.go @@ -0,0 +1 @@ +package user diff --git a/internal/service/user/update.go b/internal/service/user/update.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/service/user/update.go @@ -0,0 +1 @@ +package user diff --git a/internal/services/auth/auth.go b/internal/services/auth/auth.go deleted file mode 100644 index be2b8c8..0000000 --- a/internal/services/auth/auth.go +++ /dev/null @@ -1,42 +0,0 @@ -package auth - -import ( - "log/slog" - - "github.com/Str1m/auth/internal/models" -) - -type Repo interface { - SaveUser(email string, passHash []byte) (int64, error) - UpdateUser(id int64, name, email *string) error - User(email string) (models.UserInfo, error) - DeleteUser(id int64) error -} - -type Auth struct { - log *slog.Logger - authRepo Repo -} - -func New(log *slog.Logger, authRepo Repo) *Auth { - return &Auth{ - log: log, - authRepo: authRepo, - } -} - -// func Create(ctx context.Context, name, email, password, passwordConfirm string, role desc.Role) (int64, error) { -// panic("implement me") -// } - -// func Get(ctx context.Context, id int64) (models.UserInfo, error) { -// panic("implement me") -// } - -// func Update(ctx context.Context, id int64, name, email *string) error { -// panic("implement me") -// } - -// func Delete(ctx context.Context, id int64) error { -// panic("implement me") -// } From dcf707af9d1e8d4fbd6ed9c90d75f109d5c48be0 Mon Sep 17 00:00:00 2001 From: Str1m Date: Fri, 11 Apr 2025 10:46:38 +0300 Subject: [PATCH 4/4] add password hashing --- .golangci.pipeline.yaml | 4 +- cmd/auth/main.go | 95 +--------- go.mod | 8 +- go.sum | 16 +- internal/api/user/create.go | 19 ++ internal/api/user/delete.go | 16 ++ internal/api/user/get.go | 17 ++ internal/api/user/service.go | 24 +++ internal/api/user/update.go | 23 +++ internal/app/app.go | 98 +++++++++++ internal/app/service_provider.go | 165 ++++++++++++++++++ internal/closer/closer.go | 61 +++++++ internal/config/config.go | 5 +- internal/config/env/grpc.go | 2 +- internal/converter/user.go | 37 ++++ internal/model/user.go | 19 +- internal/repository/users/converter/user.go | 18 -- internal/repository/users/model/user.go | 16 -- internal/service/service.go | 6 + internal/service/user/create.go | 30 ++++ internal/service/user/delete.go | 15 ++ internal/service/user/get.go | 17 ++ internal/service/user/service.go | 26 +++ internal/service/user/update.go | 15 ++ internal/{repository => storage}/storage.go | 2 +- internal/storage/users/converter/user.go | 17 ++ internal/storage/users/model/user.go | 24 +++ .../users/postgres/repository.go | 23 +-- 28 files changed, 662 insertions(+), 156 deletions(-) delete mode 100644 internal/repository/users/converter/user.go delete mode 100644 internal/repository/users/model/user.go rename internal/{repository => storage}/storage.go (79%) create mode 100644 internal/storage/users/converter/user.go create mode 100644 internal/storage/users/model/user.go rename internal/{repository => storage}/users/postgres/repository.go (80%) diff --git a/.golangci.pipeline.yaml b/.golangci.pipeline.yaml index 993b203..5ed35d8 100644 --- a/.golangci.pipeline.yaml +++ b/.golangci.pipeline.yaml @@ -77,7 +77,7 @@ linters: - goprintffuncname # checks that printf-like functions are named with f at the end # - gosec # inspects source code for security problems - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string - - iface # checks the incorrect use of interfaces, helping developers avoid interface pollution +# - iface # checks the incorrect use of interfaces, helping developers avoid interface pollution - ineffassign # detects when assignments to existing variables are not used - intrange # finds places where for loops could make use of an integer range - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) @@ -89,7 +89,7 @@ linters: - nestif # reports deeply nested if statements - nilerr # finds the code that returns nil even if it checks that the error is not nil - nilnesserr # reports that it checks for err != nil, but it returns a different nil value error (powered by nilness and nilerr) - # - nilnil # checks that there is no simultaneous return of nil error and an invalid value + - nilnil # checks that there is no simultaneous return of nil error and an invalid value - noctx # finds sending http request without context.Context - nolintlint # reports ill-formed or insufficient nolint directives - nonamedreturns # reports all named returns diff --git a/cmd/auth/main.go b/cmd/auth/main.go index 594d9e3..1c51e53 100644 --- a/cmd/auth/main.go +++ b/cmd/auth/main.go @@ -2,102 +2,21 @@ package main import ( "context" - "flag" - "log/slog" - "net" - "os" + "log" - "github.com/Str1m/auth/internal/grpc/auth" - "github.com/Str1m/auth/internal/repository/users/postgres" - "github.com/jackc/pgx/v5/pgxpool" - "google.golang.org/grpc" - "google.golang.org/grpc/reflection" - - "github.com/Str1m/auth/internal/config" - "github.com/Str1m/auth/internal/config/env" - "github.com/Str1m/auth/internal/lib/logger/handlers/slogpretty" - "github.com/Str1m/auth/internal/lib/logger/sl" - desc "github.com/Str1m/auth/pkg/auth_v1" -) - -const ( - envLocal = "local" - envDev = "dev" - envProd = "prod" + "github.com/Str1m/auth/internal/app" ) func main() { - var cfgPath string - flag.StringVar(&cfgPath, "config-path", ".env", "path to config file") - flag.Parse() - - config.MustLoad(cfgPath) - - log := setupLogger() - - grpcConfig, err := env.NewGRPCConfig() - if err != nil { - log.Error("failed to get grpc config", sl.Err(err)) - } + ctx := context.Background() - pgConfig, err := env.NewPGConfig() + a, err := app.NewApp(ctx) if err != nil { - log.Error("failed to get postgres config", sl.Err(err)) + log.Fatalf("failed to init app %s", err.Error()) } - pool, err := pgxpool.New(context.Background(), pgConfig.DSN()) + err = a.Run() if err != nil { - panic("") + log.Fatalf("failed to run app %s", err.Error()) } - - repo := postgres.NewRepository(pool) - - l, err := net.Listen("tcp", grpcConfig.Addr()) - if err != nil { - log.Error("failed to listen", sl.Err(err)) - } - - s := grpc.NewServer() - reflection.Register(s) - - grpcServer := auth.New(repo) - - desc.RegisterAuthV1Server(s, grpcServer) - - log.Info("server listening", slog.String("Addr", grpcConfig.Addr())) - - if err = s.Serve(l); err != nil { - log.Error("failed to serve", sl.Err(err)) - } -} - -func setupLogger() *slog.Logger { - var log *slog.Logger - env := os.Getenv("ENV") - switch env { - case envLocal: - log = setupPrettySlog() - case envDev: - log = slog.New( - slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}), - ) - case envProd: - log = slog.New( - slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}), - ) - } - - return log -} - -func setupPrettySlog() *slog.Logger { - opts := slogpretty.PrettyHandlerOptions{ - SlogOpts: &slog.HandlerOptions{ - Level: slog.LevelDebug, - }, - } - - handler := opts.NewPrettyHandler(os.Stdout) - - return slog.New(handler) } diff --git a/go.mod b/go.mod index 8f981f8..b9d737b 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/fatih/color v1.18.0 github.com/jackc/pgx/v5 v5.7.4 github.com/joho/godotenv v1.5.1 + golang.org/x/crypto v0.37.0 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.36.6 ) @@ -19,10 +20,9 @@ require ( github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - golang.org/x/crypto v0.31.0 // indirect golang.org/x/net v0.26.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect ) diff --git a/go.sum b/go.sum index fae4e9c..c2dd7c6 100644 --- a/go.sum +++ b/go.sum @@ -34,18 +34,18 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= diff --git a/internal/api/user/create.go b/internal/api/user/create.go index a00006b..514284b 100644 --- a/internal/api/user/create.go +++ b/internal/api/user/create.go @@ -1 +1,20 @@ package user + +import ( + "context" + + "github.com/Str1m/auth/internal/converter" + desc "github.com/Str1m/auth/pkg/auth_v1" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (i *Implementation) Create(ctx context.Context, req *desc.CreateRequest) (*desc.CreateResponse, error) { + id, err := i.userService.Create(ctx, converter.ToUserInfoFromDesc(req.GetUserInfo())) + if err != nil { + return nil, status.Error(codes.Internal, "failed to create user") + } + return &desc.CreateResponse{ + Id: id, + }, nil +} diff --git a/internal/api/user/delete.go b/internal/api/user/delete.go index a00006b..190c426 100644 --- a/internal/api/user/delete.go +++ b/internal/api/user/delete.go @@ -1 +1,17 @@ package user + +import ( + "context" + + desc "github.com/Str1m/auth/pkg/auth_v1" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +func (i *Implementation) Delete(ctx context.Context, req *desc.DeleteRequest) (*emptypb.Empty, error) { + if err := i.userService.Delete(ctx, req.GetId()); err != nil { + return nil, status.Error(codes.Internal, "failed to delete user") + } + return &emptypb.Empty{}, nil +} diff --git a/internal/api/user/get.go b/internal/api/user/get.go index a00006b..9090346 100644 --- a/internal/api/user/get.go +++ b/internal/api/user/get.go @@ -1 +1,18 @@ package user + +import ( + "context" + + "github.com/Str1m/auth/internal/converter" + desc "github.com/Str1m/auth/pkg/auth_v1" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (i *Implementation) Get(ctx context.Context, req *desc.GetRequest) (*desc.GetResponse, error) { + user, err := i.userService.Get(ctx, req.GetId()) + if err != nil { + return nil, status.Error(codes.Internal, "failed to get user") + } + return &desc.GetResponse{User: converter.ToUserFromService(user)}, nil +} diff --git a/internal/api/user/service.go b/internal/api/user/service.go index a00006b..62bb833 100644 --- a/internal/api/user/service.go +++ b/internal/api/user/service.go @@ -1 +1,25 @@ package user + +import ( + "context" + + modelService "github.com/Str1m/auth/internal/model" + desc "github.com/Str1m/auth/pkg/auth_v1" +) + +type Service interface { + Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) + Get(ctx context.Context, id int64) (*modelService.User, error) + Update(ctx context.Context, id int64, name *string, email *string) error + Delete(ctx context.Context, id int64) error +} +type Implementation struct { + desc.UnimplementedAuthV1Server + userService Service +} + +func NewImplementation(userService Service) *Implementation { + return &Implementation{ + userService: userService, + } +} diff --git a/internal/api/user/update.go b/internal/api/user/update.go index a00006b..57e9c06 100644 --- a/internal/api/user/update.go +++ b/internal/api/user/update.go @@ -1 +1,24 @@ package user + +import ( + "context" + + desc "github.com/Str1m/auth/pkg/auth_v1" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +func (i *Implementation) Update(ctx context.Context, req *desc.UpdateRequest) (*emptypb.Empty, error) { + var name, email *string + if req.GetName() != nil { + name = &req.Name.Value + } + if req.GetEmail() != nil { + email = &req.Email.Value + } + if err := i.userService.Update(ctx, req.GetId(), name, email); err != nil { + return nil, status.Error(codes.Internal, "failed to update user") + } + return &emptypb.Empty{}, nil +} diff --git a/internal/app/app.go b/internal/app/app.go index 4879f7a..ea8d52d 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -1 +1,99 @@ package app + +import ( + "context" + "flag" + "log/slog" + "net" + + "github.com/Str1m/auth/internal/config" + "github.com/Str1m/auth/internal/lib/logger/sl" + desc "github.com/Str1m/auth/pkg/auth_v1" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/reflection" +) + +type App struct { + serviceProvider *ServiceProvider + grpcServer *grpc.Server +} + +func NewApp(ctx context.Context) (*App, error) { + a := &App{} + err := a.initDeps(ctx) + if err != nil { + return nil, err + } + return a, nil +} + +func (a *App) Run() error { + defer func() { + a.serviceProvider.cls.CloseAll() + a.serviceProvider.cls.Wait() + }() + return a.runGRPCServer() +} + +func (a *App) initDeps(ctx context.Context) error { + inits := []func(context.Context) error{ + a.initConfig, + a.initServiceProvider, + a.initGRPC, + } + + for _, f := range inits { + err := f(ctx) + if err != nil { + return err + } + } + return nil +} + +func (a *App) initConfig(_ context.Context) error { + var cfgPath string + flag.StringVar(&cfgPath, "config-path", ".env", "path to config file") + flag.Parse() + + err := config.MustLoad(cfgPath) + if err != nil { + return err + } + return nil +} + +func (a *App) initServiceProvider(_ context.Context) error { + a.serviceProvider = newServiceProvider() + return nil +} + +func (a *App) initGRPC(ctx context.Context) error { + a.grpcServer = grpc.NewServer(grpc.Creds(insecure.NewCredentials())) + + reflection.Register(a.grpcServer) + + desc.RegisterAuthV1Server(a.grpcServer, a.serviceProvider.UserAPIImpl(ctx)) + a.serviceProvider.cls.Add(func() error { + a.grpcServer.GracefulStop() + return nil + }) + return nil +} + +func (a *App) runGRPCServer() error { + a.serviceProvider.Log().Info("server listening", slog.String("Addr", a.serviceProvider.GRPCConfig().Address())) + + l, err := net.Listen("tcp", a.serviceProvider.GRPCConfig().Address()) + if err != nil { + a.serviceProvider.Log().Error("failed to listen", sl.Err(err)) + return err + } + + err = a.grpcServer.Serve(l) + if err != nil { + return err + } + return nil +} diff --git a/internal/app/service_provider.go b/internal/app/service_provider.go index 4879f7a..fb1bba5 100644 --- a/internal/app/service_provider.go +++ b/internal/app/service_provider.go @@ -1 +1,166 @@ package app + +import ( + "context" + "log" + "log/slog" + "os" + "syscall" + + userAPI "github.com/Str1m/auth/internal/api/user" + "github.com/Str1m/auth/internal/closer" + "github.com/Str1m/auth/internal/config/env" + "github.com/Str1m/auth/internal/lib/logger/handlers/slogpretty" + modelService "github.com/Str1m/auth/internal/model" + "github.com/Str1m/auth/internal/service/user" + "github.com/Str1m/auth/internal/storage/users/postgres" + "github.com/jackc/pgx/v5/pgxpool" +) + +const ( + envLocal = "local" + envDev = "dev" + envProd = "prod" +) + +type Storage interface { + Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) + Get(ctx context.Context, id int64) (*modelService.User, error) + Update(ctx context.Context, id int64, name, email *string) error + Delete(ctx context.Context, id int64) error +} + +type Service interface { + Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) + Get(ctx context.Context, id int64) (*modelService.User, error) + Update(ctx context.Context, id int64, name *string, email *string) error + Delete(ctx context.Context, id int64) error +} + +type StorageConfig interface { + DSN() string +} + +type GRPCConfig interface { + Address() string +} + +type ServiceProvider struct { + cls *closer.Closer + log *slog.Logger + + repoConfig StorageConfig + grpcConfig GRPCConfig + + pgPool *pgxpool.Pool + userRepository Storage + + userService Service + userAPI *userAPI.Implementation +} + +func newServiceProvider() *ServiceProvider { + return &ServiceProvider{} +} + +func (s *ServiceProvider) Closer() *closer.Closer { + if s.cls == nil { + s.cls = closer.New(os.Interrupt, syscall.SIGTERM) + } + + return s.cls +} + +func (s *ServiceProvider) Log() *slog.Logger { + if s.log == nil { + env := os.Getenv("ENV") + switch env { + case envLocal: + s.log = setupPrettySlog() + case envDev: + s.log = slog.New( + slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}), + ) + case envProd: + s.log = slog.New( + slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}), + ) + } + } + return s.log +} + +func (s *ServiceProvider) RepoConfig() StorageConfig { + if s.repoConfig == nil { + cfg, err := env.NewPGConfig() + if err != nil { + log.Fatalf("failed to get pg config: %s", err.Error()) + } + s.repoConfig = cfg + } + return s.repoConfig +} + +func (s *ServiceProvider) GRPCConfig() GRPCConfig { + if s.grpcConfig == nil { + cfg, err := env.NewGRPCConfig() + if err != nil { + log.Fatalf("failed to get pg config: %s", err.Error()) + } + s.grpcConfig = cfg + } + return s.grpcConfig +} + +func (s *ServiceProvider) PGPool(ctx context.Context) *pgxpool.Pool { + if s.pgPool == nil { + pool, err := pgxpool.New(ctx, s.RepoConfig().DSN()) + if err != nil { + log.Fatalf("failed to connect to database: %s", err.Error()) + } + if err = pool.Ping(ctx); err != nil { + log.Fatalf("ping error: %s", err.Error()) + } + s.Closer().Add(func() error { + pool.Close() + return nil + }) + s.pgPool = pool + } + return s.pgPool +} + +func (s *ServiceProvider) UserRepository(ctx context.Context) Storage { + if s.userRepository == nil { + s.userRepository = postgres.NewRepository(s.PGPool(ctx)) + } + + return s.userRepository +} + +func (s *ServiceProvider) UserService(ctx context.Context) Service { + if s.userService == nil { + s.userService = user.NewService(s.Log(), s.UserRepository(ctx)) + } + return s.userService +} + +func (s *ServiceProvider) UserAPIImpl(ctx context.Context) *userAPI.Implementation { + if s.userAPI == nil { + s.userAPI = userAPI.NewImplementation(s.UserService(ctx)) + } + + return s.userAPI +} + +func setupPrettySlog() *slog.Logger { + opts := slogpretty.PrettyHandlerOptions{ + SlogOpts: &slog.HandlerOptions{ + Level: slog.LevelDebug, + }, + } + + handler := opts.NewPrettyHandler(os.Stdout) + + return slog.New(handler) +} diff --git a/internal/closer/closer.go b/internal/closer/closer.go index 4fbb746..68d1818 100644 --- a/internal/closer/closer.go +++ b/internal/closer/closer.go @@ -1 +1,62 @@ package closer + +import ( + "log" + "os" + "os/signal" + "sync" +) + +type Closer struct { + mu sync.Mutex + once sync.Once + done chan struct{} + funcs []func() error +} + +func New(sig ...os.Signal) *Closer { + c := &Closer{done: make(chan struct{})} + if len(sig) > 0 { + go func() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, sig...) + <-ch + signal.Stop(ch) + c.CloseAll() + }() + } + return c +} + +func (c *Closer) Add(f ...func() error) { + c.mu.Lock() + c.funcs = append(c.funcs, f...) + c.mu.Unlock() +} + +func (c *Closer) Wait() { + <-c.done +} + +func (c *Closer) CloseAll() { + c.once.Do(func() { + defer close(c.done) + + c.mu.Lock() + funcs := c.funcs + c.funcs = nil + c.mu.Unlock() + + errs := make(chan error, len(funcs)) + for _, f := range funcs { + go func(f func() error) { + errs <- f() + }(f) + } + for range len(errs) { + if err := <-errs; err != nil { + log.Println("error returned from Closer") + } + } + }) +} diff --git a/internal/config/config.go b/internal/config/config.go index 73dc295..d159e8e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,8 +2,9 @@ package config import "github.com/joho/godotenv" -func MustLoad(path string) { +func MustLoad(path string) error { if err := godotenv.Load(path); err != nil { - panic("failed to load config") + return err } + return nil } diff --git a/internal/config/env/grpc.go b/internal/config/env/grpc.go index 0dd79d3..88b0692 100644 --- a/internal/config/env/grpc.go +++ b/internal/config/env/grpc.go @@ -29,6 +29,6 @@ func NewGRPCConfig() (*GRPCConfig, error) { return &GRPCConfig{addr: net.JoinHostPort(host, port)}, nil } -func (g *GRPCConfig) Addr() string { +func (g *GRPCConfig) Address() string { return g.addr } diff --git a/internal/converter/user.go b/internal/converter/user.go index 89f617e..9c324f6 100644 --- a/internal/converter/user.go +++ b/internal/converter/user.go @@ -1 +1,38 @@ package converter + +import ( + "github.com/Str1m/auth/internal/model" + desc "github.com/Str1m/auth/pkg/auth_v1" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func ToUserFromService(user *model.User) *desc.User { + return &desc.User{ + Id: user.ID, + Name: user.Name, + Email: user.Email, + Role: user.Role, + CreatedAt: timestamppb.New(user.CreatedAt), + UpdatedAt: timestamppb.New(user.UpdatedAt), + } +} + +func ToUserInfoFromService(userInfo *model.UserInfo) *desc.UserInfo { + return &desc.UserInfo{ + Name: userInfo.Name, + Email: userInfo.Email, + Password: userInfo.Password, + PasswordConfirm: userInfo.PasswordConfirm, + Role: userInfo.Role, + } +} + +func ToUserInfoFromDesc(userInfo *desc.UserInfo) *model.UserInfo { + return &model.UserInfo{ + Name: userInfo.GetName(), + Email: userInfo.GetEmail(), + Password: userInfo.GetPassword(), + PasswordConfirm: userInfo.GetPasswordConfirm(), + Role: userInfo.GetRole(), + } +} diff --git a/internal/model/user.go b/internal/model/user.go index cdaff34..f5a0727 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -1,15 +1,24 @@ -package models +package model import ( + "time" + desc "github.com/Str1m/auth/pkg/auth_v1" - "google.golang.org/protobuf/types/known/timestamppb" ) -type UserInfo struct { +type User struct { ID int64 Name string Email string Role desc.Role - CreatedAt *timestamppb.Timestamp - UpdatedAt *timestamppb.Timestamp + CreatedAt time.Time + UpdatedAt time.Time +} + +type UserInfo struct { + Name string + Email string + Password string + PasswordConfirm string + Role desc.Role } diff --git a/internal/repository/users/converter/user.go b/internal/repository/users/converter/user.go deleted file mode 100644 index 26722ed..0000000 --- a/internal/repository/users/converter/user.go +++ /dev/null @@ -1,18 +0,0 @@ -package converter - -import ( - "github.com/Str1m/auth/internal/repository/users/model" - desc "github.com/Str1m/auth/pkg/auth_v1" - "google.golang.org/protobuf/types/known/timestamppb" -) - -func ToUserFromRepo(user *model.User) *desc.User { - return &desc.User{ - Id: user.ID, - Name: user.Name, - Email: user.Email, - Role: user.Role, - CreatedAt: timestamppb.New(user.CreatedAt), - UpdatedAt: timestamppb.New(user.UpdatedAt), - } -} diff --git a/internal/repository/users/model/user.go b/internal/repository/users/model/user.go deleted file mode 100644 index 9a3c879..0000000 --- a/internal/repository/users/model/user.go +++ /dev/null @@ -1,16 +0,0 @@ -package model - -import ( - "time" - - desc "github.com/Str1m/auth/pkg/auth_v1" -) - -type User struct { - ID int64 `db:"id"` - Name string `db:"name"` - Email string `db:"email"` - Role desc.Role `db:"role"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` -} diff --git a/internal/service/service.go b/internal/service/service.go index 6d43c33..bb3368d 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -1 +1,7 @@ package service + +import "errors" + +var ( + ErrPassNotEqual = errors.New("passwords are not equal") +) diff --git a/internal/service/user/create.go b/internal/service/user/create.go index a00006b..f10adbc 100644 --- a/internal/service/user/create.go +++ b/internal/service/user/create.go @@ -1 +1,31 @@ package user + +import ( + "context" + "fmt" + + "github.com/Str1m/auth/internal/service" + + modelService "github.com/Str1m/auth/internal/model" + "golang.org/x/crypto/bcrypt" +) + +func (s *Service) Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) { + const op = "service.user.Create" + if userInfo.Password != userInfo.PasswordConfirm { + return 0, fmt.Errorf("%s: failed to hash password: %w", op, service.ErrPassNotEqual) + } + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(userInfo.Password), bcrypt.DefaultCost) + if err != nil { + return 0, fmt.Errorf("%s: failed to hash password: %w", op, err) + } + + userInfo.Password, userInfo.PasswordConfirm = "", "" + + id, err := s.UserRepository.Create(ctx, userInfo, hashedPassword) + if err != nil { + return 0, fmt.Errorf("%s: %w", op, err) + } + + return id, nil +} diff --git a/internal/service/user/delete.go b/internal/service/user/delete.go index a00006b..4e06727 100644 --- a/internal/service/user/delete.go +++ b/internal/service/user/delete.go @@ -1 +1,16 @@ package user + +import ( + "context" + "fmt" +) + +func (s *Service) Delete(ctx context.Context, id int64) error { + const op = "service.user.Delete" + err := s.UserRepository.Delete(ctx, id) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/service/user/get.go b/internal/service/user/get.go index a00006b..8d9a683 100644 --- a/internal/service/user/get.go +++ b/internal/service/user/get.go @@ -1 +1,18 @@ package user + +import ( + "context" + "fmt" + + modelService "github.com/Str1m/auth/internal/model" +) + +func (s *Service) Get(ctx context.Context, id int64) (*modelService.User, error) { + const op = "service.user.Get" + user, err := s.UserRepository.Get(ctx, id) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return user, nil +} diff --git a/internal/service/user/service.go b/internal/service/user/service.go index a00006b..651b498 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -1 +1,27 @@ package user + +import ( + "context" + "log/slog" + + modelService "github.com/Str1m/auth/internal/model" +) + +type Repository interface { + Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) + Get(ctx context.Context, id int64) (*modelService.User, error) + Update(ctx context.Context, id int64, name, email *string) error + Delete(ctx context.Context, id int64) error +} + +type Service struct { + log *slog.Logger + UserRepository Repository +} + +func NewService(log *slog.Logger, authRepo Repository) *Service { + return &Service{ + log: log, + UserRepository: authRepo, + } +} diff --git a/internal/service/user/update.go b/internal/service/user/update.go index a00006b..25f1546 100644 --- a/internal/service/user/update.go +++ b/internal/service/user/update.go @@ -1 +1,16 @@ package user + +import ( + "context" + "fmt" +) + +func (s *Service) Update(ctx context.Context, id int64, name *string, email *string) error { + const op = "service.user.Update" + err := s.UserRepository.Update(ctx, id, name, email) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/repository/storage.go b/internal/storage/storage.go similarity index 79% rename from internal/repository/storage.go rename to internal/storage/storage.go index b3fe2c6..4ff8c3f 100644 --- a/internal/repository/storage.go +++ b/internal/storage/storage.go @@ -1,4 +1,4 @@ -package repository +package storage import "errors" diff --git a/internal/storage/users/converter/user.go b/internal/storage/users/converter/user.go new file mode 100644 index 0000000..3e3dfc5 --- /dev/null +++ b/internal/storage/users/converter/user.go @@ -0,0 +1,17 @@ +package converter + +import ( + modelService "github.com/Str1m/auth/internal/model" + modelRepo "github.com/Str1m/auth/internal/storage/users/model" +) + +func ToUserFromStorage(user *modelRepo.User) *modelService.User { + return &modelService.User{ + ID: user.ID, + Name: user.Name, + Email: user.Email, + Role: user.Role, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + } +} diff --git a/internal/storage/users/model/user.go b/internal/storage/users/model/user.go new file mode 100644 index 0000000..f5a0727 --- /dev/null +++ b/internal/storage/users/model/user.go @@ -0,0 +1,24 @@ +package model + +import ( + "time" + + desc "github.com/Str1m/auth/pkg/auth_v1" +) + +type User struct { + ID int64 + Name string + Email string + Role desc.Role + CreatedAt time.Time + UpdatedAt time.Time +} + +type UserInfo struct { + Name string + Email string + Password string + PasswordConfirm string + Role desc.Role +} diff --git a/internal/repository/users/postgres/repository.go b/internal/storage/users/postgres/repository.go similarity index 80% rename from internal/repository/users/postgres/repository.go rename to internal/storage/users/postgres/repository.go index a76fe49..e5a8143 100644 --- a/internal/repository/users/postgres/repository.go +++ b/internal/storage/users/postgres/repository.go @@ -4,10 +4,11 @@ import ( "context" "fmt" - "github.com/Str1m/auth/internal/repository" - "github.com/Str1m/auth/internal/repository/users/converter" - "github.com/Str1m/auth/internal/repository/users/model" - desc "github.com/Str1m/auth/pkg/auth_v1" + modelService "github.com/Str1m/auth/internal/model" + + "github.com/Str1m/auth/internal/storage" + "github.com/Str1m/auth/internal/storage/users/converter" + modelRepo "github.com/Str1m/auth/internal/storage/users/model" sq "github.com/Masterminds/squirrel" "github.com/jackc/pgx/v5/pgxpool" @@ -37,13 +38,13 @@ func (r *Repo) Close() { r.db.Close() } -func (r *Repo) Create(ctx context.Context, info *desc.UserInfo) (int64, error) { +func (r *Repo) Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) { const op = "repository.users.Create" builder := sq.Insert(tableName). PlaceholderFormat(sq.Dollar). Columns(nameColumn, emailColumn, passwordHashColumn, roleColumn). - Values(info.GetName(), info.GetEmail(), info.GetPassword(), info.GetRole()).Suffix("RETURNING id") + Values(info.Name, info.Email, hashedPassword, info.Role).Suffix("RETURNING id") query, args, err := builder.ToSql() if err != nil { @@ -59,7 +60,7 @@ func (r *Repo) Create(ctx context.Context, info *desc.UserInfo) (int64, error) { return id, nil } -func (r *Repo) Get(ctx context.Context, id int64) (*desc.User, error) { +func (r *Repo) Get(ctx context.Context, id int64) (*modelService.User, error) { const op = "repository.users.Get" builder := sq.Select(idColumn, nameColumn, emailColumn, roleColumn, createdAtColumn, updatedAtColumn). @@ -67,7 +68,7 @@ func (r *Repo) Get(ctx context.Context, id int64) (*desc.User, error) { From(tableName). Where(sq.Eq{idColumn: id}) - var user model.User + var user modelRepo.User query, args, err := builder.ToSql() if err != nil { return nil, fmt.Errorf("%s: %w", op, err) @@ -83,7 +84,7 @@ func (r *Repo) Get(ctx context.Context, id int64) (*desc.User, error) { return nil, fmt.Errorf("%s: %w", op, err) } - return converter.ToUserFromRepo(&user), nil + return converter.ToUserFromStorage(&user), nil } func (r *Repo) Update(ctx context.Context, id int64, name, email *string) error { @@ -112,7 +113,7 @@ func (r *Repo) Update(ctx context.Context, id int64, name, email *string) error } if result.RowsAffected() == 0 { - return repository.ErrUserNotFound + return storage.ErrUserNotFound } return nil @@ -136,7 +137,7 @@ func (r *Repo) Delete(ctx context.Context, id int64) error { } if result.RowsAffected() == 0 { - return repository.ErrUserNotFound + return storage.ErrUserNotFound } return nil