US10: Make it possible for horse information to be displayed on the view page, add a get image method to the API

This commit is contained in:
Ivaylo Ivanov 2020-03-28 10:43:15 +01:00
parent 3cc36466df
commit 4be964b537
8 changed files with 127 additions and 8 deletions

View File

@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
@ -146,4 +147,24 @@ public class HorseEndpoint {
"Something went wrong while uploading the file"); "Something went wrong while uploading the file");
} }
} }
@RequestMapping(value = "/image/{path}", produces = {MediaType.IMAGE_JPEG_VALUE,MediaType.IMAGE_PNG_VALUE}, method=RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public @ResponseBody byte[] getImage(@PathVariable("path") String path) {
// Get the image as byte array and return it with a media type assigned
// https://www.baeldung.com/spring-controller-return-image-file
// https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/MediaType.html
LOGGER.info("GET " + BASE_URL + "/image/{}", path);
try {
return horseService.getImage(path);
} catch(ValidationException e) {
LOGGER.error(e.getMessage());
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"Unsupported file type provided. Supported file types are jpg and png.");
} catch (IOException e) {
LOGGER.error(e.getMessage());
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
"Something went wrong while reading the file");
}
}
} }

View File

@ -18,4 +18,12 @@ public interface FileDao {
* @throws IOException if something goes wrong with deleting the file * @throws IOException if something goes wrong with deleting the file
*/ */
void delete(String fileName) throws IOException; void delete(String fileName) throws IOException;
/**
* Used fot getting a file from the local filesystem
* @param fileName of the file to open
* @return the file as bytes
* @throws IOException if something goes wrong during file access
*/
byte[] get(String fileName) throws IOException;
} }

View File

@ -63,4 +63,21 @@ public class HorseFileDao implements FileDao {
throw new IOException("Deleting the file specified failed"); throw new IOException("Deleting the file specified failed");
} }
} }
@Override
public byte[] get(String fileName) throws IOException {
if(fileName == null || fileName.equals(""))
throw new IOException("Cannot open an unexisting file");
LOGGER.trace("Getting file from " + FILE_BASE_PATH + fileName);
try {
// Read the file and return a byte array
// https://stackoverflow.com/a/5083666
File imageFile = new File(FILE_BASE_PATH + fileName);
return Files.readAllBytes(imageFile.toPath());
} catch(Exception e) {
LOGGER.error(e.getMessage());
throw new IOException("Opening the file specified failed");
}
}
} }

View File

@ -64,4 +64,12 @@ public interface HorseService {
* @throws ValidationException will be thrown if the file is in the incorrect format * @throws ValidationException will be thrown if the file is in the incorrect format
*/ */
void saveImage(MultipartFile img) throws IOException, ValidationException; void saveImage(MultipartFile img) throws IOException, ValidationException;
/**
* @param path of the image to get
* @return the image
* @throws IOException will be thrown if the file could not be accessed
* @throws ValidationException will be thrown if the path does not end with .jpg, .jpeg or .png
*/
byte[] getImage(String path) throws IOException, ValidationException;
} }

View File

@ -78,4 +78,11 @@ public class SimpleHorseService implements HorseService {
LOGGER.trace("saveImage({})", img.getName()); LOGGER.trace("saveImage({})", img.getName());
horseFileDao.save(img); horseFileDao.save(img);
} }
@Override
public byte[] getImage(String path) throws IOException, ValidationException {
this.validator.validateImageRequest(path);
LOGGER.trace("getImage({})", path);
return horseFileDao.get(path);
}
} }

View File

@ -68,6 +68,15 @@ public class Validator {
} }
} }
public void validateImageRequest(String path) throws ValidationException {
if(path == null || path.isEmpty()) {
throw new ValidationException("No image path supplied");
}
if(!path.endsWith(".png") && !path.endsWith(".jpg") && !path.endsWith(".jpeg")) {
throw new ValidationException("Unsupported file extension supplied.");
}
}
public void validateHorseFilter(Map<String, String> filters) throws ValidationException { public void validateHorseFilter(Map<String, String> filters) throws ValidationException {
if(filters.get("score") != null) { if(filters.get("score") != null) {
try { try {

View File

@ -6,12 +6,40 @@
</div> </div>
<div class="container mt-3" *ngIf="horse"> <div class="container mt-3" *ngIf="horse">
<div class="alert alert-success" role="alert"> <h1>Horse Info for {{ horse.name }}</h1>
<h4 class="alert-heading">Well done!</h4>
<p>Your application is up and running</p>
<hr> <hr>
<p>This is the name of the horse with id 1 from your backend system: <div class="row">
<span class="font-weight-bold">{{horse.name}}</span> <img class="img-thumbnail float-left col-md-6" src="http://localhost:8080/horses/image/{{ horse.imagePath }}" *ngIf="horse.imagePath">
</p> <table class="table table-striped float-right col-md-6">
<tbody>
<tr>
<th scope="row">Name</th>
<td>{{ horse.name }}</td>
</tr>
<tr>
<th scope="row">Owner</th>
<td>
<a href="owner/{{ horse.owner }}" *ngIf="horse.owner; else noOwner">{{ ownerName }}</a>
<ng-template #noOwner> {{ ownerName }} </ng-template>
</td>
</tr>
<tr>
<th scope="row">Birthday</th>
<td>{{ horse.birthday }}</td>
</tr>
<tr>
<th scope="row">Race</th>
<td>{{ horse.race }}</td>
</tr>
<tr>
<th scope="row">Score</th>
<td>{{ horse.score }}</td>
</tr>
<tr>
<th scope="row">Description</th>
<td>{{ horse.description }}</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>

View File

@ -2,6 +2,8 @@ import { Component, OnInit } from '@angular/core';
import { HorseService } from '../../service/horse.service'; import { HorseService } from '../../service/horse.service';
import { Horse } from '../../dto/horse'; import { Horse } from '../../dto/horse';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { OwnerService } from 'src/app/service/owner.service';
import { Owner } from 'src/app/dto/owner';
@Component({ @Component({
selector: 'app-horse', selector: 'app-horse',
@ -13,8 +15,9 @@ export class HorseComponent implements OnInit {
error = false; error = false;
errorMessage = ''; errorMessage = '';
horse: Horse; horse: Horse;
ownerName: string;
constructor(private horseService: HorseService, private route: ActivatedRoute) { } constructor(private horseService: HorseService, private route: ActivatedRoute, private ownerService: OwnerService) { }
ngOnInit(): void { ngOnInit(): void {
// Extract id from url // Extract id from url
@ -37,6 +40,7 @@ export class HorseComponent implements OnInit {
this.horseService.getHorseById(id).subscribe( this.horseService.getHorseById(id).subscribe(
(horse: Horse) => { (horse: Horse) => {
this.horse = horse; this.horse = horse;
this.loadOwnerNameForHorse(horse.owner);
}, },
error => { error => {
this.defaultServiceErrorHandling(error); this.defaultServiceErrorHandling(error);
@ -44,6 +48,23 @@ export class HorseComponent implements OnInit {
); );
} }
/**
* Loads the name of the owner of a horse
* @param id of the owner to get
*/
private loadOwnerNameForHorse(id: number) {
this.ownerService.getOwnerById(id).subscribe(
(owner: Owner) => {
console.log(owner.name)
this.ownerName = owner.name;
console.log(this.ownerName);
},
error => {
this.ownerName = "N/A";
}
)
}
private defaultServiceErrorHandling(error: any) { private defaultServiceErrorHandling(error: any) {
console.log(error); console.log(error);
this.error = true; this.error = true;