Skip to content

Commit 6fa8702

Browse files
committed
Add Service layer
1 parent bf26377 commit 6fa8702

File tree

2 files changed

+97
-48
lines changed

2 files changed

+97
-48
lines changed
Lines changed: 17 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,71 @@
11
package lol.maki.dev.todo;
22

33
import java.net.URI;
4-
import java.time.Clock;
5-
import java.time.Instant;
64
import java.util.List;
7-
import java.util.Objects;
8-
import java.util.Optional;
9-
5+
import java.util.Map;
106
import org.springframework.http.HttpStatus;
117
import org.springframework.http.ResponseEntity;
128
import org.springframework.security.core.annotation.AuthenticationPrincipal;
139
import org.springframework.security.oauth2.jwt.Jwt;
14-
import org.springframework.util.IdGenerator;
1510
import org.springframework.web.bind.annotation.DeleteMapping;
11+
import org.springframework.web.bind.annotation.ExceptionHandler;
1612
import org.springframework.web.bind.annotation.GetMapping;
1713
import org.springframework.web.bind.annotation.PatchMapping;
1814
import org.springframework.web.bind.annotation.PathVariable;
1915
import org.springframework.web.bind.annotation.PostMapping;
2016
import org.springframework.web.bind.annotation.RequestBody;
2117
import org.springframework.web.bind.annotation.RequestMapping;
2218
import org.springframework.web.bind.annotation.RestController;
23-
import org.springframework.web.server.ResponseStatusException;
2419
import org.springframework.web.util.UriComponentsBuilder;
2520

2621
@RestController
2722
@RequestMapping(path = "/todos")
2823
public class TodoController {
2924

30-
private final TodoRepository todoRepository;
31-
32-
private final IdGenerator idGenerator;
33-
34-
private final Clock clock;
25+
private final TodoService todoService;
3526

36-
public TodoController(TodoRepository todoRepository, IdGenerator idGenerator, Clock clock) {
37-
this.todoRepository = todoRepository;
38-
this.idGenerator = idGenerator;
39-
this.clock = clock;
27+
public TodoController(TodoService todoService) {
28+
this.todoService = todoService;
4029
}
4130

4231
@GetMapping(path = "")
4332
public ResponseEntity<List<Todo>> getTodos() {
44-
List<Todo> todos = this.todoRepository.findAll();
33+
List<Todo> todos = this.todoService.getTodos();
4534
return ResponseEntity.ok(todos);
4635
}
4736

4837
@GetMapping(path = "/{todoId}")
4938
public ResponseEntity<Todo> getTodo(@PathVariable("todoId") String todoId) {
50-
Todo todo = this.todoRepository.findById(todoId)
51-
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
52-
"Todo not found: todoId=%s".formatted(todoId)));
39+
Todo todo = this.todoService.getTodo(todoId);
5340
return ResponseEntity.ok(todo);
5441
}
5542

5643
@PostMapping(path = "")
5744
public ResponseEntity<Todo> postTodos(@RequestBody Todo todo, @AuthenticationPrincipal Jwt jwt,
5845
UriComponentsBuilder builder) {
59-
Instant now = this.clock.instant();
6046
String email = jwt.getClaimAsString("email");
61-
Todo initialized = TodoBuilder.from(todo)
62-
.todoId(this.idGenerator.generateId().toString())
63-
.createdBy(email)
64-
.createdAt(now)
65-
.updatedBy(email)
66-
.updatedAt(now)
67-
.build();
68-
Todo created = this.todoRepository.save(initialized);
47+
Todo created = this.todoService.create(todo.todoTitle(), email);
6948
URI uri = builder.pathSegment("todos", created.todoId()).build().toUri();
7049
return ResponseEntity.created(uri).body(created);
7150
}
7251

