Skip to content

Commit 51ed34b

Browse files
authored
Merge pull request #10 from IlyaLisov/#5
#5 Implement GraphQL
2 parents 9566e8f + 57ee8f6 commit 51ed34b

File tree

13 files changed

+198
-17
lines changed

13 files changed

+198
-17
lines changed

pom.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
<minio.version>8.5.3</minio.version>
3030
<xml-format.version>3.2.2</xml-format.version>
3131
<checkstyle.version>3.2.2</checkstyle.version>
32+
<graphql-java-kickstart.version>15.0.0</graphql-java-kickstart.version>
33+
<graphql-java-extended-scalars.version>20.2</graphql-java-extended-scalars.version>
3234
</properties>
3335

3436
<dependencies>
@@ -58,6 +60,23 @@
5860
<artifactId>spring-boot-starter-security</artifactId>
5961
</dependency>
6062

63+
<dependency>
64+
<groupId>org.springframework.boot</groupId>
65+
<artifactId>spring-boot-starter-graphql</artifactId>
66+
</dependency>
67+
68+
<dependency>
69+
<groupId>com.graphql-java-kickstart</groupId>
70+
<artifactId>graphql-java-kickstart</artifactId>
71+
<version>${graphql-java-kickstart.version}</version>
72+
</dependency>
73+
74+
<dependency>
75+
<groupId>com.graphql-java</groupId>
76+
<artifactId>graphql-java-extended-scalars</artifactId>
77+
<version>${graphql-java-extended-scalars.version}</version>
78+
</dependency>
79+
6180
<dependency>
6281
<groupId>org.springframework.boot</groupId>
6382
<artifactId>spring-boot-configuration-processor</artifactId>

src/main/java/com/example/tasklist/config/ApplicationConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ public SecurityFilterChain filterChain(final HttpSecurity httpSecurity)
129129
.permitAll()
130130
.requestMatchers("/v3/api-docs/**")
131131
.permitAll()
132+
.requestMatchers("/graphiql")
133+
.permitAll()
132134
.anyRequest().authenticated())
133135
.anonymous(AbstractHttpConfigurer::disable)
134136
.addFilterBefore(new JwtTokenFilter(tokenProvider),
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.example.tasklist.config;
2+
3+
import graphql.schema.GraphQLScalarType;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
7+
8+
@Configuration
9+
public class GraphqlConfig {
10+
11+
@Bean
12+
public GraphQLScalarType localDateTimeScalar() {
13+
return GraphQLScalarType.newScalar()
14+
.name("LocalDateTime")
15+
.description("LocalDateTime scalar")
16+
.coercing(new LocalDateTimeCoercing())
17+
.build();
18+
}
19+
20+
@Bean
21+
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
22+
return wiringBuilder -> wiringBuilder
23+
.scalar(localDateTimeScalar())
24+
.build();
25+
}
26+
27+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.example.tasklist.config;
2+
3+
import graphql.GraphQLContext;
4+
import graphql.execution.CoercedVariables;
5+
import graphql.language.StringValue;
6+
import graphql.language.Value;
7+
import graphql.schema.Coercing;
8+
import graphql.schema.CoercingParseLiteralException;
9+
import graphql.schema.CoercingParseValueException;
10+
import graphql.schema.CoercingSerializeException;
11+
import org.jetbrains.annotations.NotNull;
12+
import org.jetbrains.annotations.Nullable;
13+
14+
import java.text.SimpleDateFormat;
15+
import java.time.LocalDateTime;
16+
import java.time.ZoneId;
17+
import java.util.Date;
18+
import java.util.Locale;
19+
20+
public class LocalDateTimeCoercing implements Coercing<LocalDateTime, String> {
21+
22+
@Override
23+
public @Nullable
24+
String serialize(
25+
@NotNull final Object dataFetcherResult,
26+
@NotNull final GraphQLContext graphQLContext,
27+
@NotNull final Locale locale
28+
) throws CoercingSerializeException {
29+
SimpleDateFormat formatter
30+
= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
31+
Locale.ENGLISH);
32+
return formatter.format(
33+
Date.from(((LocalDateTime) dataFetcherResult)
34+
.atZone(ZoneId.systemDefault())
35+
.toInstant())
36+
);
37+
}
38+
39+
@Override
40+
public @Nullable
41+
LocalDateTime parseValue(
42+
@NotNull final Object input,
43+
@NotNull final GraphQLContext graphQLContext,
44+
@NotNull final Locale locale
45+
) throws CoercingParseValueException {
46+
return LocalDateTime.parse((String) input);
47+
}
48+
49+
@Override
50+
public @Nullable
51+
LocalDateTime parseLiteral(
52+
@NotNull final Value<?> input,
53+
@NotNull final CoercedVariables variables,
54+
@NotNull final GraphQLContext graphQLContext,
55+
@NotNull final Locale locale
56+
) throws CoercingParseLiteralException {
57+
return LocalDateTime.parse(((StringValue) input).getValue());
58+
}
59+
60+
}

