"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
Creating a Facebook Clone - Part XXVI - Transcript.pdf
1. Creating a Facebook Clone - Part XXVI
With this last part we’ll finally finish the server code!
2. @Controller
@RequestMapping("/post")
@RestController
public class PostWebService {
@Autowired
private PostService posts;
@ExceptionHandler(PermissionException.class)
@ResponseStatus(value=HttpStatus.FORBIDDEN)
public @ResponseBody
ErrorDAO handlePermissionException(PermissionException e) {
return new ErrorDAO(e.getMessage(), 0);
}
@RequestMapping(method=RequestMethod.GET, value="/list")
public List<PostDAO> postsOf(
@RequestHeader(name="auth", required=true) String auth,
@RequestParam(name="user", required=true) String user,
@RequestParam(name="page") int page,
@RequestParam(name="size") int size) {
return posts.postsOf(auth, user, page, size);
PostWebService
PostWebService is even simpler than UserWebService as it delegates to the PostService class which is smaller.
The post service prefixes all calls with /post as opposed to the UserWebService which used /user
3. @Controller
@RequestMapping("/post")
@RestController
public class PostWebService {
@Autowired
private PostService posts;
@ExceptionHandler(PermissionException.class)
@ResponseStatus(value=HttpStatus.FORBIDDEN)
public @ResponseBody
ErrorDAO handlePermissionException(PermissionException e) {
return new ErrorDAO(e.getMessage(), 0);
}
@RequestMapping(method=RequestMethod.GET, value="/list")
public List<PostDAO> postsOf(
@RequestHeader(name="auth", required=true) String auth,
@RequestParam(name="user", required=true) String user,
@RequestParam(name="page") int page,
@RequestParam(name="size") int size) {
return posts.postsOf(auth, user, page, size);
PostWebService
This class carries a 1 to 1 relationship to the PostService class we make no use of other services
4. @Controller
@RequestMapping("/post")
@RestController
public class PostWebService {
@Autowired
private PostService posts;
@ExceptionHandler(PermissionException.class)
@ResponseStatus(value=HttpStatus.FORBIDDEN)
public @ResponseBody
ErrorDAO handlePermissionException(PermissionException e) {
return new ErrorDAO(e.getMessage(), 0);
}
@RequestMapping(method=RequestMethod.GET, value="/list")
public List<PostDAO> postsOf(
@RequestHeader(name="auth", required=true) String auth,
@RequestParam(name="user", required=true) String user,
@RequestParam(name="page") int page,
@RequestParam(name="size") int size) {
return posts.postsOf(auth, user, page, size);
PostWebService
The methods here can throw a PermissionException which as usual will translate to an ErrorDAO
5. public @ResponseBody
ErrorDAO handlePermissionException(PermissionException e) {
return new ErrorDAO(e.getMessage(), 0);
}
@RequestMapping(method=RequestMethod.GET, value="/list")
public List<PostDAO> postsOf(
@RequestHeader(name="auth", required=true) String auth,
@RequestParam(name="user", required=true) String user,
@RequestParam(name="page") int page,
@RequestParam(name="size") int size) {
return posts.postsOf(auth, user, page, size);
}
@RequestMapping(method=RequestMethod.GET, value="/feed")
public List<PostDAO> newsfeed(
@RequestHeader(name="auth", required=true) String authToken,
@RequestParam(name="page") int page,
@RequestParam(name="size") int size) {
return posts.newsfeed(authToken, page, size);
}
@RequestMapping(value="/new", method=RequestMethod.POST)
public String post(
@RequestHeader(name="auth", required=true) String auth,
PostWebService
The methods of the class translate the business logic to WebServices as before. They are trivial for the most part. List & feed just return pageable data for the given user,
notice that every method here accepts the auth header
6. @RequestParam(name="size") int size) {
return posts.newsfeed(authToken, page, size);
}
@RequestMapping(value="/new", method=RequestMethod.POST)
public String post(
@RequestHeader(name="auth", required=true) String auth,
@RequestBody PostDAO pd) {
return posts.post(auth, pd);
}
@RequestMapping(value="/comment", method=RequestMethod.POST)
public String comment(
@RequestHeader(name="auth", required=true) String auth,
@RequestBody CommentDAO cd) throws PermissionException {
return posts.comment(auth, cd.getPostId(), cd);
}
@RequestMapping(value="/like", method=RequestMethod.GET)
public String like(
@RequestHeader(name="auth", required=true) String auth,
@RequestParam String postId) {
posts.like(auth, postId);
return "OK";
}
PostWebService
When making a new post or comment the DAO object is passed along with the header. This allows for clean client side code that can be unaware of the authorization for
the most part.
That's it, the class itself is a trivial wrapper around the service class!
7. @Controller
@RequestMapping("/media")
@RestController
public class MediaWebService {
@Autowired
private MediaService medias;
@ExceptionHandler(PermissionException.class)
@ResponseStatus(value=HttpStatus.FORBIDDEN)
public @ResponseBody
ErrorDAO handlePermissionException(PermissionException e) {
return new ErrorDAO(e.getMessage(), 0);
}
@RequestMapping(value="/public/{id:.+}", method=RequestMethod.GET)
public ResponseEntity<byte[]> getPublic(@PathVariable("id") String id)
throws PermissionException {
MediaDAO av = medias.getPublicMedia(id);
if(av != null) {
return ResponseEntity.ok().
contentType(MediaType.valueOf(av.getMimeType())).
MediaWebService
This brings us to the last class we will cover in the server before going back to the client… The MediaWebService. The MediaWebService is as simple as the
PostWebService but has a few gotchas due to the complexity of mime types & file upload.
The media is mapped to the /media base URL
8. @Controller
@RequestMapping("/media")
@RestController
public class MediaWebService {
@Autowired
private MediaService medias;
@ExceptionHandler(PermissionException.class)
@ResponseStatus(value=HttpStatus.FORBIDDEN)
public @ResponseBody
ErrorDAO handlePermissionException(PermissionException e) {
return new ErrorDAO(e.getMessage(), 0);
}
@RequestMapping(value="/public/{id:.+}", method=RequestMethod.GET)
public ResponseEntity<byte[]> getPublic(@PathVariable("id") String id)
throws PermissionException {
MediaDAO av = medias.getPublicMedia(id);
if(av != null) {
return ResponseEntity.ok().
contentType(MediaType.valueOf(av.getMimeType())).
MediaWebService
Again the class can throw a `PermissionException` which will translate to an `ErrorDAO`
9. @ResponseStatus(value=HttpStatus.FORBIDDEN)
public @ResponseBody
ErrorDAO handlePermissionException(PermissionException e) {
return new ErrorDAO(e.getMessage(), 0);
}
@RequestMapping(value="/public/{id:.+}", method=RequestMethod.GET)
public ResponseEntity<byte[]> getPublic(@PathVariable("id") String id)
throws PermissionException {
MediaDAO av = medias.getPublicMedia(id);
if(av != null) {
return ResponseEntity.ok().
contentType(MediaType.valueOf(av.getMimeType())).
contentLength(av.getData().length).
body(av.getData());
}
return ResponseEntity.notFound().build();
}
@RequestMapping(value="/all/{id:.+}", method=RequestMethod.GET)
public ResponseEntity<byte[]> getAll(
@RequestHeader(name="auth", required=true) String auth,
@PathVariable("id") String id)
throws PermissionException {
MediaDAO av = medias.getMedia(auth, id);
MediaWebService
Now that we got the main boilerplate out of the way lets look at the methods…
This lets us fetch a public image or media file it assumes the media is public and so we need no authorization. The id of the media is passed in the URL path itself
10. @ResponseStatus(value=HttpStatus.FORBIDDEN)
public @ResponseBody
ErrorDAO handlePermissionException(PermissionException e) {
return new ErrorDAO(e.getMessage(), 0);
}
@RequestMapping(value="/public/{id:.+}", method=RequestMethod.GET)
public ResponseEntity<byte[]> getPublic(@PathVariable("id") String id)
throws PermissionException {
MediaDAO av = medias.getPublicMedia(id);
if(av != null) {
return ResponseEntity.ok().
contentType(MediaType.valueOf(av.getMimeType())).
contentLength(av.getData().length).
body(av.getData());
}
return ResponseEntity.notFound().build();
}
@RequestMapping(value="/all/{id:.+}", method=RequestMethod.GET)
public ResponseEntity<byte[]> getAll(
@RequestHeader(name="auth", required=true) String auth,
@PathVariable("id") String id)
throws PermissionException {
MediaDAO av = medias.getMedia(auth, id);
MediaWebService
We get the mime type from the media entry in the database since media can be literally anything
11. contentLength(av.getData().length).
body(av.getData());
}
return ResponseEntity.notFound().build();
}
@RequestMapping(value="/all/{id:.+}", method=RequestMethod.GET)
public ResponseEntity<byte[]> getAll(
@RequestHeader(name="auth", required=true) String auth,
@PathVariable("id") String id)
throws PermissionException {
MediaDAO av = medias.getMedia(auth, id);
if(av != null) {
return ResponseEntity.ok().
contentType(MediaType.valueOf(av.getMimeType())).
contentLength(av.getData().length).
body(av.getData());
}
return ResponseEntity.notFound().build();
}
@RequestMapping(method=RequestMethod.POST, value="/upload")
public @ResponseBody
String upload(
@RequestHeader(name="auth", required=true) String auth,
MediaWebService
An all request will also return a friends only media entry and is thus marked as all. Other than the auth entry it's identical to the public request
12. MediaDAO av = medias.getMedia(auth, id);
if(av != null) {
return ResponseEntity.ok().
contentType(MediaType.valueOf(av.getMimeType())).
contentLength(av.getData().length).
body(av.getData());
}
return ResponseEntity.notFound().build();
}
@RequestMapping(method=RequestMethod.POST, value="/upload")
public @ResponseBody
String upload(
@RequestHeader(name="auth", required=true) String auth,
@RequestParam(name="file", required=true) MultipartFile file,
@RequestParam("role") String role,
@RequestParam("visibility") String visibility)
throws IOException {
return medias.storeMedia(auth, file.getBytes(), file.
getContentType(),
role, visibility, file.getName());
}
}
MediaWebService
upload is a multipart upload request that accepts a file with additional meta data as a multipart request.
Multipart is a part of the HTTP specification that includes a specific structure for submitting a file in a web based form. Codename One has builtin support for uploading
files using MultipartRequest so this is a pretty convenient solution for file upload.
With that we are done with the server!
This has been a lot of work for the server but most of it was simple boilerplate... The server isn't hard technically, in fact it's simple and thus a bit boring. But we had to
get through that in order to get to the more interesting pieces. The following lessons would be far more interesting as a result!