From e6dc682215ff275574a3a7ceb8b1cac4bae6adc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laczk=C3=B3=20Csongor=20Lor=C3=A1nd?= <laczko.csongor.lorand@hallgato.ppke.hu> Date: Tue, 14 May 2024 23:42:58 +0200 Subject: [PATCH] feat: Add file upload to AddDog and make picture sending optional - Implement file upload in AddDog - Make picture sending optional in both frontend and backend - Fix token sending issue in delete operation style: Perform minor UI adjustments in AllDogs --- .../hu/pazmany/controller/Controller.java | 25 +++++++-- .../java/hu/pazmany/service/DogService.java | 25 ++++++--- frontend/src/components/dogs/AddDog.vue | 56 ++++++++++++++++++- frontend/src/components/dogs/AllDogs.vue | 6 +- frontend/src/components/dogs/EditDog.vue | 15 ++--- frontend/src/components/dogs/SingleDog.vue | 19 +++++-- frontend/src/index.css | 2 +- 7 files changed, 116 insertions(+), 32 deletions(-) diff --git a/backend/src/main/java/hu/pazmany/controller/Controller.java b/backend/src/main/java/hu/pazmany/controller/Controller.java index 5c858b9..bf9396b 100644 --- a/backend/src/main/java/hu/pazmany/controller/Controller.java +++ b/backend/src/main/java/hu/pazmany/controller/Controller.java @@ -51,13 +51,24 @@ public class Controller { .orElse(ResponseEntity.notFound().build()); } - @PostMapping("/newdog") - public ResponseEntity<?> addNewDog(@RequestBody DetailedDogDTO dto, @RequestHeader("Authorization") String token) { + @PostMapping(value = "/newdog") + public ResponseEntity<?> addNewDog(@RequestHeader("Authorization") String token, @RequestParam("dog") String stringDogDTO, @RequestParam(value = "picture", required = false) MultipartFile mpf) { if (!isValidToken(token)) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + ObjectMapper objectMapper = new ObjectMapper(); + DetailedDogDTO dogDTO; + try { + dogDTO = objectMapper.readValue(stringDogDTO, DetailedDogDTO.class); + } catch (IOException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Hibás JSON formátum"); + } // Save the dog and picture try { - dogService.addNewDog(dto); + if (mpf != null && !mpf.isEmpty()) { + dogService.addNewDog(dogDTO, mpf); + } else { + dogService.addNewDog(dogDTO, null); + } } catch (IOException e) { return ResponseEntity.status(HttpStatus.NO_CONTENT).body("Hibás képformátum"); } @@ -65,7 +76,7 @@ public class Controller { } @PostMapping(value = "/dogs/{id}/edit", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity<?> editDog(@PathVariable Integer id, @RequestHeader("Authorization") String token, @RequestParam("dog") String stringDogDTO, @RequestParam("picture") MultipartFile mpf) { + public ResponseEntity<?> editDog(@PathVariable Integer id, @RequestHeader("Authorization") String token, @RequestParam("dog") String stringDogDTO, @RequestParam(value = "picture", required = false) MultipartFile mpf) { if (!isValidToken(token)) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); // Retrieve the dog entity from the database @@ -80,7 +91,11 @@ public class Controller { } // Save the updated dog entity try { - dogService.editDog(id, dogDTO, mpf); + if (mpf != null && !mpf.isEmpty()) { + dogService.editDog(id, dogDTO, mpf); + } else { + dogService.editDog(id, dogDTO, null); + } } catch (IOException e) { return ResponseEntity.status(HttpStatus.NO_CONTENT).body("Hibás képformátum"); } diff --git a/backend/src/main/java/hu/pazmany/service/DogService.java b/backend/src/main/java/hu/pazmany/service/DogService.java index 51be267..54224f8 100644 --- a/backend/src/main/java/hu/pazmany/service/DogService.java +++ b/backend/src/main/java/hu/pazmany/service/DogService.java @@ -32,14 +32,19 @@ public class DogService { dogEntity.getPicture(), dogEntity.getAge(), dogEntity.getBreed())); } - public void addNewDog(DetailedDogDTO dto) throws IOException { - DogEntity newDog = new DogEntity(); + public void addNewDog(DetailedDogDTO newDogRequest, MultipartFile mpf) throws IOException { + DogEntity dogEntity = new DogEntity(); - newDog.setName(dto.getName()); - //newDog.setPicture(pic.getBytes()); - newDog.setBreed(dto.getBreed()); - newDog.setAge(dto.getAge()); - dogRepository.save(newDog); + // Set fields from newDogRequest + dogEntity.setName(newDogRequest.getName()); + dogEntity.setBreed(newDogRequest.getBreed()); + dogEntity.setAge(newDogRequest.getAge()); + if (mpf != null && !mpf.isEmpty()) { + dogEntity.setPicture(mpf.getBytes()); + } + + // Save the new entity + dogRepository.save(dogEntity); } public void editDog(Integer id, DetailedDogDTO editRequest, MultipartFile mpf) throws IOException { @@ -51,8 +56,10 @@ public class DogService { if (editRequest.getName() != null) { dogEntity.setName(editRequest.getName()); } - mpf.getBytes(); - dogEntity.setPicture(mpf.getBytes()); + if (mpf != null) { + mpf.getBytes(); + dogEntity.setPicture(mpf.getBytes()); + } if (editRequest.getAge() != null) { dogEntity.setAge(editRequest.getAge()); } diff --git a/frontend/src/components/dogs/AddDog.vue b/frontend/src/components/dogs/AddDog.vue index fb0e972..c9ed691 100644 --- a/frontend/src/components/dogs/AddDog.vue +++ b/frontend/src/components/dogs/AddDog.vue @@ -17,6 +17,18 @@ <label for="age">Kora</label> <input id="age" v-model="dog.age" type="number" required> </div> + <div class="input-group"> + <label for="dog-image">Kép</label> + <file-pond + id="dog-image" + name="dogPicture" + ref="pond" + label-idle="Húzza ide a fájlt..." + allow-multiple="false" + accepted-file-types="image/jpeg, image/png" + v-on:init="handleFilePondInit" + /> + </div> <button type="submit">Mentés</button> </form> </div> @@ -25,8 +37,18 @@ <script> import { axios, apiURL } from '@/axiosConfig.js'; import { mapState } from 'vuex'; +import vueFilePond from 'vue-filepond'; +import FilePondPluginImagePreview from 'filepond-plugin-image-preview'; +import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'; +import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css'; +import 'filepond/dist/filepond.min.css'; + +const FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview); export default { + components: { + FilePond + }, data() { return { dog: {}, @@ -37,6 +59,9 @@ export default { ...mapState(['token']), }, methods: { + handleFilePondInit: function() { + console.log('FilePond has initialized'); + }, validateAndAddDog() { if (this.validateForm()) { this.addDog(); @@ -67,11 +92,36 @@ export default { return true; }, async addDog() { + const files = this.$refs.pond.getFiles(); + const pictureFile = files.length > 0 ? files[0].file : null; + + const dogData = { + name: this.dog.name, + breed: this.dog.breed, + age: this.dog.age + }; + + const formData = new FormData(); + formData.append('dog', JSON.stringify(dogData)); // Convert dog data to JSON string + if (pictureFile) { + formData.append('picture', pictureFile, pictureFile.name); // Append picture as a blob only if it exists + } else { + formData.append('picture', null); // Append null if picture does not exist + } + const config = { - headers: { Authorization: `Bearer ${this.token}` }, + headers: { + Authorization: `Bearer ${this.token}`, + }, }; - await axios.post(apiURL + '/newdog', this.dog, config); - this.$router.push(`/dogs`); + + try { + await axios.post(apiURL + `/newdog`, formData, config); + this.$router.push(`/dogs`); + } catch (error) { + console.error('Hiba történt a kutya hozzáadása közben:', error); + // Handle error + } }, }, }; diff --git a/frontend/src/components/dogs/AllDogs.vue b/frontend/src/components/dogs/AllDogs.vue index a4e258b..2844349 100644 --- a/frontend/src/components/dogs/AllDogs.vue +++ b/frontend/src/components/dogs/AllDogs.vue @@ -5,7 +5,7 @@ Betöltés... </div> <div v-else-if="errorMessage || fetchError || !hasDogs" class="alert alert-error"> - {{ errorMessage || (fetchError ? 'Could not fetch dogs.' : 'No dogs found.') }} + {{ errorMessage || (fetchError ? 'Nem sikerült lekérdezni a kutyákat.' : 'Nem találhatók kutyák.') }} </div> <div v-else class="dog-item-container"> <div v-for="dog in dogs" :key="dog.id" class="dog-item" @click="viewDog(dog.id)"> @@ -77,11 +77,11 @@ export default { } .dog-item { - @apply flex flex-col items-center bg-white m-4 p-4 rounded shadow w-1/4 min-w-64 min-h-64; + @apply flex flex-col items-center m-4 p-4 rounded w-1/4 min-w-64 min-h-64; } .dog-image { - @apply w-full h-64 object-cover mb-4 rounded min-w-64 min-h-64; + @apply w-full h-64 object-cover bg-white mb-4 rounded min-w-64 min-h-64; } .dog-name { diff --git a/frontend/src/components/dogs/EditDog.vue b/frontend/src/components/dogs/EditDog.vue index 7d6f757..106a936 100644 --- a/frontend/src/components/dogs/EditDog.vue +++ b/frontend/src/components/dogs/EditDog.vue @@ -97,7 +97,8 @@ export default { return true; }, async editDog() { - const pictureFile = this.$refs.pond.getFiles()[0].file; + const files = this.$refs.pond.getFiles(); + const pictureFile = files.length > 0 ? files[0].file : null; const dogData = { name: this.dog.name, @@ -107,12 +108,15 @@ export default { const formData = new FormData(); formData.append('dog', JSON.stringify(dogData)); // Convert dog data to JSON string - formData.append('picture', pictureFile, pictureFile.name); // Append picture as a blob + if (pictureFile) { + formData.append('picture', pictureFile, pictureFile.name); // Append picture as a blob only if it exists + } else { + formData.append('picture', null); // Append null if picture does not exist + } const config = { headers: { Authorization: `Bearer ${this.token}`, - // 'Content-Type': 'multipart/form-data' }, }; @@ -120,7 +124,7 @@ export default { await axios.post(apiURL + `/dogs/${this.$route.params.id}/edit`, formData, config); this.$router.push(`/dog/${this.$route.params.id}`); } catch (error) { - console.error('Error editing dog:', error); + console.error('Hiba történt a kutya szerkesztése közben:', error); // Handle error } }, @@ -132,9 +136,6 @@ export default { </script> <style lang="postcss"> -.edit-dog-container { - @apply flex flex-col items-center justify-center min-h-screen bg-blue-200; -} .input-group { @apply mb-4; diff --git a/frontend/src/components/dogs/SingleDog.vue b/frontend/src/components/dogs/SingleDog.vue index 002250a..46c025b 100644 --- a/frontend/src/components/dogs/SingleDog.vue +++ b/frontend/src/components/dogs/SingleDog.vue @@ -1,7 +1,9 @@ <template> <div v-if="dog" class="global-container"> <h1 class="text-2xl font-bold mb-4">{{ dog.name }}</h1> - <img :src="dog.picture" :alt="`${dog.name} képe`" class="w-64 h-64 object-cover mb-4 rounded shadow"/> + <img v-if="dog" + :src="dog.picture ? 'data:image/jpeg;base64,' + dog.picture : 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 50 50\'%3E%3Ctext y=\'.9em\' font-size=\'20\'%3E' + dog.id + '%3C/text%3E%3C/svg%3E'" + :alt="`Image of ${dog.name}`" class="dog-image"/> <p class="text-lg mb-2"><strong>Kor:</strong> {{ dog.age }}</p> <p class="text-lg"><strong>Faj:</strong> {{ dog.breed }}</p> <router-link :to="`/edit-dog/${dog.id}`" tag="button" class="button">Szerkesztés</router-link> @@ -12,6 +14,7 @@ <script> import { axios, apiURL } from '@/axiosConfig.js'; +import {mapState} from "vuex"; export default { name: 'SingleDog', @@ -30,11 +33,16 @@ export default { // Handle error } }, + computed: { + ...mapState(['token']), + }, methods: { async deleteDog() { if (window.confirm('Biztosan törölni akarod ezt a kutyát?')) { const config = { - headers: { Authorization: `Bearer ${this.token}` }, + headers: { + Authorization: `Bearer ${this.token}`, + }, }; try { await axios.delete(apiURL + `/dogs/${this.$route.params.id}`, config); @@ -42,13 +50,16 @@ export default { } catch (error) { if (error.response && error.response.status === 401) { this.$router.push('/login'); + } else if (error.response && error.response.status === 404) { + // Handle "Not Found" error + console.error('Dog not found:', error); } else { - console.error(error); // Handle other types of errors + console.error('Error deleting dog:', error); } } } - }, + } }, }; </script> diff --git a/frontend/src/index.css b/frontend/src/index.css index 4a6b44d..a014ef5 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -51,7 +51,7 @@ p { } .global-container { - @apply flex flex-col items-center justify-center min-h-screen bg-blue-200; + @apply flex flex-col items-center justify-center bg-blue-200; } .input-group { -- GitLab