diff --git a/internal/interceptors/interceptors.go b/internal/interceptors/interceptors.go new file mode 100644 index 0000000..c0db291 --- /dev/null +++ b/internal/interceptors/interceptors.go @@ -0,0 +1,143 @@ +package interceptors + +//var defaultUser = &models.User{ +// UserId: nil, +// Role: models.RoleSpectator.AsPointer(), +// UpdatedAt: nil, +//} +// +//func extractToken(ctx context.Context) string { +// md, ok := metadata.FromIncomingContext(ctx) +// if !ok { +// return "" +// } +// tokens := md.Get("token") +// +// if len(tokens) == 0 { +// return "" +// } +// +// return tokens[0] +//} +// +//func (s *TesterServer) readSessionAndReadUser(ctx context.Context, token string) (*models.User, error) { +// // FIXME: possible bottle neck: should we cache it? (think of it in future) +// // FIXME: maybe use single connection instead of multiple requests +// userId, err := s.sessionClient.Read(ctx, &sessionv1.ReadSessionRequest{Token: token}) +// if err != nil { +// return nil, err +// } +// +// user, err := s.userService.ReadUserById(ctx, userId.GetUserId()) // FIXME: must be cached! +// if err != nil { +// if errors.Is(err, utils.ErrNotFound) { +// user = &models.User{ +// UserId: utils.AsInt32P(userId.GetUserId()), +// Role: models.RoleParticipant.AsPointer(), +// } +// err = s.userService.CreateUser(ctx, user) +// if err != nil { +// return nil, err +// } +// } else { +// return nil, err +// } +// } +// +// return user, nil +//} +// +//func insertUser(ctx context.Context, user *models.User) context.Context { +// return context.WithValue(ctx, "user", user) +//} +// +//func (s *TesterServer) AuthUnaryInterceptor() grpc.UnaryServerInterceptor { +// return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { +// token := extractToken(ctx) +// if token == "" { +// return handler(insertUser(ctx, defaultUser), req) +// } +// +// user, err := s.readSessionAndReadUser(ctx, token) +// if err != nil { +// return handler(insertUser(ctx, defaultUser), req) +// } +// +// return handler(insertUser(ctx, user), req) +// } +//} +// +//type ssWrapper struct { +// grpc.ServerStream +// ctx context.Context +//} +// +//func (s *ssWrapper) Context() context.Context { +// return s.ctx +//} +// +//func (s *TesterServer) AuthStreamInterceptor() grpc.StreamServerInterceptor { +// return func(server interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { +// ctx := ss.Context() +// +// token := extractToken(ctx) +// if token == "" { +// return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, defaultUser)}) +// } +// +// user, err := s.readSessionAndReadUser(ctx, token) +// if err != nil { +// return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, defaultUser)}) +// } +// +// return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, user)}) +// } +//} +// +//func ToGrpcError(err error) error { +// if err == nil { +// return nil +// } +// +// // should I use map instead? +// switch { +// case errors.Is(err, utils.ErrValidationFailed): +// return status.Error(codes.InvalidArgument, err.Error()) +// case errors.Is(err, utils.ErrInternal): +// return status.Error(codes.Internal, err.Error()) +// case errors.Is(err, utils.ErrExternal): +// return status.Error(codes.Unavailable, err.Error()) +// case errors.Is(err, utils.ErrNoPermission): +// return status.Error(codes.PermissionDenied, err.Error()) +// case errors.Is(err, utils.ErrUnknown): +// return status.Error(codes.Unknown, err.Error()) +// case errors.Is(err, utils.ErrDeadlineExceeded): +// return status.Error(codes.DeadlineExceeded, err.Error()) +// case errors.Is(err, utils.ErrNotFound): +// return status.Error(codes.NotFound, err.Error()) +// case errors.Is(err, utils.ErrAlreadyExists): +// return status.Error(codes.AlreadyExists, err.Error()) +// case errors.Is(err, utils.ErrConflict): +// return status.Error(codes.Unimplemented, err.Error()) +// case errors.Is(err, utils.ErrUnimplemented): +// return status.Error(codes.Unimplemented, err.Error()) +// case errors.Is(err, utils.ErrUnauthenticated): +// return status.Error(codes.Unauthenticated, err.Error()) +// default: +// return status.Error(codes.Unknown, err.Error()) +// } +//} +// +//func (s *TesterServer) ErrUnwrappingUnaryInterceptor() grpc.UnaryServerInterceptor { +// return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { +// resp, err := handler(ctx, req) +// return resp, ToGrpcError(err) +// } +//} +// +//func (s *TesterServer) ErrUnwrappingStreamInterceptor() grpc.StreamServerInterceptor { +// return func(server interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { +// err := handler(server, ss) +// return ToGrpcError(err) +// } +//} diff --git a/internal/transport/interceptors.go b/internal/transport/interceptors.go deleted file mode 100644 index b83747f..0000000 --- a/internal/transport/interceptors.go +++ /dev/null @@ -1,155 +0,0 @@ -package transport - -import ( - "context" - "errors" - "git.sch9.ru/new_gate/models" - sessionv1 "git.sch9.ru/new_gate/ms-tester/pkg/go/gen/proto/session/v1" - "git.sch9.ru/new_gate/ms-tester/pkg/utils" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" -) - -var defaultUser = &models.User{ - UserId: nil, - Role: models.RoleSpectator.AsPointer(), - UpdatedAt: nil, -} - -func extractToken(ctx context.Context) string { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return "" - } - tokens := md.Get("token") - - if len(tokens) == 0 { - return "" - } - - return tokens[0] -} - -func (s *TesterServer) readSessionAndReadUser(ctx context.Context, token string) (*models.User, error) { - // FIXME: possible bottle neck: should we cache it? (think of it in future) - // FIXME: maybe use single connection instead of multiple requests - userId, err := s.sessionClient.Read(ctx, &sessionv1.ReadSessionRequest{Token: token}) - if err != nil { - return nil, err - } - - user, err := s.userService.ReadUserById(ctx, userId.GetUserId()) // FIXME: must be cached! - if err != nil { - if errors.Is(err, utils.ErrNotFound) { - user = &models.User{ - UserId: utils.AsInt32P(userId.GetUserId()), - Role: models.RoleParticipant.AsPointer(), - } - err = s.userService.CreateUser(ctx, user) - if err != nil { - return nil, err - } - } else { - return nil, err - } - } - - return user, nil -} - -func insertUser(ctx context.Context, user *models.User) context.Context { - return context.WithValue(ctx, "user", user) -} - -func (s *TesterServer) AuthUnaryInterceptor() grpc.UnaryServerInterceptor { - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - token := extractToken(ctx) - if token == "" { - return handler(insertUser(ctx, defaultUser), req) - } - - user, err := s.readSessionAndReadUser(ctx, token) - if err != nil { - return handler(insertUser(ctx, defaultUser), req) - } - - return handler(insertUser(ctx, user), req) - } -} - -type ssWrapper struct { - grpc.ServerStream - ctx context.Context -} - -func (s *ssWrapper) Context() context.Context { - return s.ctx -} - -func (s *TesterServer) AuthStreamInterceptor() grpc.StreamServerInterceptor { - return func(server interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - ctx := ss.Context() - - token := extractToken(ctx) - if token == "" { - return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, defaultUser)}) - } - - user, err := s.readSessionAndReadUser(ctx, token) - if err != nil { - return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, defaultUser)}) - } - - return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, user)}) - } -} - -func ToGrpcError(err error) error { - if err == nil { - return nil - } - - // should I use map instead? - switch { - case errors.Is(err, utils.ErrValidationFailed): - return status.Error(codes.InvalidArgument, err.Error()) - case errors.Is(err, utils.ErrInternal): - return status.Error(codes.Internal, err.Error()) - case errors.Is(err, utils.ErrExternal): - return status.Error(codes.Unavailable, err.Error()) - case errors.Is(err, utils.ErrNoPermission): - return status.Error(codes.PermissionDenied, err.Error()) - case errors.Is(err, utils.ErrUnknown): - return status.Error(codes.Unknown, err.Error()) - case errors.Is(err, utils.ErrDeadlineExceeded): - return status.Error(codes.DeadlineExceeded, err.Error()) - case errors.Is(err, utils.ErrNotFound): - return status.Error(codes.NotFound, err.Error()) - case errors.Is(err, utils.ErrAlreadyExists): - return status.Error(codes.AlreadyExists, err.Error()) - case errors.Is(err, utils.ErrConflict): - return status.Error(codes.Unimplemented, err.Error()) - case errors.Is(err, utils.ErrUnimplemented): - return status.Error(codes.Unimplemented, err.Error()) - case errors.Is(err, utils.ErrUnauthenticated): - return status.Error(codes.Unauthenticated, err.Error()) - default: - return status.Error(codes.Unknown, err.Error()) - } -} - -func (s *TesterServer) ErrUnwrappingUnaryInterceptor() grpc.UnaryServerInterceptor { - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - resp, err := handler(ctx, req) - return resp, ToGrpcError(err) - } -} - -func (s *TesterServer) ErrUnwrappingStreamInterceptor() grpc.StreamServerInterceptor { - return func(server interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - err := handler(server, ss) - return ToGrpcError(err) - } -} diff --git a/internal/transport/server.go b/internal/transport/server.go deleted file mode 100644 index 2f87a62..0000000 --- a/internal/transport/server.go +++ /dev/null @@ -1,99 +0,0 @@ -package transport - -import ( - "context" - "git.sch9.ru/new_gate/models" - problemv1 "git.sch9.ru/new_gate/ms-tester/pkg/go/gen/proto/problem/v1" - sessionv1 "git.sch9.ru/new_gate/ms-tester/pkg/go/gen/proto/session/v1" - "go.uber.org/zap" - "google.golang.org/protobuf/types/known/timestamppb" - "net" - "time" - - "google.golang.org/grpc" -) - -type ProblemService interface { - CanCreateProblem(ctx context.Context) error - CreateProblem(ctx context.Context, problem *models.Problem) (int32, error) - ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) - UpdateProblem(ctx context.Context, problem *models.Problem) error - DeleteProblem(ctx context.Context, id int32) error -} - -type SessionClient interface { - Read(ctx context.Context, - in *sessionv1.ReadSessionRequest, - opts ...grpc.CallOption, - ) (*sessionv1.ReadSessionResponse, error) -} - -type UserService interface { - CreateUser(ctx context.Context, user *models.User) error - ReadUserById(ctx context.Context, userId int32) (*models.User, error) -} - -type TesterServer struct { - problemv1.UnimplementedProblemServiceServer - problemService ProblemService - - sessionClient SessionClient - userService UserService - - grpcServer *grpc.Server - logger *zap.Logger -} - -func NewTesterServer( - problemService ProblemService, - sessionClient SessionClient, - userService UserService, - logger *zap.Logger, -) *TesterServer { - server := &TesterServer{ - problemService: problemService, - sessionClient: sessionClient, - userService: userService, - logger: logger, - } - - grpcServer := grpc.NewServer( - grpc.ChainUnaryInterceptor( - server.ErrUnwrappingUnaryInterceptor(), - server.AuthUnaryInterceptor(), - ), - grpc.ChainStreamInterceptor( - server.ErrUnwrappingStreamInterceptor(), - server.AuthStreamInterceptor(), - ), - ) - - problemv1.RegisterProblemServiceServer(grpcServer, server) - - server.grpcServer = grpcServer - - return server -} - -func (s *TesterServer) Start(lis net.Listener) error { - return s.grpcServer.Serve(lis) -} - -func (s *TesterServer) Stop() { - s.grpcServer.GracefulStop() -} - -func AsTimeP(t *timestamppb.Timestamp) *time.Time { - if t == nil { - return nil - } - tt := t.AsTime() - return &tt -} - -func AsTimestampP(t *time.Time) *timestamppb.Timestamp { - if t == nil { - return nil - } - return timestamppb.New(*t) -}