diff --git a/README.md b/README.md new file mode 100644 index 0000000..600d058 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +You have to select the pb package first, by running this command: + +package pb + +Then you would be able to show the service of that package: +show service + +Next, you select the API service by running: +service xufin \ No newline at end of file diff --git a/gapi/converter.go b/gapi/converter.go new file mode 100644 index 0000000..fa60479 --- /dev/null +++ b/gapi/converter.go @@ -0,0 +1,17 @@ +package gapi + +import ( + db "github.com/arya2004/xyfin/db/sqlc" + "github.com/arya2004/xyfin/pb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func ConvertUser(user db.User) *pb.User { + return &pb.User{ + Username: user.Username, + FullName: user.FullName, + Email: user.Email, + PasswordChangedAt: timestamppb.New(user.PasswordChangedAt), + CreatedAt: timestamppb.New(user.CreatedAt), + } +} \ No newline at end of file diff --git a/gapi/rpc_create_user.go b/gapi/rpc_create_user.go new file mode 100644 index 0000000..70837c8 --- /dev/null +++ b/gapi/rpc_create_user.go @@ -0,0 +1,48 @@ +package gapi + +import ( + "context" + + db "github.com/arya2004/xyfin/db/sqlc" + "github.com/arya2004/xyfin/pb" + "github.com/arya2004/xyfin/utils" + "github.com/lib/pq" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + + +func (server *Server) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) { + + hashedPassword, err := utils.HashPassword(req.GetPassword()) + if err != nil { + return nil, status.Errorf(codes.Internal, "failled to hash -password: %v", err) + } + + arg := db.CreateUserParams{ + FullName: req.GetFullName(), + Email: req.GetEmail(), + Username: req.GetUsername(), + HashedPassword: hashedPassword, + } + + user, err := server.store.CreateUser(ctx, arg) + if err != nil { + if pqErr, ok := err.(*pq.Error); ok { + switch pqErr.Code.Name() { + case "unique_violation": + return nil, status.Errorf(codes.AlreadyExists, "username already exist %v", err) + + } + } + return nil, status.Errorf(codes.Internal, "failled to create user: %v", err) + + } + + resp := &pb.CreateUserResponse{ + User: ConvertUser(user), + } + + return resp, nil +} + diff --git a/gapi/rpc_login_user.go b/gapi/rpc_login_user.go new file mode 100644 index 0000000..e8e72b7 --- /dev/null +++ b/gapi/rpc_login_user.go @@ -0,0 +1,73 @@ +package gapi + +import ( + "context" + "database/sql" + + db "github.com/arya2004/xyfin/db/sqlc" + "github.com/arya2004/xyfin/pb" + "github.com/arya2004/xyfin/utils" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" +) + + + +func (server *Server) LoginUser(ctx context.Context, req *pb.LoginUserRequest) (*pb.LoginUserResponse, error) { + + user, err := server.store.GetUser(ctx, req.GetUsername()) + if err != nil { + if err == sql.ErrNoRows{ + return nil, status.Errorf(codes.NotFound, "user not found") + } + return nil, status.Errorf(codes.Internal, "falled to find user") + } + + err = utils.CheckPassword(req.Password, user.HashedPassword) + if err != nil { + return nil, status.Errorf(codes.NotFound, "incorrect password") + } + + accessToken,accessPayload, err := server.tokenMaker.CreateToken( + user.Username, + server.config.AccessTokenDuration, + ) + + if err != nil { + return nil, status.Errorf(codes.Internal, "failled to create access token") + } + + refreshToken,refreshPayload, err := server.tokenMaker.CreateToken( + user.Username, + server.config.RefreshTokenDuration, + ) + + if err != nil { + return nil, status.Errorf(codes.Internal, "faliled to create refresh token") + } + + session, err := server.store.CreateSession(ctx, db.CreateSessionParams{ + ID: refreshPayload.ID, + Username: user.Username, + RefreshToken: refreshToken, + UserAgent: "ctx.Request.UserAgent()", + ClientIp: "ctx.ClientIP()", + IsBlocked: false, + ExpiresAt: refreshPayload.ExpiredAt, + }) + + if err != nil { + return nil, status.Errorf(codes.Internal, "failled to create session") + } + + rsp := &pb.LoginUserResponse{ + User: ConvertUser(user), + SessionId: session.ID.String(), + AccessToken: accessToken, + RefreshToken: refreshToken, + AccessTokenExpiresAt: timestamppb.New(accessPayload.ExpiredAt), + RefreshTokenExpiresAt: timestamppb.New(refreshPayload.ExpiredAt), + } + return rsp, nil +} \ No newline at end of file diff --git a/main.go b/main.go index 5af6387..7e9cf41 100644 --- a/main.go +++ b/main.go @@ -42,11 +42,12 @@ func main() { } func runGrpcServer(config utils.Configuration, store db.Store) { - grpcServer := grpc.NewServer() + server, err := gapi.NewServer(config, store) if err != nil { log.Fatal("cannot create server", err) } + grpcServer := grpc.NewServer() pb.RegisterXyfinServer(grpcServer, server) reflection.Register(grpcServer) diff --git a/pb/service_xyfin.pb.go b/pb/service_xyfin.pb.go index 64009c3..a83beb8 100644 --- a/pb/service_xyfin.pb.go +++ b/pb/service_xyfin.pb.go @@ -25,24 +25,34 @@ var file_service_xyfin_proto_rawDesc = []byte{ 0x0a, 0x13, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x78, 0x79, 0x66, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70, 0x62, 0x1a, 0x15, 0x72, 0x70, 0x63, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x32, 0x46, 0x0a, 0x05, 0x58, 0x79, 0x66, 0x69, 0x6e, 0x12, 0x3d, 0x0a, 0x0a, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x1a, 0x14, 0x72, 0x70, 0x63, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x82, 0x01, 0x0a, 0x05, 0x58, 0x79, 0x66, 0x69, 0x6e, + 0x12, 0x3d, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x1e, 0x5a, 0x1c, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x79, 0x61, 0x32, 0x30, 0x30, 0x34, 0x2f, - 0x78, 0x79, 0x66, 0x69, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x3a, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x14, 0x2e, 0x70, + 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x1e, 0x5a, 0x1c, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x79, 0x61, 0x32, 0x30, + 0x30, 0x34, 0x2f, 0x78, 0x79, 0x66, 0x69, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var file_service_xyfin_proto_goTypes = []any{ (*CreateUserRequest)(nil), // 0: pb.CreateUserRequest - (*CreateUserResponse)(nil), // 1: pb.CreateUserResponse + (*LoginUserRequest)(nil), // 1: pb.LoginUserRequest + (*CreateUserResponse)(nil), // 2: pb.CreateUserResponse + (*LoginUserResponse)(nil), // 3: pb.LoginUserResponse } var file_service_xyfin_proto_depIdxs = []int32{ 0, // 0: pb.Xyfin.CreateUser:input_type -> pb.CreateUserRequest - 1, // 1: pb.Xyfin.CreateUser:output_type -> pb.CreateUserResponse - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type + 1, // 1: pb.Xyfin.LoginUser:input_type -> pb.LoginUserRequest + 2, // 2: pb.Xyfin.CreateUser:output_type -> pb.CreateUserResponse + 3, // 3: pb.Xyfin.LoginUser:output_type -> pb.LoginUserResponse + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -54,6 +64,7 @@ func file_service_xyfin_proto_init() { return } file_rpc_create_user_proto_init() + file_rpc_login_user_proto_init() type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/pb/service_xyfin_grpc.pb.go b/pb/service_xyfin_grpc.pb.go index 85616b1..1a1765f 100644 --- a/pb/service_xyfin_grpc.pb.go +++ b/pb/service_xyfin_grpc.pb.go @@ -20,6 +20,7 @@ const _ = grpc.SupportPackageIsVersion7 const ( Xyfin_CreateUser_FullMethodName = "/pb.Xyfin/CreateUser" + Xyfin_LoginUser_FullMethodName = "/pb.Xyfin/LoginUser" ) // XyfinClient is the client API for Xyfin service. @@ -27,6 +28,7 @@ const ( // 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 XyfinClient interface { CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*CreateUserResponse, error) + LoginUser(ctx context.Context, in *LoginUserRequest, opts ...grpc.CallOption) (*LoginUserResponse, error) } type xyfinClient struct { @@ -47,11 +49,22 @@ func (c *xyfinClient) CreateUser(ctx context.Context, in *CreateUserRequest, opt return out, nil } +func (c *xyfinClient) LoginUser(ctx context.Context, in *LoginUserRequest, opts ...grpc.CallOption) (*LoginUserResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LoginUserResponse) + err := c.cc.Invoke(ctx, Xyfin_LoginUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // XyfinServer is the server API for Xyfin service. // All implementations must embed UnimplementedXyfinServer // for forward compatibility. type XyfinServer interface { CreateUser(context.Context, *CreateUserRequest) (*CreateUserResponse, error) + LoginUser(context.Context, *LoginUserRequest) (*LoginUserResponse, error) mustEmbedUnimplementedXyfinServer() } @@ -65,6 +78,9 @@ type UnimplementedXyfinServer struct{} func (UnimplementedXyfinServer) CreateUser(context.Context, *CreateUserRequest) (*CreateUserResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateUser not implemented") } +func (UnimplementedXyfinServer) LoginUser(context.Context, *LoginUserRequest) (*LoginUserResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method LoginUser not implemented") +} func (UnimplementedXyfinServer) mustEmbedUnimplementedXyfinServer() {} func (UnimplementedXyfinServer) testEmbeddedByValue() {} @@ -104,6 +120,24 @@ func _Xyfin_CreateUser_Handler(srv interface{}, ctx context.Context, dec func(in return interceptor(ctx, in, info, handler) } +func _Xyfin_LoginUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LoginUserRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(XyfinServer).LoginUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Xyfin_LoginUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(XyfinServer).LoginUser(ctx, req.(*LoginUserRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Xyfin_ServiceDesc is the grpc.ServiceDesc for Xyfin service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -115,6 +149,10 @@ var Xyfin_ServiceDesc = grpc.ServiceDesc{ MethodName: "CreateUser", Handler: _Xyfin_CreateUser_Handler, }, + { + MethodName: "LoginUser", + Handler: _Xyfin_LoginUser_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "service_xyfin.proto", diff --git a/proto/service_xyfin.proto b/proto/service_xyfin.proto index c676408..c8e44f8 100644 --- a/proto/service_xyfin.proto +++ b/proto/service_xyfin.proto @@ -3,10 +3,11 @@ syntax = "proto3"; package pb; import "rpc_create_user.proto"; +import "rpc_login_user.proto"; option go_package = "github.com/arya2004/xyfin/pb"; service Xyfin { rpc CreateUser (CreateUserRequest) returns (CreateUserResponse){} - + rpc LoginUser (LoginUserRequest) returns (LoginUserResponse) {} } \ No newline at end of file