src/main/java/com/example/tasklist/domain/task/Task.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import jakarta.persistence.Entity;
77
import jakarta.persistence.EnumType;
88
import jakarta.persistence.Enumerated;
9+
import jakarta.persistence.FetchType;
910
import jakarta.persistence.GeneratedValue;
1011
import jakarta.persistence.GenerationType;
1112
import jakarta.persistence.Id;
@@ -22,7 +23,7 @@
2223
public class Task implements Serializable {
2324

2425
@Id
25-
@GeneratedValue(strategy = GenerationType.AUTO)
26+
@GeneratedValue(strategy = GenerationType.IDENTITY)
2627
private Long id;
2728

2829
private String title;
@@ -35,7 +36,7 @@ public class Task implements Serializable {
3536

3637
@Column(name = "image")
3738
@CollectionTable(name = "tasks_images")
38-
@ElementCollection
39+
@ElementCollection(fetch = FetchType.EAGER)
3940
private List<String> images;
4041

4142
}

src/main/java/com/example/tasklist/domain/user/User.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import jakarta.persistence.GenerationType;
1313
import jakarta.persistence.Id;
1414
import jakarta.persistence.JoinColumn;
15+
import jakarta.persistence.JoinTable;
1516
import jakarta.persistence.OneToMany;
1617
import jakarta.persistence.Table;
1718
import jakarta.persistence.Transient;
@@ -27,7 +28,7 @@
2728
public class User implements Serializable {
2829

2930
@Id
30-
@GeneratedValue(strategy = GenerationType.AUTO)
31+
@GeneratedValue(strategy = GenerationType.IDENTITY)
3132
private Long id;
3233

3334
private String name;
@@ -43,9 +44,8 @@ public class User implements Serializable {
4344
@Enumerated(value = EnumType.STRING)
4445
private Set<Role> roles;
4546

46-
@CollectionTable(name = "users_tasks")
47-
@OneToMany
48-
@JoinColumn(name = "task_id")
47+
@OneToMany(fetch = FetchType.EAGER)
48+
@JoinTable(inverseJoinColumns = @JoinColumn(name = "task_id"))
4949
private List<Task> tasks;
5050

5151
}

src/main/java/com/example/tasklist/service/impl/TaskServiceImpl.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,13 @@ public Task update(final Task task) {
5454

5555
@Override
5656
@Transactional
57-
@Cacheable(value = "TaskService::getById", key = "#task.id")
57+
@Cacheable(value = "TaskService::getById",
58+
condition = "#task.id!=null",
59+
key = "#task.id")
5860
public Task create(final Task task, final Long userId) {
5961
User user = userService.getById(userId);
6062
task.setStatus(Status.TODO);
63+
taskRepository.save(task);
6164
user.getTasks().add(task);
6265
userService.update(user);
6366
return task;

src/main/java/com/example/tasklist/service/impl/UserServiceImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ public class UserServiceImpl implements UserService {
2424
private final PasswordEncoder passwordEncoder;
2525

2626
@Override
27-
@Transactional(readOnly = true)
2827
@Cacheable(value = "UserService::getById", key = "#id")
2928
public User getById(final Long id) {
3029
return userRepository.findById(id)
@@ -59,8 +58,10 @@ public User update(final User user) {
5958
@Transactional
6059
@Caching(cacheable = {
6160
@Cacheable(value = "UserService::getById",
61+
condition = "#user.id!=null",
6262
key = "#user.id"),
6363
@Cacheable(value = "UserService::getByUsername",
64+
condition = "#user.username!=null",
6465
key = "#user.username")
6566
})
6667
public User create(final User user) {

src/main/java/com/example/tasklist/web/controller/TaskController.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import io.swagger.v3.oas.annotations.Operation;
1212
import io.swagger.v3.oas.annotations.tags.Tag;
1313
import lombok.RequiredArgsConstructor;
14+
import org.springframework.graphql.data.method.annotation.Argument;
15+
import org.springframework.graphql.data.method.annotation.MutationMapping;
16+
import org.springframework.graphql.data.method.annotation.QueryMapping;
1417
import org.springframework.security.access.prepost.PreAuthorize;
1518
import org.springframework.validation.annotation.Validated;
1619
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -36,27 +39,30 @@ public class TaskController {
3639
private final TaskImageMapper taskImageMapper;
3740

3841
@PutMapping
42+
@MutationMapping(name = "updateTask")
3943
@Operation(summary = "Update task")
4044
@PreAuthorize("canAccessTask(#dto.id)")
4145
public TaskDto update(@Validated(OnUpdate.class)
42-
@RequestBody final TaskDto dto) {
46+
@RequestBody @Argument final TaskDto dto) {
4347
Task task = taskMapper.toEntity(dto);
4448
Task updatedTask = taskService.update(task);
4549
return taskMapper.toDto(updatedTask);
4650
}
4751

4852
@GetMapping("/{id}")
53+
@QueryMapping(name = "taskById")
4954
@Operation(summary = "Get TaskDto by id")
5055
@PreAuthorize("canAccessTask(#id)")
51-
public TaskDto getById(@PathVariable final Long id) {
56+
public TaskDto getById(@PathVariable @Argument final Long id) {
5257
Task task = taskService.getById(id);
5358
return taskMapper.toDto(task);
5459
}
5560

5661
@DeleteMapping("/{id}")
62+
@MutationMapping(name = "deleteTask")
5763
@Operation(summary = "Delete task")
5864
@PreAuthorize("canAccessTask(#id)")
59-
public void deleteById(@PathVariable final Long id) {
65+
public void deleteById(@PathVariable @Argument final Long id) {
6066
taskService.delete(id);
6167
}
6268

src/main/java/com/example/tasklist/web/controller/UserController.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
import io.swagger.v3.oas.annotations.Operation;
1414
import io.swagger.v3.oas.annotations.tags.Tag;
1515
import lombok.RequiredArgsConstructor;
16+
import org.springframework.graphql.data.method.annotation.Argument;
17+
import org.springframework.graphql.data.method.annotation.MutationMapping;
18+
import org.springframework.graphql.data.method.annotation.QueryMapping;
1619
import org.springframework.security.access.prepost.PreAuthorize;
1720
import org.springframework.validation.annotation.Validated;
1821
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -40,44 +43,50 @@ public class UserController {
4043
private final TaskMapper taskMapper;
4144

4245
@PutMapping
46+
@MutationMapping(name = "updateUser")
4347
@Operation(summary = "Update user")
4448
@PreAuthorize("@customSecurityExpression.canAccessUser(#dto.id)")
4549
public UserDto update(@Validated(OnUpdate.class)
46-
@RequestBody final UserDto dto) {
50+
@RequestBody @Argument final UserDto dto) {
4751
User user = userMapper.toEntity(dto);
4852
User updatedUser = userService.update(user);
4953
return userMapper.toDto(updatedUser);
5054
}
5155

5256
@GetMapping("/{id}")
57+
@QueryMapping(name = "userById")
5358
@Operation(summary = "Get UserDto by id")
5459
@PreAuthorize("@customSecurityExpression.canAccessUser(#id)")
55-
public UserDto getById(@PathVariable final Long id) {
60+
public UserDto getById(@PathVariable @Argument final Long id) {
5661
User user = userService.getById(id);
5762
return userMapper.toDto(user);
5863
}
5964

6065
@DeleteMapping("/{id}")
66+
@MutationMapping(name = "deleteUserById")
6167
@Operation(summary = "Delete user by id")
6268
@PreAuthorize("@customSecurityExpression.canAccessUser(#id)")
63-
public void deleteById(@PathVariable final Long id) {
69+
public void deleteById(@PathVariable @Argument final Long id) {
6470
userService.delete(id);
6571
}
6672

6773
@GetMapping("/{id}/tasks")
74+
@QueryMapping(name = "tasksByUserId")
6875
@Operation(summary = "Get all User tasks")
6976
@PreAuthorize("@customSecurityExpression.canAccessUser(#id)")
70-
public List<TaskDto> getTasksByUserId(@PathVariable final Long id) {
77+
public List<TaskDto> getTasksByUserId(@PathVariable
78+
@Argument final Long id) {
7179
List<Task> tasks = taskService.getAllByUserId(id);
7280
return taskMapper.toDto(tasks);
7381
}
7482

7583
@PostMapping("/{id}/tasks")
84+
@MutationMapping(name = "createTask")
7685
@Operation(summary = "Add task to user")
7786
@PreAuthorize("@customSecurityExpression.canAccessUser(#id)")
78-
public TaskDto createTask(@PathVariable final Long id,
87+
public TaskDto createTask(@PathVariable @Argument final Long id,
7988
@Validated(OnCreate.class)
80-
@RequestBody final TaskDto dto) {
89+
@RequestBody @Argument final TaskDto dto) {
8190
Task task = taskMapper.toEntity(dto);
8291
Task createdTask = taskService.create(task, id);
8392
return taskMapper.toDto(createdTask);

0 commit comments

Comments
 (0)