7352
@PatchMapping(path = "/{todoId}")
7453
public ResponseEntity<Todo> patchTodo(@PathVariable("todoId") String todoId, @RequestBody Todo todo,
7554
@AuthenticationPrincipal Jwt jwt) {
76-
Optional<Todo> updated = this.todoRepository.findById(todoId).map(t -> {
77-
TodoBuilder builder = TodoBuilder.from(t);
78-
boolean touched = false;
79-
if (todo.todoTitle() != null) {
80-
builder.todoTitle(todo.todoTitle());
81-
touched = true;
82-
}
83-
if (!Objects.equals(todo.finished(), t.finished())) {
84-
builder.finished(todo.finished());
85-
touched = true;
86-
}
87-
if (touched) {
88-
builder.updatedBy(jwt.getClaimAsString("email"));
89-
builder.updatedAt(this.clock.instant());
90-
}
91-
return builder.build();
92-
}).map(this.todoRepository::save);
93-
return ResponseEntity.of(updated);
55+
String email = jwt.getClaimAsString("email");
56+
Todo updated = this.todoService.update(todoId, todo.todoTitle(), todo.finished(), email);
57+
return ResponseEntity.ok(updated);
9458
}
9559

9660
@DeleteMapping(path = "/{todoId}")
9761
public ResponseEntity<Void> deleteTodo(@PathVariable("todoId") String todoId) {
98-
this.todoRepository.deleteById(todoId);
62+
this.todoService.deleteById(todoId);
9963
return ResponseEntity.noContent().build();
10064
}
10165

66+
@ExceptionHandler(TodoService.NotFoundException.class)
67+
public ResponseEntity<?> handleNotFound(TodoService.NotFoundException e) {
68+
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("message", e.getMessage()));
69+
}
70+
10271
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package lol.maki.dev.todo;
2+
3+
import java.time.Clock;
4+
import java.time.Instant;
5+
import java.util.List;
6+
import java.util.Objects;
7+
import org.springframework.stereotype.Service;
8+
import org.springframework.util.IdGenerator;
9+
10+
@Service
11+
public class TodoService {
12+
13+
private final TodoRepository todoRepository;
14+
15+
private final IdGenerator idGenerator;
16+
17+
private final Clock clock;
18+
19+
public TodoService(TodoRepository todoRepository, IdGenerator idGenerator, Clock clock) {
20+
this.todoRepository = todoRepository;
21+
this.idGenerator = idGenerator;
22+
this.clock = clock;
23+
}
24+
25+
public List<Todo> getTodos() {
26+
return this.todoRepository.findAll();
27+
}
28+
29+
public Todo getTodo(String todoId) {
30+
return this.todoRepository.findById(todoId).orElseThrow(() -> new NotFoundException(todoId));
31+
}
32+
33+
public Todo create(String todoTitle, String email) {
34+
Instant now = this.clock.instant();
35+
Todo todo = TodoBuilder.todo()
36+
.todoId(this.idGenerator.generateId().toString())
37+
.todoTitle(todoTitle)
38+
.finished(false)
39+
.createdBy(email)
40+
.createdAt(now)
41+
.updatedBy(email)
42+
.updatedAt(now)
43+
.build();
44+
Todo updated = this.todoRepository.save(todo);
45+
return this.todoRepository.save(updated);
46+
}
47+
48+
public Todo update(String todoId, String todoTitle, boolean finished, String email) {
49+
return this.todoRepository.findById(todoId).map(t -> {
50+
TodoBuilder builder = TodoBuilder.from(t);
51+
boolean touched = false;
52+
if (todoTitle != null) {
53+
builder.todoTitle(todoTitle);
54+
touched = true;
55+
}
56+
if (!Objects.equals(finished, t.finished())) {
57+
builder.finished(finished);
58+
touched = true;
59+
}
60+
if (touched) {
61+
builder.updatedBy(email);
62+
builder.updatedAt(this.clock.instant());
63+
}
64+
return builder.build();
65+
}).map(this.todoRepository::save).orElseThrow(() -> new NotFoundException(todoId));
66+
}
67+
68+
public void deleteById(String todoId) {
69+
this.todoRepository.deleteById(todoId);
70+
}
71+
72+
public static class NotFoundException extends RuntimeException {
73+
74+
public NotFoundException(String todoId) {
75+
super("Todo not found: todoId=%s".formatted(todoId));
76+
}
77+
78+
}
79+
80+
}

0 commit comments

Comments
 (0)