diff --git a/src/main/java/polish_community_group_11/polish_community/feed/WebConfig.java b/src/main/java/polish_community_group_11/polish_community/feed/WebConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..20968c93dd696c75a7633cb585f897cc05be557e --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/feed/WebConfig.java @@ -0,0 +1,19 @@ +package polish_community_group_11.polish_community.feed; + + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Value("${file.upload-dir}") + private String uploadDir; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/uploads/**") + .addResourceLocations("file:" + uploadDir + "/"); + } +} \ No newline at end of file diff --git a/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java b/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java index 1005fac412d47839512588dbf82a5b9093395d84..85958abbb513e6795c036677dc55f3cf8b5d229b 100644 --- a/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java +++ b/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java @@ -8,9 +8,11 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import polish_community_group_11.polish_community.feed.models.FeedImpl; import polish_community_group_11.polish_community.feed.repository.FeedRepository; import polish_community_group_11.polish_community.feed.services.FeedService; +import polish_community_group_11.polish_community.feed.services.FileStorageService; import polish_community_group_11.polish_community.register.models.User; import polish_community_group_11.polish_community.register.services.UserService; @@ -23,14 +25,16 @@ public class FeedApisController { private final UserService userService; private final FeedService feedService; + private final FileStorageService fileStorageService; private static final Logger log = LoggerFactory.getLogger(FeedApisController.class); - public FeedApisController(FeedRepository feedRepository, FeedService feedService, UserService userService) { + public FeedApisController(FeedRepository feedRepository, FeedService feedService, UserService userService, FileStorageService fileStorageService) { this.feedService = feedService; this.feedRepository = feedRepository; this.userService = userService; + this.fileStorageService = fileStorageService; } // getting all posts @@ -80,18 +84,27 @@ public class FeedApisController { } // creating a new post - @PostMapping("/add") - public ResponseEntity<?> addNewPost(@RequestBody FeedImpl feed) { + public ResponseEntity<?> addNewPost( + @RequestPart("post") FeedImpl feed, + @RequestPart(value = "image", required = false) MultipartFile image) { try { + if (image != null && !image.isEmpty()) { + String imageUrl = fileStorageService.storeFile(image); + feed.setPostImageUrl(imageUrl); + } feedService.addNewFeedPost(feed); return ResponseEntity.ok().body("Post created successfully"); } catch (SecurityException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User not authenticated"); } catch (Exception e) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error creating post: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Error creating post: " + e.getMessage()); } } + + + // updating the post @PatchMapping("/{postId}") public ResponseEntity<Void> updatePost(@PathVariable int postId, @RequestBody FeedImpl feed) { diff --git a/src/main/java/polish_community_group_11/polish_community/feed/services/FileStorageService.java b/src/main/java/polish_community_group_11/polish_community/feed/services/FileStorageService.java new file mode 100644 index 0000000000000000000000000000000000000000..bbbff28910cda3f5b671c4d459f331d76619d19f --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/feed/services/FileStorageService.java @@ -0,0 +1,48 @@ +package polish_community_group_11.polish_community.feed.services; + +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; + +@Service +public class FileStorageService { + + @Value("${file.upload-dir:uploads}") + private String uploadDir; + + private Path root; + + @PostConstruct + public void init() throws IOException { + this.root = Paths.get(uploadDir); + if (!Files.exists(root)) { + Files.createDirectories(root); + } + } + + + public String storeFile(MultipartFile file) throws IOException { + // making unique file name + String filename = UUID.randomUUID().toString() + "_" + file.getOriginalFilename(); + + // full path of file + Path filePath = this.root.resolve(filename); + + // save file + Files.copy(file.getInputStream(), filePath); + + // relative path usable in urls + return "/uploads/" + filename; + } + + public void deleteFile(String filename) throws IOException { + Path filePath = this.root.resolve(filename); + Files.deleteIfExists(filePath); + } +} \ No newline at end of file diff --git a/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java b/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java index 34029e3e1f9f3bc064cd7be86809ac353bb14b27..074fc314e387021ece0e02305997ba58dd042f04 100644 --- a/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java +++ b/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java @@ -1,5 +1,6 @@ package polish_community_group_11.polish_community.security; +import lombok.Value; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; @@ -14,6 +15,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import polish_community_group_11.polish_community.register.models.User; import polish_community_group_11.polish_community.register.services.UserService; import polish_community_group_11.polish_community.register.models.Role; @@ -117,4 +120,6 @@ public class WebSecurityConfig { }; } + + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9c018eed4862e2e907d9f0c07073af0300277e96..c7bdcf734f7eb4c6dc6d5917305b142ff531b137 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -22,6 +22,9 @@ spring.sql.init.data-locations=classpath:/data.sql, classpath:/data_information_ #spring.jpa.hibernate.ddl-auto=none spring.jpa.defer-datasource-initialization=true - +spring.servlet.multipart.enabled=true +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB +file.upload-dir=uploads diff --git a/src/main/resources/static/js/feed/feed.js b/src/main/resources/static/js/feed/feed.js index 75330a347b2d75b892ddad050b2a75853a0ee8a0..f520113959203f58c8add8f91ac72ed501152a0e 100644 --- a/src/main/resources/static/js/feed/feed.js +++ b/src/main/resources/static/js/feed/feed.js @@ -153,28 +153,33 @@ async function handleLike(postId, likeCountElement) { postForm.addEventListener('submit', async (event) => { event.preventDefault(); - // getting form data - const postTitle = document.getElementById('postTitle').value; - const postDescription = document.getElementById('postDescription').value; - const postTagsInput = document.getElementById('postTags').value; - - // convert comma-separated tags to array and trim whitespace - const tags = postTagsInput - .split(',') - .map(tag => tag.trim()) - .filter(tag => tag.length > 0); - - const data = { - postTitle, - postDescription, - tags + + const formData = new FormData(); + + // getting post data + const postData = { + postTitle: document.getElementById('postTitle').value, + postDescription: document.getElementById('postDescription').value, + tags: document.getElementById('postTags').value + .split(',') + .map(tag => tag.trim()) + .filter(tag => tag.length > 0) }; + formData.append('post', new Blob([JSON.stringify(postData)], { + type: 'application/json' + })); + + // handle the case of an image selected + const imageFile = document.getElementById('postImage').files[0]; + if (imageFile) { + formData.append('image', imageFile); + } + try { let url = `${API_BASE_URL}/add`; let method = 'POST'; - // if it is edit change endpoint if (isEditing && editPostId) { url = `${API_BASE_URL}/${editPostId}`; method = 'PATCH'; @@ -182,11 +187,8 @@ postForm.addEventListener('submit', async (event) => { const response = await fetch(url, { method: method, - headers: { - 'Content-Type': 'application/json', - }, credentials: 'include', - body: JSON.stringify(data) + body: formData }); if (!response.ok) { @@ -203,6 +205,22 @@ postForm.addEventListener('submit', async (event) => { } }); +//previewing the image selected +document.getElementById('postImage').addEventListener('change', function(e) { + const file = e.target.files[0]; + const preview = document.getElementById('imagePreview'); + + if (file) { + const reader = new FileReader(); + reader.onload = function(e) { + preview.innerHTML = `<img src="${e.target.result}" style="max-width: 200px; max-height: 200px;">`; + } + reader.readAsDataURL(file); + } else { + preview.innerHTML = ''; + } +}); + // onclick handling for edit document.addEventListener('click', async (event) => { const editButton = event.target.closest('.edit-post'); diff --git a/src/main/resources/templates/feed/feed.html b/src/main/resources/templates/feed/feed.html index 189c8e7ee3fc2665235a373f7d4373f96a6d07cc..22d741511465a03b310f94c996b039cbe9f6f5d7 100644 --- a/src/main/resources/templates/feed/feed.html +++ b/src/main/resources/templates/feed/feed.html @@ -100,6 +100,15 @@ required></textarea> </label> + <label for="postImage">Image + <input type="file" + id="postImage" + name="postImage" + accept="image/*" + class="form-control"> + <div id="imagePreview" class="mt-2"></div> + </label> + <label for="postTags">Tags <input type="text" id="postTags" name="postTags" placeholder="Enter tags separated by commas (e.g., news, community, event)" diff --git a/uploads/36b2c38d-c9d5-4e14-a433-895a565d3abf_steve-johnson-D7AuHpLxLPA-unsplash.jpg b/uploads/36b2c38d-c9d5-4e14-a433-895a565d3abf_steve-johnson-D7AuHpLxLPA-unsplash.jpg new file mode 100644 index 0000000000000000000000000000000000000000..82f2f1fa5fefccb98af0ffb041279b1a87d3fae4 Binary files /dev/null and b/uploads/36b2c38d-c9d5-4e14-a433-895a565d3abf_steve-johnson-D7AuHpLxLPA-unsplash.jpg differ diff --git a/uploads/5720f047-a3ca-4d4e-ab20-343aae7cc485_premium_photo-1733514691627-e62171fc052c.avif b/uploads/5720f047-a3ca-4d4e-ab20-343aae7cc485_premium_photo-1733514691627-e62171fc052c.avif new file mode 100644 index 0000000000000000000000000000000000000000..9631a6ae6f56719d409cad7d80ff047a2b6c78c4 Binary files /dev/null and b/uploads/5720f047-a3ca-4d4e-ab20-343aae7cc485_premium_photo-1733514691627-e62171fc052c.avif differ diff --git a/uploads/d0753820-30b3-429c-92c4-d82c910ba083_nicolas-jehly-0UU9-_1EMvM-unsplash.jpg b/uploads/d0753820-30b3-429c-92c4-d82c910ba083_nicolas-jehly-0UU9-_1EMvM-unsplash.jpg new file mode 100644 index 0000000000000000000000000000000000000000..648e6c93860d6984c454a484b2ef392bd8fbc642 Binary files /dev/null and b/uploads/d0753820-30b3-429c-92c4-d82c910ba083_nicolas-jehly-0UU9-_1EMvM-unsplash.jpg differ