Skip to content

Commit

Permalink
Merge pull request #50 from eyakauleva/49
Browse files Browse the repository at this point in the history
graphql
  • Loading branch information
Aliaksei Bialiauski committed Jun 6, 2023
2 parents d0c09d0 + 679c337 commit db47ac4
Show file tree
Hide file tree
Showing 9 changed files with 315 additions and 7 deletions.
11 changes: 11 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,20 @@
<testcontainers.version>1.18.0</testcontainers.version>
<elasticsearch.version>5.1.0</elasticsearch.version>
<prometheus.version>1.11.0</prometheus.version>
<graphql.validation.version>20.0</graphql.validation.version>
</properties>

<dependencies>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-validation</artifactId>
<version>${graphql.validation.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/com/solvd/micro9/users/web/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.solvd.micro9.users.web.controller.graphql.LocalDateTimeToStringCoercing;
import graphql.schema.GraphQLScalarType;
import graphql.validation.rules.OnValidationErrorStrategy;
import graphql.validation.rules.ValidationRules;
import graphql.validation.schemawiring.ValidationSchemaWiring;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.ReactivePageableHandlerMethodArgumentResolver;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
Expand Down Expand Up @@ -43,4 +49,33 @@ public WebClient.Builder webClient() {
return WebClient.builder();
}

@Bean
public GraphQLScalarType localDateTimeScalar(
final LocalDateTimeToStringCoercing coercing
) {
return GraphQLScalarType.newScalar()
.name("LocalDateTime")
.description("Java 8 LocalDateTime as scalar")
.coercing(coercing)
.build();
}

@Bean
public ValidationSchemaWiring schemaWiring() {
ValidationRules validationRules = ValidationRules.newValidationRules()
.onValidationErrorStrategy(OnValidationErrorStrategy.RETURN_NULL)
.build();
return new ValidationSchemaWiring(validationRules);
}

@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer(
final GraphQLScalarType scalarType,
final ValidationSchemaWiring schemaWiring
) {
return wiringBuilder -> wiringBuilder
.scalar(scalarType)
.directiveWiring(schemaWiring);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.solvd.micro9.users.web.controller;

import com.solvd.micro9.users.domain.aggregate.User;
import com.solvd.micro9.users.domain.command.CreateUserCommand;
import com.solvd.micro9.users.domain.command.DeleteUserCommand;
import com.solvd.micro9.users.domain.criteria.UserCriteria;
import com.solvd.micro9.users.domain.es.EsUser;
import com.solvd.micro9.users.domain.query.EsUserQuery;
import com.solvd.micro9.users.service.EsUserCommandHandler;
import com.solvd.micro9.users.service.UserQueryHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Controller
@RequiredArgsConstructor
public class GraphqlUserController {

private final EsUserCommandHandler commandHandler;
private final UserQueryHandler queryHandler;

@QueryMapping("getAllUsers")
public Flux<User> getAll() {
return queryHandler.getAll();
}

@QueryMapping("findByCriteria")
public Flux<User> findByCriteria(@Argument final UserCriteria criteria,
@Argument final int size,
@Argument final int page) {
Pageable pageable = PageRequest.of(page, size);
return queryHandler.findByCriteria(criteria, pageable);
}

@QueryMapping("findUserById")
public Mono<User> findByUserId(@Argument final String userId) {
EsUserQuery query = new EsUserQuery(userId);
return queryHandler.findById(query);
}

@MutationMapping("createUser")
public Mono<EsUser> create(
@Argument final User user,
@Argument final String commandBy
) {
CreateUserCommand command = new CreateUserCommand(user, commandBy);
return commandHandler.apply(command);
}

@MutationMapping("deleteUser")
public Mono<EsUser> delete(
@Argument final String id,
@Argument final String commandBy
) {
DeleteUserCommand command = new DeleteUserCommand(id, commandBy);
return commandHandler.apply(command);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.solvd.micro9.users.web.dto.TicketDto;
import com.solvd.micro9.users.web.dto.UserDto;
import com.solvd.micro9.users.web.dto.criteria.UserCriteriaDto;
import com.solvd.micro9.users.web.mapper.EsMapper;
import com.solvd.micro9.users.web.mapper.UserCriteriaMapper;
import com.solvd.micro9.users.web.mapper.UserMapper;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
Expand All @@ -35,20 +36,22 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
public class RestUserController {

@Value("${ticket-service}")
private String ticketService;
private final EsUserCommandHandler commandHandler;
private final UserQueryHandler queryHandler;
private final UserMapper userMapper;
private final EsMapper esMapper;
private final WebClient.Builder webClientBuilder;
private final UserCriteriaMapper criteriaMapper;
private static final String USER_SERVICE = "user-service";
Expand Down Expand Up @@ -117,16 +120,24 @@ public Mono<EsDto> createTicket(
}

@PostMapping
public Mono<EsUser> create(@RequestBody @Validated final UserDto userDto) {
public Mono<EsDto> create(
@RequestHeader("command_by") final String commandBy,
@RequestBody @Validated final UserDto userDto
) {
User user = userMapper.dtoToDomain(userDto);
CreateUserCommand command = new CreateUserCommand(user, "Liza123");
return commandHandler.apply(command);
CreateUserCommand command = new CreateUserCommand(user, commandBy);
Mono<EsUser> esUserMono = commandHandler.apply(command);
return esMapper.domainToDto(esUserMono);
}

@DeleteMapping(value = "/{id}")
public Mono<EsUser> delete(@PathVariable("id") final String id) {
DeleteUserCommand command = new DeleteUserCommand(id, "Liza123");
return commandHandler.apply(command);
public Mono<EsDto> delete(
@RequestHeader("command_by") final String commandBy,
@PathVariable("id") final String id
) {
DeleteUserCommand command = new DeleteUserCommand(id, commandBy);
Mono<EsUser> esUserMono = commandHandler.apply(command);
return esMapper.domainToDto(esUserMono);
}

@SneakyThrows
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.solvd.micro9.users.web.controller.exception;

import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.schema.DataFetchingEnvironment;
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.stereotype.Component;

@Component
public class GraphqlExceptionResolver extends DataFetcherExceptionResolverAdapter {

@Override
protected GraphQLError resolveToSingleError(
final Throwable ex, final DataFetchingEnvironment env
) {
return GraphqlErrorBuilder.newError()
.errorType(ErrorType.BAD_REQUEST)
.message(ex.getMessage())
.path(env.getExecutionStepInfo().getPath())
.location(env.getField().getSourceLocation())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.solvd.micro9.users.web.controller.graphql;

import graphql.language.StringValue;
import graphql.schema.Coercing;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

@Component
public class LocalDateTimeToStringCoercing implements Coercing<LocalDateTime, String> {

@Override
public String serialize(final Object input) {
if (input instanceof LocalDateTime) {
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return ((LocalDateTime) input).format(formatter);
} else {
throw new CoercingSerializeException(
"Expected a LocalDateTime object"
);
}
}

@Override
public LocalDateTime parseValue(final Object input) {
return this.parse(input);
}

@Override
public LocalDateTime parseLiteral(final Object input) {
return this.parse(input);
}

private LocalDateTime parse(final Object input) {
try {
if (input instanceof StringValue) {
return LocalDateTime.parse(((StringValue) input).getValue());
} else if (input instanceof String) {
return LocalDateTime.parse((String) input);
} else {
throw new CoercingParseLiteralException(
"Expected String or StringValue"
);
}
} catch (DateTimeParseException e) {
throw new CoercingParseValueException(
String.format("Not a valid date: '%s'.", input), e
);
}
}

}
17 changes: 17 additions & 0 deletions src/main/java/com/solvd/micro9/users/web/mapper/EsMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.solvd.micro9.users.web.mapper;

import com.solvd.micro9.users.domain.es.EsUser;
import com.solvd.micro9.users.web.dto.EsDto;
import org.mapstruct.Mapper;
import reactor.core.publisher.Mono;

@Mapper(componentModel = "spring")
public interface EsMapper {

EsDto domainToDto(EsUser esUser);

default Mono<EsDto> domainToDto(Mono<EsUser> esUserMono) {
return esUserMono.map(this::domainToDto);
}

}
3 changes: 3 additions & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ logging:
name: logs/application-info.log

spring:
graphql:
graphiql:
enabled: true
config:
import: optional:file:.env[.properties]
jackson:
Expand Down
83 changes: 83 additions & 0 deletions src/main/resources/graphql/schema.graphqls
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
scalar LocalDateTime

directive @NotBlank(message : String = "graphql.validation.NotBlank.message")
on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
directive @Min(value : Int = 0, message : String = "graphql.validation.Min.message")
on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION

type Query {
getAllUsers: [User]
findUserById(userId: ID!): User
findByCriteria(criteria: CriteriaInput!, size: Int! @Min(value: 1), page: Int! @Min): [User]
}

type Mutation {
createUser(user: UserInput!, commandBy: String!): Es
deleteUser(id: ID!, commandBy: String!): Es
}

type User {
id: ID
firstName: String!
lastName: String!
email: String!
phone: String!
age: Int
gender: Gender
height: Float
weight: Float
eyesColor: EyesColor
startStudyYear: Int
endStudyYear: Int
}

enum Gender {
MALE
FEMALE
UNSET
}

enum EyesColor {
BLUE
GREEN
BROWN
GREY
UNSET
}

input UserInput {
firstName: String @NotBlank
lastName: String @NotBlank
email: String @NotBlank
phone: String @NotBlank
age: Int
gender: Gender
height: Float
weight: Float
eyesColor: EyesColor
startStudyYear: Int
endStudyYear: Int
}

input CriteriaInput {
name: String
phone: String
age: Int
heightFrom: Float
heightTo: Float
weightFrom: Float
weightTo: Float
genders: [Gender]
eyesColors: [EyesColor]
studyYear: Int
}

type Es {
id: ID
type: String
time: LocalDateTime
createdBy: String
entityId: String
payload: String
status: String
}

0 comments on commit db47ac4

Please sign in to comment.