Skip to content
Snippets Groups Projects
Commit e6dc6822 authored by Laczkó Csongor Loránd's avatar Laczkó Csongor Loránd
Browse files

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
parent e24362b8
No related branches found
No related tags found
No related merge requests found
...@@ -51,13 +51,24 @@ public class Controller { ...@@ -51,13 +51,24 @@ public class Controller {
.orElse(ResponseEntity.notFound().build()); .orElse(ResponseEntity.notFound().build());
} }
@PostMapping("/newdog") @PostMapping(value = "/newdog")
public ResponseEntity<?> addNewDog(@RequestBody DetailedDogDTO dto, @RequestHeader("Authorization") String token) { 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(); 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 // Save the dog and picture
try { try {
dogService.addNewDog(dto); if (mpf != null && !mpf.isEmpty()) {
dogService.addNewDog(dogDTO, mpf);
} else {
dogService.addNewDog(dogDTO, null);
}
} catch (IOException e) { } catch (IOException e) {
return ResponseEntity.status(HttpStatus.NO_CONTENT).body("Hibás képformátum"); return ResponseEntity.status(HttpStatus.NO_CONTENT).body("Hibás képformátum");
} }
...@@ -65,7 +76,7 @@ public class Controller { ...@@ -65,7 +76,7 @@ public class Controller {
} }
@PostMapping(value = "/dogs/{id}/edit", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @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(); if (!isValidToken(token)) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
// Retrieve the dog entity from the database // Retrieve the dog entity from the database
...@@ -80,7 +91,11 @@ public class Controller { ...@@ -80,7 +91,11 @@ public class Controller {
} }
// Save the updated dog entity // Save the updated dog entity
try { try {
if (mpf != null && !mpf.isEmpty()) {
dogService.editDog(id, dogDTO, mpf); dogService.editDog(id, dogDTO, mpf);
} else {
dogService.editDog(id, dogDTO, null);
}
} catch (IOException e) { } catch (IOException e) {
return ResponseEntity.status(HttpStatus.NO_CONTENT).body("Hibás képformátum"); return ResponseEntity.status(HttpStatus.NO_CONTENT).body("Hibás képformátum");
} }
......
...@@ -32,14 +32,19 @@ public class DogService { ...@@ -32,14 +32,19 @@ public class DogService {
dogEntity.getPicture(), dogEntity.getAge(), dogEntity.getBreed())); dogEntity.getPicture(), dogEntity.getAge(), dogEntity.getBreed()));
} }
public void addNewDog(DetailedDogDTO dto) throws IOException { public void addNewDog(DetailedDogDTO newDogRequest, MultipartFile mpf) throws IOException {
DogEntity newDog = new DogEntity(); DogEntity dogEntity = new DogEntity();
newDog.setName(dto.getName()); // Set fields from newDogRequest
//newDog.setPicture(pic.getBytes()); dogEntity.setName(newDogRequest.getName());
newDog.setBreed(dto.getBreed()); dogEntity.setBreed(newDogRequest.getBreed());
newDog.setAge(dto.getAge()); dogEntity.setAge(newDogRequest.getAge());
dogRepository.save(newDog); 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 { public void editDog(Integer id, DetailedDogDTO editRequest, MultipartFile mpf) throws IOException {
...@@ -51,8 +56,10 @@ public class DogService { ...@@ -51,8 +56,10 @@ public class DogService {
if (editRequest.getName() != null) { if (editRequest.getName() != null) {
dogEntity.setName(editRequest.getName()); dogEntity.setName(editRequest.getName());
} }
if (mpf != null) {
mpf.getBytes(); mpf.getBytes();
dogEntity.setPicture(mpf.getBytes()); dogEntity.setPicture(mpf.getBytes());
}
if (editRequest.getAge() != null) { if (editRequest.getAge() != null) {
dogEntity.setAge(editRequest.getAge()); dogEntity.setAge(editRequest.getAge());
} }
......
...@@ -17,6 +17,18 @@ ...@@ -17,6 +17,18 @@
<label for="age">Kora</label> <label for="age">Kora</label>
<input id="age" v-model="dog.age" type="number" required> <input id="age" v-model="dog.age" type="number" required>
</div> </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> <button type="submit">Mentés</button>
</form> </form>
</div> </div>
...@@ -25,8 +37,18 @@ ...@@ -25,8 +37,18 @@
<script> <script>
import { axios, apiURL } from '@/axiosConfig.js'; import { axios, apiURL } from '@/axiosConfig.js';
import { mapState } from 'vuex'; 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 { export default {
components: {
FilePond
},
data() { data() {
return { return {
dog: {}, dog: {},
...@@ -37,6 +59,9 @@ export default { ...@@ -37,6 +59,9 @@ export default {
...mapState(['token']), ...mapState(['token']),
}, },
methods: { methods: {
handleFilePondInit: function() {
console.log('FilePond has initialized');
},
validateAndAddDog() { validateAndAddDog() {
if (this.validateForm()) { if (this.validateForm()) {
this.addDog(); this.addDog();
...@@ -67,11 +92,36 @@ export default { ...@@ -67,11 +92,36 @@ export default {
return true; return true;
}, },
async addDog() { 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 = { const config = {
headers: { Authorization: `Bearer ${this.token}` }, headers: {
Authorization: `Bearer ${this.token}`,
},
}; };
await axios.post(apiURL + '/newdog', this.dog, config);
try {
await axios.post(apiURL + `/newdog`, formData, config);
this.$router.push(`/dogs`); this.$router.push(`/dogs`);
} catch (error) {
console.error('Hiba történt a kutya hozzáadása közben:', error);
// Handle error
}
}, },
}, },
}; };
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
Betöltés... Betöltés...
</div> </div>
<div v-else-if="errorMessage || fetchError || !hasDogs" class="alert alert-error"> <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>
<div v-else class="dog-item-container"> <div v-else class="dog-item-container">
<div v-for="dog in dogs" :key="dog.id" class="dog-item" @click="viewDog(dog.id)"> <div v-for="dog in dogs" :key="dog.id" class="dog-item" @click="viewDog(dog.id)">
...@@ -77,11 +77,11 @@ export default { ...@@ -77,11 +77,11 @@ export default {
} }
.dog-item { .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 { .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 { .dog-name {
......
...@@ -97,7 +97,8 @@ export default { ...@@ -97,7 +97,8 @@ export default {
return true; return true;
}, },
async editDog() { 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 = { const dogData = {
name: this.dog.name, name: this.dog.name,
...@@ -107,12 +108,15 @@ export default { ...@@ -107,12 +108,15 @@ export default {
const formData = new FormData(); const formData = new FormData();
formData.append('dog', JSON.stringify(dogData)); // Convert dog data to JSON string 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 = { const config = {
headers: { headers: {
Authorization: `Bearer ${this.token}`, Authorization: `Bearer ${this.token}`,
// 'Content-Type': 'multipart/form-data'
}, },
}; };
...@@ -120,7 +124,7 @@ export default { ...@@ -120,7 +124,7 @@ export default {
await axios.post(apiURL + `/dogs/${this.$route.params.id}/edit`, formData, config); await axios.post(apiURL + `/dogs/${this.$route.params.id}/edit`, formData, config);
this.$router.push(`/dog/${this.$route.params.id}`); this.$router.push(`/dog/${this.$route.params.id}`);
} catch (error) { } catch (error) {
console.error('Error editing dog:', error); console.error('Hiba történt a kutya szerkesztése közben:', error);
// Handle error // Handle error
} }
}, },
...@@ -132,9 +136,6 @@ export default { ...@@ -132,9 +136,6 @@ export default {
</script> </script>
<style lang="postcss"> <style lang="postcss">
.edit-dog-container {
@apply flex flex-col items-center justify-center min-h-screen bg-blue-200;
}
.input-group { .input-group {
@apply mb-4; @apply mb-4;
......
<template> <template>
<div v-if="dog" class="global-container"> <div v-if="dog" class="global-container">
<h1 class="text-2xl font-bold mb-4">{{ dog.name }}</h1> <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 mb-2"><strong>Kor:</strong> {{ dog.age }}</p>
<p class="text-lg"><strong>Faj:</strong> {{ dog.breed }}</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> <router-link :to="`/edit-dog/${dog.id}`" tag="button" class="button">Szerkesztés</router-link>
...@@ -12,6 +14,7 @@ ...@@ -12,6 +14,7 @@
<script> <script>
import { axios, apiURL } from '@/axiosConfig.js'; import { axios, apiURL } from '@/axiosConfig.js';
import {mapState} from "vuex";
export default { export default {
name: 'SingleDog', name: 'SingleDog',
...@@ -30,11 +33,16 @@ export default { ...@@ -30,11 +33,16 @@ export default {
// Handle error // Handle error
} }
}, },
computed: {
...mapState(['token']),
},
methods: { methods: {
async deleteDog() { async deleteDog() {
if (window.confirm('Biztosan törölni akarod ezt a kutyát?')) { if (window.confirm('Biztosan törölni akarod ezt a kutyát?')) {
const config = { const config = {
headers: { Authorization: `Bearer ${this.token}` }, headers: {
Authorization: `Bearer ${this.token}`,
},
}; };
try { try {
await axios.delete(apiURL + `/dogs/${this.$route.params.id}`, config); await axios.delete(apiURL + `/dogs/${this.$route.params.id}`, config);
...@@ -42,13 +50,16 @@ export default { ...@@ -42,13 +50,16 @@ export default {
} catch (error) { } catch (error) {
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
this.$router.push('/login'); this.$router.push('/login');
} else if (error.response && error.response.status === 404) {
// Handle "Not Found" error
console.error('Dog not found:', error);
} else { } else {
console.error(error);
// Handle other types of errors // Handle other types of errors
console.error('Error deleting dog:', error);
}
} }
} }
} }
},
}, },
}; };
</script> </script>
......
...@@ -51,7 +51,7 @@ p { ...@@ -51,7 +51,7 @@ p {
} }
.global-container { .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 { .input-group {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment