From 038236ce54888af9566d76b8e2e432e9ca1e5a7f Mon Sep 17 00:00:00 2001 From: Wethmi Ranasinghe Date: Fri, 6 Sep 2024 14:07:12 +0530 Subject: [PATCH] Adding cover images to News and Events Images can be stored in the database --- .../com/example/demo/news/NewsAndEvents.java | 13 +- .../demo/news/NewsAndEventsController.java | 31 +- .../demo/news/NewsAndEventsRequest.java | 4 +- .../demo/news/NewsAndEventsResponse.java | 31 ++ .../demo/news/NewsAndEventsService.java | 89 +++-- .../demo/NewsAndEventsControllerTest.java | 52 ++- .../demo/NewsAndEventsServiceTest.java | 33 +- front-end/package-lock.json | 350 +++++++++--------- front-end/package.json | 2 +- front-end/src/Pages/News.jsx | 195 ++++++---- 10 files changed, 473 insertions(+), 327 deletions(-) create mode 100644 back-end/src/main/java/com/example/demo/news/NewsAndEventsResponse.java diff --git a/back-end/src/main/java/com/example/demo/news/NewsAndEvents.java b/back-end/src/main/java/com/example/demo/news/NewsAndEvents.java index f5468e1a..4d2515e8 100644 --- a/back-end/src/main/java/com/example/demo/news/NewsAndEvents.java +++ b/back-end/src/main/java/com/example/demo/news/NewsAndEvents.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import java.time.LocalDate; /** * Represents a news or event entity stored in the database. @@ -28,12 +29,20 @@ public class NewsAndEvents { generator = "news_and_events_sequence" ) private Long newsID; + private String newsTitle; + @Column(columnDefinition = "TEXT") private String newsDescription; + private String newsUrl; - private String newsCoverImage; + + @Lob + @Column(name = "newsCoverImage", columnDefinition = "BLOB") + private byte[] newsCoverImage; + private String newsDate; + private String newsAuthor; /** @@ -44,7 +53,7 @@ public NewsAndEvents(String newsTitle, String newsUrl, String newsAuthor, String newsDate, - String newsCoverImage){ + byte[] newsCoverImage) { this.newsTitle = newsTitle; this.newsDescription = newsDescription; this.newsUrl = newsUrl; diff --git a/back-end/src/main/java/com/example/demo/news/NewsAndEventsController.java b/back-end/src/main/java/com/example/demo/news/NewsAndEventsController.java index f7157510..3388bc15 100644 --- a/back-end/src/main/java/com/example/demo/news/NewsAndEventsController.java +++ b/back-end/src/main/java/com/example/demo/news/NewsAndEventsController.java @@ -1,8 +1,9 @@ package com.example.demo.news; -import com.example.demo.registration.RegistrationRequest; import lombok.AllArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Optional; @@ -17,25 +18,32 @@ public class NewsAndEventsController { private final NewsAndEventsService newsAndEventsService; - /** - * Endpoint to fetch all news and events. - */ @GetMapping - public List getAllNewsAndEvents() { + public List getAllNewsAndEvents() { return newsAndEventsService.getAllNewsAndEvents(); } @GetMapping("/{newsId}") - public Optional getNewsById(@PathVariable Long newsId) { - return newsAndEventsService.getNewsAndEventById(newsId); + public NewsAndEventsResponse getNewsById(@PathVariable Long newsId) { + return newsAndEventsService.getNewsAndEventById(newsId) + .map(NewsAndEventsResponse::new) + .orElseThrow(() -> new RuntimeException("News not found")); } /** - * Endpoint to add a new news or event. + * Endpoint to add a new news or event with image upload. */ - @PostMapping - public NewsAndEvents addNewsAndEvents(@RequestBody NewsAndEventsRequest newsAndEventsRequest) { - return newsAndEventsService.addNewsAndEvent(newsAndEventsRequest); + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public NewsAndEvents addNewsAndEvents( + @RequestParam("newsTitle") String newsTitle, + @RequestParam("newsDescription") String newsDescription, + @RequestParam("newsUrl") String newsUrl, + @RequestParam("newsAuthor") String newsAuthor, + @RequestParam("newsDate") String newsDate, + @RequestParam("newsCoverImage") MultipartFile newsCoverImage) { + + // Call the service to add the news with the uploaded image + return newsAndEventsService.addNewsAndEvent(newsTitle, newsDescription, newsUrl, newsAuthor, newsDate, newsCoverImage); } /** @@ -45,5 +53,4 @@ public NewsAndEvents addNewsAndEvents(@RequestBody NewsAndEventsRequest newsAndE public void deleteNewsById(@PathVariable Long newsId) { newsAndEventsService.deleteNewsAndEvent(newsId); } - } diff --git a/back-end/src/main/java/com/example/demo/news/NewsAndEventsRequest.java b/back-end/src/main/java/com/example/demo/news/NewsAndEventsRequest.java index bfd8dff5..0b428fde 100644 --- a/back-end/src/main/java/com/example/demo/news/NewsAndEventsRequest.java +++ b/back-end/src/main/java/com/example/demo/news/NewsAndEventsRequest.java @@ -5,6 +5,8 @@ import lombok.Getter; import lombok.ToString; +import java.time.LocalDate; + /** * Represents a request object for creating or updating a NewsAndEvents entity. */ @@ -20,5 +22,5 @@ public class NewsAndEventsRequest { private final String newsUrl; private final String newsAuthor; private final String newsDate; - private final String newsCoverImage; + private final byte[] newsCoverImage; } diff --git a/back-end/src/main/java/com/example/demo/news/NewsAndEventsResponse.java b/back-end/src/main/java/com/example/demo/news/NewsAndEventsResponse.java new file mode 100644 index 00000000..405fc1a9 --- /dev/null +++ b/back-end/src/main/java/com/example/demo/news/NewsAndEventsResponse.java @@ -0,0 +1,31 @@ +package com.example.demo.news; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Base64; + +@Getter +@Setter +public class NewsAndEventsResponse { + + private Long newsID; + private String newsTitle; + private String newsDescription; + private String newsUrl; + private String newsAuthor; + private String newsDate; + private String newsCoverImage; // Base64 encoded image + + public NewsAndEventsResponse(NewsAndEvents newsAndEvents) { + this.newsID = newsAndEvents.getNewsID(); + this.newsTitle = newsAndEvents.getNewsTitle(); + this.newsDescription = newsAndEvents.getNewsDescription(); + this.newsUrl = newsAndEvents.getNewsUrl(); + this.newsAuthor = newsAndEvents.getNewsAuthor(); + this.newsDate = newsAndEvents.getNewsDate().toString(); + + // Convert image byte array to Base64 encoded string + this.newsCoverImage = Base64.getEncoder().encodeToString(newsAndEvents.getNewsCoverImage()); + } +} diff --git a/back-end/src/main/java/com/example/demo/news/NewsAndEventsService.java b/back-end/src/main/java/com/example/demo/news/NewsAndEventsService.java index 67bb0ad5..a565864f 100644 --- a/back-end/src/main/java/com/example/demo/news/NewsAndEventsService.java +++ b/back-end/src/main/java/com/example/demo/news/NewsAndEventsService.java @@ -3,51 +3,90 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Service @AllArgsConstructor public class NewsAndEventsService { private final NewsAndEventsRepository newsAndEventsRepository; - - public List getAllNewsAndEvents() { - return newsAndEventsRepository.findAllOrderByNewsIDDesc(); - } - + + /** + * Fetch all news and events and map them to NewsAndEventsResponse objects. + */ + public List getAllNewsAndEvents() { + return newsAndEventsRepository.findAllOrderByNewsIDDesc() + .stream() + .map(NewsAndEventsResponse::new) // Map each entity to its response DTO + .collect(Collectors.toList()); + } + + /** + * Fetch a specific news or event by its ID. + */ public Optional getNewsAndEventById(Long newsID) { return newsAndEventsRepository.findById(newsID); } + /** + * Add a new news or event with an optional image upload. + * Handles both text data and the image file. + * + * @param newsTitle The title of the news/event. + * @param newsDescription The description of the news/event. + * @param newsUrl An optional URL for the news/event. + * @param newsAuthor The author of the news/event. + * @param newsDate The date of the news/event. + * @param newsCoverImage An optional image file to be uploaded. + * @return The saved NewsAndEvents entity. + */ @Transactional - public NewsAndEvents addNewsAndEvent(NewsAndEventsRequest newsAndEventsRequest) { + public NewsAndEvents addNewsAndEvent(String newsTitle, String newsDescription, String newsUrl, + String newsAuthor, String newsDate, MultipartFile newsCoverImage) { + + byte[] imageBytes = null; + if (newsCoverImage != null && !newsCoverImage.isEmpty()) { + try { + imageBytes = newsCoverImage.getBytes(); // Get the image bytes from MultipartFile + } catch (IOException e) { + throw new RuntimeException("Failed to process image file", e); // Better error handling + } + } + NewsAndEvents newsAndEvents = new NewsAndEvents( - newsAndEventsRequest.getNewsTitle(), - newsAndEventsRequest.getNewsDescription(), - newsAndEventsRequest.getNewsUrl(), - newsAndEventsRequest.getNewsAuthor(), - newsAndEventsRequest.getNewsDate(), - newsAndEventsRequest.getNewsCoverImage() + newsTitle, + newsDescription, + newsUrl, + newsAuthor, + newsDate, + imageBytes // Store the image bytes ); - System.out.println("News Added"); - return newsAndEventsRepository.save(newsAndEvents); + + System.out.println("News Added: " + newsTitle); // Optional: log the added news + + return newsAndEventsRepository.save(newsAndEvents); // Save the entity to the database } + /** + * Delete a news or event by its ID. + * + * @param newsID The ID of the news/event to be deleted. + */ @Transactional public void deleteNewsAndEvent(Long newsID) { - newsAndEventsRepository.deleteById(newsID); + if (!newsAndEventsRepository.existsById(newsID)) { + throw new IllegalArgumentException("News with ID " + newsID + " does not exist."); // Handle non-existing entries + } + newsAndEventsRepository.deleteById(newsID); // Perform the delete operation } -// public NewsAndEvents updateNewsAndEvent(Long newsID, NewsAndEvents updatednewsAndEvent) { -// return NewsAndEventsRepository.findByID(newsID) -// .map(newsAndEvents -> { -// newsAndEvents.setNewsAuthor(updatednewsAndEvent.getNewsAuthor()); -// return NewsAndEventsRepository.save(newsAndEvents); -// }) -// .orElseGet(() -> { -// updatednewsAndEvent.setNewsAuthor(updatednewsAndEvent.getNewsAuthor()); -// }); -// } + // Uncomment and modify this method if you need to implement update functionality + // public NewsAndEvents updateNewsAndEvent(Long newsID, NewsAndEventsRequest updatedRequest) { + // // Fetch existing news by ID, update fields, and save. + // } } diff --git a/back-end/src/test/java/com/example/demo/NewsAndEventsControllerTest.java b/back-end/src/test/java/com/example/demo/NewsAndEventsControllerTest.java index 29cbaae8..c851a112 100644 --- a/back-end/src/test/java/com/example/demo/NewsAndEventsControllerTest.java +++ b/back-end/src/test/java/com/example/demo/NewsAndEventsControllerTest.java @@ -2,7 +2,6 @@ import com.example.demo.news.NewsAndEvents; import com.example.demo.news.NewsAndEventsController; -import com.example.demo.news.NewsAndEventsRequest; import com.example.demo.news.NewsAndEventsService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -10,6 +9,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -43,7 +43,6 @@ void setUp() { responds with an HTTP 200 status and returns an empty JSON array when there are no news or events available from the service layer */ - @Test void testGetAllNewsAndEvents() throws Exception { when(newsAndEventsService.getAllNewsAndEvents()).thenReturn(Collections.emptyList()); @@ -59,11 +58,10 @@ void testGetAllNewsAndEvents() throws Exception { Test to verify that when a GET request is made to /api/v1/news/{newsId}, the endpoint correctly retrieves and returns the news item with the specified newsId */ - @Test void testGetNewsById() throws Exception { Long newsId = 1L; - NewsAndEvents newsAndEvents = new NewsAndEvents("Title", "Description", "Url", "Author", "Date", "Image"); + NewsAndEvents newsAndEvents = new NewsAndEvents("Title", "Description", "Url", "Author", "Date", null); when(newsAndEventsService.getNewsAndEventById(newsId)).thenReturn(Optional.of(newsAndEvents)); mockMvc.perform(get("/api/v1/news/{newsId}", newsId)) @@ -75,25 +73,44 @@ void testGetNewsById() throws Exception { /* Test to ensure that when a POST request is made to /api/v1/news with a - JSON payload representing a new news item, the endpoint correctly processes - the request, adds the item using the service layer, and returns a response - indicating success with the added news item's details + MultipartFile and other parameters representing a new news item, the endpoint + correctly processes the request, adds the item using the service layer, and returns + a response indicating success with the added news item's details. */ - @Test void testAddNewsAndEvents() throws Exception { - NewsAndEventsRequest request = new NewsAndEventsRequest("Title", "Description", "Url", "Author", "Date", "Image"); - NewsAndEvents newsAndEvents = new NewsAndEvents(request.getNewsTitle(), request.getNewsDescription(), request.getNewsUrl(), request.getNewsAuthor(), request.getNewsDate(), request.getNewsCoverImage()); - - when(newsAndEventsService.addNewsAndEvent(any(NewsAndEventsRequest.class))).thenReturn(newsAndEvents); - - mockMvc.perform(post("/api/v1/news") - .contentType(MediaType.APPLICATION_JSON) - .content("{\"newsTitle\":\"Title\",\"newsDescription\":\"Description\",\"newsUrl\":\"Url\",\"newsAuthor\":\"Author\",\"newsDate\":\"Date\",\"newsCoverImage\":\"Image\"}")) + MockMultipartFile imageFile = new MockMultipartFile("newsCoverImage", "image.jpg", MediaType.IMAGE_JPEG_VALUE, "test image".getBytes()); + + NewsAndEvents newsAndEvents = new NewsAndEvents("Title", "Description", "Url", "Author", "Date", imageFile.getBytes()); + + when(newsAndEventsService.addNewsAndEvent( + eq("Title"), + eq("Description"), + eq("Url"), + eq("Author"), + eq("Date"), + any(MockMultipartFile.class) + )).thenReturn(newsAndEvents); + + mockMvc.perform(multipart("/api/v1/news") + .file(imageFile) + .param("newsTitle", "Title") + .param("newsDescription", "Description") + .param("newsUrl", "Url") + .param("newsAuthor", "Author") + .param("newsDate", "Date") + .contentType(MediaType.MULTIPART_FORM_DATA)) .andExpect(status().isOk()) .andExpect(jsonPath("$.newsTitle").value("Title")); - verify(newsAndEventsService, times(1)).addNewsAndEvent(any(NewsAndEventsRequest.class)); + verify(newsAndEventsService, times(1)).addNewsAndEvent( + eq("Title"), + eq("Description"), + eq("Url"), + eq("Author"), + eq("Date"), + any(MockMultipartFile.class) + ); } /* @@ -101,7 +118,6 @@ void testAddNewsAndEvents() throws Exception { the endpoint correctly handles the request to delete the news or events item with the specified newsId. */ - @Test void testDeleteNewsById() throws Exception { Long newsId = 1L; diff --git a/back-end/src/test/java/com/example/demo/NewsAndEventsServiceTest.java b/back-end/src/test/java/com/example/demo/NewsAndEventsServiceTest.java index 599ae4a2..39132806 100644 --- a/back-end/src/test/java/com/example/demo/NewsAndEventsServiceTest.java +++ b/back-end/src/test/java/com/example/demo/NewsAndEventsServiceTest.java @@ -2,19 +2,20 @@ import com.example.demo.news.NewsAndEvents; import com.example.demo.news.NewsAndEventsRepository; -import com.example.demo.news.NewsAndEventsRequest; import com.example.demo.news.NewsAndEventsService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.mock.web.MockMultipartFile; +import java.io.IOException; import java.util.Collections; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; - import static org.mockito.Mockito.*; +import static org.mockito.Mockito.*; class NewsAndEventsServiceTest { @@ -24,7 +25,6 @@ class NewsAndEventsServiceTest { @InjectMocks private NewsAndEventsService newsAndEventsService; -// annotations for test fixtures @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); @@ -37,7 +37,6 @@ void setUp() { the NewsAndEventsService correctly interacts with the NewsAndEventsRepository to fetch all news and events */ - @Test void testGetAllNewsAndEvents() { when(newsAndEventsRepository.findAllOrderByNewsIDDesc()).thenReturn(Collections.emptyList()); @@ -50,11 +49,10 @@ void testGetAllNewsAndEvents() { retrieves a news and event entry by its ID and interacts with the NewsAndEventsRepository as expected */ - @Test void testGetNewsAndEventById() { Long newsId = 1L; - NewsAndEvents newsAndEvents = new NewsAndEvents("Title", "Description", "Url", "Author", "Date", "Image"); + NewsAndEvents newsAndEvents = new NewsAndEvents("Title", "Description", "Url", "Author", "Date", null); when(newsAndEventsRepository.findById(newsId)).thenReturn(Optional.of(newsAndEvents)); Optional foundNews = newsAndEventsService.getNewsAndEventById(newsId); @@ -65,27 +63,34 @@ void testGetNewsAndEventById() { /* Test to check whether the addNewsAndEvent method in the NewsAndEventsService class correctly - adds a new news and event entry based on the provided request object + adds a new news and event entry based on the provided request object and the file upload */ - @Test - void testAddNewsAndEvent() { - NewsAndEventsRequest request = new NewsAndEventsRequest("Title", "Description", "Url", "Author", "Date", "Image"); - NewsAndEvents newsAndEvents = new NewsAndEvents(request.getNewsTitle(), request.getNewsDescription(), request.getNewsUrl(), request.getNewsAuthor(), request.getNewsDate(), request.getNewsCoverImage()); + void testAddNewsAndEvent() throws IOException { + MockMultipartFile mockImage = new MockMultipartFile("newsCoverImage", "image.jpg", "image/jpeg", "image data".getBytes()); + + NewsAndEvents newsAndEvents = new NewsAndEvents("Title", "Description", "Url", "Author", "Date", mockImage.getBytes()); when(newsAndEventsRepository.save(any(NewsAndEvents.class))).thenReturn(newsAndEvents); - NewsAndEvents addedNews = newsAndEventsService.addNewsAndEvent(request); + NewsAndEvents addedNews = newsAndEventsService.addNewsAndEvent( + "Title", + "Description", + "Url", + "Author", + "Date", + mockImage + ); + assertNotNull(addedNews); assertEquals("Title", addedNews.getNewsTitle()); verify(newsAndEventsRepository, times(1)).save(any(NewsAndEvents.class)); } /* - Test for deleteNewsAndEvent whether it correctly delete entry + Test for deleteNewsAndEvent whether it correctly deletes an entry by its ID and properly interacts with NewsAndEventsRepository */ - @Test void testDeleteNewsAndEvent() { Long newsId = 1L; diff --git a/front-end/package-lock.json b/front-end/package-lock.json index 5c23ab4c..caf3476b 100644 --- a/front-end/package-lock.json +++ b/front-end/package-lock.json @@ -36,7 +36,7 @@ "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", - "vite": "^5.2.0" + "vite": "^5.4.3" } }, "node_modules/@ampproject/remapping": { @@ -531,9 +531,9 @@ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -547,9 +547,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -563,9 +563,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -579,9 +579,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -595,9 +595,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -611,9 +611,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -627,9 +627,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -643,9 +643,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -659,9 +659,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -675,9 +675,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -691,9 +691,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -707,9 +707,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -723,9 +723,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -739,9 +739,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -755,9 +755,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -771,9 +771,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -787,9 +787,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -803,9 +803,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -819,9 +819,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -835,9 +835,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -851,9 +851,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -867,9 +867,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -883,9 +883,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -1730,9 +1730,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", - "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz", + "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==", "cpu": [ "arm" ], @@ -1743,9 +1743,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", - "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz", + "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==", "cpu": [ "arm64" ], @@ -1756,9 +1756,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", - "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz", + "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==", "cpu": [ "arm64" ], @@ -1769,9 +1769,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz", + "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==", "cpu": [ "x64" ], @@ -1782,9 +1782,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", - "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz", + "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==", "cpu": [ "arm" ], @@ -1795,9 +1795,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", - "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz", + "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==", "cpu": [ "arm" ], @@ -1808,9 +1808,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", - "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz", + "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==", "cpu": [ "arm64" ], @@ -1821,9 +1821,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", - "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz", + "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==", "cpu": [ "arm64" ], @@ -1834,9 +1834,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", - "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz", + "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==", "cpu": [ "ppc64" ], @@ -1847,9 +1847,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", - "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz", + "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==", "cpu": [ "riscv64" ], @@ -1860,9 +1860,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", - "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz", + "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==", "cpu": [ "s390x" ], @@ -1873,9 +1873,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", - "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz", + "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==", "cpu": [ "x64" ], @@ -1886,9 +1886,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", - "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz", + "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==", "cpu": [ "x64" ], @@ -1899,9 +1899,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", - "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz", + "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==", "cpu": [ "arm64" ], @@ -1912,9 +1912,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", - "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz", + "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==", "cpu": [ "ia32" ], @@ -1925,9 +1925,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", - "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz", + "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==", "cpu": [ "x64" ], @@ -3022,9 +3022,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -3034,29 +3034,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -4918,9 +4918,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.45", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", + "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", "dev": true, "funding": [ { @@ -4938,7 +4938,7 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "source-map-js": "^1.2.0" }, "engines": { @@ -5249,9 +5249,9 @@ } }, "node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", + "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -5264,22 +5264,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.0", - "@rollup/rollup-android-arm64": "4.18.0", - "@rollup/rollup-darwin-arm64": "4.18.0", - "@rollup/rollup-darwin-x64": "4.18.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", - "@rollup/rollup-linux-arm-musleabihf": "4.18.0", - "@rollup/rollup-linux-arm64-gnu": "4.18.0", - "@rollup/rollup-linux-arm64-musl": "4.18.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", - "@rollup/rollup-linux-riscv64-gnu": "4.18.0", - "@rollup/rollup-linux-s390x-gnu": "4.18.0", - "@rollup/rollup-linux-x64-gnu": "4.18.0", - "@rollup/rollup-linux-x64-musl": "4.18.0", - "@rollup/rollup-win32-arm64-msvc": "4.18.0", - "@rollup/rollup-win32-ia32-msvc": "4.18.0", - "@rollup/rollup-win32-x64-msvc": "4.18.0", + "@rollup/rollup-android-arm-eabi": "4.21.2", + "@rollup/rollup-android-arm64": "4.21.2", + "@rollup/rollup-darwin-arm64": "4.21.2", + "@rollup/rollup-darwin-x64": "4.21.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", + "@rollup/rollup-linux-arm-musleabihf": "4.21.2", + "@rollup/rollup-linux-arm64-gnu": "4.21.2", + "@rollup/rollup-linux-arm64-musl": "4.21.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", + "@rollup/rollup-linux-riscv64-gnu": "4.21.2", + "@rollup/rollup-linux-s390x-gnu": "4.21.2", + "@rollup/rollup-linux-x64-gnu": "4.21.2", + "@rollup/rollup-linux-x64-musl": "4.21.2", + "@rollup/rollup-win32-arm64-msvc": "4.21.2", + "@rollup/rollup-win32-ia32-msvc": "4.21.2", + "@rollup/rollup-win32-x64-msvc": "4.21.2", "fsevents": "~2.3.2" } }, @@ -5883,14 +5883,14 @@ "peer": true }, "node_modules/vite": { - "version": "5.2.13", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.13.tgz", - "integrity": "sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz", + "integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==", "dev": true, "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -5909,6 +5909,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -5926,6 +5927,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/front-end/package.json b/front-end/package.json index 50631c19..f9432e1e 100644 --- a/front-end/package.json +++ b/front-end/package.json @@ -38,6 +38,6 @@ "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", - "vite": "^5.2.0" + "vite": "^5.4.3" } } diff --git a/front-end/src/Pages/News.jsx b/front-end/src/Pages/News.jsx index b4d03a80..0a50416a 100644 --- a/front-end/src/Pages/News.jsx +++ b/front-end/src/Pages/News.jsx @@ -1,134 +1,167 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; -import { loggedInUser } from '../Pages/Login'; -import '../components/News.css'; // Import your CSS file -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faPen, faEye, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { loggedInUser } from '../Pages/Login'; // Import logged-in user details +import '../components/News.css'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; // FontAwesome for icons +import { faPen, faEye, faTrash } from '@fortawesome/free-solid-svg-icons'; // Import necessary icons +// Function to fetch news from the API, sorted by newsID in descending order const fetchNews = async () => { try { - const response = await axios.get('http://localhost:8080/api/v1/news'); - return response.data.sort((a, b) => b.newsID - a.newsID); + const response = await axios.get('http://localhost:8080/api/v1/news'); // Fetch news from backend + return response.data.sort((a, b) => b.newsID - a.newsID); // Sort news by most recent (highest ID first) } catch (error) { - console.error('Error fetching news:', error); - return []; + console.error('Error fetching news:', error); + return []; // Return empty array if there's an error } }; const News = () => { + const [news, setNews] = useState([]); // State to hold the list of news + const [showForm, setShowForm] = useState(false); // State to toggle add news form visibility + const [file, setFile] = useState(null); // State to hold the selected image file - const [news, setNews] = useState([]); - const [showForm, setShowForm] = useState(false); - + // useEffect to fetch news when the component mounts useEffect(() => { const getNews = async () => { - const latestNews = await fetchNews(); - setNews(latestNews); + const latestNews = await fetchNews(); // Fetch latest news + setNews(latestNews); // Set the fetched news in the state }; - getNews(); - }, []); + getNews(); // Call the function to fetch news + }, []); // Empty dependency array means this runs only on the initial render + // Function to toggle the visibility of the add news form const toggleForm = () => { - setShowForm(!showForm); + setShowForm(!showForm); // Toggle the showForm state between true and false }; - const handleSubmit = (event) => { - event.preventDefault(); + // Function to handle file selection + const handleFileChange = (event) => { + setFile(event.target.files[0]); // Set the selected file in state + }; + + // Function to handle the form submission for adding news + const handleSubmit = async (event) => { + event.preventDefault(); // Prevent the default form submission behavior const form = event.target; - const newsData = { - newsTitle: form.newsTitle.value, - newsDescription: form.newsDescription.value, - newsUrl: form.newsUrl.value, - newsAuthor: form.newsAuthor.value, - newsDate: form.newsDate.value, - newsCoverImage: form.newsCoverImage.value, - }; - axios.post('http://localhost:8080/api/v1/news', newsData) - .then(response => { - toggleForm(); // Close the form after successful submission - fetchNews().then(latestNews => setNews(latestNews)); // Refresh news list - }) - .catch(error => { - console.error('Error adding news:', error); + // Create FormData to send the image file and other data + const formData = new FormData(); + formData.append('newsTitle', form.newsTitle.value); + formData.append('newsDescription', form.newsDescription.value); + formData.append('newsUrl', form.newsUrl.value); + formData.append('newsAuthor', form.newsAuthor.value); + formData.append('newsDate', form.newsDate.value); + formData.append('newsCoverImage', file); // Append the image file + + // Send POST request to backend to add news + try { + const response = await axios.post('http://localhost:8080/api/v1/news', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } }); + toggleForm(); // Close the form after successful submission + const latestNews = await fetchNews(); // Fetch the latest news + setNews(latestNews); // Update the news list with the new entry + } catch (error) { + console.error('Error adding news:', error); // Log any errors + } }; + // Function to handle news deletion const onDeleteClick = async (newsID) => { console.log("Delete button clicked"); - console.log(newsID); + console.log(newsID); // Log the newsID for debugging try { - await axios.delete(`http://localhost:8080/api/v1/news/${newsID}`); - fetchNews(); + await axios.delete(`http://localhost:8080/api/v1/news/${newsID}`); // Send DELETE request + + // Fetch the updated list of news and update the state + const latestNews = await fetchNews(); + setNews(latestNews); } catch (error) { - console.error("Error deleting deliverable:", error); + console.error("Error deleting news:", error); // Log any errors } } + return ( <>

News

+ {/* Show Add News button only if the user is logged in */} {loggedInUser.isLoggedIn && (
)} + + {/* Conditional rendering of the add news form */} {showForm && (
-
-

Add News

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
+
+

Add News

+
+ {/* Form fields to add new news */} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ {/* Submit button */} +
+
+
-
)} + )}
+ + {/* Display news items */}
{news.map((item) => (
- {item.newsTitle} -

{item.newsTitle}

-

{item.newsDescription}

-

Date: {item.newsDate}

-

Author: {item.newsAuthor}

- Read more - {loggedInUser.isLoggedIn && + {item.newsTitle} +

{item.newsTitle}

{/* News title */} +

{item.newsDescription}

{/* News description */} +

Date: {item.newsDate}

{/* News date */} +

Author: {item.newsAuthor}

{/* News author */} + Read more {/* Link to full news */} + + {/* Show edit and delete buttons only if user is logged in */} + {loggedInUser.isLoggedIn && (
- - + + -
- } +
+ )}
))}