From 054e25bb66269d1d99ee0b0afa3b26abee2db80f Mon Sep 17 00:00:00 2001 From: realtradam Date: Wed, 24 Jul 2024 23:15:03 -0400 Subject: port api endpoints to json and add react pages to interact with them --- .../blog/web/controllers/ArticleController.java | 29 +++-- .../com/blog/web/repository/ArticleRepository.java | 4 +- .../java/com/blog/web/services/ArticleService.java | 7 +- .../blog/web/services/impl/ArticleServiceImpl.java | 21 +++- frontend/index.html | 6 +- ...abstract-polygonal-banner-background-vector.jpg | Bin 0 -> 1841814 bytes frontend/src/components/Layout.tsx | 132 ++++++++++++++++++++- frontend/src/pages/Article.tsx | 25 ++++ frontend/src/pages/Home.tsx | 111 +++++++++++++++-- frontend/src/pages/Register.tsx | 55 +++++++++ frontend/src/routes/index.tsx | 25 +--- 11 files changed, 354 insertions(+), 61 deletions(-) create mode 100644 frontend/public/abstract-polygonal-banner-background-vector.jpg create mode 100644 frontend/src/pages/Article.tsx create mode 100644 frontend/src/pages/Register.tsx diff --git a/backend/src/main/java/com/blog/web/controllers/ArticleController.java b/backend/src/main/java/com/blog/web/controllers/ArticleController.java index df9d40c..6bd1abe 100644 --- a/backend/src/main/java/com/blog/web/controllers/ArticleController.java +++ b/backend/src/main/java/com/blog/web/controllers/ArticleController.java @@ -16,6 +16,7 @@ import java.time.LocalDateTime; import java.util.HashSet; import java.util.List; +@RequestMapping("/api/v1") @RestController @Controller public class ArticleController { @@ -51,21 +52,23 @@ public class ArticleController { return articles; } - @GetMapping("/articles/{articleId}") - public String showArticle(@PathVariable("articleId") long articleId, Model model) { - ArticleDto articleDto = articleService.findArticleById(articleId); - model.addAttribute("article", articleDto); - UserEntity user = userService.getLoggedInUser().orElse(new UserEntity()); - model.addAttribute("user", user); - return "articles/show"; + @CrossOrigin + @GetMapping("/article/{articleId}") + public ArticlePublicDto showArticle(@PathVariable("articleId") long articleId, Model model) { + ArticlePublicDto articlePublicDto = articleService.findArticlePublicById(articleId); + //model.addAttribute("article", articlePublicDto); + //UserEntity user = userService.getLoggedInUser().orElse(new UserEntity()); + //model.addAttribute("user", user); + //return "articles/show"; + return articlePublicDto; } - @GetMapping("/articles/new") + /*@GetMapping("/articles/new") public String createArticleForm(Model model) { model.addAttribute("user", userService.getLoggedInUser().orElse(new UserEntity())); model.addAttribute("article", new Article()); return "articles/new"; - } + }*/ @PostMapping("/articles/new") public String saveArticle(@Valid @ModelAttribute("article") ArticleDto articleDto, BindingResult result, Model model) { @@ -112,10 +115,10 @@ public class ArticleController { @GetMapping("/articles/search") public String searchArticle(@RequestParam(value = "search") String search, Model model) { - UserEntity user = userService.getLoggedInUser().orElse(new UserEntity()); - model.addAttribute("user", user); - List articles = articleService.searchArticles(search); - model.addAttribute("articles", articles); + //UserEntity user = userService.getLoggedInUser().orElse(new UserEntity()); + //model.addAttribute("user", user); + HashSet articles = articleService.searchPublicArticles(search); + //model.addAttribute("articles", articles); return "index"; } diff --git a/backend/src/main/java/com/blog/web/repository/ArticleRepository.java b/backend/src/main/java/com/blog/web/repository/ArticleRepository.java index 594cb15..f40eada 100644 --- a/backend/src/main/java/com/blog/web/repository/ArticleRepository.java +++ b/backend/src/main/java/com/blog/web/repository/ArticleRepository.java @@ -4,9 +4,9 @@ import com.blog.web.models.Article; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import java.util.List; +import java.util.HashSet; public interface ArticleRepository extends JpaRepository { @Query("SELECT a from Article a WHERE a.title LIKE CONCAT('%', :search, '%')") - List
searchArticles(String search); + HashSet
searchArticles(String search); } diff --git a/backend/src/main/java/com/blog/web/services/ArticleService.java b/backend/src/main/java/com/blog/web/services/ArticleService.java index 1bfe38f..1e50df0 100644 --- a/backend/src/main/java/com/blog/web/services/ArticleService.java +++ b/backend/src/main/java/com/blog/web/services/ArticleService.java @@ -4,6 +4,7 @@ import com.blog.web.dto.ArticleDto; import com.blog.web.dto.ArticlePublicDto; import com.blog.web.models.Article; +import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -18,5 +19,9 @@ public interface ArticleService { boolean delete(Long articleId); - List searchArticles(String search); + //List searchArticles(String search); + + ArticlePublicDto findArticlePublicById(long articleId); + + HashSet searchPublicArticles(String search); } diff --git a/backend/src/main/java/com/blog/web/services/impl/ArticleServiceImpl.java b/backend/src/main/java/com/blog/web/services/impl/ArticleServiceImpl.java index 7073073..993d798 100644 --- a/backend/src/main/java/com/blog/web/services/impl/ArticleServiceImpl.java +++ b/backend/src/main/java/com/blog/web/services/impl/ArticleServiceImpl.java @@ -11,14 +11,14 @@ import com.blog.web.services.ArticleService; import com.blog.web.services.UserService; import org.springframework.stereotype.Service; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import com.blog.web.mappers.ArticleMapper; -import static com.blog.web.mappers.ArticleMapper.mapToArticle; -import static com.blog.web.mappers.ArticleMapper.mapToArticleDto; +import static com.blog.web.mappers.ArticleMapper.*; @Service public class ArticleServiceImpl implements ArticleService { @@ -86,9 +86,20 @@ public class ArticleServiceImpl implements ArticleService { } } + //@Override + //public List searchArticles(String search) { + // List
articles = articleRepository.searchArticles(search); + // return articles.stream().map(article -> mapToArticleDto(article)).collect(Collectors.toList()); + //} + + @Override + public ArticlePublicDto findArticlePublicById(long articleId) { + return new ArticlePublicDto(articleRepository.findById(articleId).get()); + } + @Override - public List searchArticles(String search) { - List
articles = articleRepository.searchArticles(search); - return articles.stream().map(article -> mapToArticleDto(article)).collect(Collectors.toList()); + public HashSet searchPublicArticles(String search) { + HashSet
articles = articleRepository.searchArticles(search); + return articles.stream().map(article -> mapToArticlePublicDto(article)).collect(Collectors.toCollection(HashSet::new)); } } diff --git a/frontend/index.html b/frontend/index.html index e4b78ea..82a3d13 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -6,8 +6,8 @@ Vite + React + TS - -
+ +
- + diff --git a/frontend/public/abstract-polygonal-banner-background-vector.jpg b/frontend/public/abstract-polygonal-banner-background-vector.jpg new file mode 100644 index 0000000..1c6746e Binary files /dev/null and b/frontend/public/abstract-polygonal-banner-background-vector.jpg differ diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index f58f1fc..f7b8591 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -5,12 +5,132 @@ export default function Layout() { return( <> -
-

Spring Blog

-
- -
-
+
+ + + +{/*slide in nav*/} + + +{/*Header*/} +
+
+ {/*Title*/} +

+ ☕ Spring! +

+

Welcome to my Blog

+
+
+ + + +
+
+ +
+
+ + + +
+
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+ + + +
+
+ + + + +
); } diff --git a/frontend/src/pages/Article.tsx b/frontend/src/pages/Article.tsx new file mode 100644 index 0000000..b367811 --- /dev/null +++ b/frontend/src/pages/Article.tsx @@ -0,0 +1,25 @@ +import { useState, useEffect } from "react"; +import { useParams } from "react-router-dom"; + +export default function Article () { + const { id } = useParams(); + const [articleData, setArticleData] = useState(); + + useEffect(() => { + const url = `${import.meta.env.VITE_API_TITLE}/api/v1/article/${id}`; + fetch(url).then((response) => { + if (response.ok) { + return response.json(); + } + throw new Error("Network response was not ok."); + }).then((response) => setArticleData(response)); //.catch(() => navigate("/")); + }, [id]); + + return( + <> +

{articleData?.title}

+
{articleData?.content}
+ + ); + +} diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index cb68bde..eed745b 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -1,10 +1,10 @@ import { useState, useEffect } from "react"; export default function Home() { - const [articles, setArticles] = useState([]); + const [articles, setArticles] = useState([]); useEffect(() => { - const url = `${import.meta.env.VITE_API_TITLE}/articles`; + const url = `${import.meta.env.VITE_API_TITLE}/api/v1/articles`; fetch(url).then((response) => { if (response.ok) { return response.json(); @@ -14,19 +14,110 @@ export default function Home() { }, []); const allArticles = articles.map((article) => ( -
-
{article.title}
-
{article.content}
-
+
+
+ {/*th:href="@{/articles/{articleId}(articleId=${article.id})}"*/} + + {/*th:src="${article.photoUrl}"*/} + + {/*th:text="${article.title}"*/} +
{article.title}
+

+
+ {/*th:if="${user.id} == ${article.createdBy.id}"*/} +
+ {/*th:href="@{/articles/edit/{articleId}(articleId=${article.id})}"*/} + Edit + {/*th:href="@{/articles/delete/{articleId}(articleId=${article.id})}"*/} + Delete + +
+
+
+

+
+
+
)); return( <> -
-

Welcome to Spring Blog

-
+ {/*Container*/} +
+ +
+ + {/**/} + + + +
+
+
); } diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx new file mode 100644 index 0000000..7030842 --- /dev/null +++ b/frontend/src/pages/Register.tsx @@ -0,0 +1,55 @@ + +export default function Register () { + return( + <> +
+
Username or Email already exists
+
+
+
+ + +

Please fill out this field.

+
+
+ + +

Please fill out this field.

+
+
+
+
+ + +

Please fill out this field.

+
+
+
+
+ + +
+
+ + ); + +} diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx index 450a1fe..718268a 100644 --- a/frontend/src/routes/index.tsx +++ b/frontend/src/routes/index.tsx @@ -1,35 +1,18 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import Home from "../pages/Home"; import Layout from "../components/Layout"; +import Article from "../pages/Article"; +import Register from "../pages/Register"; export default function Index() { - /* - useEffect(() => { - const update_login_status = () => { - //localStorage.removeItem("logged in trigger"); - const url = `${import.meta.env.VITE_API_TITLE}/api/v1/auth/data`; - try { fetch(url, { - credentials: "include" - }).then((response) => { - if(response.ok) { - return response.json(); - } - //throw new Error("Network response was not ok."); - }).then((response) => response && setUserData(response.user_data)).catch((err) => { console.log(err); });} - catch(err) { console.log(err); } - }; - window.addEventListener('storage', update_login_status ); - update_login_status(); - return () => { window.removeEventListener('storage', update_login_status); }; - }, []); - */ - return (<> }> } /> + } /> + } /> -- cgit v1.2.3