-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HT-14 implementation of Pure Rest API #1
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.example.purerestapi.configuration; | ||
|
||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.web.filter.ShallowEtagHeaderFilter; | ||
|
||
@Configuration | ||
public class ETagConfiguration { | ||
|
||
@Bean | ||
public ShallowEtagHeaderFilter shallowEtagHeaderFilter() { | ||
return new ShallowEtagHeaderFilter(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.example.purerestapi.controller; | ||
|
||
import com.example.purerestapi.exception.UserNotFoundException; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
|
||
@org.springframework.web.bind.annotation.ControllerAdvice | ||
public class RestExceptionHandler { | ||
|
||
@ExceptionHandler(value = {UserNotFoundException.class}) | ||
protected ResponseEntity<Object> handleConflict(RuntimeException ex) { | ||
return ResponseEntity.badRequest().body(ex.getMessage()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package com.example.purerestapi.controller; | ||
|
||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; | ||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; | ||
|
||
import com.example.purerestapi.controller.assembler.NoteAssembler; | ||
import com.example.purerestapi.controller.assembler.UserAssembler; | ||
import com.example.purerestapi.entity.Note; | ||
import com.example.purerestapi.entity.User; | ||
import com.example.purerestapi.service.UserService; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.hateoas.CollectionModel; | ||
import org.springframework.hateoas.EntityModel; | ||
import org.springframework.hateoas.IanaLinkRelations; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.DeleteMapping; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.ResponseBody; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
|
||
@Controller | ||
@ResponseBody | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use '@RestController' instead that is itself annotated with @controller and @responsebody. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I have just done it as a test, and one more time show @RestController it is easier and more understandable than just annotation |
||
@RequestMapping("/users") | ||
@RequiredArgsConstructor | ||
public class UserController { | ||
|
||
private final UserService userService; | ||
private final UserAssembler userAssembler; | ||
private final NoteAssembler noteAssembler; | ||
|
||
@GetMapping | ||
public CollectionModel<EntityModel<User>> getAll() { | ||
var users = userService.getAll(); | ||
return userAssembler.toCollectionModel(users) | ||
.add(linkTo(methodOn(UserController.class).getAll()).withSelfRel()); | ||
} | ||
|
||
@GetMapping("/{id}") | ||
public EntityModel<User> getUser(@PathVariable String id) { | ||
var user = userService.getUser(id); | ||
|
||
return userAssembler.toModel(user); | ||
} | ||
|
||
@PostMapping | ||
public ResponseEntity<EntityModel<User>> addUser(@RequestBody User user) { | ||
userService.addUser(user); | ||
EntityModel<User> entityModel = userAssembler.toModel(user); | ||
|
||
return ResponseEntity | ||
.created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri()) | ||
.body(entityModel); | ||
} | ||
|
||
@DeleteMapping("/{id}") | ||
@ResponseStatus(value = HttpStatus.NO_CONTENT) | ||
public void deleteUser(@PathVariable String id) { | ||
userService.deleteUser(id); | ||
} | ||
|
||
@PostMapping("/{id}/notes") | ||
public EntityModel<User> addNote(@PathVariable(name = "id") String userId, @RequestBody Note note) { | ||
var user = userService.addNote(userId, note); | ||
|
||
return userAssembler.toModel(user); | ||
} | ||
|
||
@GetMapping("/{id}/notes") | ||
public CollectionModel<EntityModel<Note>> getUserNotes(@PathVariable(name = "id") String userId) { | ||
var notes = userService.getAllNotes(userId); | ||
return noteAssembler.toCollectionModel(notes) | ||
.add(linkTo(methodOn(UserController.class).getUserNotes(userId)).withSelfRel()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.example.purerestapi.controller.assembler; | ||
|
||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; | ||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; | ||
|
||
import com.example.purerestapi.controller.UserController; | ||
import com.example.purerestapi.entity.Note; | ||
import org.springframework.hateoas.EntityModel; | ||
import org.springframework.hateoas.server.RepresentationModelAssembler; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class NoteAssembler implements RepresentationModelAssembler<Note, EntityModel<Note>> { | ||
|
||
@Override | ||
public EntityModel<Note> toModel(Note note) { | ||
return EntityModel.of( | ||
note, | ||
linkTo(methodOn(UserController.class).getUserNotes(note.getUserUuid())).withSelfRel(), | ||
linkTo(methodOn(UserController.class).getUser(note.getUserUuid())).withRel("user") | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.example.purerestapi.controller.assembler; | ||
|
||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; | ||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; | ||
|
||
import com.example.purerestapi.controller.UserController; | ||
import com.example.purerestapi.entity.User; | ||
import org.springframework.hateoas.EntityModel; | ||
import org.springframework.hateoas.server.RepresentationModelAssembler; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class UserAssembler implements RepresentationModelAssembler<User, EntityModel<User>> { | ||
|
||
@Override | ||
public EntityModel<User> toModel(User user) { | ||
return EntityModel.of( | ||
user, | ||
linkTo(methodOn(UserController.class).getUser(user.getId())).withSelfRel(), | ||
linkTo(methodOn(UserController.class).getAll()).withRel("allUsers"), | ||
linkTo(methodOn(UserController.class).getUserNotes(user.getId())).withRel("notes") | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.example.purerestapi.entity; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnore; | ||
import lombok.Data; | ||
|
||
@Data | ||
public class Note { | ||
|
||
@JsonIgnore | ||
private String userUuid; | ||
private String subject; | ||
private String text; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.example.purerestapi.entity; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnore; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@Getter | ||
@Setter | ||
public class User { | ||
|
||
private String id; | ||
private String firstname; | ||
private String surname; | ||
@JsonIgnore | ||
@Setter(AccessLevel.NONE) | ||
private List<Note> notes; | ||
|
||
public void addNote(Note note) { | ||
if (notes == null) { | ||
notes = new ArrayList<>(); | ||
} | ||
notes.add(note); | ||
} | ||
|
||
public List<Note> getNotes() { | ||
return Collections.unmodifiableList(notes); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If |
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.example.purerestapi.exception; | ||
|
||
public class UserNotFoundException extends RuntimeException { | ||
|
||
public UserNotFoundException() { | ||
super("User Not Found"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.example.purerestapi.repository; | ||
|
||
import com.example.purerestapi.entity.User; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.UUID; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public class UserRepository { | ||
|
||
private final static Map<String, User> users = new ConcurrentHashMap<>(); | ||
|
||
|
||
public List<User> getAll() { | ||
return users.values().stream().toList(); | ||
} | ||
public void addUser(User user) { | ||
if (user.getId() == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How can you have here non-null user ID? It's create operation, right? |
||
user.setId(UUID.randomUUID().toString()); | ||
} | ||
if (!users.containsKey(user.getId())) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's an interesting part of the code in concept "what if there is already UUID in the map?". Do you expect duplicates? |
||
users.put(user.getId(), user); | ||
Comment on lines
+25
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to use here |
||
} | ||
} | ||
|
||
public Optional<User> getUser(String id) { | ||
return Optional.ofNullable(users.get(id)); | ||
} | ||
|
||
public void deleteUser(String id) { | ||
users.remove(id); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package com.example.purerestapi.service; | ||
|
||
import com.example.purerestapi.exception.UserNotFoundException; | ||
import com.example.purerestapi.entity.Note; | ||
import com.example.purerestapi.entity.User; | ||
import com.example.purerestapi.repository.UserRepository; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class UserService { | ||
|
||
private final UserRepository userRepository; | ||
public void addUser(User user) { | ||
userRepository.addUser(user); | ||
} | ||
|
||
public User getUser(String id) { | ||
return userRepository.getUser(id).orElseThrow(UserNotFoundException::new); | ||
} | ||
|
||
public void deleteUser(String id) { | ||
userRepository.deleteUser(id); | ||
} | ||
|
||
public List<User> getAll() { | ||
return userRepository.getAll(); | ||
} | ||
|
||
public User addNote(String userUuid, Note note) { | ||
var user = this.getUser(userUuid); | ||
|
||
note.setUserUuid(userUuid); | ||
user.addNote(note); | ||
return user; | ||
} | ||
|
||
public List<Note> getAllNotes(String userUuid) { | ||
var user = this.getUser(userUuid); | ||
return user.getNotes() == null ? Collections.emptyList() : user.getNotes(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to handle this case in one place - |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to put here full qualified name with package?