// Package grpc provides gRPC client implementations for service clients. package grpc import ( "context" "fmt" identityv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/identity/v1" "git.dcentral.systems/toolz/goplt/pkg/registry" "git.dcentral.systems/toolz/goplt/pkg/services" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) // IdentityClient implements IdentityServiceClient using gRPC. type IdentityClient struct { registry registry.ServiceRegistry conn *grpc.ClientConn client identityv1.IdentityServiceClient } // NewIdentityClient creates a new gRPC client for the Identity Service. func NewIdentityClient(reg registry.ServiceRegistry) (services.IdentityServiceClient, error) { client := &IdentityClient{ registry: reg, } return client, nil } // connect connects to the Identity Service. func (c *IdentityClient) connect(ctx context.Context) error { if c.conn != nil { return nil } instances, err := c.registry.Discover(ctx, "identity-service") if err != nil { return fmt.Errorf("failed to discover identity service: %w", err) } if len(instances) == 0 { return fmt.Errorf("no instances found for identity-service") } instance := instances[0] address := fmt.Sprintf("%s:%d", instance.Address, instance.Port) conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return fmt.Errorf("failed to connect to identity-service at %s: %w", address, err) } c.conn = conn c.client = identityv1.NewIdentityServiceClient(conn) return nil } // GetUser retrieves a user by ID. func (c *IdentityClient) GetUser(ctx context.Context, id string) (*services.User, error) { if err := c.connect(ctx); err != nil { return nil, err } resp, err := c.client.GetUser(ctx, &identityv1.GetUserRequest{Id: id}) if err != nil { return nil, fmt.Errorf("get user failed: %w", err) } return protoUserToServiceUser(resp.User), nil } // GetUserByEmail retrieves a user by email address. func (c *IdentityClient) GetUserByEmail(ctx context.Context, email string) (*services.User, error) { if err := c.connect(ctx); err != nil { return nil, err } resp, err := c.client.GetUserByEmail(ctx, &identityv1.GetUserByEmailRequest{Email: email}) if err != nil { return nil, fmt.Errorf("get user by email failed: %w", err) } return protoUserToServiceUser(resp.User), nil } // CreateUser creates a new user. func (c *IdentityClient) CreateUser(ctx context.Context, user *services.CreateUserRequest) (*services.User, error) { if err := c.connect(ctx); err != nil { return nil, err } resp, err := c.client.CreateUser(ctx, &identityv1.CreateUserRequest{ Email: user.Email, Username: user.Username, Password: user.Password, FirstName: user.FirstName, LastName: user.LastName, }) if err != nil { return nil, fmt.Errorf("create user failed: %w", err) } return protoUserToServiceUser(resp.User), nil } // UpdateUser updates an existing user. func (c *IdentityClient) UpdateUser(ctx context.Context, id string, user *services.UpdateUserRequest) (*services.User, error) { if err := c.connect(ctx); err != nil { return nil, err } var email, username, firstName, lastName *string if user.Email != nil && *user.Email != "" { email = user.Email } if user.Username != nil && *user.Username != "" { username = user.Username } if user.FirstName != nil && *user.FirstName != "" { firstName = user.FirstName } if user.LastName != nil && *user.LastName != "" { lastName = user.LastName } resp, err := c.client.UpdateUser(ctx, &identityv1.UpdateUserRequest{ Id: id, Email: email, Username: username, FirstName: firstName, LastName: lastName, }) if err != nil { return nil, fmt.Errorf("update user failed: %w", err) } return protoUserToServiceUser(resp.User), nil } // DeleteUser deletes a user. func (c *IdentityClient) DeleteUser(ctx context.Context, id string) error { if err := c.connect(ctx); err != nil { return err } _, err := c.client.DeleteUser(ctx, &identityv1.DeleteUserRequest{Id: id}) if err != nil { return fmt.Errorf("delete user failed: %w", err) } return nil } // VerifyEmail verifies a user's email address using a verification token. func (c *IdentityClient) VerifyEmail(ctx context.Context, token string) error { if err := c.connect(ctx); err != nil { return err } _, err := c.client.VerifyEmail(ctx, &identityv1.VerifyEmailRequest{Token: token}) if err != nil { return fmt.Errorf("verify email failed: %w", err) } return nil } // RequestPasswordReset requests a password reset token. func (c *IdentityClient) RequestPasswordReset(ctx context.Context, email string) error { if err := c.connect(ctx); err != nil { return err } _, err := c.client.RequestPasswordReset(ctx, &identityv1.RequestPasswordResetRequest{Email: email}) if err != nil { return fmt.Errorf("request password reset failed: %w", err) } return nil } // ResetPassword resets a user's password using a reset token. func (c *IdentityClient) ResetPassword(ctx context.Context, token, newPassword string) error { if err := c.connect(ctx); err != nil { return err } _, err := c.client.ResetPassword(ctx, &identityv1.ResetPasswordRequest{ Token: token, NewPassword: newPassword, }) if err != nil { return fmt.Errorf("reset password failed: %w", err) } return nil } // VerifyPassword verifies a user's password and returns the user if valid. func (c *IdentityClient) VerifyPassword(ctx context.Context, email, password string) (*services.User, error) { if err := c.connect(ctx); err != nil { return nil, err } resp, err := c.client.VerifyPassword(ctx, &identityv1.VerifyPasswordRequest{ Email: email, Password: password, }) if err != nil { return nil, fmt.Errorf("verify password failed: %w", err) } return protoUserToServiceUser(resp.User), nil } // protoUserToServiceUser converts a proto User to a service User. func protoUserToServiceUser(u *identityv1.User) *services.User { if u == nil { return nil } return &services.User{ ID: u.Id, Email: u.Email, Username: u.Username, FirstName: u.FirstName, LastName: u.LastName, EmailVerified: u.EmailVerified, CreatedAt: u.CreatedAt, UpdatedAt: u.UpdatedAt, } }